@poolzin/pool-bot 2026.2.4 → 2026.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/auth-profiles/profiles.js +9 -0
- package/dist/agents/auth-profiles.js +1 -1
- package/dist/agents/huggingface-models.js +166 -0
- package/dist/agents/model-auth.js +6 -0
- package/dist/agents/model-forward-compat.js +187 -0
- package/dist/agents/pi-embedded-runner/model.js +10 -56
- package/dist/browser/constants.js +1 -1
- package/dist/browser/profiles.js +1 -1
- package/dist/build-info.json +3 -3
- package/dist/cli/config-cli.js +17 -3
- package/dist/cli/program/register.onboard.js +38 -5
- package/dist/commands/auth-choice-options.js +71 -7
- package/dist/commands/auth-choice.apply.api-providers.js +202 -97
- package/dist/commands/auth-choice.apply.huggingface.js +130 -0
- package/dist/commands/auth-choice.apply.openrouter.js +77 -0
- package/dist/commands/auth-choice.apply.plugin-provider.js +1 -56
- package/dist/commands/auth-choice.apply.vllm.js +92 -0
- package/dist/commands/auth-choice.preferred-provider.js +10 -0
- package/dist/commands/models/auth.js +1 -58
- package/dist/commands/models/list.errors.js +14 -0
- package/dist/commands/models/list.list-command.js +32 -21
- package/dist/commands/models/list.registry.js +120 -28
- package/dist/commands/models/list.status-command.js +1 -0
- package/dist/commands/models/shared.js +14 -0
- package/dist/commands/onboard-auth.config-core.js +265 -8
- package/dist/commands/onboard-auth.credentials.js +47 -6
- package/dist/commands/onboard-auth.js +3 -3
- package/dist/commands/onboard-auth.models.js +67 -0
- package/dist/commands/onboard-custom.js +181 -70
- package/dist/commands/onboard-non-interactive/api-keys.js +10 -1
- package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +15 -7
- package/dist/commands/onboard-non-interactive/local/auth-choice.js +322 -124
- package/dist/commands/provider-auth-helpers.js +61 -0
- package/dist/commands/zai-endpoint-detect.js +97 -0
- package/dist/config/legacy.migrations.part-3.js +57 -0
- package/dist/terminal/theme.js +1 -1
- package/package.json +1 -1
|
@@ -33,6 +33,15 @@ export function upsertAuthProfile(params) {
|
|
|
33
33
|
store.profiles[params.profileId] = params.credential;
|
|
34
34
|
saveAuthProfileStore(store, params.agentDir);
|
|
35
35
|
}
|
|
36
|
+
export async function upsertAuthProfileWithLock(params) {
|
|
37
|
+
return await updateAuthProfileStoreWithLock({
|
|
38
|
+
agentDir: params.agentDir,
|
|
39
|
+
updater: (store) => {
|
|
40
|
+
store.profiles[params.profileId] = params.credential;
|
|
41
|
+
return true;
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
36
45
|
export function listProfilesForProvider(store, provider) {
|
|
37
46
|
const providerKey = normalizeProviderId(provider);
|
|
38
47
|
return Object.entries(store.profiles)
|
|
@@ -4,7 +4,7 @@ export { formatAuthDoctorHint } from "./auth-profiles/doctor.js";
|
|
|
4
4
|
export { resolveApiKeyForProfile } from "./auth-profiles/oauth.js";
|
|
5
5
|
export { resolveAuthProfileOrder } from "./auth-profiles/order.js";
|
|
6
6
|
export { resolveAuthStorePathForDisplay } from "./auth-profiles/paths.js";
|
|
7
|
-
export { listProfilesForProvider, markAuthProfileGood, setAuthProfileOrder, upsertAuthProfile, } from "./auth-profiles/profiles.js";
|
|
7
|
+
export { listProfilesForProvider, markAuthProfileGood, setAuthProfileOrder, upsertAuthProfile, upsertAuthProfileWithLock, } from "./auth-profiles/profiles.js";
|
|
8
8
|
export { repairOAuthProfileIdMismatch, suggestOAuthProfileIdForLegacyDefault, } from "./auth-profiles/repair.js";
|
|
9
9
|
export { ensureAuthProfileStore, loadAuthProfileStore, saveAuthProfileStore, } from "./auth-profiles/store.js";
|
|
10
10
|
export { calculateAuthProfileCooldownMs, clearAuthProfileCooldown, isProfileInCooldown, markAuthProfileCooldown, markAuthProfileFailure, markAuthProfileUsed, resolveProfileUnusableUntilForDisplay, } from "./auth-profiles/usage.js";
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/** Hugging Face Inference Providers (router) — OpenAI-compatible chat completions. */
|
|
2
|
+
export const HUGGINGFACE_BASE_URL = "https://router.huggingface.co/v1";
|
|
3
|
+
/** Router policy suffixes: router picks backend by cost or speed; no specific provider selection. */
|
|
4
|
+
export const HUGGINGFACE_POLICY_SUFFIXES = ["cheapest", "fastest"];
|
|
5
|
+
/**
|
|
6
|
+
* True when the model ref uses :cheapest or :fastest. When true, provider choice is locked
|
|
7
|
+
* (router decides); do not show an interactive "prefer specific backend" option.
|
|
8
|
+
*/
|
|
9
|
+
export function isHuggingfacePolicyLocked(modelRef) {
|
|
10
|
+
const ref = String(modelRef).trim();
|
|
11
|
+
return HUGGINGFACE_POLICY_SUFFIXES.some((s) => ref.endsWith(`:${s}`) || ref === s);
|
|
12
|
+
}
|
|
13
|
+
/** Default cost when not in static catalog (HF pricing varies by provider). */
|
|
14
|
+
const HUGGINGFACE_DEFAULT_COST = {
|
|
15
|
+
input: 0,
|
|
16
|
+
output: 0,
|
|
17
|
+
cacheRead: 0,
|
|
18
|
+
cacheWrite: 0,
|
|
19
|
+
};
|
|
20
|
+
/** Defaults for models discovered from GET /v1/models. */
|
|
21
|
+
const HUGGINGFACE_DEFAULT_CONTEXT_WINDOW = 131072;
|
|
22
|
+
const HUGGINGFACE_DEFAULT_MAX_TOKENS = 8192;
|
|
23
|
+
export const HUGGINGFACE_MODEL_CATALOG = [
|
|
24
|
+
{
|
|
25
|
+
id: "deepseek-ai/DeepSeek-R1",
|
|
26
|
+
name: "DeepSeek R1",
|
|
27
|
+
reasoning: true,
|
|
28
|
+
input: ["text"],
|
|
29
|
+
contextWindow: 131072,
|
|
30
|
+
maxTokens: 8192,
|
|
31
|
+
cost: { input: 3.0, output: 7.0, cacheRead: 3.0, cacheWrite: 3.0 },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "deepseek-ai/DeepSeek-V3.1",
|
|
35
|
+
name: "DeepSeek V3.1",
|
|
36
|
+
reasoning: false,
|
|
37
|
+
input: ["text"],
|
|
38
|
+
contextWindow: 131072,
|
|
39
|
+
maxTokens: 8192,
|
|
40
|
+
cost: { input: 0.6, output: 1.25, cacheRead: 0.6, cacheWrite: 0.6 },
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
|
44
|
+
name: "Llama 3.3 70B Instruct Turbo",
|
|
45
|
+
reasoning: false,
|
|
46
|
+
input: ["text"],
|
|
47
|
+
contextWindow: 131072,
|
|
48
|
+
maxTokens: 8192,
|
|
49
|
+
cost: { input: 0.88, output: 0.88, cacheRead: 0.88, cacheWrite: 0.88 },
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "openai/gpt-oss-120b",
|
|
53
|
+
name: "GPT-OSS 120B",
|
|
54
|
+
reasoning: false,
|
|
55
|
+
input: ["text"],
|
|
56
|
+
contextWindow: 131072,
|
|
57
|
+
maxTokens: 8192,
|
|
58
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
export function buildHuggingfaceModelDefinition(model) {
|
|
62
|
+
return {
|
|
63
|
+
id: model.id,
|
|
64
|
+
name: model.name,
|
|
65
|
+
reasoning: model.reasoning,
|
|
66
|
+
input: model.input,
|
|
67
|
+
cost: model.cost,
|
|
68
|
+
contextWindow: model.contextWindow,
|
|
69
|
+
maxTokens: model.maxTokens,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Infer reasoning and display name from Hub-style model id (e.g. "deepseek-ai/DeepSeek-R1").
|
|
74
|
+
*/
|
|
75
|
+
function inferredMetaFromModelId(id) {
|
|
76
|
+
const base = id.split("/").pop() ?? id;
|
|
77
|
+
const reasoning = /r1|reasoning|thinking|reason/i.test(id) || /-\d+[tb]?-thinking/i.test(base);
|
|
78
|
+
const name = base.replace(/-/g, " ").replace(/\b(\w)/g, (c) => c.toUpperCase());
|
|
79
|
+
return { name, reasoning };
|
|
80
|
+
}
|
|
81
|
+
/** Prefer API-supplied display name, then owned_by/id, then inferred from id. */
|
|
82
|
+
function displayNameFromApiEntry(entry, inferredName) {
|
|
83
|
+
const fromApi = (typeof entry.name === "string" && entry.name.trim()) ||
|
|
84
|
+
(typeof entry.title === "string" && entry.title.trim()) ||
|
|
85
|
+
(typeof entry.display_name === "string" && entry.display_name.trim());
|
|
86
|
+
if (fromApi) {
|
|
87
|
+
return fromApi;
|
|
88
|
+
}
|
|
89
|
+
if (typeof entry.owned_by === "string" && entry.owned_by.trim()) {
|
|
90
|
+
const base = entry.id.split("/").pop() ?? entry.id;
|
|
91
|
+
return `${entry.owned_by.trim()}/${base}`;
|
|
92
|
+
}
|
|
93
|
+
return inferredName;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Discover chat-completion models from Hugging Face Inference Providers (GET /v1/models).
|
|
97
|
+
* Requires a valid HF token. Falls back to static catalog on failure or in test env.
|
|
98
|
+
*/
|
|
99
|
+
export async function discoverHuggingfaceModels(apiKey) {
|
|
100
|
+
if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") {
|
|
101
|
+
return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
|
102
|
+
}
|
|
103
|
+
const trimmedKey = apiKey?.trim();
|
|
104
|
+
if (!trimmedKey) {
|
|
105
|
+
return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
// GET https://router.huggingface.co/v1/models — response: { object, data: [{ id, owned_by, architecture: { input_modalities }, providers: [{ provider, context_length?, pricing? }] }] }. POST /v1/chat/completions requires Authorization.
|
|
109
|
+
const response = await fetch(`${HUGGINGFACE_BASE_URL}/models`, {
|
|
110
|
+
signal: AbortSignal.timeout(10_000),
|
|
111
|
+
headers: {
|
|
112
|
+
Authorization: `Bearer ${trimmedKey}`,
|
|
113
|
+
"Content-Type": "application/json",
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
console.warn(`[huggingface-models] GET /v1/models failed: HTTP ${response.status}, using static catalog`);
|
|
118
|
+
return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
|
119
|
+
}
|
|
120
|
+
const body = (await response.json());
|
|
121
|
+
const data = body?.data;
|
|
122
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
123
|
+
console.warn("[huggingface-models] No models in response, using static catalog");
|
|
124
|
+
return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
|
125
|
+
}
|
|
126
|
+
const catalogById = new Map(HUGGINGFACE_MODEL_CATALOG.map((m) => [m.id, m]));
|
|
127
|
+
const seen = new Set();
|
|
128
|
+
const models = [];
|
|
129
|
+
for (const entry of data) {
|
|
130
|
+
const id = typeof entry?.id === "string" ? entry.id.trim() : "";
|
|
131
|
+
if (!id || seen.has(id)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
seen.add(id);
|
|
135
|
+
const catalogEntry = catalogById.get(id);
|
|
136
|
+
if (catalogEntry) {
|
|
137
|
+
models.push(buildHuggingfaceModelDefinition(catalogEntry));
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const inferred = inferredMetaFromModelId(id);
|
|
141
|
+
const name = displayNameFromApiEntry(entry, inferred.name);
|
|
142
|
+
const modalities = entry.architecture?.input_modalities;
|
|
143
|
+
const input = Array.isArray(modalities) && modalities.includes("image") ? ["text", "image"] : ["text"];
|
|
144
|
+
const providers = Array.isArray(entry.providers) ? entry.providers : [];
|
|
145
|
+
const providerWithContext = providers.find((p) => typeof p?.context_length === "number" && p.context_length > 0);
|
|
146
|
+
const contextLength = providerWithContext?.context_length ?? HUGGINGFACE_DEFAULT_CONTEXT_WINDOW;
|
|
147
|
+
models.push({
|
|
148
|
+
id,
|
|
149
|
+
name,
|
|
150
|
+
reasoning: inferred.reasoning,
|
|
151
|
+
input,
|
|
152
|
+
cost: HUGGINGFACE_DEFAULT_COST,
|
|
153
|
+
contextWindow: contextLength,
|
|
154
|
+
maxTokens: HUGGINGFACE_DEFAULT_MAX_TOKENS,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return models.length > 0
|
|
159
|
+
? models
|
|
160
|
+
: HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.warn(`[huggingface-models] Discovery failed: ${String(error)}, using static catalog`);
|
|
164
|
+
return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -205,6 +205,9 @@ export function resolveEnvApiKey(provider) {
|
|
|
205
205
|
if (normalized === "kimi-coding") {
|
|
206
206
|
return pick("KIMI_API_KEY") ?? pick("KIMICODE_API_KEY");
|
|
207
207
|
}
|
|
208
|
+
if (normalized === "huggingface") {
|
|
209
|
+
return pick("HUGGINGFACE_HUB_TOKEN") ?? pick("HF_TOKEN");
|
|
210
|
+
}
|
|
208
211
|
const envMap = {
|
|
209
212
|
openai: "OPENAI_API_KEY",
|
|
210
213
|
google: "GEMINI_API_KEY",
|
|
@@ -214,6 +217,7 @@ export function resolveEnvApiKey(provider) {
|
|
|
214
217
|
cerebras: "CEREBRAS_API_KEY",
|
|
215
218
|
xai: "XAI_API_KEY",
|
|
216
219
|
openrouter: "OPENROUTER_API_KEY",
|
|
220
|
+
litellm: "LITELLM_API_KEY",
|
|
217
221
|
"vercel-ai-gateway": "AI_GATEWAY_API_KEY",
|
|
218
222
|
"cloudflare-ai-gateway": "CLOUDFLARE_AI_GATEWAY_API_KEY",
|
|
219
223
|
moonshot: "MOONSHOT_API_KEY",
|
|
@@ -225,7 +229,9 @@ export function resolveEnvApiKey(provider) {
|
|
|
225
229
|
opencode: "OPENCODE_API_KEY",
|
|
226
230
|
together: "TOGETHER_API_KEY",
|
|
227
231
|
qianfan: "QIANFAN_API_KEY",
|
|
232
|
+
nvidia: "NVIDIA_API_KEY",
|
|
228
233
|
ollama: "OLLAMA_API_KEY",
|
|
234
|
+
vllm: "VLLM_API_KEY",
|
|
229
235
|
};
|
|
230
236
|
const envVar = envMap[normalized];
|
|
231
237
|
if (!envVar)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { DEFAULT_CONTEXT_TOKENS } from "./defaults.js";
|
|
2
|
+
import { normalizeModelCompat } from "./model-compat.js";
|
|
3
|
+
import { normalizeProviderId } from "./model-selection.js";
|
|
4
|
+
const OPENAI_CODEX_GPT_53_MODEL_ID = "gpt-5.3-codex";
|
|
5
|
+
const OPENAI_CODEX_TEMPLATE_MODEL_IDS = ["gpt-5.2-codex"];
|
|
6
|
+
const ANTHROPIC_OPUS_46_MODEL_ID = "claude-opus-4-6";
|
|
7
|
+
const ANTHROPIC_OPUS_46_DOT_MODEL_ID = "claude-opus-4.6";
|
|
8
|
+
const ANTHROPIC_OPUS_TEMPLATE_MODEL_IDS = ["claude-opus-4-5", "claude-opus-4.5"];
|
|
9
|
+
const ZAI_GLM5_MODEL_ID = "glm-5";
|
|
10
|
+
const ZAI_GLM5_TEMPLATE_MODEL_IDS = ["glm-4.7"];
|
|
11
|
+
const ANTIGRAVITY_OPUS_46_MODEL_ID = "claude-opus-4-6";
|
|
12
|
+
const ANTIGRAVITY_OPUS_46_DOT_MODEL_ID = "claude-opus-4.6";
|
|
13
|
+
const ANTIGRAVITY_OPUS_TEMPLATE_MODEL_IDS = ["claude-opus-4-5", "claude-opus-4.5"];
|
|
14
|
+
const ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID = "claude-opus-4-6-thinking";
|
|
15
|
+
const ANTIGRAVITY_OPUS_46_DOT_THINKING_MODEL_ID = "claude-opus-4.6-thinking";
|
|
16
|
+
const ANTIGRAVITY_OPUS_THINKING_TEMPLATE_MODEL_IDS = [
|
|
17
|
+
"claude-opus-4-5-thinking",
|
|
18
|
+
"claude-opus-4.5-thinking",
|
|
19
|
+
];
|
|
20
|
+
export const ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES = [
|
|
21
|
+
{
|
|
22
|
+
id: ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID,
|
|
23
|
+
templatePrefixes: [
|
|
24
|
+
"google-antigravity/claude-opus-4-5-thinking",
|
|
25
|
+
"google-antigravity/claude-opus-4.5-thinking",
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: ANTIGRAVITY_OPUS_46_MODEL_ID,
|
|
30
|
+
templatePrefixes: ["google-antigravity/claude-opus-4-5", "google-antigravity/claude-opus-4.5"],
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
function resolveOpenAICodexGpt53FallbackModel(provider, modelId, modelRegistry) {
|
|
34
|
+
const normalizedProvider = normalizeProviderId(provider);
|
|
35
|
+
const trimmedModelId = modelId.trim();
|
|
36
|
+
if (normalizedProvider !== "openai-codex") {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
if (trimmedModelId.toLowerCase() !== OPENAI_CODEX_GPT_53_MODEL_ID) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
for (const templateId of OPENAI_CODEX_TEMPLATE_MODEL_IDS) {
|
|
43
|
+
const template = modelRegistry.find(normalizedProvider, templateId);
|
|
44
|
+
if (!template) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
return normalizeModelCompat({
|
|
48
|
+
...template,
|
|
49
|
+
id: trimmedModelId,
|
|
50
|
+
name: trimmedModelId,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return normalizeModelCompat({
|
|
54
|
+
id: trimmedModelId,
|
|
55
|
+
name: trimmedModelId,
|
|
56
|
+
api: "openai-codex-responses",
|
|
57
|
+
provider: normalizedProvider,
|
|
58
|
+
baseUrl: "https://chatgpt.com/backend-api",
|
|
59
|
+
reasoning: true,
|
|
60
|
+
input: ["text", "image"],
|
|
61
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
62
|
+
contextWindow: DEFAULT_CONTEXT_TOKENS,
|
|
63
|
+
maxTokens: DEFAULT_CONTEXT_TOKENS,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function resolveAnthropicOpus46ForwardCompatModel(provider, modelId, modelRegistry) {
|
|
67
|
+
const normalizedProvider = normalizeProviderId(provider);
|
|
68
|
+
if (normalizedProvider !== "anthropic") {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const trimmedModelId = modelId.trim();
|
|
72
|
+
const lower = trimmedModelId.toLowerCase();
|
|
73
|
+
const isOpus46 = lower === ANTHROPIC_OPUS_46_MODEL_ID ||
|
|
74
|
+
lower === ANTHROPIC_OPUS_46_DOT_MODEL_ID ||
|
|
75
|
+
lower.startsWith(`${ANTHROPIC_OPUS_46_MODEL_ID}-`) ||
|
|
76
|
+
lower.startsWith(`${ANTHROPIC_OPUS_46_DOT_MODEL_ID}-`);
|
|
77
|
+
if (!isOpus46) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const templateIds = [];
|
|
81
|
+
if (lower.startsWith(ANTHROPIC_OPUS_46_MODEL_ID)) {
|
|
82
|
+
templateIds.push(lower.replace(ANTHROPIC_OPUS_46_MODEL_ID, "claude-opus-4-5"));
|
|
83
|
+
}
|
|
84
|
+
if (lower.startsWith(ANTHROPIC_OPUS_46_DOT_MODEL_ID)) {
|
|
85
|
+
templateIds.push(lower.replace(ANTHROPIC_OPUS_46_DOT_MODEL_ID, "claude-opus-4.5"));
|
|
86
|
+
}
|
|
87
|
+
templateIds.push(...ANTHROPIC_OPUS_TEMPLATE_MODEL_IDS);
|
|
88
|
+
for (const templateId of [...new Set(templateIds)].filter(Boolean)) {
|
|
89
|
+
const template = modelRegistry.find(normalizedProvider, templateId);
|
|
90
|
+
if (!template) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
return normalizeModelCompat({
|
|
94
|
+
...template,
|
|
95
|
+
id: trimmedModelId,
|
|
96
|
+
name: trimmedModelId,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
// Z.ai's GLM-5 may not be present in pi-ai's built-in model catalog yet.
|
|
102
|
+
// When a user configures zai/glm-5 without a models.json entry, clone glm-4.7 as a forward-compat fallback.
|
|
103
|
+
function resolveZaiGlm5ForwardCompatModel(provider, modelId, modelRegistry) {
|
|
104
|
+
if (normalizeProviderId(provider) !== "zai") {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
const trimmed = modelId.trim();
|
|
108
|
+
const lower = trimmed.toLowerCase();
|
|
109
|
+
if (lower !== ZAI_GLM5_MODEL_ID && !lower.startsWith(`${ZAI_GLM5_MODEL_ID}-`)) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
for (const templateId of ZAI_GLM5_TEMPLATE_MODEL_IDS) {
|
|
113
|
+
const template = modelRegistry.find("zai", templateId);
|
|
114
|
+
if (!template) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
return normalizeModelCompat({
|
|
118
|
+
...template,
|
|
119
|
+
id: trimmed,
|
|
120
|
+
name: trimmed,
|
|
121
|
+
reasoning: true,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return normalizeModelCompat({
|
|
125
|
+
id: trimmed,
|
|
126
|
+
name: trimmed,
|
|
127
|
+
api: "openai-completions",
|
|
128
|
+
provider: "zai",
|
|
129
|
+
reasoning: true,
|
|
130
|
+
input: ["text"],
|
|
131
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
132
|
+
contextWindow: DEFAULT_CONTEXT_TOKENS,
|
|
133
|
+
maxTokens: DEFAULT_CONTEXT_TOKENS,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function resolveAntigravityOpus46ForwardCompatModel(provider, modelId, modelRegistry) {
|
|
137
|
+
const normalizedProvider = normalizeProviderId(provider);
|
|
138
|
+
if (normalizedProvider !== "google-antigravity") {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
const trimmedModelId = modelId.trim();
|
|
142
|
+
const lower = trimmedModelId.toLowerCase();
|
|
143
|
+
const isOpus46 = lower === ANTIGRAVITY_OPUS_46_MODEL_ID ||
|
|
144
|
+
lower === ANTIGRAVITY_OPUS_46_DOT_MODEL_ID ||
|
|
145
|
+
lower.startsWith(`${ANTIGRAVITY_OPUS_46_MODEL_ID}-`) ||
|
|
146
|
+
lower.startsWith(`${ANTIGRAVITY_OPUS_46_DOT_MODEL_ID}-`);
|
|
147
|
+
const isOpus46Thinking = lower === ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID ||
|
|
148
|
+
lower === ANTIGRAVITY_OPUS_46_DOT_THINKING_MODEL_ID ||
|
|
149
|
+
lower.startsWith(`${ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID}-`) ||
|
|
150
|
+
lower.startsWith(`${ANTIGRAVITY_OPUS_46_DOT_THINKING_MODEL_ID}-`);
|
|
151
|
+
if (!isOpus46 && !isOpus46Thinking) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
const templateIds = [];
|
|
155
|
+
if (lower.startsWith(ANTIGRAVITY_OPUS_46_MODEL_ID)) {
|
|
156
|
+
templateIds.push(lower.replace(ANTIGRAVITY_OPUS_46_MODEL_ID, "claude-opus-4-5"));
|
|
157
|
+
}
|
|
158
|
+
if (lower.startsWith(ANTIGRAVITY_OPUS_46_DOT_MODEL_ID)) {
|
|
159
|
+
templateIds.push(lower.replace(ANTIGRAVITY_OPUS_46_DOT_MODEL_ID, "claude-opus-4.5"));
|
|
160
|
+
}
|
|
161
|
+
if (lower.startsWith(ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID)) {
|
|
162
|
+
templateIds.push(lower.replace(ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID, "claude-opus-4-5-thinking"));
|
|
163
|
+
}
|
|
164
|
+
if (lower.startsWith(ANTIGRAVITY_OPUS_46_DOT_THINKING_MODEL_ID)) {
|
|
165
|
+
templateIds.push(lower.replace(ANTIGRAVITY_OPUS_46_DOT_THINKING_MODEL_ID, "claude-opus-4.5-thinking"));
|
|
166
|
+
}
|
|
167
|
+
templateIds.push(...ANTIGRAVITY_OPUS_TEMPLATE_MODEL_IDS);
|
|
168
|
+
templateIds.push(...ANTIGRAVITY_OPUS_THINKING_TEMPLATE_MODEL_IDS);
|
|
169
|
+
for (const templateId of [...new Set(templateIds)].filter(Boolean)) {
|
|
170
|
+
const template = modelRegistry.find(normalizedProvider, templateId);
|
|
171
|
+
if (!template) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
return normalizeModelCompat({
|
|
175
|
+
...template,
|
|
176
|
+
id: trimmedModelId,
|
|
177
|
+
name: trimmedModelId,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
export function resolveForwardCompatModel(provider, modelId, modelRegistry) {
|
|
183
|
+
return (resolveOpenAICodexGpt53FallbackModel(provider, modelId, modelRegistry) ??
|
|
184
|
+
resolveAnthropicOpus46ForwardCompatModel(provider, modelId, modelRegistry) ??
|
|
185
|
+
resolveZaiGlm5ForwardCompatModel(provider, modelId, modelRegistry) ??
|
|
186
|
+
resolveAntigravityOpus46ForwardCompatModel(provider, modelId, modelRegistry));
|
|
187
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { discoverAuthStorage, discoverModels } from "../pi-model-discovery.js";
|
|
2
1
|
import { resolvePoolbotAgentDir } from "../agent-paths.js";
|
|
3
2
|
import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js";
|
|
4
3
|
import { normalizeModelCompat } from "../model-compat.js";
|
|
4
|
+
import { resolveForwardCompatModel } from "../model-forward-compat.js";
|
|
5
5
|
import { normalizeProviderId } from "../model-selection.js";
|
|
6
|
+
import { discoverAuthStorage, discoverModels, } from "../pi-model-discovery.js";
|
|
6
7
|
export function buildInlineProviderModels(providers) {
|
|
7
8
|
return Object.entries(providers).flatMap(([providerId, entry]) => {
|
|
8
9
|
const trimmed = providerId.trim();
|
|
@@ -12,7 +13,7 @@ export function buildInlineProviderModels(providers) {
|
|
|
12
13
|
...model,
|
|
13
14
|
provider: trimmed,
|
|
14
15
|
baseUrl: entry?.baseUrl,
|
|
15
|
-
|
|
16
|
+
api: model.api ?? entry?.api,
|
|
16
17
|
}));
|
|
17
18
|
});
|
|
18
19
|
}
|
|
@@ -32,65 +33,12 @@ export function buildModelAliasLines(cfg) {
|
|
|
32
33
|
.toSorted((a, b) => a.alias.localeCompare(b.alias))
|
|
33
34
|
.map((entry) => `- ${entry.alias}: ${entry.model}`);
|
|
34
35
|
}
|
|
35
|
-
// Well-known model IDs for forward-compatible resolution
|
|
36
|
-
export const OPENAI_CODEX_GPT_53_MODEL_ID = "codex-gpt-5.3";
|
|
37
|
-
export const OPENAI_CODEX_TEMPLATE_MODEL_IDS = ["codex-mini-*"];
|
|
38
|
-
export const ANTHROPIC_OPUS_46_MODEL_ID = "claude-opus-4-0624";
|
|
39
|
-
export const ANTHROPIC_OPUS_46_DOT_MODEL_ID = "claude-opus-4.6";
|
|
40
|
-
export const ANTHROPIC_OPUS_TEMPLATE_MODEL_IDS = ["claude-opus-4-*", "claude-opus-4.*"];
|
|
41
|
-
/**
|
|
42
|
-
* Resolve forward-compat fallback for OpenAI codex-gpt-5.3 requests.
|
|
43
|
-
* If the exact model is not in the registry, try `codex-mini-*` template matches.
|
|
44
|
-
*/
|
|
45
|
-
export function resolveOpenAICodexGpt53FallbackModel(modelRegistry, provider) {
|
|
46
|
-
for (const templateId of OPENAI_CODEX_TEMPLATE_MODEL_IDS) {
|
|
47
|
-
const match = modelRegistry.find(provider, templateId);
|
|
48
|
-
if (match)
|
|
49
|
-
return match;
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Resolve forward-compat fallback for Anthropic claude-opus-4.6 requests.
|
|
55
|
-
* If the exact model is not in the registry, try `claude-opus-4-*` template matches.
|
|
56
|
-
*/
|
|
57
|
-
export function resolveAnthropicOpus46ForwardCompatModel(modelRegistry, provider, modelId) {
|
|
58
|
-
if (modelId !== ANTHROPIC_OPUS_46_MODEL_ID && modelId !== ANTHROPIC_OPUS_46_DOT_MODEL_ID) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
for (const templateId of ANTHROPIC_OPUS_TEMPLATE_MODEL_IDS) {
|
|
62
|
-
const match = modelRegistry.find(provider, templateId);
|
|
63
|
-
if (match)
|
|
64
|
-
return match;
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
36
|
export function resolveModel(provider, modelId, agentDir, cfg) {
|
|
69
37
|
const resolvedAgentDir = agentDir ?? resolvePoolbotAgentDir();
|
|
70
38
|
const authStorage = discoverAuthStorage(resolvedAgentDir);
|
|
71
39
|
const modelRegistry = discoverModels(authStorage, resolvedAgentDir);
|
|
72
40
|
const model = modelRegistry.find(provider, modelId);
|
|
73
41
|
if (!model) {
|
|
74
|
-
// Try codex forward-compat fallback
|
|
75
|
-
if (modelId === OPENAI_CODEX_GPT_53_MODEL_ID) {
|
|
76
|
-
const codexFallback = resolveOpenAICodexGpt53FallbackModel(modelRegistry, provider);
|
|
77
|
-
if (codexFallback) {
|
|
78
|
-
return {
|
|
79
|
-
model: normalizeModelCompat(codexFallback),
|
|
80
|
-
authStorage,
|
|
81
|
-
modelRegistry,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
// Try Anthropic opus forward-compat fallback
|
|
86
|
-
const opusFallback = resolveAnthropicOpus46ForwardCompatModel(modelRegistry, provider, modelId);
|
|
87
|
-
if (opusFallback) {
|
|
88
|
-
return {
|
|
89
|
-
model: normalizeModelCompat(opusFallback),
|
|
90
|
-
authStorage,
|
|
91
|
-
modelRegistry,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
42
|
const providers = cfg?.models?.providers ?? {};
|
|
95
43
|
const inlineModels = buildInlineProviderModels(providers);
|
|
96
44
|
const normalizedProvider = normalizeProviderId(provider);
|
|
@@ -103,6 +51,12 @@ export function resolveModel(provider, modelId, agentDir, cfg) {
|
|
|
103
51
|
modelRegistry,
|
|
104
52
|
};
|
|
105
53
|
}
|
|
54
|
+
// Forward-compat fallbacks must be checked BEFORE the generic providerCfg fallback.
|
|
55
|
+
// Otherwise, configured providers can default to a generic API and break specific transports.
|
|
56
|
+
const forwardCompat = resolveForwardCompatModel(provider, modelId, modelRegistry);
|
|
57
|
+
if (forwardCompat) {
|
|
58
|
+
return { model: forwardCompat, authStorage, modelRegistry };
|
|
59
|
+
}
|
|
106
60
|
const providerCfg = providers[provider];
|
|
107
61
|
if (providerCfg || modelId.startsWith("mock-")) {
|
|
108
62
|
const fallbackModel = normalizeModelCompat({
|
|
@@ -110,12 +64,12 @@ export function resolveModel(provider, modelId, agentDir, cfg) {
|
|
|
110
64
|
name: modelId,
|
|
111
65
|
api: providerCfg?.api ?? "openai-responses",
|
|
112
66
|
provider,
|
|
67
|
+
baseUrl: providerCfg?.baseUrl,
|
|
113
68
|
reasoning: false,
|
|
114
69
|
input: ["text"],
|
|
115
70
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
116
71
|
contextWindow: providerCfg?.models?.[0]?.contextWindow ?? DEFAULT_CONTEXT_TOKENS,
|
|
117
72
|
maxTokens: providerCfg?.models?.[0]?.maxTokens ?? DEFAULT_CONTEXT_TOKENS,
|
|
118
|
-
baseUrl: providerCfg?.baseUrl,
|
|
119
73
|
});
|
|
120
74
|
return { model: fallbackModel, authStorage, modelRegistry };
|
|
121
75
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const DEFAULT_CLAWD_BROWSER_ENABLED = true;
|
|
2
2
|
export const DEFAULT_BROWSER_EVALUATE_ENABLED = true;
|
|
3
|
-
export const DEFAULT_CLAWD_BROWSER_COLOR = "#
|
|
3
|
+
export const DEFAULT_CLAWD_BROWSER_COLOR = "#A855F7";
|
|
4
4
|
export const DEFAULT_CLAWD_BROWSER_PROFILE_NAME = "clawd";
|
|
5
5
|
export const DEFAULT_BROWSER_DEFAULT_PROFILE_NAME = "chrome";
|
|
6
6
|
export const DEFAULT_AI_SNAPSHOT_MAX_CHARS = 80_000;
|
package/dist/browser/profiles.js
CHANGED
package/dist/build-info.json
CHANGED
package/dist/cli/config-cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import JSON5 from "json5";
|
|
2
|
-
import { readConfigFileSnapshot, writeConfigFile } from "../config/config.js";
|
|
2
|
+
import { readConfigFileSnapshot, validateConfigObjectWithPlugins, writeConfigFile, } from "../config/config.js";
|
|
3
3
|
import { danger, info } from "../globals.js";
|
|
4
4
|
import { defaultRuntime } from "../runtime.js";
|
|
5
5
|
import { formatDocsLink } from "../terminal/links.js";
|
|
@@ -259,7 +259,14 @@ export function registerConfigCli(program) {
|
|
|
259
259
|
const snapshot = await loadValidConfig();
|
|
260
260
|
const next = snapshot.config;
|
|
261
261
|
setAtPath(next, parsedPath, parsedValue);
|
|
262
|
-
|
|
262
|
+
const validated = validateConfigObjectWithPlugins(next);
|
|
263
|
+
if (!validated.ok) {
|
|
264
|
+
const issue = validated.issues[0];
|
|
265
|
+
defaultRuntime.error(danger(`Config invalid after set: ${issue.path}: ${issue.message}`));
|
|
266
|
+
defaultRuntime.exit(1);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
await writeConfigFile(validated.config);
|
|
263
270
|
defaultRuntime.log(info(`Updated ${path}. Restart the gateway to apply.`));
|
|
264
271
|
}
|
|
265
272
|
catch (err) {
|
|
@@ -284,7 +291,14 @@ export function registerConfigCli(program) {
|
|
|
284
291
|
defaultRuntime.exit(1);
|
|
285
292
|
return;
|
|
286
293
|
}
|
|
287
|
-
|
|
294
|
+
const validated = validateConfigObjectWithPlugins(next);
|
|
295
|
+
if (!validated.ok) {
|
|
296
|
+
const issue = validated.issues[0];
|
|
297
|
+
defaultRuntime.error(danger(`Config invalid after unset: ${issue.path}: ${issue.message}`));
|
|
298
|
+
defaultRuntime.exit(1);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
await writeConfigFile(validated.config);
|
|
288
302
|
defaultRuntime.log(info(`Removed ${path}. Restart the gateway to apply.`));
|
|
289
303
|
}
|
|
290
304
|
catch (err) {
|