feat: add Quotio as provider option in onboarding wizard
Quotio is a local OpenAI-compatible proxy that routes to various AI models (Claude via Gemini credits, etc.). This adds it as a first-class provider option in the onboarding wizard, eliminating the need for manual config edits. - Add 'quotio' to AuthChoice type - Add Quotio group to provider options - Create dedicated handler with URL/API key prompts - Configure provider with Claude Opus 4.5, Sonnet 4, Gemini 3 Flash models - Use openai-completions API for compatibility
This commit is contained in:
parent
109ac1c549
commit
dd49d122fe
@ -20,7 +20,8 @@ export type AuthChoiceGroupId =
|
|||||||
| "minimax"
|
| "minimax"
|
||||||
| "synthetic"
|
| "synthetic"
|
||||||
| "venice"
|
| "venice"
|
||||||
| "qwen";
|
| "qwen"
|
||||||
|
| "quotio";
|
||||||
|
|
||||||
export type AuthChoiceGroup = {
|
export type AuthChoiceGroup = {
|
||||||
value: AuthChoiceGroupId;
|
value: AuthChoiceGroupId;
|
||||||
@ -113,6 +114,12 @@ const AUTH_CHOICE_GROUP_DEFS: {
|
|||||||
hint: "API key",
|
hint: "API key",
|
||||||
choices: ["opencode-zen"],
|
choices: ["opencode-zen"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "quotio",
|
||||||
|
label: "Quotio",
|
||||||
|
hint: "Local OpenAI-compatible proxy",
|
||||||
|
choices: ["quotio"],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function buildAuthChoiceOptions(params: {
|
export function buildAuthChoiceOptions(params: {
|
||||||
@ -183,6 +190,11 @@ export function buildAuthChoiceOptions(params: {
|
|||||||
label: "MiniMax M2.1 Lightning",
|
label: "MiniMax M2.1 Lightning",
|
||||||
hint: "Faster, higher output cost",
|
hint: "Faster, higher output cost",
|
||||||
});
|
});
|
||||||
|
options.push({
|
||||||
|
value: "quotio",
|
||||||
|
label: "Quotio (local proxy)",
|
||||||
|
hint: "OpenAI-compatible proxy for Claude, Gemini, etc.",
|
||||||
|
});
|
||||||
if (params.includeSkip) {
|
if (params.includeSkip) {
|
||||||
options.push({ value: "skip", label: "Skip for now" });
|
options.push({ value: "skip", label: "Skip for now" });
|
||||||
}
|
}
|
||||||
|
|||||||
153
src/commands/auth-choice.apply.quotio.ts
Normal file
153
src/commands/auth-choice.apply.quotio.ts
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
||||||
|
import { resolveClawdbotAgentDir } from "../agents/agent-paths.js";
|
||||||
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
||||||
|
import { applyAuthProfileConfig } from "./onboard-auth.js";
|
||||||
|
|
||||||
|
const QUOTIO_DEFAULT_BASE_URL = "http://127.0.0.1:18317/v1";
|
||||||
|
const QUOTIO_DEFAULT_API_KEY = "quotio-local";
|
||||||
|
const QUOTIO_DEFAULT_MODEL = "quotio/gemini-claude-sonnet-4-thinking";
|
||||||
|
|
||||||
|
const QUOTIO_MODELS = [
|
||||||
|
{
|
||||||
|
id: "gemini-claude-opus-4-5-thinking",
|
||||||
|
name: "Claude Opus 4.5 (Quotio)",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text", "image"] as Array<"text" | "image">,
|
||||||
|
contextWindow: 200000,
|
||||||
|
maxTokens: 32000,
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "gemini-claude-sonnet-4-thinking",
|
||||||
|
name: "Claude Sonnet 4 (Quotio)",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text", "image"] as Array<"text" | "image">,
|
||||||
|
contextWindow: 200000,
|
||||||
|
maxTokens: 32000,
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "gemini-3-flash",
|
||||||
|
name: "Gemini 3 Flash (Quotio)",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text", "image"] as Array<"text" | "image">,
|
||||||
|
contextWindow: 1000000,
|
||||||
|
maxTokens: 65536,
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function applyQuotioProviderConfig(
|
||||||
|
config: ClawdbotConfig,
|
||||||
|
baseUrl: string,
|
||||||
|
apiKey: string,
|
||||||
|
): ClawdbotConfig {
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
models: {
|
||||||
|
...config.models,
|
||||||
|
providers: {
|
||||||
|
...config.models?.providers,
|
||||||
|
quotio: {
|
||||||
|
baseUrl,
|
||||||
|
apiKey,
|
||||||
|
api: "openai-completions",
|
||||||
|
models: QUOTIO_MODELS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyQuotioDefaultModel(config: ClawdbotConfig): ClawdbotConfig {
|
||||||
|
const models = { ...config.agents?.defaults?.models };
|
||||||
|
models[QUOTIO_DEFAULT_MODEL] = models[QUOTIO_DEFAULT_MODEL] ?? {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
agents: {
|
||||||
|
...config.agents,
|
||||||
|
defaults: {
|
||||||
|
...config.agents?.defaults,
|
||||||
|
models,
|
||||||
|
model: {
|
||||||
|
primary: QUOTIO_DEFAULT_MODEL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function applyAuthChoiceQuotio(
|
||||||
|
params: ApplyAuthChoiceParams,
|
||||||
|
): Promise<ApplyAuthChoiceResult | null> {
|
||||||
|
if (params.authChoice !== "quotio") return null;
|
||||||
|
|
||||||
|
let nextConfig = params.config;
|
||||||
|
const agentDir = params.agentDir ?? resolveClawdbotAgentDir();
|
||||||
|
|
||||||
|
await params.prompter.note(
|
||||||
|
[
|
||||||
|
"Quotio is a local OpenAI-compatible proxy that routes to various AI models.",
|
||||||
|
"Make sure Quotio is running before using clawdbot.",
|
||||||
|
"Default endpoint: http://127.0.0.1:18317/v1",
|
||||||
|
].join("\n"),
|
||||||
|
"Quotio",
|
||||||
|
);
|
||||||
|
|
||||||
|
const baseUrl = await params.prompter.text({
|
||||||
|
message: "Enter Quotio base URL",
|
||||||
|
initialValue: QUOTIO_DEFAULT_BASE_URL,
|
||||||
|
validate: (value) => {
|
||||||
|
if (!value?.trim()) return "Base URL is required";
|
||||||
|
try {
|
||||||
|
new URL(value);
|
||||||
|
return undefined;
|
||||||
|
} catch {
|
||||||
|
return "Invalid URL format";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const apiKey = await params.prompter.text({
|
||||||
|
message: "Enter Quotio API key (or leave default for local)",
|
||||||
|
initialValue: QUOTIO_DEFAULT_API_KEY,
|
||||||
|
});
|
||||||
|
|
||||||
|
upsertAuthProfile({
|
||||||
|
profileId: "quotio:default",
|
||||||
|
credential: {
|
||||||
|
type: "api_key",
|
||||||
|
provider: "quotio",
|
||||||
|
key: String(apiKey).trim() || QUOTIO_DEFAULT_API_KEY,
|
||||||
|
},
|
||||||
|
agentDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: "quotio:default",
|
||||||
|
provider: "quotio",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
|
||||||
|
nextConfig = applyQuotioProviderConfig(
|
||||||
|
nextConfig,
|
||||||
|
String(baseUrl).trim() || QUOTIO_DEFAULT_BASE_URL,
|
||||||
|
String(apiKey).trim() || QUOTIO_DEFAULT_API_KEY,
|
||||||
|
);
|
||||||
|
|
||||||
|
let agentModelOverride: string | undefined;
|
||||||
|
if (params.setDefaultModel) {
|
||||||
|
nextConfig = applyQuotioDefaultModel(nextConfig);
|
||||||
|
await params.prompter.note(`Default model set to ${QUOTIO_DEFAULT_MODEL}`, "Model configured");
|
||||||
|
} else if (params.agentId) {
|
||||||
|
agentModelOverride = QUOTIO_DEFAULT_MODEL;
|
||||||
|
await params.prompter.note(
|
||||||
|
`Default model set to ${QUOTIO_DEFAULT_MODEL} for agent "${params.agentId}".`,
|
||||||
|
"Model configured",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { config: nextConfig, agentModelOverride };
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ import { applyAuthChoiceMiniMax } from "./auth-choice.apply.minimax.js";
|
|||||||
import { applyAuthChoiceOAuth } from "./auth-choice.apply.oauth.js";
|
import { applyAuthChoiceOAuth } from "./auth-choice.apply.oauth.js";
|
||||||
import { applyAuthChoiceOpenAI } from "./auth-choice.apply.openai.js";
|
import { applyAuthChoiceOpenAI } from "./auth-choice.apply.openai.js";
|
||||||
import { applyAuthChoiceQwenPortal } from "./auth-choice.apply.qwen-portal.js";
|
import { applyAuthChoiceQwenPortal } from "./auth-choice.apply.qwen-portal.js";
|
||||||
|
import { applyAuthChoiceQuotio } from "./auth-choice.apply.quotio.js";
|
||||||
import type { AuthChoice } from "./onboard-types.js";
|
import type { AuthChoice } from "./onboard-types.js";
|
||||||
|
|
||||||
export type ApplyAuthChoiceParams = {
|
export type ApplyAuthChoiceParams = {
|
||||||
@ -46,6 +47,7 @@ export async function applyAuthChoice(
|
|||||||
applyAuthChoiceGoogleGeminiCli,
|
applyAuthChoiceGoogleGeminiCli,
|
||||||
applyAuthChoiceCopilotProxy,
|
applyAuthChoiceCopilotProxy,
|
||||||
applyAuthChoiceQwenPortal,
|
applyAuthChoiceQwenPortal,
|
||||||
|
applyAuthChoiceQuotio,
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const handler of handlers) {
|
for (const handler of handlers) {
|
||||||
|
|||||||
@ -28,6 +28,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<AuthChoice, string>> = {
|
|||||||
minimax: "lmstudio",
|
minimax: "lmstudio",
|
||||||
"opencode-zen": "opencode",
|
"opencode-zen": "opencode",
|
||||||
"qwen-portal": "qwen-portal",
|
"qwen-portal": "qwen-portal",
|
||||||
|
quotio: "quotio",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resolvePreferredProviderForAuthChoice(choice: AuthChoice): string | undefined {
|
export function resolvePreferredProviderForAuthChoice(choice: AuthChoice): string | undefined {
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export type AuthChoice =
|
|||||||
| "github-copilot"
|
| "github-copilot"
|
||||||
| "copilot-proxy"
|
| "copilot-proxy"
|
||||||
| "qwen-portal"
|
| "qwen-portal"
|
||||||
|
| "quotio"
|
||||||
| "skip";
|
| "skip";
|
||||||
export type GatewayAuthChoice = "token" | "password";
|
export type GatewayAuthChoice = "token" | "password";
|
||||||
export type ResetScope = "config" | "config+creds+sessions" | "full";
|
export type ResetScope = "config" | "config+creds+sessions" | "full";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user