@eddyskywalker/dsh-chatgpt-subscription 0.10.14 → 0.10.20
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/CHANGELOG.md +42 -1
- package/README.md +8 -4
- package/lib/client.js +641 -121
- package/lib/client.js.map +1 -1
- package/lib/index.js +2040 -305
- package/lib/types/client/ProviderHubSection.d.ts.map +1 -1
- package/lib/types/client/command-code/CommandCodeSection.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +1 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +4 -4
- package/lib/types/client/ollama/OllamaSection.d.ts +25 -0
- package/lib/types/client/ollama/OllamaSection.d.ts.map +1 -0
- package/lib/types/client/ollama/api.d.ts +36 -0
- package/lib/types/client/ollama/api.d.ts.map +1 -0
- package/lib/types/client/ollama/locales.d.ts +413 -0
- package/lib/types/client/ollama/locales.d.ts.map +1 -0
- package/lib/types/host/antigravity/adapter.d.ts.map +1 -1
- package/lib/types/host/claude/adapter.d.ts +3 -5
- package/lib/types/host/claude/adapter.d.ts.map +1 -1
- package/lib/types/host/claude/model-catalog.d.ts +30 -16
- package/lib/types/host/claude/model-catalog.d.ts.map +1 -1
- package/lib/types/host/command-code/adapter.d.ts.map +1 -1
- package/lib/types/host/command-code/mapper.d.ts.map +1 -1
- package/lib/types/host/command-code/model-catalog.d.ts +19 -2
- package/lib/types/host/command-code/model-catalog.d.ts.map +1 -1
- package/lib/types/host/command-code/types.d.ts +29 -1
- package/lib/types/host/command-code/types.d.ts.map +1 -1
- package/lib/types/host/common/output-reservation.d.ts +11 -0
- package/lib/types/host/common/output-reservation.d.ts.map +1 -0
- package/lib/types/host/kimi-code/adapter.d.ts.map +1 -1
- package/lib/types/host/kimi-code/model-catalog.d.ts +1 -1
- package/lib/types/host/kimi-code/model-catalog.d.ts.map +1 -1
- package/lib/types/host/minimax-code/adapter.d.ts +2 -3
- package/lib/types/host/minimax-code/adapter.d.ts.map +1 -1
- package/lib/types/host/model-catalog.d.ts.map +1 -1
- package/lib/types/host/ollama/account-pool.d.ts +65 -0
- package/lib/types/host/ollama/account-pool.d.ts.map +1 -0
- package/lib/types/host/ollama/adapter.d.ts +47 -0
- package/lib/types/host/ollama/adapter.d.ts.map +1 -0
- package/lib/types/host/ollama/client.d.ts +101 -0
- package/lib/types/host/ollama/client.d.ts.map +1 -0
- package/lib/types/host/ollama/mapper.d.ts +44 -0
- package/lib/types/host/ollama/mapper.d.ts.map +1 -0
- package/lib/types/host/ollama/routes.d.ts +22 -0
- package/lib/types/host/ollama/routes.d.ts.map +1 -0
- package/lib/types/host/ollama/token-store.d.ts +73 -0
- package/lib/types/host/ollama/token-store.d.ts.map +1 -0
- package/lib/types/host/ollama/types.d.ts +115 -0
- package/lib/types/host/ollama/types.d.ts.map +1 -0
- package/lib/types/host/responses-mapper.d.ts +0 -2
- package/lib/types/host/responses-mapper.d.ts.map +1 -1
- package/lib/types/host/search-provider-switcher.d.ts +12 -0
- package/lib/types/host/search-provider-switcher.d.ts.map +1 -1
- package/lib/types/host/workbuddy/adapter.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/shared/command-code-contracts.d.ts +10 -4
- package/lib/types/shared/command-code-contracts.d.ts.map +1 -1
- package/lib/types/shared/model-catalog.d.ts +58 -1
- package/lib/types/shared/model-catalog.d.ts.map +1 -1
- package/lib/types/shared/ollama-contracts.d.ts +36 -0
- package/lib/types/shared/ollama-contracts.d.ts.map +1 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -19,6 +19,21 @@ import { ProxyAgent, fetch as fetch$1 } from "undici";
|
|
|
19
19
|
import * as SettingsModule from "@deepseek-ai/dsh-settings";
|
|
20
20
|
import { idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-timeout";
|
|
21
21
|
import { URL as URL$1, URLSearchParams as URLSearchParams$1, fileURLToPath } from "node:url";
|
|
22
|
+
//#region src/host/common/output-reservation.ts
|
|
23
|
+
/**
|
|
24
|
+
* Keep fixed defaults only when they fit DSH compaction-basic's default policy.
|
|
25
|
+
* This is host metadata, NEVER a wire cap. Callers must retain their original
|
|
26
|
+
* wire fallback when the metadata is omitted. Explicit request caps still win.
|
|
27
|
+
* Custom compaction policies and windows too small even at zero reservation
|
|
28
|
+
* require deployment configuration; do not invent a larger context window.
|
|
29
|
+
*/
|
|
30
|
+
function outputReservation(contextWindow, maxTokens) {
|
|
31
|
+
const messageBudget = contextWindow - maxTokens;
|
|
32
|
+
const threshold = Math.floor(Math.min(contextWindow * .8, messageBudget - 65536));
|
|
33
|
+
const retain = Math.floor(messageBudget * .16);
|
|
34
|
+
return Number.isSafeInteger(maxTokens) && maxTokens > 0 && threshold > 0 && retain < threshold ? { defaultMaxTokens: maxTokens } : {};
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
22
37
|
//#region src/shared/minimax-code-contracts.ts
|
|
23
38
|
const MINIMAX_CODE_PROVIDER_ID = "minimax-code";
|
|
24
39
|
const MINIMAX_CODE_PROVIDER_NAME = "MiniMax Code(编程订阅)";
|
|
@@ -78,7 +93,7 @@ const CODEX_ORIGINATOR = "opencode";
|
|
|
78
93
|
/** OAuth presents the same identity the request path does (see above). */
|
|
79
94
|
const OAUTH_ORIGINATOR = CODEX_ORIGINATOR;
|
|
80
95
|
const TOKEN_REFRESH_MARGIN_MS = 6e4;
|
|
81
|
-
const ROUTE_PREFIX$
|
|
96
|
+
const ROUTE_PREFIX$5 = "/api/dsh-chatgpt-subscription";
|
|
82
97
|
const PLUGIN_VERSION = "0.1.0-alpha.0";
|
|
83
98
|
const CODEX_CHATGPT_PROVIDER_ID = "codex-chatgpt";
|
|
84
99
|
const CODEX_API_BASE = "https://chatgpt.com/backend-api/codex";
|
|
@@ -122,7 +137,9 @@ const CODEX_MODEL_CATALOG = [
|
|
|
122
137
|
defaultReasoningEffort: "medium",
|
|
123
138
|
reasoningProfile: "gpt-5.6",
|
|
124
139
|
supportsReasoningSummary: true,
|
|
125
|
-
fallbackModelId: "gpt-5.6-terra"
|
|
140
|
+
fallbackModelId: "gpt-5.6-terra",
|
|
141
|
+
supportsOutputVerbosity: true,
|
|
142
|
+
defaultOutputVerbosity: "low"
|
|
126
143
|
},
|
|
127
144
|
{
|
|
128
145
|
id: "gpt-6-astra",
|
|
@@ -132,7 +149,9 @@ const CODEX_MODEL_CATALOG = [
|
|
|
132
149
|
defaultReasoningEffort: "medium",
|
|
133
150
|
reasoningProfile: "gpt-6",
|
|
134
151
|
supportsReasoningSummary: true,
|
|
135
|
-
maxTokens: GPT_6_MAX_TOKENS
|
|
152
|
+
maxTokens: GPT_6_MAX_TOKENS,
|
|
153
|
+
supportsOutputVerbosity: true,
|
|
154
|
+
defaultOutputVerbosity: "low"
|
|
136
155
|
},
|
|
137
156
|
{
|
|
138
157
|
id: "gpt-6.1-sol",
|
|
@@ -142,7 +161,9 @@ const CODEX_MODEL_CATALOG = [
|
|
|
142
161
|
defaultReasoningEffort: "medium",
|
|
143
162
|
reasoningProfile: "gpt-6",
|
|
144
163
|
supportsReasoningSummary: true,
|
|
145
|
-
maxTokens: GPT_6_MAX_TOKENS
|
|
164
|
+
maxTokens: GPT_6_MAX_TOKENS,
|
|
165
|
+
supportsOutputVerbosity: true,
|
|
166
|
+
defaultOutputVerbosity: "low"
|
|
146
167
|
},
|
|
147
168
|
{
|
|
148
169
|
id: "gpt-6-sol",
|
|
@@ -152,7 +173,9 @@ const CODEX_MODEL_CATALOG = [
|
|
|
152
173
|
defaultReasoningEffort: "medium",
|
|
153
174
|
reasoningProfile: "gpt-6",
|
|
154
175
|
supportsReasoningSummary: true,
|
|
155
|
-
maxTokens: GPT_6_MAX_TOKENS
|
|
176
|
+
maxTokens: GPT_6_MAX_TOKENS,
|
|
177
|
+
supportsOutputVerbosity: true,
|
|
178
|
+
defaultOutputVerbosity: "low"
|
|
156
179
|
},
|
|
157
180
|
{
|
|
158
181
|
id: "gpt-6-luna",
|
|
@@ -162,7 +185,9 @@ const CODEX_MODEL_CATALOG = [
|
|
|
162
185
|
defaultReasoningEffort: "medium",
|
|
163
186
|
reasoningProfile: "gpt-6",
|
|
164
187
|
supportsReasoningSummary: true,
|
|
165
|
-
maxTokens: GPT_6_MAX_TOKENS
|
|
188
|
+
maxTokens: GPT_6_MAX_TOKENS,
|
|
189
|
+
supportsOutputVerbosity: true,
|
|
190
|
+
defaultOutputVerbosity: "low"
|
|
166
191
|
},
|
|
167
192
|
{
|
|
168
193
|
id: "gpt-5.6-terra",
|
|
@@ -172,7 +197,9 @@ const CODEX_MODEL_CATALOG = [
|
|
|
172
197
|
defaultReasoningEffort: "medium",
|
|
173
198
|
reasoningProfile: "gpt-5.6",
|
|
174
199
|
supportsReasoningSummary: true,
|
|
175
|
-
fallbackModelId: "gpt-5.5"
|
|
200
|
+
fallbackModelId: "gpt-5.5",
|
|
201
|
+
supportsOutputVerbosity: true,
|
|
202
|
+
defaultOutputVerbosity: "low"
|
|
176
203
|
},
|
|
177
204
|
{
|
|
178
205
|
id: "gpt-5.6-luna",
|
|
@@ -182,7 +209,9 @@ const CODEX_MODEL_CATALOG = [
|
|
|
182
209
|
defaultReasoningEffort: "medium",
|
|
183
210
|
reasoningProfile: "gpt-5.6",
|
|
184
211
|
supportsReasoningSummary: true,
|
|
185
|
-
fallbackModelId: "gpt-5.5"
|
|
212
|
+
fallbackModelId: "gpt-5.5",
|
|
213
|
+
supportsOutputVerbosity: true,
|
|
214
|
+
defaultOutputVerbosity: "low"
|
|
186
215
|
},
|
|
187
216
|
{
|
|
188
217
|
id: "gpt-5.5",
|
|
@@ -191,7 +220,9 @@ const CODEX_MODEL_CATALOG = [
|
|
|
191
220
|
inputModalities: ["text", "image"],
|
|
192
221
|
defaultReasoningEffort: "medium",
|
|
193
222
|
reasoningProfile: "standard",
|
|
194
|
-
supportsReasoningSummary: true
|
|
223
|
+
supportsReasoningSummary: true,
|
|
224
|
+
supportsOutputVerbosity: true,
|
|
225
|
+
defaultOutputVerbosity: "low"
|
|
195
226
|
},
|
|
196
227
|
{
|
|
197
228
|
id: "gpt-5.4",
|
|
@@ -275,7 +306,44 @@ function codexModelSupportsImageInput(model) {
|
|
|
275
306
|
function codexModelSupportsReasoningSummary(model) {
|
|
276
307
|
return resolveCodexCatalogEntry(model).supportsReasoningSummary;
|
|
277
308
|
}
|
|
278
|
-
/**
|
|
309
|
+
/**
|
|
310
|
+
* One catalog entry by exact id, or undefined when this table has no record.
|
|
311
|
+
*
|
|
312
|
+
* Deliberately NOT {@link resolveCodexCatalogEntry}, which answers an unknown id
|
|
313
|
+
* with the default entry: that substitution is the right guide for a model whose
|
|
314
|
+
* capabilities only need a sensible floor, but it cannot tell "this model
|
|
315
|
+
* declares verbosity support" apart from "nobody said". A wire field must not be
|
|
316
|
+
* invented from silence.
|
|
317
|
+
*/
|
|
318
|
+
function codexCatalogEntryById(model) {
|
|
319
|
+
return CODEX_MODEL_CATALOG.find((entry) => entry.id === model);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Verbosity to send when the user has not chosen one.
|
|
323
|
+
*
|
|
324
|
+
* "Follow the provider" has to mean what the provider's own client sends. The
|
|
325
|
+
* official catalog declares `default_verbosity: "low"` for every model it
|
|
326
|
+
* lists, so the answer is low — not the server's implicit medium, which is what
|
|
327
|
+
* omitting the field gets.
|
|
328
|
+
*
|
|
329
|
+
* @returns the verbosity to send, or `undefined` to omit the field entirely.
|
|
330
|
+
*/
|
|
331
|
+
function codexDefaultOutputVerbosity(model) {
|
|
332
|
+
const entry = codexCatalogEntryById(model);
|
|
333
|
+
if (entry?.supportsOutputVerbosity !== true) return void 0;
|
|
334
|
+
return entry.defaultOutputVerbosity ?? "low";
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Declared output ceiling for one model, or the pre-GPT-6 default when it
|
|
338
|
+
* declares none.
|
|
339
|
+
*
|
|
340
|
+
* This is NOT a request field. The subscription Responses endpoint rejects
|
|
341
|
+
* `max_output_tokens` outright (400 Unsupported parameter, issue #29 — and the
|
|
342
|
+
* official CLI's own request struct has no such field), so nothing is sent to
|
|
343
|
+
* the wire from here. The value reaches DSH only as the adapter's
|
|
344
|
+
* `defaultMaxTokens`, which compaction uses as a local completion reservation
|
|
345
|
+
* when it plans a fold; that number never leaves this process.
|
|
346
|
+
*/
|
|
279
347
|
function codexModelMaxTokens(model) {
|
|
280
348
|
return resolveCodexCatalogEntry(model).maxTokens ?? 32768;
|
|
281
349
|
}
|
|
@@ -296,26 +364,31 @@ function resolveCodexFallbackModel(model) {
|
|
|
296
364
|
}
|
|
297
365
|
//#endregion
|
|
298
366
|
//#region src/host/model-catalog.ts
|
|
299
|
-
const PROVIDER_ID$
|
|
300
|
-
const PROVIDER_NAME$
|
|
367
|
+
const PROVIDER_ID$7 = CODEX_CHATGPT_PROVIDER_ID;
|
|
368
|
+
const PROVIDER_NAME$7 = "Codex(ChatGPT 订阅)";
|
|
301
369
|
function listCodexModels(preferences, live) {
|
|
302
370
|
const status = preferences?.status();
|
|
303
371
|
if (status?.enabled === false) return [];
|
|
304
|
-
const
|
|
372
|
+
const listed = live !== void 0 && live.length > 0 ? live.map((entry) => ({
|
|
305
373
|
id: entry.id,
|
|
306
374
|
name: entry.name,
|
|
307
375
|
inputModalities: entry.inputModalities
|
|
308
|
-
})) :
|
|
376
|
+
})) : void 0;
|
|
377
|
+
const visible = new Set(status?.visibleModelIds ?? (listed ?? shippedModels()).map((entry) => entry.id));
|
|
378
|
+
const selected = (listed ?? shippedModels()).filter((entry) => visible.has(entry.id));
|
|
379
|
+
return (listed === void 0 || selected.length > 0 ? selected : shippedModels().filter((entry) => visible.has(entry.id))).map((entry) => ({
|
|
380
|
+
provider: PROVIDER_ID$7,
|
|
309
381
|
id: entry.id,
|
|
310
382
|
name: entry.name,
|
|
311
|
-
inputModalities: entry.inputModalities
|
|
383
|
+
inputModalities: [...entry.inputModalities]
|
|
312
384
|
}));
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
385
|
+
}
|
|
386
|
+
/** The shipped table as picker rows. */
|
|
387
|
+
function shippedModels() {
|
|
388
|
+
return CODEX_MODEL_CATALOG.map((entry) => ({
|
|
316
389
|
id: entry.id,
|
|
317
390
|
name: entry.name,
|
|
318
|
-
inputModalities:
|
|
391
|
+
inputModalities: entry.inputModalities
|
|
319
392
|
}));
|
|
320
393
|
}
|
|
321
394
|
function resolveCodexModel(model, preferences, live) {
|
|
@@ -327,12 +400,12 @@ function resolveCodexModel(model, preferences, live) {
|
|
|
327
400
|
const statedDefault = liveEntry?.defaultReasoningEffort ?? entry.defaultReasoningEffort;
|
|
328
401
|
const defaultEffort = isCodexReasoningEffort(statedDefault) && efforts.includes(statedDefault) ? statedDefault : efforts[0];
|
|
329
402
|
return {
|
|
330
|
-
provider: PROVIDER_ID$
|
|
403
|
+
provider: PROVIDER_ID$7,
|
|
331
404
|
id: model,
|
|
332
405
|
name: liveEntry?.name ?? (entry.id === model ? entry.name : model),
|
|
333
406
|
inputModalities: [...liveEntry?.inputModalities ?? entry.inputModalities],
|
|
334
407
|
context: { contextWindow: configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow },
|
|
335
|
-
|
|
408
|
+
...outputReservation(configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow, codexModelMaxTokens(model)),
|
|
336
409
|
reasoning: {
|
|
337
410
|
efforts: efforts.map((effort) => ({
|
|
338
411
|
id: ReasoningEffortId(effort),
|
|
@@ -344,7 +417,7 @@ function resolveCodexModel(model, preferences, live) {
|
|
|
344
417
|
}
|
|
345
418
|
//#endregion
|
|
346
419
|
//#region src/host/adapter.ts
|
|
347
|
-
const RETRY_POLICY$
|
|
420
|
+
const RETRY_POLICY$6 = resolveRetryPolicy({
|
|
348
421
|
mode: "normal",
|
|
349
422
|
maxRetries: 3,
|
|
350
423
|
retryableCodes: [
|
|
@@ -374,11 +447,11 @@ var CodexChatGptAdapter = class extends LlmAdapter {
|
|
|
374
447
|
providerInfo(provider) {
|
|
375
448
|
return {
|
|
376
449
|
id: provider,
|
|
377
|
-
name: PROVIDER_NAME$
|
|
450
|
+
name: PROVIDER_NAME$7
|
|
378
451
|
};
|
|
379
452
|
}
|
|
380
453
|
providerRetryPolicy() {
|
|
381
|
-
return RETRY_POLICY$
|
|
454
|
+
return RETRY_POLICY$6;
|
|
382
455
|
}
|
|
383
456
|
imageRequestPricing(_provider, _model) {}
|
|
384
457
|
async listModels() {
|
|
@@ -2625,7 +2698,7 @@ async function oauthErrorIdentifier(response) {
|
|
|
2625
2698
|
function codexPoolPath() {
|
|
2626
2699
|
return path.join(dshHomeDir(), "storages", "codex-pool.json");
|
|
2627
2700
|
}
|
|
2628
|
-
function optionalString$
|
|
2701
|
+
function optionalString$9(record, key) {
|
|
2629
2702
|
const value = record[key];
|
|
2630
2703
|
if (value === void 0 || value === null) return void 0;
|
|
2631
2704
|
if (typeof value !== "string") throw new Error("ChatGPT pool account field is invalid");
|
|
@@ -2648,11 +2721,11 @@ function parseCodexPoolData(value) {
|
|
|
2648
2721
|
addedAt: typeof raw.addedAt === "number" ? raw.addedAt : Date.now(),
|
|
2649
2722
|
isPrimary: raw.isPrimary === true
|
|
2650
2723
|
};
|
|
2651
|
-
const email = optionalString$
|
|
2724
|
+
const email = optionalString$9(raw, "email") ?? credentials.email;
|
|
2652
2725
|
if (email !== void 0) account.email = email;
|
|
2653
|
-
const planType = optionalString$
|
|
2726
|
+
const planType = optionalString$9(raw, "planType") ?? credentials.planType;
|
|
2654
2727
|
if (planType !== void 0) account.planType = planType;
|
|
2655
|
-
const accountId = optionalString$
|
|
2728
|
+
const accountId = optionalString$9(raw, "accountId") ?? credentials.accountId;
|
|
2656
2729
|
if (accountId !== void 0) account.accountId = accountId;
|
|
2657
2730
|
if (typeof raw.lastUsedAt === "number") account.lastUsedAt = raw.lastUsedAt;
|
|
2658
2731
|
if (typeof raw.cooldownUntil === "number") account.cooldownUntil = raw.cooldownUntil;
|
|
@@ -3409,8 +3482,7 @@ function sendSearch(fetchFn, credentials, query, model, id, signal) {
|
|
|
3409
3482
|
settings: {
|
|
3410
3483
|
allowed_callers: ["direct"],
|
|
3411
3484
|
external_web_access: true
|
|
3412
|
-
}
|
|
3413
|
-
max_output_tokens: 4096
|
|
3485
|
+
}
|
|
3414
3486
|
}),
|
|
3415
3487
|
signal
|
|
3416
3488
|
});
|
|
@@ -3673,8 +3745,8 @@ var ProxyManager = class {
|
|
|
3673
3745
|
};
|
|
3674
3746
|
//#endregion
|
|
3675
3747
|
//#region src/host/antigravity/types.ts
|
|
3676
|
-
const PROVIDER_NAME$
|
|
3677
|
-
const PROVIDER_ID$
|
|
3748
|
+
const PROVIDER_NAME$6 = "Antigravity";
|
|
3749
|
+
const PROVIDER_ID$6 = "antigravity";
|
|
3678
3750
|
const STREAM_IDLE_TIMEOUT_MS$5 = 3e5;
|
|
3679
3751
|
const STREAM_IDLE_TIMEOUT_CODE$6 = "LLM_STREAM_IDLE_TIMEOUT";
|
|
3680
3752
|
const DISCOVERY_TIMEOUT_MS$4 = 8e3;
|
|
@@ -4138,13 +4210,13 @@ function parseAntigravityCredentials(value) {
|
|
|
4138
4210
|
if (!(credentials.access || credentials.access_token || credentials.refresh || credentials.refresh_token)) throw new Error("Antigravity credential tokens are missing");
|
|
4139
4211
|
return credentials;
|
|
4140
4212
|
}
|
|
4141
|
-
function credentialAccount$
|
|
4213
|
+
function credentialAccount$4(filePath) {
|
|
4142
4214
|
return createHash("sha256").update(path.resolve(filePath)).digest("hex");
|
|
4143
4215
|
}
|
|
4144
|
-
function createCredentialBackend$
|
|
4216
|
+
function createCredentialBackend$4(filePath) {
|
|
4145
4217
|
if (process.platform === "win32") return new WindowsDpapiCredentialStore(`${filePath}.dpapi`, parseAntigravityCredentials);
|
|
4146
|
-
if (process.platform === "darwin") return new MacKeychainCredentialStore("dsh-antigravity", credentialAccount$
|
|
4147
|
-
if (process.platform === "linux") return new SecretServiceCredentialStore("dsh-antigravity", credentialAccount$
|
|
4218
|
+
if (process.platform === "darwin") return new MacKeychainCredentialStore("dsh-antigravity", credentialAccount$4(filePath), parseAntigravityCredentials);
|
|
4219
|
+
if (process.platform === "linux") return new SecretServiceCredentialStore("dsh-antigravity", credentialAccount$4(filePath), parseAntigravityCredentials);
|
|
4148
4220
|
throw new Error("Antigravity encrypted credential storage requires Windows, macOS, or Linux.");
|
|
4149
4221
|
}
|
|
4150
4222
|
const credentialOperations$3 = /* @__PURE__ */ new Map();
|
|
@@ -4152,13 +4224,13 @@ const credentialOperations$3 = /* @__PURE__ */ new Map();
|
|
|
4152
4224
|
var FileCredentialStore$2 = class {
|
|
4153
4225
|
filePath;
|
|
4154
4226
|
backend;
|
|
4155
|
-
constructor(filePath = credentialPath$2(), backend = createCredentialBackend$
|
|
4227
|
+
constructor(filePath = credentialPath$2(), backend = createCredentialBackend$4(filePath)) {
|
|
4156
4228
|
this.filePath = filePath;
|
|
4157
4229
|
this.backend = backend;
|
|
4158
4230
|
}
|
|
4159
4231
|
path() {
|
|
4160
4232
|
if (process.platform === "win32") return `${this.filePath}.dpapi`;
|
|
4161
|
-
return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: dsh-antigravity/${credentialAccount$
|
|
4233
|
+
return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: dsh-antigravity/${credentialAccount$4(this.filePath)}`;
|
|
4162
4234
|
}
|
|
4163
4235
|
serialize(operation) {
|
|
4164
4236
|
const key = path.resolve(this.filePath);
|
|
@@ -4872,15 +4944,15 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
|
|
|
4872
4944
|
payload.tool_choice = "auto";
|
|
4873
4945
|
payload.parallel_tool_calls = true;
|
|
4874
4946
|
}
|
|
4875
|
-
|
|
4947
|
+
const providerVerbosity = codexDefaultOutputVerbosity(options.model);
|
|
4948
|
+
if (providerVerbosity !== void 0) payload.text = { verbosity: outputVerbosity ?? providerVerbosity };
|
|
4876
4949
|
if (fastMode) payload.service_tier = "priority";
|
|
4877
|
-
const modelMaxTokens = codexModelMaxTokens(options.model);
|
|
4878
|
-
payload.max_output_tokens = options.maxTokens === void 0 ? modelMaxTokens : Math.min(options.maxTokens, modelMaxTokens);
|
|
4879
4950
|
if (options.reasoningEffort !== void 0) {
|
|
4880
4951
|
const effort = codexWireReasoningEffort(options.model, options.reasoningEffort);
|
|
4881
|
-
|
|
4952
|
+
const summary = reasoningSummary === null || reasoningSummary === "none" ? void 0 : reasoningSummary;
|
|
4953
|
+
payload.reasoning = summary !== void 0 && codexModelSupportsReasoningSummary(options.model) ? {
|
|
4882
4954
|
effort,
|
|
4883
|
-
summary
|
|
4955
|
+
summary
|
|
4884
4956
|
} : { effort };
|
|
4885
4957
|
}
|
|
4886
4958
|
return payload;
|
|
@@ -6403,13 +6475,13 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
6403
6475
|
}
|
|
6404
6476
|
try {
|
|
6405
6477
|
switch (url.pathname) {
|
|
6406
|
-
case `${ROUTE_PREFIX$
|
|
6478
|
+
case `${ROUTE_PREFIX$5}/login/start`:
|
|
6407
6479
|
json(response, {
|
|
6408
6480
|
ok: true,
|
|
6409
6481
|
value: await oauth.startLogin()
|
|
6410
6482
|
});
|
|
6411
6483
|
return;
|
|
6412
|
-
case `${ROUTE_PREFIX$
|
|
6484
|
+
case `${ROUTE_PREFIX$5}/login/cancel`: {
|
|
6413
6485
|
const loginId = field(body, "loginId");
|
|
6414
6486
|
if (loginId === null) throw new Error("missing loginId");
|
|
6415
6487
|
oauth.cancelLogin(loginId);
|
|
@@ -6419,7 +6491,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
6419
6491
|
});
|
|
6420
6492
|
return;
|
|
6421
6493
|
}
|
|
6422
|
-
case `${ROUTE_PREFIX$
|
|
6494
|
+
case `${ROUTE_PREFIX$5}/accounts`: {
|
|
6423
6495
|
if (accountPool === void 0) throw new Error("The ChatGPT account pool is not installed.");
|
|
6424
6496
|
const action = field(body, "action");
|
|
6425
6497
|
const accountId = field(body, "accountId");
|
|
@@ -6441,7 +6513,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
6441
6513
|
});
|
|
6442
6514
|
return;
|
|
6443
6515
|
}
|
|
6444
|
-
case `${ROUTE_PREFIX$
|
|
6516
|
+
case `${ROUTE_PREFIX$5}/logout`:
|
|
6445
6517
|
await oauth.logout(field(body, "accountId") ?? void 0);
|
|
6446
6518
|
usage.clear();
|
|
6447
6519
|
json(response, {
|
|
@@ -6449,7 +6521,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
6449
6521
|
value: { authenticated: false }
|
|
6450
6522
|
});
|
|
6451
6523
|
return;
|
|
6452
|
-
case `${ROUTE_PREFIX$
|
|
6524
|
+
case `${ROUTE_PREFIX$5}/token/refresh`: {
|
|
6453
6525
|
const oauthStatus = await oauth.refresh();
|
|
6454
6526
|
json(response, {
|
|
6455
6527
|
ok: true,
|
|
@@ -6461,27 +6533,27 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
6461
6533
|
});
|
|
6462
6534
|
return;
|
|
6463
6535
|
}
|
|
6464
|
-
case `${ROUTE_PREFIX$
|
|
6536
|
+
case `${ROUTE_PREFIX$5}/quota/refresh`:
|
|
6465
6537
|
if (!(await oauth.status()).authenticated) throw new Error("not authenticated");
|
|
6466
6538
|
json(response, {
|
|
6467
6539
|
ok: true,
|
|
6468
6540
|
value: await usage.status(true, true)
|
|
6469
6541
|
});
|
|
6470
6542
|
return;
|
|
6471
|
-
case `${ROUTE_PREFIX$
|
|
6543
|
+
case `${ROUTE_PREFIX$5}/quota/reset-credit/use`:
|
|
6472
6544
|
if (!(await oauth.status()).authenticated) throw new Error("not authenticated");
|
|
6473
6545
|
json(response, {
|
|
6474
6546
|
ok: true,
|
|
6475
6547
|
value: await usage.consumeResetCredit()
|
|
6476
6548
|
});
|
|
6477
6549
|
return;
|
|
6478
|
-
case `${ROUTE_PREFIX$
|
|
6550
|
+
case `${ROUTE_PREFIX$5}/connection/test`:
|
|
6479
6551
|
json(response, {
|
|
6480
6552
|
ok: true,
|
|
6481
6553
|
value: await usage.testConnection()
|
|
6482
6554
|
});
|
|
6483
6555
|
return;
|
|
6484
|
-
case `${ROUTE_PREFIX$
|
|
6556
|
+
case `${ROUTE_PREFIX$5}/preferences/update`: {
|
|
6485
6557
|
const patch = readPreferencesUpdate(body, preferences.status());
|
|
6486
6558
|
const value = await preferences.update(patch);
|
|
6487
6559
|
if (patch.visibleModelIds !== void 0 || patch.enabled !== void 0 || patch.contextWindowOverrides !== void 0) ctx.emit?.("llm/adapters-updated");
|
|
@@ -6558,11 +6630,11 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
6558
6630
|
};
|
|
6559
6631
|
const disposers = [ctx.webServer.register({
|
|
6560
6632
|
kind: "prefix",
|
|
6561
|
-
path: ROUTE_PREFIX$
|
|
6633
|
+
path: ROUTE_PREFIX$5,
|
|
6562
6634
|
handler
|
|
6563
6635
|
}), ctx.webServer.register({
|
|
6564
6636
|
kind: "exact",
|
|
6565
|
-
path: `${ROUTE_PREFIX$
|
|
6637
|
+
path: `${ROUTE_PREFIX$5}/login/events`,
|
|
6566
6638
|
handler: events
|
|
6567
6639
|
})];
|
|
6568
6640
|
return () => {
|
|
@@ -6670,6 +6742,27 @@ function statusFor(error) {
|
|
|
6670
6742
|
}
|
|
6671
6743
|
//#endregion
|
|
6672
6744
|
//#region src/host/search-provider-switcher.ts
|
|
6745
|
+
/**
|
|
6746
|
+
* Cordis fiber states, mirrored from its `FiberState` const enum.
|
|
6747
|
+
*
|
|
6748
|
+
* It is a const enum, so importing the values would inline them here anyway and
|
|
6749
|
+
* the numbers would be duplicated in the emitted JavaScript either way. Naming
|
|
6750
|
+
* them locally keeps this module free of a type-only import that a consumer's
|
|
6751
|
+
* build would still have to resolve, and the values are asserted against the
|
|
6752
|
+
* installed cordis in the test below.
|
|
6753
|
+
*/
|
|
6754
|
+
const FIBER_LOADING = 1;
|
|
6755
|
+
const FIBER_ACTIVE = 2;
|
|
6756
|
+
const FIBER_UNLOADING = 5;
|
|
6757
|
+
/** How long to wait for a restarted entry to become active again. */
|
|
6758
|
+
const RESTART_TIMEOUT_MS = 1e4;
|
|
6759
|
+
/** Gap between restart-state samples. */
|
|
6760
|
+
const RESTART_POLL_MS = 10;
|
|
6761
|
+
function delay(ms) {
|
|
6762
|
+
return new Promise((resolve) => {
|
|
6763
|
+
setTimeout(resolve, ms);
|
|
6764
|
+
});
|
|
6765
|
+
}
|
|
6673
6766
|
var SearchProviderSwitcher = class {
|
|
6674
6767
|
loader;
|
|
6675
6768
|
originalSearchProvider;
|
|
@@ -6728,15 +6821,40 @@ var SearchProviderSwitcher = class {
|
|
|
6728
6821
|
else nextConfig.fetchProvider = nextFetch;
|
|
6729
6822
|
this.state = "applying";
|
|
6730
6823
|
try {
|
|
6731
|
-
if (configured && entry.fiber)
|
|
6824
|
+
if (configured && entry.fiber) entry.fiber.update(nextConfig, true);
|
|
6732
6825
|
else await entry.update({ config: nextConfig });
|
|
6733
|
-
await
|
|
6826
|
+
await this.awaitSettled(entry);
|
|
6734
6827
|
this.state = "applied";
|
|
6735
6828
|
} catch (error) {
|
|
6736
6829
|
this.state = "failed";
|
|
6737
6830
|
throw error;
|
|
6738
6831
|
}
|
|
6739
6832
|
}
|
|
6833
|
+
/**
|
|
6834
|
+
* Wait until the entry's fiber has finished restarting, not just until it
|
|
6835
|
+
* happens not to be mid-restart when asked.
|
|
6836
|
+
*
|
|
6837
|
+
* `Fiber.await()` alone is a single sample: it returns at once when `inertia`
|
|
6838
|
+
* is unset, which is exactly the state the fiber is in on the first tick after
|
|
6839
|
+
* `update()` queues its restart. Sampling that reports success while the entry
|
|
6840
|
+
* is still UNLOADING. This waits for a restart to actually be observed, then
|
|
6841
|
+
* lets `Fiber.await()` drain whatever queue is left, so a slow or multi-pass
|
|
6842
|
+
* restart cannot be reported as applied either.
|
|
6843
|
+
*/
|
|
6844
|
+
async awaitSettled(entry) {
|
|
6845
|
+
const fiber = entry.fiber;
|
|
6846
|
+
if (fiber === void 0) return;
|
|
6847
|
+
const deadline = Date.now() + RESTART_TIMEOUT_MS;
|
|
6848
|
+
let sawRestart = false;
|
|
6849
|
+
while (Date.now() < deadline) {
|
|
6850
|
+
const state = fiber.state;
|
|
6851
|
+
if (state === FIBER_LOADING || state === FIBER_UNLOADING) sawRestart = true;
|
|
6852
|
+
if (sawRestart && state === FIBER_ACTIVE) return;
|
|
6853
|
+
await fiber.await();
|
|
6854
|
+
if (fiber.state === FIBER_ACTIVE) return;
|
|
6855
|
+
await delay(RESTART_POLL_MS);
|
|
6856
|
+
}
|
|
6857
|
+
}
|
|
6740
6858
|
findWebEntry() {
|
|
6741
6859
|
for (const entry of this.loader.entries()) if (entry.options.id === "web" || entry.options.name === "@deepseek-ai/dsh-web") return entry;
|
|
6742
6860
|
return null;
|
|
@@ -7966,7 +8084,7 @@ function buildRequest$2(options, model, projectId, runtimeModel, effort, images
|
|
|
7966
8084
|
requestId: `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
7967
8085
|
};
|
|
7968
8086
|
}
|
|
7969
|
-
function createStreamState$
|
|
8087
|
+
function createStreamState$6() {
|
|
7970
8088
|
return {
|
|
7971
8089
|
blocks: [],
|
|
7972
8090
|
replayBlocks: [],
|
|
@@ -8027,7 +8145,7 @@ function processStreamLine$2(line, state) {
|
|
|
8027
8145
|
const json = line.slice(5).trim();
|
|
8028
8146
|
if (json === "[DONE]") {
|
|
8029
8147
|
state.done = true;
|
|
8030
|
-
return closeStream$
|
|
8148
|
+
return closeStream$5(state);
|
|
8031
8149
|
}
|
|
8032
8150
|
if (!json) return [];
|
|
8033
8151
|
const chunk = safeJsonParse$5(json);
|
|
@@ -8144,7 +8262,7 @@ function processStreamLine$2(line, state) {
|
|
|
8144
8262
|
}
|
|
8145
8263
|
return out;
|
|
8146
8264
|
}
|
|
8147
|
-
function closeStream$
|
|
8265
|
+
function closeStream$5(state) {
|
|
8148
8266
|
if (state.finished) return [];
|
|
8149
8267
|
if (!state.finishReason && !state.done) throw new LlmError$1("Antigravity stream ended before its terminal response", "PROVIDER_ERROR");
|
|
8150
8268
|
state.finished = true;
|
|
@@ -8158,7 +8276,7 @@ function closeStream$4(state) {
|
|
|
8158
8276
|
type: "finish",
|
|
8159
8277
|
reason,
|
|
8160
8278
|
replayState: {
|
|
8161
|
-
response: { provider: PROVIDER_ID$
|
|
8279
|
+
response: { provider: PROVIDER_ID$6 },
|
|
8162
8280
|
blocks: state.replayBlocks
|
|
8163
8281
|
}
|
|
8164
8282
|
});
|
|
@@ -8366,7 +8484,7 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
8366
8484
|
providerInfo(provider) {
|
|
8367
8485
|
return {
|
|
8368
8486
|
id: provider,
|
|
8369
|
-
name: PROVIDER_NAME$
|
|
8487
|
+
name: PROVIDER_NAME$6
|
|
8370
8488
|
};
|
|
8371
8489
|
}
|
|
8372
8490
|
providerRetryPolicy() {}
|
|
@@ -8384,7 +8502,7 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
8384
8502
|
name: model.name,
|
|
8385
8503
|
inputModalities: model.inputModalities,
|
|
8386
8504
|
context: { contextWindow: overrides[model.id] || model.contextWindow },
|
|
8387
|
-
|
|
8505
|
+
...outputReservation(overrides[model.id] || model.contextWindow, model.maxTokens),
|
|
8388
8506
|
...model.reasoningEfforts ? { reasoningEfforts: model.reasoningEfforts } : {}
|
|
8389
8507
|
}));
|
|
8390
8508
|
}
|
|
@@ -8411,7 +8529,7 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
8411
8529
|
name: model.name,
|
|
8412
8530
|
inputModalities: model.inputModalities,
|
|
8413
8531
|
context: { contextWindow: overrides[model.id] || model.contextWindow },
|
|
8414
|
-
|
|
8532
|
+
...outputReservation(overrides[model.id] || model.contextWindow, model.maxTokens),
|
|
8415
8533
|
...model.reasoningEfforts ? { reasoning: {
|
|
8416
8534
|
efforts: efforts.map((effort) => ({
|
|
8417
8535
|
id: ReasoningEffortId(effort),
|
|
@@ -8441,7 +8559,10 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
8441
8559
|
...options,
|
|
8442
8560
|
reasoningEffort: effectiveEffort
|
|
8443
8561
|
} : options;
|
|
8444
|
-
yield* wrapStreamWithWatchdog((watchdogSignal) => this.requestStream(
|
|
8562
|
+
yield* wrapStreamWithWatchdog((watchdogSignal) => this.requestStream({
|
|
8563
|
+
...effectiveOptions,
|
|
8564
|
+
maxTokens: options.maxTokens ?? model.maxTokens
|
|
8565
|
+
}, model, watchdogSignal), options.signal, STREAM_IDLE_TIMEOUT_MS$5, STREAM_IDLE_TIMEOUT_CODE$6, "Antigravity");
|
|
8445
8566
|
}
|
|
8446
8567
|
async *requestStream(options, model, signal) {
|
|
8447
8568
|
const fetchFn = this.options.fetchFn ?? fetch;
|
|
@@ -8514,7 +8635,7 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
8514
8635
|
if (!response.body) throw new LlmError("Antigravity returned empty response body", "PROVIDER_ERROR");
|
|
8515
8636
|
const reader = response.body.getReader();
|
|
8516
8637
|
const decoder = new TextDecoder();
|
|
8517
|
-
const state = createStreamState$
|
|
8638
|
+
const state = createStreamState$6();
|
|
8518
8639
|
let buffer = "";
|
|
8519
8640
|
try {
|
|
8520
8641
|
while (true) {
|
|
@@ -8536,7 +8657,7 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
8536
8657
|
const chunks = processStreamLine$2(buffer.trim(), state);
|
|
8537
8658
|
for (const chunk of chunks) yield chunk;
|
|
8538
8659
|
}
|
|
8539
|
-
for (const chunk of closeStream$
|
|
8660
|
+
for (const chunk of closeStream$5(state)) yield chunk;
|
|
8540
8661
|
} finally {
|
|
8541
8662
|
reader.cancel().catch(() => void 0);
|
|
8542
8663
|
}
|
|
@@ -8601,17 +8722,17 @@ var QuotaRefresh = class {
|
|
|
8601
8722
|
};
|
|
8602
8723
|
//#endregion
|
|
8603
8724
|
//#region src/host/antigravity/routes.ts
|
|
8604
|
-
function sendJson$
|
|
8725
|
+
function sendJson$8(response, status, body) {
|
|
8605
8726
|
response.writeHead(status, { "Content-Type": "application/json" });
|
|
8606
8727
|
response.end(JSON.stringify(body));
|
|
8607
8728
|
}
|
|
8608
|
-
function sendMethodNotAllowed$
|
|
8609
|
-
sendJson$
|
|
8729
|
+
function sendMethodNotAllowed$6(response) {
|
|
8730
|
+
sendJson$8(response, 405, {
|
|
8610
8731
|
ok: false,
|
|
8611
8732
|
error: "Method Not Allowed"
|
|
8612
8733
|
});
|
|
8613
8734
|
}
|
|
8614
|
-
async function readRequestJson$
|
|
8735
|
+
async function readRequestJson$7(request) {
|
|
8615
8736
|
return new Promise((resolve, reject) => {
|
|
8616
8737
|
let raw = "";
|
|
8617
8738
|
request.on("data", (chunk) => {
|
|
@@ -8671,7 +8792,7 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8671
8792
|
const path = new URL(request.url || "/", "http://dsh.local").pathname.replace(/^\/antigravity\/api\/?/, "");
|
|
8672
8793
|
try {
|
|
8673
8794
|
if (path === "status" || path === "") {
|
|
8674
|
-
if (request.method !== "GET") return sendMethodNotAllowed$
|
|
8795
|
+
if (request.method !== "GET") return sendMethodNotAllowed$6(response);
|
|
8675
8796
|
const credentials = await store.read();
|
|
8676
8797
|
const authenticated = !!(credentials?.access || credentials?.access_token);
|
|
8677
8798
|
const cached = getCachedQuota();
|
|
@@ -8680,7 +8801,7 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8680
8801
|
if (cached === void 0) await quotaRefresh.run(refresh);
|
|
8681
8802
|
else quotaRefresh.start(refresh);
|
|
8682
8803
|
}
|
|
8683
|
-
return sendJson$
|
|
8804
|
+
return sendJson$8(response, 200, {
|
|
8684
8805
|
ok: true,
|
|
8685
8806
|
value: {
|
|
8686
8807
|
...await getAntigravityWebStatus(store, modelSettings, preferences, accountPool),
|
|
@@ -8689,8 +8810,8 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8689
8810
|
});
|
|
8690
8811
|
}
|
|
8691
8812
|
if (path === "login" || path === "accounts/login") {
|
|
8692
|
-
if (request.method !== "POST") return sendMethodNotAllowed$
|
|
8693
|
-
return sendJson$
|
|
8813
|
+
if (request.method !== "POST") return sendMethodNotAllowed$6(response);
|
|
8814
|
+
return sendJson$8(response, 200, {
|
|
8694
8815
|
ok: true,
|
|
8695
8816
|
value: await beginWebLogin$2(store, fetchFn, void 0, async (creds) => {
|
|
8696
8817
|
await accountPool.addAccount(creds);
|
|
@@ -8698,19 +8819,19 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8698
8819
|
});
|
|
8699
8820
|
}
|
|
8700
8821
|
if (path === "login/status") {
|
|
8701
|
-
if (request.method !== "GET") return sendMethodNotAllowed$
|
|
8702
|
-
return sendJson$
|
|
8822
|
+
if (request.method !== "GET") return sendMethodNotAllowed$6(response);
|
|
8823
|
+
return sendJson$8(response, 200, {
|
|
8703
8824
|
ok: true,
|
|
8704
8825
|
value: getWebLoginStatus$3()
|
|
8705
8826
|
});
|
|
8706
8827
|
}
|
|
8707
8828
|
if (path === "accounts") {
|
|
8708
|
-
if (request.method === "GET") return sendJson$
|
|
8829
|
+
if (request.method === "GET") return sendJson$8(response, 200, {
|
|
8709
8830
|
ok: true,
|
|
8710
8831
|
value: await accountPool.listAccounts()
|
|
8711
8832
|
});
|
|
8712
8833
|
if (request.method === "POST") {
|
|
8713
|
-
const body = await readRequestJson$
|
|
8834
|
+
const body = await readRequestJson$7(request);
|
|
8714
8835
|
const action = String(body.action || "");
|
|
8715
8836
|
if (action === "set-primary" && typeof body.accountId === "string") await accountPool.setPrimary(body.accountId);
|
|
8716
8837
|
else if (action === "set-alias" && typeof body.accountId === "string" && typeof body.alias === "string") await accountPool.setAlias(body.accountId, body.alias);
|
|
@@ -8718,17 +8839,17 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8718
8839
|
else if (action === "strategy" && (body.strategy === "sequential" || body.strategy === "round-robin" || body.strategy === "sticky")) await accountPool.setStrategy(body.strategy);
|
|
8719
8840
|
else if (action === "clear-cooldown" && typeof body.accountId === "string") await accountPool.clearCooldown(body.accountId);
|
|
8720
8841
|
clearCachedQuota();
|
|
8721
|
-
return sendJson$
|
|
8842
|
+
return sendJson$8(response, 200, {
|
|
8722
8843
|
ok: true,
|
|
8723
8844
|
value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
|
|
8724
8845
|
});
|
|
8725
8846
|
}
|
|
8726
|
-
return sendMethodNotAllowed$
|
|
8847
|
+
return sendMethodNotAllowed$6(response);
|
|
8727
8848
|
}
|
|
8728
8849
|
if (path === "quota") {
|
|
8729
|
-
if (request.method !== "GET" && request.method !== "POST") return sendMethodNotAllowed$
|
|
8850
|
+
if (request.method !== "GET" && request.method !== "POST") return sendMethodNotAllowed$6(response);
|
|
8730
8851
|
const quota = await fetchAccountQuota(store, modelSettings, fetchFn, true);
|
|
8731
|
-
return sendJson$
|
|
8852
|
+
return sendJson$8(response, 200, {
|
|
8732
8853
|
ok: true,
|
|
8733
8854
|
value: {
|
|
8734
8855
|
...await getAntigravityWebStatus(store, modelSettings, preferences, accountPool),
|
|
@@ -8737,38 +8858,38 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8737
8858
|
});
|
|
8738
8859
|
}
|
|
8739
8860
|
if (path === "settings") {
|
|
8740
|
-
if (request.method !== "POST") return sendMethodNotAllowed$
|
|
8741
|
-
const body = await readRequestJson$
|
|
8861
|
+
if (request.method !== "POST") return sendMethodNotAllowed$6(response);
|
|
8862
|
+
const body = await readRequestJson$7(request);
|
|
8742
8863
|
if (preferences) await preferences.update(body);
|
|
8743
8864
|
else await modelSettings.updateSettings(body);
|
|
8744
8865
|
if (body.enabledModelIds !== void 0 || body.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
|
|
8745
|
-
return sendJson$
|
|
8866
|
+
return sendJson$8(response, 200, {
|
|
8746
8867
|
ok: true,
|
|
8747
8868
|
value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
|
|
8748
8869
|
});
|
|
8749
8870
|
}
|
|
8750
8871
|
if (path === "models") {
|
|
8751
|
-
if (request.method === "GET") return sendJson$
|
|
8872
|
+
if (request.method === "GET") return sendJson$8(response, 200, {
|
|
8752
8873
|
ok: true,
|
|
8753
8874
|
value: (await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)).models
|
|
8754
8875
|
});
|
|
8755
8876
|
if (request.method === "POST") {
|
|
8756
|
-
const body = await readRequestJson$
|
|
8877
|
+
const body = await readRequestJson$7(request);
|
|
8757
8878
|
if (Array.isArray(body.enabledModelIds) || body.enabled !== void 0 || body.contextWindowOverrides || body.defaultReasoningEffort !== void 0) {
|
|
8758
8879
|
if (preferences) await preferences.update(body);
|
|
8759
8880
|
else await modelSettings.updateSettings(body);
|
|
8760
8881
|
if (body.enabledModelIds !== void 0 || body.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
|
|
8761
8882
|
}
|
|
8762
|
-
return sendJson$
|
|
8883
|
+
return sendJson$8(response, 200, {
|
|
8763
8884
|
ok: true,
|
|
8764
8885
|
value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
|
|
8765
8886
|
});
|
|
8766
8887
|
}
|
|
8767
|
-
return sendMethodNotAllowed$
|
|
8888
|
+
return sendMethodNotAllowed$6(response);
|
|
8768
8889
|
}
|
|
8769
8890
|
if (path === "logout") {
|
|
8770
|
-
if (request.method !== "POST") return sendMethodNotAllowed$
|
|
8771
|
-
const body = await readRequestJson$
|
|
8891
|
+
if (request.method !== "POST") return sendMethodNotAllowed$6(response);
|
|
8892
|
+
const body = await readRequestJson$7(request).catch(() => ({}));
|
|
8772
8893
|
const targetId = typeof body.accountId === "string" ? body.accountId : void 0;
|
|
8773
8894
|
if (targetId) await accountPool.deleteAccount(targetId);
|
|
8774
8895
|
else {
|
|
@@ -8777,17 +8898,17 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8777
8898
|
else await store.delete();
|
|
8778
8899
|
}
|
|
8779
8900
|
clearCachedQuota();
|
|
8780
|
-
return sendJson$
|
|
8901
|
+
return sendJson$8(response, 200, {
|
|
8781
8902
|
ok: true,
|
|
8782
8903
|
value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
|
|
8783
8904
|
});
|
|
8784
8905
|
}
|
|
8785
|
-
return sendJson$
|
|
8906
|
+
return sendJson$8(response, 404, {
|
|
8786
8907
|
ok: false,
|
|
8787
8908
|
error: "not-found"
|
|
8788
8909
|
});
|
|
8789
8910
|
} catch (err) {
|
|
8790
|
-
return sendJson$
|
|
8911
|
+
return sendJson$8(response, 500, {
|
|
8791
8912
|
ok: false,
|
|
8792
8913
|
error: err instanceof Error ? err.message : String(err)
|
|
8793
8914
|
});
|
|
@@ -8799,6 +8920,20 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8799
8920
|
//#region src/host/command-code/model-catalog.ts
|
|
8800
8921
|
/** Every model the Command Code registry describes, in registry order. */
|
|
8801
8922
|
const COMMAND_CODE_MODELS = [
|
|
8923
|
+
{
|
|
8924
|
+
id: "claude-sonnet-5-5",
|
|
8925
|
+
name: "Claude Sonnet 5.5",
|
|
8926
|
+
inputModalities: ["text", "image"],
|
|
8927
|
+
reasoningEfforts: [
|
|
8928
|
+
"low",
|
|
8929
|
+
"medium",
|
|
8930
|
+
"high",
|
|
8931
|
+
"xhigh",
|
|
8932
|
+
"max"
|
|
8933
|
+
],
|
|
8934
|
+
contextWindow: 1e6,
|
|
8935
|
+
maxTokens: null
|
|
8936
|
+
},
|
|
8802
8937
|
{
|
|
8803
8938
|
id: "claude-sonnet-5",
|
|
8804
8939
|
name: "Claude Sonnet 5",
|
|
@@ -8855,6 +8990,20 @@ const COMMAND_CODE_MODELS = [
|
|
|
8855
8990
|
contextWindow: 1e6,
|
|
8856
8991
|
maxTokens: null
|
|
8857
8992
|
},
|
|
8993
|
+
{
|
|
8994
|
+
id: "claude-opus-5-5",
|
|
8995
|
+
name: "Claude Opus 5.5",
|
|
8996
|
+
inputModalities: ["text", "image"],
|
|
8997
|
+
reasoningEfforts: [
|
|
8998
|
+
"low",
|
|
8999
|
+
"medium",
|
|
9000
|
+
"high",
|
|
9001
|
+
"xhigh",
|
|
9002
|
+
"max"
|
|
9003
|
+
],
|
|
9004
|
+
contextWindow: 1e6,
|
|
9005
|
+
maxTokens: null
|
|
9006
|
+
},
|
|
8858
9007
|
{
|
|
8859
9008
|
id: "claude-opus-5",
|
|
8860
9009
|
name: "Claude Opus 5",
|
|
@@ -8919,6 +9068,48 @@ const COMMAND_CODE_MODELS = [
|
|
|
8919
9068
|
contextWindow: 105e4,
|
|
8920
9069
|
maxTokens: null
|
|
8921
9070
|
},
|
|
9071
|
+
{
|
|
9072
|
+
id: "gpt-6.1-sol",
|
|
9073
|
+
name: "GPT-6.1 Sol",
|
|
9074
|
+
inputModalities: ["text", "image"],
|
|
9075
|
+
reasoningEfforts: [
|
|
9076
|
+
"low",
|
|
9077
|
+
"medium",
|
|
9078
|
+
"high",
|
|
9079
|
+
"xhigh",
|
|
9080
|
+
"max"
|
|
9081
|
+
],
|
|
9082
|
+
contextWindow: 105e4,
|
|
9083
|
+
maxTokens: null
|
|
9084
|
+
},
|
|
9085
|
+
{
|
|
9086
|
+
id: "gpt-6-sol",
|
|
9087
|
+
name: "GPT-6 Sol",
|
|
9088
|
+
inputModalities: ["text", "image"],
|
|
9089
|
+
reasoningEfforts: [
|
|
9090
|
+
"low",
|
|
9091
|
+
"medium",
|
|
9092
|
+
"high",
|
|
9093
|
+
"xhigh",
|
|
9094
|
+
"max"
|
|
9095
|
+
],
|
|
9096
|
+
contextWindow: 105e4,
|
|
9097
|
+
maxTokens: null
|
|
9098
|
+
},
|
|
9099
|
+
{
|
|
9100
|
+
id: "gpt-6-luna",
|
|
9101
|
+
name: "GPT-6 Luna",
|
|
9102
|
+
inputModalities: ["text", "image"],
|
|
9103
|
+
reasoningEfforts: [
|
|
9104
|
+
"low",
|
|
9105
|
+
"medium",
|
|
9106
|
+
"high",
|
|
9107
|
+
"xhigh",
|
|
9108
|
+
"max"
|
|
9109
|
+
],
|
|
9110
|
+
contextWindow: 105e4,
|
|
9111
|
+
maxTokens: null
|
|
9112
|
+
},
|
|
8922
9113
|
{
|
|
8923
9114
|
id: "gpt-5.6-sol",
|
|
8924
9115
|
name: "GPT-5.6 Sol",
|
|
@@ -9016,7 +9207,11 @@ const COMMAND_CODE_MODELS = [
|
|
|
9016
9207
|
id: "deepseek/deepseek-v4-pro",
|
|
9017
9208
|
name: "DeepSeek V4 Pro (latest)",
|
|
9018
9209
|
inputModalities: ["text"],
|
|
9019
|
-
reasoningEfforts: [
|
|
9210
|
+
reasoningEfforts: [
|
|
9211
|
+
"off",
|
|
9212
|
+
"high",
|
|
9213
|
+
"max"
|
|
9214
|
+
],
|
|
9020
9215
|
contextWindow: 1e6,
|
|
9021
9216
|
maxTokens: null
|
|
9022
9217
|
},
|
|
@@ -9024,7 +9219,11 @@ const COMMAND_CODE_MODELS = [
|
|
|
9024
9219
|
id: "deepseek/deepseek-v4-flash",
|
|
9025
9220
|
name: "DeepSeek V4 Flash (latest)",
|
|
9026
9221
|
inputModalities: ["text"],
|
|
9027
|
-
reasoningEfforts: [
|
|
9222
|
+
reasoningEfforts: [
|
|
9223
|
+
"off",
|
|
9224
|
+
"high",
|
|
9225
|
+
"max"
|
|
9226
|
+
],
|
|
9028
9227
|
contextWindow: 1e6,
|
|
9029
9228
|
maxTokens: null
|
|
9030
9229
|
},
|
|
@@ -9032,7 +9231,11 @@ const COMMAND_CODE_MODELS = [
|
|
|
9032
9231
|
id: "deepseek/deepseek-v4-flash-vision-exp",
|
|
9033
9232
|
name: "DeepSeek V4 Flash Vision (exp)",
|
|
9034
9233
|
inputModalities: ["text", "image"],
|
|
9035
|
-
reasoningEfforts: [
|
|
9234
|
+
reasoningEfforts: [
|
|
9235
|
+
"off",
|
|
9236
|
+
"high",
|
|
9237
|
+
"max"
|
|
9238
|
+
],
|
|
9036
9239
|
contextWindow: 1e6,
|
|
9037
9240
|
maxTokens: null
|
|
9038
9241
|
},
|
|
@@ -9053,6 +9256,20 @@ const COMMAND_CODE_MODELS = [
|
|
|
9053
9256
|
name: "DeepSeek V4.1 Flash",
|
|
9054
9257
|
inputModalities: ["text", "image"],
|
|
9055
9258
|
reasoningEfforts: [
|
|
9259
|
+
"off",
|
|
9260
|
+
"low",
|
|
9261
|
+
"high",
|
|
9262
|
+
"max"
|
|
9263
|
+
],
|
|
9264
|
+
contextWindow: 1e6,
|
|
9265
|
+
maxTokens: null
|
|
9266
|
+
},
|
|
9267
|
+
{
|
|
9268
|
+
id: "deepseek/deepseek-v4.1-flash-fast",
|
|
9269
|
+
name: "DeepSeek V4.1 Flash Fast",
|
|
9270
|
+
inputModalities: ["text", "image"],
|
|
9271
|
+
reasoningEfforts: [
|
|
9272
|
+
"off",
|
|
9056
9273
|
"low",
|
|
9057
9274
|
"high",
|
|
9058
9275
|
"max"
|
|
@@ -9116,6 +9333,18 @@ const COMMAND_CODE_MODELS = [
|
|
|
9116
9333
|
contextWindow: 1048576,
|
|
9117
9334
|
maxTokens: 131072
|
|
9118
9335
|
},
|
|
9336
|
+
{
|
|
9337
|
+
id: "z-ai/glm-5.3-flashx",
|
|
9338
|
+
name: "GLM-5.3 FlashX",
|
|
9339
|
+
inputModalities: ["text", "image"],
|
|
9340
|
+
reasoningEfforts: [
|
|
9341
|
+
"low",
|
|
9342
|
+
"high",
|
|
9343
|
+
"max"
|
|
9344
|
+
],
|
|
9345
|
+
contextWindow: 1e6,
|
|
9346
|
+
maxTokens: 131072
|
|
9347
|
+
},
|
|
9119
9348
|
{
|
|
9120
9349
|
id: "zai-org/GLM-5.3",
|
|
9121
9350
|
name: "GLM-5.3",
|
|
@@ -9149,7 +9378,7 @@ const COMMAND_CODE_MODELS = [
|
|
|
9149
9378
|
name: "GLM-5.1",
|
|
9150
9379
|
inputModalities: ["text"],
|
|
9151
9380
|
reasoningEfforts: [],
|
|
9152
|
-
contextWindow:
|
|
9381
|
+
contextWindow: 2e5,
|
|
9153
9382
|
maxTokens: null
|
|
9154
9383
|
},
|
|
9155
9384
|
{
|
|
@@ -9177,35 +9406,39 @@ const COMMAND_CODE_MODELS = [
|
|
|
9177
9406
|
name: "MiniMax M2.7",
|
|
9178
9407
|
inputModalities: ["text"],
|
|
9179
9408
|
reasoningEfforts: [],
|
|
9180
|
-
contextWindow:
|
|
9409
|
+
contextWindow: 2e5,
|
|
9410
|
+
maxTokens: null
|
|
9411
|
+
},
|
|
9412
|
+
{
|
|
9413
|
+
id: "MiniMaxAI/MiniMax-M2.5",
|
|
9414
|
+
name: "MiniMax M2.5",
|
|
9415
|
+
inputModalities: ["text"],
|
|
9416
|
+
reasoningEfforts: [],
|
|
9417
|
+
contextWindow: 2e5,
|
|
9181
9418
|
maxTokens: null
|
|
9182
9419
|
},
|
|
9183
9420
|
{
|
|
9184
|
-
id: "
|
|
9185
|
-
name: "
|
|
9421
|
+
id: "xiaomi/mimo-v2.6-pro",
|
|
9422
|
+
name: "MiMo V2.6 Pro",
|
|
9186
9423
|
inputModalities: ["text", "image"],
|
|
9187
|
-
reasoningEfforts: [
|
|
9188
|
-
|
|
9189
|
-
"medium",
|
|
9190
|
-
"high"
|
|
9191
|
-
],
|
|
9192
|
-
contextWindow: 1e6,
|
|
9424
|
+
reasoningEfforts: [],
|
|
9425
|
+
contextWindow: 1048576,
|
|
9193
9426
|
maxTokens: null
|
|
9194
9427
|
},
|
|
9195
9428
|
{
|
|
9196
|
-
id: "
|
|
9197
|
-
name: "
|
|
9198
|
-
inputModalities: ["text"],
|
|
9429
|
+
id: "xiaomi/mimo-v2.6-pro-ultraspeed",
|
|
9430
|
+
name: "MiMo V2.6 Pro UltraSpeed",
|
|
9431
|
+
inputModalities: ["text", "image"],
|
|
9199
9432
|
reasoningEfforts: [],
|
|
9200
|
-
contextWindow:
|
|
9433
|
+
contextWindow: 1048576,
|
|
9201
9434
|
maxTokens: null
|
|
9202
9435
|
},
|
|
9203
9436
|
{
|
|
9204
|
-
id: "
|
|
9205
|
-
name: "
|
|
9206
|
-
inputModalities: ["text"],
|
|
9437
|
+
id: "xiaomi/mimo-v2.6-flash",
|
|
9438
|
+
name: "MiMo V2.6 Flash",
|
|
9439
|
+
inputModalities: ["text", "image"],
|
|
9207
9440
|
reasoningEfforts: [],
|
|
9208
|
-
contextWindow:
|
|
9441
|
+
contextWindow: 1048576,
|
|
9209
9442
|
maxTokens: null
|
|
9210
9443
|
},
|
|
9211
9444
|
{
|
|
@@ -9224,6 +9457,18 @@ const COMMAND_CODE_MODELS = [
|
|
|
9224
9457
|
contextWindow: 1e6,
|
|
9225
9458
|
maxTokens: null
|
|
9226
9459
|
},
|
|
9460
|
+
{
|
|
9461
|
+
id: "Qwen/Qwen3.8-Omni-Flash",
|
|
9462
|
+
name: "Qwen 3.8 Omni Flash",
|
|
9463
|
+
inputModalities: ["text", "image"],
|
|
9464
|
+
reasoningEfforts: [
|
|
9465
|
+
"low",
|
|
9466
|
+
"medium",
|
|
9467
|
+
"xhigh"
|
|
9468
|
+
],
|
|
9469
|
+
contextWindow: 1e6,
|
|
9470
|
+
maxTokens: 131072
|
|
9471
|
+
},
|
|
9227
9472
|
{
|
|
9228
9473
|
id: "Qwen/Qwen3.8-Max-0902",
|
|
9229
9474
|
name: "Qwen 3.8 Max 0902",
|
|
@@ -9301,7 +9546,7 @@ const COMMAND_CODE_MODELS = [
|
|
|
9301
9546
|
name: "Qwen 3.6 Max Preview",
|
|
9302
9547
|
inputModalities: ["text"],
|
|
9303
9548
|
reasoningEfforts: [],
|
|
9304
|
-
contextWindow:
|
|
9549
|
+
contextWindow: 2e5,
|
|
9305
9550
|
maxTokens: null
|
|
9306
9551
|
},
|
|
9307
9552
|
{
|
|
@@ -9309,17 +9554,29 @@ const COMMAND_CODE_MODELS = [
|
|
|
9309
9554
|
name: "Qwen 3.6 Plus",
|
|
9310
9555
|
inputModalities: ["text", "image"],
|
|
9311
9556
|
reasoningEfforts: [],
|
|
9312
|
-
contextWindow:
|
|
9557
|
+
contextWindow: 2e5,
|
|
9313
9558
|
maxTokens: null
|
|
9314
9559
|
},
|
|
9315
9560
|
{
|
|
9316
|
-
id: "meituan/LongCat-2.0
|
|
9561
|
+
id: "meituan/LongCat-2.0",
|
|
9317
9562
|
name: "LongCat 2.0",
|
|
9318
9563
|
inputModalities: ["text"],
|
|
9319
9564
|
reasoningEfforts: [],
|
|
9320
9565
|
contextWindow: 1048576,
|
|
9321
9566
|
maxTokens: null
|
|
9322
9567
|
},
|
|
9568
|
+
{
|
|
9569
|
+
id: "stepfun/Step-5-Preview",
|
|
9570
|
+
name: "Step 5 Preview",
|
|
9571
|
+
inputModalities: ["text", "image"],
|
|
9572
|
+
reasoningEfforts: [
|
|
9573
|
+
"low",
|
|
9574
|
+
"medium",
|
|
9575
|
+
"high"
|
|
9576
|
+
],
|
|
9577
|
+
contextWindow: 1e6,
|
|
9578
|
+
maxTokens: null
|
|
9579
|
+
},
|
|
9323
9580
|
{
|
|
9324
9581
|
id: "stepfun/Step-3.7-Flash",
|
|
9325
9582
|
name: "Step 3.7 Flash",
|
|
@@ -9333,14 +9590,6 @@ const COMMAND_CODE_MODELS = [
|
|
|
9333
9590
|
name: "Step 3.5 Flash",
|
|
9334
9591
|
inputModalities: ["text"],
|
|
9335
9592
|
reasoningEfforts: [],
|
|
9336
|
-
contextWindow: 1e6,
|
|
9337
|
-
maxTokens: null
|
|
9338
|
-
},
|
|
9339
|
-
{
|
|
9340
|
-
id: "tencent/Hy3",
|
|
9341
|
-
name: "Tencent Hy3 (Free)",
|
|
9342
|
-
inputModalities: ["text"],
|
|
9343
|
-
reasoningEfforts: [],
|
|
9344
9593
|
contextWindow: 262144,
|
|
9345
9594
|
maxTokens: null
|
|
9346
9595
|
},
|
|
@@ -9468,6 +9717,19 @@ const COMMAND_CODE_MODELS = [
|
|
|
9468
9717
|
contextWindow: 1e6,
|
|
9469
9718
|
maxTokens: null
|
|
9470
9719
|
},
|
|
9720
|
+
{
|
|
9721
|
+
id: "stealth/space-bunny-alpha",
|
|
9722
|
+
name: "Space Bunny Alpha",
|
|
9723
|
+
inputModalities: ["text", "image"],
|
|
9724
|
+
reasoningEfforts: [
|
|
9725
|
+
"low",
|
|
9726
|
+
"medium",
|
|
9727
|
+
"high",
|
|
9728
|
+
"max"
|
|
9729
|
+
],
|
|
9730
|
+
contextWindow: 1e6,
|
|
9731
|
+
maxTokens: 524288
|
|
9732
|
+
},
|
|
9471
9733
|
{
|
|
9472
9734
|
id: "poolside/laguna-s-2.1-free",
|
|
9473
9735
|
name: "Laguna S 2.1",
|
|
@@ -9477,18 +9739,22 @@ const COMMAND_CODE_MODELS = [
|
|
|
9477
9739
|
maxTokens: 32768
|
|
9478
9740
|
},
|
|
9479
9741
|
{
|
|
9480
|
-
id: "inclusionai/ling-3.0-flash-free",
|
|
9481
|
-
name: "Ling 3.0 Flash",
|
|
9742
|
+
id: "inclusionai/ling-3.0-flash-sante:free",
|
|
9743
|
+
name: "Ling 3.0 Flash Sante",
|
|
9482
9744
|
inputModalities: ["text"],
|
|
9483
9745
|
reasoningEfforts: [],
|
|
9484
|
-
contextWindow:
|
|
9746
|
+
contextWindow: 262144,
|
|
9485
9747
|
maxTokens: 32768
|
|
9486
9748
|
},
|
|
9487
9749
|
{
|
|
9488
|
-
id: "inclusionai/ling-3.
|
|
9489
|
-
name: "Ling 3.
|
|
9750
|
+
id: "inclusionai/ling-3.1-flash:free",
|
|
9751
|
+
name: "Ling 3.1 Flash",
|
|
9490
9752
|
inputModalities: ["text"],
|
|
9491
|
-
reasoningEfforts: [
|
|
9753
|
+
reasoningEfforts: [
|
|
9754
|
+
"low",
|
|
9755
|
+
"medium",
|
|
9756
|
+
"high"
|
|
9757
|
+
],
|
|
9492
9758
|
contextWindow: 262144,
|
|
9493
9759
|
maxTokens: 32768
|
|
9494
9760
|
},
|
|
@@ -9582,6 +9848,19 @@ const COMMAND_CODE_MODELS = [
|
|
|
9582
9848
|
],
|
|
9583
9849
|
contextWindow: 5e5,
|
|
9584
9850
|
maxTokens: null
|
|
9851
|
+
},
|
|
9852
|
+
{
|
|
9853
|
+
id: "xai/grok-4.7",
|
|
9854
|
+
name: "Grok 4.7",
|
|
9855
|
+
inputModalities: ["text", "image"],
|
|
9856
|
+
reasoningEfforts: [
|
|
9857
|
+
"low",
|
|
9858
|
+
"medium",
|
|
9859
|
+
"high",
|
|
9860
|
+
"xhigh"
|
|
9861
|
+
],
|
|
9862
|
+
contextWindow: 5e5,
|
|
9863
|
+
maxTokens: null
|
|
9585
9864
|
}
|
|
9586
9865
|
];
|
|
9587
9866
|
/** Registry entry for one model id, or undefined when it describes no such model. */
|
|
@@ -9590,8 +9869,8 @@ function commandCodeModelDef(modelId) {
|
|
|
9590
9869
|
}
|
|
9591
9870
|
//#endregion
|
|
9592
9871
|
//#region src/host/command-code/types.ts
|
|
9593
|
-
const PROVIDER_ID$
|
|
9594
|
-
const PROVIDER_NAME$
|
|
9872
|
+
const PROVIDER_ID$5 = "command-code";
|
|
9873
|
+
const PROVIDER_NAME$5 = "Command Code";
|
|
9595
9874
|
/** Browser sign-in page the CLI opens; it redirects back to our loopback server. */
|
|
9596
9875
|
const STUDIO_PATH = "/studio/auth/cli";
|
|
9597
9876
|
/** Query parameter that carries the loopback callback URL to the studio page. */
|
|
@@ -9669,11 +9948,11 @@ function providerUrl(env = resolveApiEnv()) {
|
|
|
9669
9948
|
* @param modelId - exact model id from the live catalog or a caller.
|
|
9670
9949
|
* @returns the wire dialect to build the request for.
|
|
9671
9950
|
*/
|
|
9672
|
-
function wireForModel$
|
|
9951
|
+
function wireForModel$2(modelId) {
|
|
9673
9952
|
return /^claude[-/]/i.test(modelId.trim()) ? "anthropic" : "openai";
|
|
9674
9953
|
}
|
|
9675
9954
|
/**
|
|
9676
|
-
* Reasoning levels one model advertises.
|
|
9955
|
+
* Reasoning levels one model advertises, in DSH's vocabulary.
|
|
9677
9956
|
*
|
|
9678
9957
|
* The registry is the authority: a model it describes but gives no
|
|
9679
9958
|
* `reasoningEfforts` is a non-reasoning model, which is why that answers with an
|
|
@@ -9681,9 +9960,42 @@ function wireForModel$1(modelId) {
|
|
|
9681
9960
|
* describe declares nothing either — guessing from the family name is exactly
|
|
9682
9961
|
* how `deepseek-v4-flash` got treated as a reasoner with image input when it is
|
|
9683
9962
|
* text-only with a different effort set.
|
|
9963
|
+
*
|
|
9964
|
+
* One level is translated rather than copied. Command Code names the
|
|
9965
|
+
* "do not think" level `off`; DSH names the same level `none` (as the Kimi and
|
|
9966
|
+
* Anthropic lines here already do). Both spellings are accepted on the way in —
|
|
9967
|
+
* a stored selection or a session started elsewhere may carry either — but the
|
|
9968
|
+
* adapter only ever advertises `none`, so the picker shows the level DSH
|
|
9969
|
+
* understands.
|
|
9970
|
+
*
|
|
9971
|
+
* `off` is also what the CLI's `thinkingHook` keys on to send no reasoning
|
|
9972
|
+
* field at all, and the OpenAI-family wire has no such level, so
|
|
9973
|
+
* {@link wireReasoningEffort} drops it before the request is built rather than
|
|
9974
|
+
* transmitting a value the endpoint would reject.
|
|
9684
9975
|
*/
|
|
9685
9976
|
function reasoningEffortsFor$1(modelId) {
|
|
9686
|
-
return
|
|
9977
|
+
return (commandCodeModelDef(modelId)?.reasoningEfforts ?? []).map((effort) => effort === "off" ? DISABLED_THINKING_EFFORT : effort);
|
|
9978
|
+
}
|
|
9979
|
+
/**
|
|
9980
|
+
* The level that means "think as little as possible" on this route.
|
|
9981
|
+
*
|
|
9982
|
+
* DSH's spelling, matching the Kimi and Claude lines. Kept as a constant
|
|
9983
|
+
* because three separate places compare against it — the catalog translation,
|
|
9984
|
+
* the wire filter below, and the Anthropic thinking budget.
|
|
9985
|
+
*/
|
|
9986
|
+
const DISABLED_THINKING_EFFORT = "none";
|
|
9987
|
+
/**
|
|
9988
|
+
* Whether one effort survives onto the provider wire.
|
|
9989
|
+
*
|
|
9990
|
+
* `none` is a real DSH level but not a Command Code one: it is expressed by
|
|
9991
|
+
* OMITTING the field, which is exactly what the official CLI does when its
|
|
9992
|
+
* effort is `off`. Every other level this route advertises passes through.
|
|
9993
|
+
*/
|
|
9994
|
+
function wireReasoningEffort(effort) {
|
|
9995
|
+
if (effort === void 0 || effort === null) return void 0;
|
|
9996
|
+
const value = String(effort).trim();
|
|
9997
|
+
if (value === "" || value === "none" || value === "off") return void 0;
|
|
9998
|
+
return value;
|
|
9687
9999
|
}
|
|
9688
10000
|
/**
|
|
9689
10001
|
* Accepted request modalities for one model.
|
|
@@ -9771,7 +10083,7 @@ function maxOutputTokensFor$4(modelId) {
|
|
|
9771
10083
|
* from the registry rather than hand-listed, so a model cannot drift between the
|
|
9772
10084
|
* offline fallback and its real capability entry.
|
|
9773
10085
|
*/
|
|
9774
|
-
const FALLBACK_MODELS$
|
|
10086
|
+
const FALLBACK_MODELS$4 = COMMAND_CODE_MODELS.filter((model) => model.contextWindow !== null).map((model) => ({
|
|
9775
10087
|
id: model.id,
|
|
9776
10088
|
name: model.name,
|
|
9777
10089
|
contextWindow: model.contextWindow
|
|
@@ -9780,6 +10092,7 @@ const FALLBACK_MODELS$3 = COMMAND_CODE_MODELS.filter((model) => model.contextWin
|
|
|
9780
10092
|
//#region src/shared/command-code-contracts.ts
|
|
9781
10093
|
/** Every level, in escalating order; the settings card renders exactly these. */
|
|
9782
10094
|
const COMMAND_CODE_REASONING_EFFORTS = [
|
|
10095
|
+
"none",
|
|
9783
10096
|
"minimal",
|
|
9784
10097
|
"low",
|
|
9785
10098
|
"medium",
|
|
@@ -9794,7 +10107,7 @@ const COMMAND_CODE_PREFERENCES_NAMESPACE = "dsh-command-code";
|
|
|
9794
10107
|
function isReasoningEffort$3(value) {
|
|
9795
10108
|
return typeof value === "string" && COMMAND_CODE_REASONING_EFFORTS.includes(value);
|
|
9796
10109
|
}
|
|
9797
|
-
const DEFAULT_ENABLED_MODEL_IDS$4 = FALLBACK_MODELS$
|
|
10110
|
+
const DEFAULT_ENABLED_MODEL_IDS$4 = FALLBACK_MODELS$4.map((model) => model.id);
|
|
9798
10111
|
/**
|
|
9799
10112
|
* Bind the model selection to the DSH settings document when the harness still
|
|
9800
10113
|
* offers one, and to the JSON file beside it otherwise: a harness without the
|
|
@@ -9860,7 +10173,7 @@ function credentialPath$1() {
|
|
|
9860
10173
|
function modelSettingsPath$1() {
|
|
9861
10174
|
return path.join(dshHomeDir$2(), "storages", "command-code-models.json");
|
|
9862
10175
|
}
|
|
9863
|
-
function optionalString$
|
|
10176
|
+
function optionalString$8(record, key) {
|
|
9864
10177
|
const value = record[key];
|
|
9865
10178
|
if (value === void 0 || value === null) return void 0;
|
|
9866
10179
|
if (typeof value !== "string") throw new Error("Command Code credential payload is invalid");
|
|
@@ -9881,7 +10194,7 @@ function parseCommandCodeCredentials(value) {
|
|
|
9881
10194
|
"planLabel",
|
|
9882
10195
|
"planId"
|
|
9883
10196
|
]) {
|
|
9884
|
-
const parsed = optionalString$
|
|
10197
|
+
const parsed = optionalString$8(record, key);
|
|
9885
10198
|
if (parsed !== void 0) credentials[key] = parsed;
|
|
9886
10199
|
}
|
|
9887
10200
|
const authenticatedAt = record.authenticatedAt;
|
|
@@ -9896,13 +10209,13 @@ function parseCommandCodeCredentials(value) {
|
|
|
9896
10209
|
}
|
|
9897
10210
|
return credentials;
|
|
9898
10211
|
}
|
|
9899
|
-
function credentialAccount$
|
|
10212
|
+
function credentialAccount$3(filePath) {
|
|
9900
10213
|
return createHash("sha256").update(path.resolve(filePath)).digest("hex");
|
|
9901
10214
|
}
|
|
9902
|
-
function createCredentialBackend$
|
|
10215
|
+
function createCredentialBackend$3(filePath) {
|
|
9903
10216
|
if (process.platform === "win32") return new WindowsDpapiCredentialStore(`${filePath}.dpapi`, parseCommandCodeCredentials);
|
|
9904
|
-
if (process.platform === "darwin") return new MacKeychainCredentialStore(PROVIDER_ID$
|
|
9905
|
-
if (process.platform === "linux") return new SecretServiceCredentialStore(PROVIDER_ID$
|
|
10217
|
+
if (process.platform === "darwin") return new MacKeychainCredentialStore(PROVIDER_ID$5, credentialAccount$3(filePath), parseCommandCodeCredentials);
|
|
10218
|
+
if (process.platform === "linux") return new SecretServiceCredentialStore(PROVIDER_ID$5, credentialAccount$3(filePath), parseCommandCodeCredentials);
|
|
9906
10219
|
throw new Error("Command Code credential storage requires Windows, macOS, or Linux.");
|
|
9907
10220
|
}
|
|
9908
10221
|
const credentialOperations$2 = /* @__PURE__ */ new Map();
|
|
@@ -9910,13 +10223,13 @@ const credentialOperations$2 = /* @__PURE__ */ new Map();
|
|
|
9910
10223
|
var FileCredentialStore$1 = class {
|
|
9911
10224
|
filePath;
|
|
9912
10225
|
backend;
|
|
9913
|
-
constructor(filePath = credentialPath$1(), backend = createCredentialBackend$
|
|
10226
|
+
constructor(filePath = credentialPath$1(), backend = createCredentialBackend$3(filePath)) {
|
|
9914
10227
|
this.filePath = filePath;
|
|
9915
10228
|
this.backend = backend;
|
|
9916
10229
|
}
|
|
9917
10230
|
path() {
|
|
9918
10231
|
if (process.platform === "win32") return `${this.filePath}.dpapi`;
|
|
9919
|
-
return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: ${PROVIDER_ID$
|
|
10232
|
+
return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: ${PROVIDER_ID$5}/${credentialAccount$3(this.filePath)}`;
|
|
9920
10233
|
}
|
|
9921
10234
|
serialize(operation) {
|
|
9922
10235
|
const key = path.resolve(this.filePath);
|
|
@@ -10131,7 +10444,7 @@ function commandCodeHeaders(apiKey, extra = {}) {
|
|
|
10131
10444
|
authorization: `Bearer ${apiKey}`,
|
|
10132
10445
|
"content-type": "application/json",
|
|
10133
10446
|
accept: "application/json",
|
|
10134
|
-
"user-agent": `${PROVIDER_ID$
|
|
10447
|
+
"user-agent": `${PROVIDER_ID$5}/${CLI_VERSION}`,
|
|
10135
10448
|
[HEADER_CLI_VERSION]: CLI_VERSION,
|
|
10136
10449
|
[HEADER_CLI_ENVIRONMENT]: process.platform === "win32" ? "windows" : process.platform === "darwin" ? "macos" : "linux",
|
|
10137
10450
|
[HEADER_PROJECT_SLUG]: "dsh-chatgpt-subscription",
|
|
@@ -10167,7 +10480,7 @@ function firstString$1(record, keys) {
|
|
|
10167
10480
|
if (value !== void 0) return value;
|
|
10168
10481
|
}
|
|
10169
10482
|
}
|
|
10170
|
-
function firstNumber$
|
|
10483
|
+
function firstNumber$2(record, keys) {
|
|
10171
10484
|
for (const key of keys) {
|
|
10172
10485
|
const value = asNumber$3(record[key]);
|
|
10173
10486
|
if (value !== void 0) return value;
|
|
@@ -10339,12 +10652,12 @@ function parseWindowLimits(payload, consumed = []) {
|
|
|
10339
10652
|
if (key === "limited" || key === "exceeded") continue;
|
|
10340
10653
|
const record = asRecord$9(limits[key]);
|
|
10341
10654
|
if (record === void 0) continue;
|
|
10342
|
-
const cap = firstNumber$
|
|
10655
|
+
const cap = firstNumber$2(record, [
|
|
10343
10656
|
"cap",
|
|
10344
10657
|
"limit",
|
|
10345
10658
|
"total"
|
|
10346
10659
|
]);
|
|
10347
|
-
const used = firstNumber$
|
|
10660
|
+
const used = firstNumber$2(record, ["used", "consumed"]);
|
|
10348
10661
|
if (cap === void 0 && used === void 0) continue;
|
|
10349
10662
|
consumed.push(record);
|
|
10350
10663
|
const usedFraction = cap !== void 0 && cap > 0 && used !== void 0 ? clamp01$2(used / cap) : null;
|
|
@@ -10374,9 +10687,9 @@ function parseCreditBalances(payload, consumed = []) {
|
|
|
10374
10687
|
const root = asRecord$9(payload) ?? {};
|
|
10375
10688
|
const credits = asRecord$9(deepValue(asRecord$9(root.data) ?? root, ["credits"]));
|
|
10376
10689
|
if (credits === void 0) return [];
|
|
10377
|
-
const monthly = firstNumber$
|
|
10378
|
-
const purchased = firstNumber$
|
|
10379
|
-
const free = firstNumber$
|
|
10690
|
+
const monthly = firstNumber$2(credits, ["monthlyCredits", "monthly_credits"]);
|
|
10691
|
+
const purchased = firstNumber$2(credits, ["purchasedCredits", "purchased_credits"]);
|
|
10692
|
+
const free = firstNumber$2(credits, ["freeCredits", "free_credits"]);
|
|
10380
10693
|
if (monthly === void 0 && purchased === void 0 && free === void 0) return [];
|
|
10381
10694
|
consumed.push(credits);
|
|
10382
10695
|
const meters = [];
|
|
@@ -10455,27 +10768,27 @@ function sweepMeters(payload, skipIds, consumed) {
|
|
|
10455
10768
|
let anonymized = 0;
|
|
10456
10769
|
for (const record of records) {
|
|
10457
10770
|
if (consumed.has(record)) continue;
|
|
10458
|
-
const limit = firstNumber$
|
|
10771
|
+
const limit = firstNumber$2(record, [
|
|
10459
10772
|
"limit",
|
|
10460
10773
|
"quota",
|
|
10461
10774
|
"allowance",
|
|
10462
10775
|
"cap",
|
|
10463
10776
|
"total"
|
|
10464
10777
|
]);
|
|
10465
|
-
const used = firstNumber$
|
|
10778
|
+
const used = firstNumber$2(record, [
|
|
10466
10779
|
"used",
|
|
10467
10780
|
"consumed",
|
|
10468
10781
|
"spent"
|
|
10469
10782
|
]);
|
|
10470
|
-
const remainingRaw = firstNumber$
|
|
10471
|
-
const balance = firstNumber$
|
|
10472
|
-
const usedPercentRaw = firstNumber$
|
|
10783
|
+
const remainingRaw = firstNumber$2(record, ["remaining", "left"]);
|
|
10784
|
+
const balance = firstNumber$2(record, ["balance", "credits"]);
|
|
10785
|
+
const usedPercentRaw = firstNumber$2(record, [
|
|
10473
10786
|
"usedPercent",
|
|
10474
10787
|
"used_percent",
|
|
10475
10788
|
"usagePercent",
|
|
10476
10789
|
"percentUsed"
|
|
10477
10790
|
]);
|
|
10478
|
-
const remainingPercentRaw = firstNumber$
|
|
10791
|
+
const remainingPercentRaw = firstNumber$2(record, [
|
|
10479
10792
|
"remainingPercent",
|
|
10480
10793
|
"remaining_percent",
|
|
10481
10794
|
"percentRemaining"
|
|
@@ -10599,7 +10912,7 @@ function parseUsageWindows$1(payload) {
|
|
|
10599
10912
|
const windows = [];
|
|
10600
10913
|
const seen = /* @__PURE__ */ new Set();
|
|
10601
10914
|
for (const record of records) {
|
|
10602
|
-
const percent = firstNumber$
|
|
10915
|
+
const percent = firstNumber$2(record, [
|
|
10603
10916
|
"usedPercent",
|
|
10604
10917
|
"used_percent",
|
|
10605
10918
|
"usagePercent",
|
|
@@ -10626,7 +10939,7 @@ function parseUsageWindows$1(payload) {
|
|
|
10626
10939
|
"window"
|
|
10627
10940
|
]) ?? id,
|
|
10628
10941
|
usedPercent: Math.round(clamp01$2(normalizePercent(percent) ?? 0) * 100),
|
|
10629
|
-
windowDurationMins: firstNumber$
|
|
10942
|
+
windowDurationMins: firstNumber$2(record, [
|
|
10630
10943
|
"windowDurationMins",
|
|
10631
10944
|
"window_minutes",
|
|
10632
10945
|
"durationMins",
|
|
@@ -10702,7 +11015,7 @@ function parseProviderModels(payload) {
|
|
|
10702
11015
|
models.push({
|
|
10703
11016
|
id,
|
|
10704
11017
|
name: firstString$1(record, ["displayName", "name"]) ?? id,
|
|
10705
|
-
contextWindow: firstNumber$
|
|
11018
|
+
contextWindow: firstNumber$2(record, [
|
|
10706
11019
|
"context_length",
|
|
10707
11020
|
"contextLength",
|
|
10708
11021
|
"context_window",
|
|
@@ -10720,7 +11033,7 @@ async function fetchProviderModels(options = {}) {
|
|
|
10720
11033
|
const response = await (options.fetchFn ?? fetch)(`${providerUrl(options.apiEnv ?? resolveApiEnv())}/models`, {
|
|
10721
11034
|
headers: {
|
|
10722
11035
|
accept: "application/json",
|
|
10723
|
-
"user-agent": PROVIDER_NAME$
|
|
11036
|
+
"user-agent": PROVIDER_NAME$5
|
|
10724
11037
|
},
|
|
10725
11038
|
signal: timeoutSignal$3(options.signal, DISCOVERY_TIMEOUT_MS$3)
|
|
10726
11039
|
});
|
|
@@ -10943,7 +11256,7 @@ function buildModelOptions$2(catalog, enabledModelIds, overrides) {
|
|
|
10943
11256
|
contextWindow: overrides[model.id] && overrides[model.id] > 0 ? overrides[model.id] : contextWindow,
|
|
10944
11257
|
defaultMaxTokens: maxOutputTokensFor$4(model.id),
|
|
10945
11258
|
...efforts.length > 0 ? { reasoningEfforts: efforts } : {},
|
|
10946
|
-
wire: wireForModel$
|
|
11259
|
+
wire: wireForModel$2(model.id)
|
|
10947
11260
|
};
|
|
10948
11261
|
});
|
|
10949
11262
|
}
|
|
@@ -11132,14 +11445,14 @@ function imageBlockToInline$3(block, images) {
|
|
|
11132
11445
|
data: resolved.data
|
|
11133
11446
|
} : void 0;
|
|
11134
11447
|
}
|
|
11135
|
-
function textOf$
|
|
11448
|
+
function textOf$5(content) {
|
|
11136
11449
|
if (typeof content === "string") return sanitizeText$4(content);
|
|
11137
11450
|
if (!Array.isArray(content)) return "";
|
|
11138
11451
|
const parts = [];
|
|
11139
11452
|
for (const block of content) {
|
|
11140
11453
|
if (!isRecord$18(block)) continue;
|
|
11141
11454
|
if (block.type === "text" && typeof block.text === "string") parts.push(sanitizeText$4(block.text));
|
|
11142
|
-
else if (block.type === "tool-result") parts.push(textOf$
|
|
11455
|
+
else if (block.type === "tool-result") parts.push(textOf$5(block.content));
|
|
11143
11456
|
}
|
|
11144
11457
|
return parts.join("");
|
|
11145
11458
|
}
|
|
@@ -11247,7 +11560,7 @@ function leadingSystemText$4(options) {
|
|
|
11247
11560
|
if (typeof options.system === "string" && options.system.trim() !== "") parts.push(options.system);
|
|
11248
11561
|
for (const message of options.messages) {
|
|
11249
11562
|
if (message.role !== "system") continue;
|
|
11250
|
-
const text = textOf$
|
|
11563
|
+
const text = textOf$5(message.content);
|
|
11251
11564
|
if (text !== "") parts.push(text);
|
|
11252
11565
|
}
|
|
11253
11566
|
return parts.length === 0 ? void 0 : parts.join("\n\n");
|
|
@@ -11367,7 +11680,7 @@ function buildOpenAIRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
|
|
|
11367
11680
|
content
|
|
11368
11681
|
});
|
|
11369
11682
|
}
|
|
11370
|
-
const effort = options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort);
|
|
11683
|
+
const effort = wireReasoningEffort(options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort));
|
|
11371
11684
|
const maxTokens = options.maxTokens ?? maxOutputTokensFor$4(options.model);
|
|
11372
11685
|
return {
|
|
11373
11686
|
model: options.model,
|
|
@@ -11501,7 +11814,7 @@ function buildAnthropicRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
|
|
|
11501
11814
|
}
|
|
11502
11815
|
const system = leadingSystemText$4(options);
|
|
11503
11816
|
const maxTokens = options.maxTokens ?? maxOutputTokensFor$4(options.model);
|
|
11504
|
-
const budget = thinkingBudgetFor$1(options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort), maxTokens);
|
|
11817
|
+
const budget = thinkingBudgetFor$1(wireReasoningEffort(options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort)), maxTokens);
|
|
11505
11818
|
return {
|
|
11506
11819
|
model: options.model,
|
|
11507
11820
|
max_tokens: maxTokens,
|
|
@@ -11524,7 +11837,7 @@ function buildAnthropicRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
|
|
|
11524
11837
|
function buildRequest$1(options, wire, images = NO_RESOLVED_IMAGES$4) {
|
|
11525
11838
|
return wire === "anthropic" ? buildAnthropicRequest$1(options, images) : buildOpenAIRequest$1(options, images);
|
|
11526
11839
|
}
|
|
11527
|
-
function createStreamState$
|
|
11840
|
+
function createStreamState$5(wire) {
|
|
11528
11841
|
return {
|
|
11529
11842
|
wire,
|
|
11530
11843
|
blocks: [],
|
|
@@ -11560,7 +11873,7 @@ function closeCurrent$3(state) {
|
|
|
11560
11873
|
block
|
|
11561
11874
|
}];
|
|
11562
11875
|
}
|
|
11563
|
-
function closeToolCalls$
|
|
11876
|
+
function closeToolCalls$4(state) {
|
|
11564
11877
|
const out = [];
|
|
11565
11878
|
for (const [wireIndex, call] of [...state.toolCalls.entries()].sort((a, b) => a[0] - b[0])) {
|
|
11566
11879
|
const block = {
|
|
@@ -11605,7 +11918,7 @@ function processOpenAIStreamLine$1(line, state) {
|
|
|
11605
11918
|
const payload = trimmed.slice(5).trim();
|
|
11606
11919
|
if (payload === "[DONE]") {
|
|
11607
11920
|
state.done = true;
|
|
11608
|
-
return closeStream$
|
|
11921
|
+
return closeStream$4(state);
|
|
11609
11922
|
}
|
|
11610
11923
|
if (payload === "") return [];
|
|
11611
11924
|
const chunk = safeJsonParse$4(payload);
|
|
@@ -11628,7 +11941,7 @@ function processOpenAIStreamLine$1(line, state) {
|
|
|
11628
11941
|
if (delta) {
|
|
11629
11942
|
const reasoning = asString$11(delta.reasoning_content) ?? asString$11(delta.reasoning);
|
|
11630
11943
|
if (reasoning !== void 0 && reasoning !== "") {
|
|
11631
|
-
out.push(...closeToolCalls$
|
|
11944
|
+
out.push(...closeToolCalls$4(state));
|
|
11632
11945
|
if (state.current === null || state.current.type !== "reasoning") out.push(...openTextBlock$3(state, "reasoning"));
|
|
11633
11946
|
state.current.text += sanitizeText$4(reasoning);
|
|
11634
11947
|
state.hasContent = true;
|
|
@@ -11640,7 +11953,7 @@ function processOpenAIStreamLine$1(line, state) {
|
|
|
11640
11953
|
}
|
|
11641
11954
|
const content = asString$11(delta.content);
|
|
11642
11955
|
if (content !== void 0 && content !== "") {
|
|
11643
|
-
out.push(...closeToolCalls$
|
|
11956
|
+
out.push(...closeToolCalls$4(state));
|
|
11644
11957
|
if (state.current === null || state.current.type !== "text") out.push(...openTextBlock$3(state, "text"));
|
|
11645
11958
|
state.current.text += sanitizeText$4(content);
|
|
11646
11959
|
state.hasContent = true;
|
|
@@ -11660,7 +11973,7 @@ function processOpenAIStreamLine$1(line, state) {
|
|
|
11660
11973
|
if (finish !== void 0 && finish !== "") {
|
|
11661
11974
|
state.finishReason = finish;
|
|
11662
11975
|
out.push(...closeCurrent$3(state));
|
|
11663
|
-
out.push(...closeToolCalls$
|
|
11976
|
+
out.push(...closeToolCalls$4(state));
|
|
11664
11977
|
}
|
|
11665
11978
|
return out;
|
|
11666
11979
|
}
|
|
@@ -11878,7 +12191,7 @@ function processAnthropicStreamLine$1(line, state) {
|
|
|
11878
12191
|
}
|
|
11879
12192
|
if (type === "message_stop") {
|
|
11880
12193
|
state.done = true;
|
|
11881
|
-
return closeStream$
|
|
12194
|
+
return closeStream$4(state);
|
|
11882
12195
|
}
|
|
11883
12196
|
if (type === "error") throw new LlmError$1(`Command Code stream error: ${asString$11((isRecord$18(event.error) ? event.error : {}).message) ?? "unknown error"}`, "PROVIDER_ERROR");
|
|
11884
12197
|
return out;
|
|
@@ -11899,10 +12212,10 @@ function finishReasonFor$4(state) {
|
|
|
11899
12212
|
return { kind: "stop" };
|
|
11900
12213
|
}
|
|
11901
12214
|
/** Flush every open block, then emit usage and the terminal finish. */
|
|
11902
|
-
function closeStream$
|
|
12215
|
+
function closeStream$4(state) {
|
|
11903
12216
|
if (state.finished) return [];
|
|
11904
12217
|
state.finished = true;
|
|
11905
|
-
const out = [...closeCurrent$3(state), ...closeToolCalls$
|
|
12218
|
+
const out = [...closeCurrent$3(state), ...closeToolCalls$4(state)];
|
|
11906
12219
|
if (state.sawUsage) out.push({
|
|
11907
12220
|
type: "usage",
|
|
11908
12221
|
usage: tokenUsage$4(state)
|
|
@@ -11932,7 +12245,7 @@ function assertStreamComplete$4(state) {
|
|
|
11932
12245
|
* the set: `INVALID_CREDENTIAL` (a rejected key fails identically on every
|
|
11933
12246
|
* attempt) and `ABORTED` (the caller already cancelled).
|
|
11934
12247
|
*/
|
|
11935
|
-
const RETRY_POLICY$
|
|
12248
|
+
const RETRY_POLICY$5 = resolveRetryPolicy({
|
|
11936
12249
|
mode: "normal",
|
|
11937
12250
|
maxRetries: 3,
|
|
11938
12251
|
retryableCodes: [
|
|
@@ -11952,7 +12265,7 @@ function resolveDefaultReasoningEffort$3(efforts, configuredEffort) {
|
|
|
11952
12265
|
if (configuredEffort && efforts.includes(configuredEffort)) return ReasoningEffortId(configuredEffort);
|
|
11953
12266
|
}
|
|
11954
12267
|
/** Cooldown one rate-limited key takes when the provider states no delay. */
|
|
11955
|
-
const POOL_COOLDOWN_MS$
|
|
12268
|
+
const POOL_COOLDOWN_MS$4 = 15 * 6e4;
|
|
11956
12269
|
var CommandCodeAdapter = class extends LlmAdapter {
|
|
11957
12270
|
store;
|
|
11958
12271
|
modelSettings;
|
|
@@ -11978,11 +12291,11 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
11978
12291
|
providerInfo(provider) {
|
|
11979
12292
|
return {
|
|
11980
12293
|
id: provider,
|
|
11981
|
-
name: PROVIDER_NAME$
|
|
12294
|
+
name: PROVIDER_NAME$5
|
|
11982
12295
|
};
|
|
11983
12296
|
}
|
|
11984
12297
|
providerRetryPolicy() {
|
|
11985
|
-
return RETRY_POLICY$
|
|
12298
|
+
return RETRY_POLICY$5;
|
|
11986
12299
|
}
|
|
11987
12300
|
imageRequestPricing() {}
|
|
11988
12301
|
settings() {
|
|
@@ -11998,7 +12311,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
11998
12311
|
apiEnv: resolveApiEnv()
|
|
11999
12312
|
})))().catch(() => []);
|
|
12000
12313
|
if (live.length > 0) return live;
|
|
12001
|
-
return FALLBACK_MODELS$
|
|
12314
|
+
return FALLBACK_MODELS$4.map((model) => ({
|
|
12002
12315
|
id: model.id,
|
|
12003
12316
|
name: model.name,
|
|
12004
12317
|
contextWindow: model.contextWindow
|
|
@@ -12034,7 +12347,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12034
12347
|
name: entry?.name ?? modelId,
|
|
12035
12348
|
inputModalities: inputModalitiesFor$1(modelId),
|
|
12036
12349
|
context: { contextWindow: this.contextWindowFor(modelId, entry, settings.contextWindowOverrides) },
|
|
12037
|
-
|
|
12350
|
+
...outputReservation(this.contextWindowFor(modelId, entry, settings.contextWindowOverrides), maxOutputTokensFor$4(modelId)),
|
|
12038
12351
|
...efforts.length === 0 ? {} : { reasoning: {
|
|
12039
12352
|
efforts: efforts.map((effort) => ({
|
|
12040
12353
|
id: ReasoningEffortId(effort),
|
|
@@ -12057,11 +12370,17 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12057
12370
|
...options,
|
|
12058
12371
|
reasoningEffort: ReasoningEffortId(String(effort))
|
|
12059
12372
|
};
|
|
12060
|
-
|
|
12373
|
+
const advertised = reasoningEffortsFor$1(options.model);
|
|
12374
|
+
const effectiveEffort = effectiveOptions.reasoningEffort === void 0 ? void 0 : String(effectiveOptions.reasoningEffort);
|
|
12375
|
+
const resolvedOptions = effectiveEffort === void 0 || advertised.length === 0 || advertised.includes(effectiveEffort) ? effectiveOptions : {
|
|
12376
|
+
...effectiveOptions,
|
|
12377
|
+
reasoningEffort: void 0
|
|
12378
|
+
};
|
|
12379
|
+
yield* wrapStreamWithWatchdog((watchdogSignal) => this.requestStream(resolvedOptions, watchdogSignal), options.signal, STREAM_IDLE_TIMEOUT_MS$4, STREAM_IDLE_TIMEOUT_CODE$4, PROVIDER_NAME$5);
|
|
12061
12380
|
}
|
|
12062
12381
|
async *requestStream(options, signal) {
|
|
12063
12382
|
const fetchFn = this.options.fetchFn ?? fetch;
|
|
12064
|
-
const wire = wireForModel$
|
|
12383
|
+
const wire = wireForModel$2(options.model);
|
|
12065
12384
|
const requestOptions = offloadOldestRequestImages$3(normalizeGenerateOptions(options));
|
|
12066
12385
|
const images = await resolveRequestImages$3(requestOptions, this.options.attachments, signal);
|
|
12067
12386
|
const body = JSON.stringify(buildRequest$1(requestOptions, wire, images));
|
|
@@ -12075,7 +12394,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12075
12394
|
let apiEnv;
|
|
12076
12395
|
if (pool === null) {
|
|
12077
12396
|
const credentials = await this.store.read();
|
|
12078
|
-
if (credentials === null) throw new LlmError(`Not signed in to ${PROVIDER_NAME$
|
|
12397
|
+
if (credentials === null) throw new LlmError(`Not signed in to ${PROVIDER_NAME$5}. Sign in from Settings > Command Code, or paste an API key there.`, "MISSING_CREDENTIAL");
|
|
12079
12398
|
apiKey = credentials.apiKey;
|
|
12080
12399
|
apiEnv = credentials.apiEnv ?? resolveApiEnv();
|
|
12081
12400
|
} else {
|
|
@@ -12111,13 +12430,13 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12111
12430
|
detail = (await response.text().catch(() => "")).slice(0, 600);
|
|
12112
12431
|
if (pool === null || accountId === void 0) break;
|
|
12113
12432
|
if (response.status === 401 || response.status === 403) {
|
|
12114
|
-
await pool.markAuthFailed(accountId, `${PROVIDER_NAME$
|
|
12433
|
+
await pool.markAuthFailed(accountId, `${PROVIDER_NAME$5} rejected the stored API key (${response.status}).`, "invalid").catch(() => void 0);
|
|
12115
12434
|
if (await pool.hasAnotherAvailableAccount(tried)) continue;
|
|
12116
12435
|
break;
|
|
12117
12436
|
}
|
|
12118
12437
|
if (response.status === 429) {
|
|
12119
12438
|
const after = retryAfterMs$1(response.headers);
|
|
12120
|
-
await pool.markCooldown(accountId, after ?? POOL_COOLDOWN_MS$
|
|
12439
|
+
await pool.markCooldown(accountId, after ?? POOL_COOLDOWN_MS$4, `${PROVIDER_NAME$5} 429`).catch(() => void 0);
|
|
12121
12440
|
if (await pool.hasAnotherAvailableAccount(tried)) continue;
|
|
12122
12441
|
break;
|
|
12123
12442
|
}
|
|
@@ -12125,21 +12444,21 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12125
12444
|
}
|
|
12126
12445
|
if (response === void 0 || !response.ok) {
|
|
12127
12446
|
const status = response?.status ?? 500;
|
|
12128
|
-
if (status === 401 || status === 403) throw new LlmError(`${PROVIDER_NAME$
|
|
12447
|
+
if (status === 401 || status === 403) throw new LlmError(`${PROVIDER_NAME$5} rejected the stored API key (${status}). Sign in again from Settings > Command Code.${detail ? ` ${detail}` : ""}`, "INVALID_CREDENTIAL", { status });
|
|
12129
12448
|
if (status === 429) {
|
|
12130
12449
|
const after = response === void 0 ? void 0 : retryAfterMs$1(response.headers);
|
|
12131
|
-
throw new LlmError(`${PROVIDER_NAME$
|
|
12450
|
+
throw new LlmError(`${PROVIDER_NAME$5} rate limit or plan quota reached (429). Check the quota card in Settings > Command Code.${detail ? ` ${detail}` : ""}`, "RATE_LIMIT", {
|
|
12132
12451
|
status: 429,
|
|
12133
12452
|
...after === void 0 ? {} : { providerRetryAfterMs: after }
|
|
12134
12453
|
});
|
|
12135
12454
|
}
|
|
12136
|
-
if (status >= 500) throw new LlmError(`${PROVIDER_NAME$
|
|
12137
|
-
throw new LlmError(`${PROVIDER_NAME$
|
|
12455
|
+
if (status >= 500) throw new LlmError(`${PROVIDER_NAME$5} upstream server error (${status}): ${detail || "No response"}`, "SERVER", { status });
|
|
12456
|
+
throw new LlmError(`${PROVIDER_NAME$5} API error (${status}): ${detail || "No response"}`, "PROVIDER_ERROR", { status });
|
|
12138
12457
|
}
|
|
12139
12458
|
if (response.body === null) throw new LlmError("Command Code returned an empty response body", "PROVIDER_ERROR");
|
|
12140
12459
|
const reader = response.body.getReader();
|
|
12141
12460
|
const decoder = new TextDecoder();
|
|
12142
|
-
const state = createStreamState$
|
|
12461
|
+
const state = createStreamState$5(wire);
|
|
12143
12462
|
let buffer = "";
|
|
12144
12463
|
try {
|
|
12145
12464
|
while (true) {
|
|
@@ -12157,7 +12476,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12157
12476
|
if (buffer.trim() !== "") for (const line of buffer.split("\n")) for (const chunk of processLine$1(line, state, wire)) yield chunk;
|
|
12158
12477
|
if (state.finished) return;
|
|
12159
12478
|
assertStreamComplete$4(state);
|
|
12160
|
-
for (const chunk of closeStream$
|
|
12479
|
+
for (const chunk of closeStream$4(state)) yield chunk;
|
|
12161
12480
|
} finally {
|
|
12162
12481
|
reader.cancel().catch(() => void 0);
|
|
12163
12482
|
}
|
|
@@ -12183,7 +12502,7 @@ function commandCodeAccountKey(credentials) {
|
|
|
12183
12502
|
if (credentials.userId !== void 0 && credentials.userId !== "") return `${credentials.userId}:${credentials.keyName ?? ""}`;
|
|
12184
12503
|
return createHash("sha256").update(credentials.apiKey).digest("hex");
|
|
12185
12504
|
}
|
|
12186
|
-
function optionalString$
|
|
12505
|
+
function optionalString$7(record, key) {
|
|
12187
12506
|
const value = record[key];
|
|
12188
12507
|
if (value === void 0 || value === null) return void 0;
|
|
12189
12508
|
if (typeof value !== "string") throw new Error("Command Code pool account field is invalid");
|
|
@@ -12215,7 +12534,7 @@ function parseCommandCodePoolData(value) {
|
|
|
12215
12534
|
"planId",
|
|
12216
12535
|
"userId"
|
|
12217
12536
|
]) {
|
|
12218
|
-
const parsed = optionalString$
|
|
12537
|
+
const parsed = optionalString$7(raw, key);
|
|
12219
12538
|
if (parsed !== void 0) account[key] = parsed;
|
|
12220
12539
|
}
|
|
12221
12540
|
if (account.email === void 0 && credentials.email !== void 0) account.email = credentials.email;
|
|
@@ -12247,7 +12566,7 @@ var CommandCodeAccountPool = class extends AccountPoolCore {
|
|
|
12247
12566
|
constructor(options = {}) {
|
|
12248
12567
|
const store = options.store ?? new FileCredentialStore$1();
|
|
12249
12568
|
const hooks = {
|
|
12250
|
-
providerId: PROVIDER_ID$
|
|
12569
|
+
providerId: PROVIDER_ID$5,
|
|
12251
12570
|
displayName: "Command Code",
|
|
12252
12571
|
poolFile: commandCodePoolPath(),
|
|
12253
12572
|
keychainService: "dsh-command-code-pool",
|
|
@@ -12413,7 +12732,7 @@ function applyCors(request, response) {
|
|
|
12413
12732
|
response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
12414
12733
|
response.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
12415
12734
|
}
|
|
12416
|
-
function sendJson$
|
|
12735
|
+
function sendJson$7(response, status, body) {
|
|
12417
12736
|
response.writeHead(status, { "content-type": "application/json" });
|
|
12418
12737
|
response.end(JSON.stringify(body));
|
|
12419
12738
|
}
|
|
@@ -12606,7 +12925,7 @@ function createAuthServer(port, expectedState, options = {}) {
|
|
|
12606
12925
|
try {
|
|
12607
12926
|
fields = fieldsFromPayload(raw, contentType);
|
|
12608
12927
|
} catch {
|
|
12609
|
-
sendJson$
|
|
12928
|
+
sendJson$7(response, 400, {
|
|
12610
12929
|
success: false,
|
|
12611
12930
|
error: "Invalid JSON"
|
|
12612
12931
|
});
|
|
@@ -12614,7 +12933,7 @@ function createAuthServer(port, expectedState, options = {}) {
|
|
|
12614
12933
|
}
|
|
12615
12934
|
if (fields.error) {
|
|
12616
12935
|
if (fields.state !== expectedState) {
|
|
12617
|
-
sendJson$
|
|
12936
|
+
sendJson$7(response, 403, {
|
|
12618
12937
|
success: false,
|
|
12619
12938
|
error: "Invalid state token"
|
|
12620
12939
|
});
|
|
@@ -12626,14 +12945,14 @@ function createAuthServer(port, expectedState, options = {}) {
|
|
|
12626
12945
|
}
|
|
12627
12946
|
const apiKey = fields.apiKey;
|
|
12628
12947
|
if (!apiKey) {
|
|
12629
|
-
sendJson$
|
|
12948
|
+
sendJson$7(response, 400, {
|
|
12630
12949
|
success: false,
|
|
12631
12950
|
error: "Missing required fields"
|
|
12632
12951
|
});
|
|
12633
12952
|
return;
|
|
12634
12953
|
}
|
|
12635
12954
|
if (fields.state !== expectedState) {
|
|
12636
|
-
sendJson$
|
|
12955
|
+
sendJson$7(response, 403, {
|
|
12637
12956
|
success: false,
|
|
12638
12957
|
error: "Invalid state token"
|
|
12639
12958
|
});
|
|
@@ -12783,18 +13102,18 @@ function isCommandCodeReasoningEffort(value) {
|
|
|
12783
13102
|
return typeof value === "string" && COMMAND_CODE_REASONING_EFFORTS.includes(value);
|
|
12784
13103
|
}
|
|
12785
13104
|
const MAX_BODY_BYTES$5 = 64 * 1024;
|
|
12786
|
-
const ROUTE_PREFIX$
|
|
12787
|
-
function sendJson$
|
|
13105
|
+
const ROUTE_PREFIX$4 = "/command-code/api";
|
|
13106
|
+
function sendJson$6(response, status, body) {
|
|
12788
13107
|
response.writeHead(status, { "Content-Type": "application/json" });
|
|
12789
13108
|
response.end(JSON.stringify(body));
|
|
12790
13109
|
}
|
|
12791
|
-
function sendMethodNotAllowed$
|
|
12792
|
-
sendJson$
|
|
13110
|
+
function sendMethodNotAllowed$5(response) {
|
|
13111
|
+
sendJson$6(response, 405, {
|
|
12793
13112
|
ok: false,
|
|
12794
13113
|
error: "Method Not Allowed"
|
|
12795
13114
|
});
|
|
12796
13115
|
}
|
|
12797
|
-
async function readRequestJson$
|
|
13116
|
+
async function readRequestJson$6(request) {
|
|
12798
13117
|
return new Promise((resolve, reject) => {
|
|
12799
13118
|
const chunks = [];
|
|
12800
13119
|
let total = 0;
|
|
@@ -12819,7 +13138,7 @@ async function readRequestJson$5(request) {
|
|
|
12819
13138
|
});
|
|
12820
13139
|
}
|
|
12821
13140
|
function fallbackCatalog$1() {
|
|
12822
|
-
return FALLBACK_MODELS$
|
|
13141
|
+
return FALLBACK_MODELS$4.map((model) => ({
|
|
12823
13142
|
id: model.id,
|
|
12824
13143
|
name: model.name,
|
|
12825
13144
|
contextWindow: model.contextWindow
|
|
@@ -12836,7 +13155,7 @@ function fallbackCatalog$1() {
|
|
|
12836
13155
|
function resolveEnabledModelIds$4(stored, catalog, enabled = true) {
|
|
12837
13156
|
if (!enabled) return [];
|
|
12838
13157
|
const catalogIds = catalog.map((model) => model.id);
|
|
12839
|
-
const shippedDefaults = new Set(FALLBACK_MODELS$
|
|
13158
|
+
const shippedDefaults = new Set(FALLBACK_MODELS$4.map((model) => model.id));
|
|
12840
13159
|
if (stored.length > 0 && stored.length === shippedDefaults.size && stored.every((id) => shippedDefaults.has(id))) return catalogIds;
|
|
12841
13160
|
const known = new Set(catalogIds);
|
|
12842
13161
|
return stored.filter((id) => known.has(id));
|
|
@@ -12921,13 +13240,13 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
12921
13240
|
const quotaRefresh = new QuotaRefresh();
|
|
12922
13241
|
return ctx.webServer.register({
|
|
12923
13242
|
kind: "prefix",
|
|
12924
|
-
path: ROUTE_PREFIX$
|
|
13243
|
+
path: ROUTE_PREFIX$4,
|
|
12925
13244
|
handler: async (request, response) => {
|
|
12926
13245
|
const path = new URL(request.url || "/", "http://dsh.local").pathname.replace(/^\/command-code\/api\/?/, "");
|
|
12927
13246
|
const method = request.method ?? "GET";
|
|
12928
13247
|
try {
|
|
12929
13248
|
if (path === "" || path === "status") {
|
|
12930
|
-
if (method !== "GET") return sendMethodNotAllowed$
|
|
13249
|
+
if (method !== "GET") return sendMethodNotAllowed$5(response);
|
|
12931
13250
|
const credentials = await store.read();
|
|
12932
13251
|
const targetId = await activeAccountId();
|
|
12933
13252
|
const cached = getCachedQuotaFor$1(targetId);
|
|
@@ -12936,7 +13255,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
12936
13255
|
if (cached === void 0) await quotaRefresh.run(refresh);
|
|
12937
13256
|
else quotaRefresh.start(refresh);
|
|
12938
13257
|
}
|
|
12939
|
-
return sendJson$
|
|
13258
|
+
return sendJson$6(response, 200, {
|
|
12940
13259
|
ok: true,
|
|
12941
13260
|
value: {
|
|
12942
13261
|
...await readStatus(),
|
|
@@ -12945,12 +13264,12 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
12945
13264
|
});
|
|
12946
13265
|
}
|
|
12947
13266
|
if (path === "login") {
|
|
12948
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
12949
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13267
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13268
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
12950
13269
|
ok: false,
|
|
12951
13270
|
error: "Cross-origin request rejected."
|
|
12952
13271
|
});
|
|
12953
|
-
return sendJson$
|
|
13272
|
+
return sendJson$6(response, 200, {
|
|
12954
13273
|
ok: true,
|
|
12955
13274
|
value: await beginWebLogin$3(store, {
|
|
12956
13275
|
fetchFn,
|
|
@@ -12959,25 +13278,25 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
12959
13278
|
});
|
|
12960
13279
|
}
|
|
12961
13280
|
if (path === "login/status") {
|
|
12962
|
-
if (method !== "GET") return sendMethodNotAllowed$
|
|
12963
|
-
return sendJson$
|
|
13281
|
+
if (method !== "GET") return sendMethodNotAllowed$5(response);
|
|
13282
|
+
return sendJson$6(response, 200, {
|
|
12964
13283
|
ok: true,
|
|
12965
13284
|
value: getWebLoginStatus()
|
|
12966
13285
|
});
|
|
12967
13286
|
}
|
|
12968
13287
|
if (path === "login/apikey") {
|
|
12969
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
12970
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13288
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13289
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
12971
13290
|
ok: false,
|
|
12972
13291
|
error: "Cross-origin request rejected."
|
|
12973
13292
|
});
|
|
12974
|
-
const body = await readRequestJson$
|
|
13293
|
+
const body = await readRequestJson$6(request);
|
|
12975
13294
|
const account = await saveApiKey(store, typeof body.apiKey === "string" ? body.apiKey : "", {
|
|
12976
13295
|
fetchFn,
|
|
12977
13296
|
...accountPool === void 0 ? {} : { onSave: (credentials) => accountPool.addAccount(credentials) }
|
|
12978
13297
|
});
|
|
12979
13298
|
clearCachedQuota$2();
|
|
12980
|
-
return sendJson$
|
|
13299
|
+
return sendJson$6(response, 200, {
|
|
12981
13300
|
ok: true,
|
|
12982
13301
|
value: {
|
|
12983
13302
|
...await readStatus(),
|
|
@@ -12986,13 +13305,13 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
12986
13305
|
});
|
|
12987
13306
|
}
|
|
12988
13307
|
if (path === "connection/test") {
|
|
12989
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
12990
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13308
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13309
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
12991
13310
|
ok: false,
|
|
12992
13311
|
error: "Cross-origin request rejected."
|
|
12993
13312
|
});
|
|
12994
13313
|
const credentials = await store.read();
|
|
12995
|
-
if (credentials === null) return sendJson$
|
|
13314
|
+
if (credentials === null) return sendJson$6(response, 400, {
|
|
12996
13315
|
ok: false,
|
|
12997
13316
|
error: "Not signed in."
|
|
12998
13317
|
});
|
|
@@ -13001,7 +13320,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13001
13320
|
fetchFn,
|
|
13002
13321
|
apiEnv: credentials.apiEnv ?? resolveApiEnv()
|
|
13003
13322
|
});
|
|
13004
|
-
return sendJson$
|
|
13323
|
+
return sendJson$6(response, 200, {
|
|
13005
13324
|
ok: true,
|
|
13006
13325
|
value: {
|
|
13007
13326
|
connected: true,
|
|
@@ -13019,24 +13338,24 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13019
13338
|
});
|
|
13020
13339
|
}
|
|
13021
13340
|
if (path === "quota") {
|
|
13022
|
-
if (method !== "GET" && method !== "POST") return sendMethodNotAllowed$
|
|
13341
|
+
if (method !== "GET" && method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13023
13342
|
await fetchAccountQuota$2(quotaStore, fetchFn, true, await activeAccountId());
|
|
13024
|
-
return sendJson$
|
|
13343
|
+
return sendJson$6(response, 200, {
|
|
13025
13344
|
ok: true,
|
|
13026
13345
|
value: await readStatus()
|
|
13027
13346
|
});
|
|
13028
13347
|
}
|
|
13029
13348
|
if (path === "models" || path === "settings") {
|
|
13030
|
-
if (method === "GET") return sendJson$
|
|
13349
|
+
if (method === "GET") return sendJson$6(response, 200, {
|
|
13031
13350
|
ok: true,
|
|
13032
13351
|
value: await readStatus()
|
|
13033
13352
|
});
|
|
13034
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
13035
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13353
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13354
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
13036
13355
|
ok: false,
|
|
13037
13356
|
error: "Cross-origin request rejected."
|
|
13038
13357
|
});
|
|
13039
|
-
const body = await readRequestJson$
|
|
13358
|
+
const body = await readRequestJson$6(request);
|
|
13040
13359
|
const patch = {};
|
|
13041
13360
|
if (typeof body.enabled === "boolean") patch.enabled = body.enabled;
|
|
13042
13361
|
if (Array.isArray(body.enabledModelIds)) patch.enabledModelIds = body.enabledModelIds.filter((id) => typeof id === "string");
|
|
@@ -13053,14 +13372,14 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13053
13372
|
if (preferences) await preferences.update(patch);
|
|
13054
13373
|
else await modelSettings.updateSettings(patch);
|
|
13055
13374
|
if (patch.enabledModelIds !== void 0 || patch.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
|
|
13056
|
-
return sendJson$
|
|
13375
|
+
return sendJson$6(response, 200, {
|
|
13057
13376
|
ok: true,
|
|
13058
13377
|
value: await readStatus()
|
|
13059
13378
|
});
|
|
13060
13379
|
}
|
|
13061
13380
|
if (path === "catalog/refresh") {
|
|
13062
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
13063
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13381
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13382
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
13064
13383
|
ok: false,
|
|
13065
13384
|
error: "Cross-origin request rejected."
|
|
13066
13385
|
});
|
|
@@ -13070,7 +13389,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13070
13389
|
force: true,
|
|
13071
13390
|
apiEnv: resolveApiEnv()
|
|
13072
13391
|
});
|
|
13073
|
-
return sendJson$
|
|
13392
|
+
return sendJson$6(response, 200, {
|
|
13074
13393
|
ok: true,
|
|
13075
13394
|
value: await readStatus()
|
|
13076
13395
|
});
|
|
@@ -13078,7 +13397,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13078
13397
|
if (path === "accounts") {
|
|
13079
13398
|
if (method === "GET") {
|
|
13080
13399
|
const data = accountPool === void 0 ? null : await accountPool.read().catch(() => null);
|
|
13081
|
-
return sendJson$
|
|
13400
|
+
return sendJson$6(response, 200, {
|
|
13082
13401
|
ok: true,
|
|
13083
13402
|
value: {
|
|
13084
13403
|
accounts: accountPool === void 0 ? [] : await accountPool.listAccounts().catch(() => []),
|
|
@@ -13087,16 +13406,16 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13087
13406
|
}
|
|
13088
13407
|
});
|
|
13089
13408
|
}
|
|
13090
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
13091
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13409
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13410
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
13092
13411
|
ok: false,
|
|
13093
13412
|
error: "Cross-origin request rejected."
|
|
13094
13413
|
});
|
|
13095
|
-
if (accountPool === void 0) return sendJson$
|
|
13414
|
+
if (accountPool === void 0) return sendJson$6(response, 400, {
|
|
13096
13415
|
ok: false,
|
|
13097
13416
|
error: "Account pool is not installed."
|
|
13098
13417
|
});
|
|
13099
|
-
const body = await readRequestJson$
|
|
13418
|
+
const body = await readRequestJson$6(request);
|
|
13100
13419
|
const action = typeof body.action === "string" ? body.action : "";
|
|
13101
13420
|
const accountId = typeof body.accountId === "string" ? body.accountId : void 0;
|
|
13102
13421
|
if (action === "set-primary" && accountId !== void 0) await accountPool.setPrimary(accountId);
|
|
@@ -13107,20 +13426,20 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13107
13426
|
else if (action === "relogin" && accountId !== void 0) await accountPool.clearAuthFailed(accountId);
|
|
13108
13427
|
clearCachedQuota$2();
|
|
13109
13428
|
clearCachedCatalog$3();
|
|
13110
|
-
return sendJson$
|
|
13429
|
+
return sendJson$6(response, 200, {
|
|
13111
13430
|
ok: true,
|
|
13112
13431
|
value: await readStatus()
|
|
13113
13432
|
});
|
|
13114
13433
|
}
|
|
13115
13434
|
if (path === "logout") {
|
|
13116
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
13117
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13435
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13436
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
13118
13437
|
ok: false,
|
|
13119
13438
|
error: "Cross-origin request rejected."
|
|
13120
13439
|
});
|
|
13121
13440
|
if (accountPool === void 0) await store.delete();
|
|
13122
13441
|
else {
|
|
13123
|
-
const body = await readRequestJson$
|
|
13442
|
+
const body = await readRequestJson$6(request).catch(() => ({}));
|
|
13124
13443
|
const data = await accountPool.read().catch(() => null);
|
|
13125
13444
|
const target = typeof body.accountId === "string" ? body.accountId : data?.activeAccountId ?? data?.accounts.find((account) => account.isPrimary)?.id ?? data?.accounts[0]?.id;
|
|
13126
13445
|
if (target === void 0) await store.delete();
|
|
@@ -13128,17 +13447,17 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13128
13447
|
}
|
|
13129
13448
|
clearCachedQuota$2();
|
|
13130
13449
|
clearCachedCatalog$3();
|
|
13131
|
-
return sendJson$
|
|
13450
|
+
return sendJson$6(response, 200, {
|
|
13132
13451
|
ok: true,
|
|
13133
13452
|
value: await readStatus()
|
|
13134
13453
|
});
|
|
13135
13454
|
}
|
|
13136
|
-
return sendJson$
|
|
13455
|
+
return sendJson$6(response, 404, {
|
|
13137
13456
|
ok: false,
|
|
13138
13457
|
error: "not-found"
|
|
13139
13458
|
});
|
|
13140
13459
|
} catch (error) {
|
|
13141
|
-
return sendJson$
|
|
13460
|
+
return sendJson$6(response, 500, {
|
|
13142
13461
|
ok: false,
|
|
13143
13462
|
error: error instanceof Error ? error.message : String(error)
|
|
13144
13463
|
});
|
|
@@ -13147,6 +13466,1377 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13147
13466
|
});
|
|
13148
13467
|
}
|
|
13149
13468
|
//#endregion
|
|
13469
|
+
//#region src/host/ollama/types.ts
|
|
13470
|
+
/**
|
|
13471
|
+
* Static facts about the Ollama provider API.
|
|
13472
|
+
*
|
|
13473
|
+
* Ollama publishes three surfaces, and this line speaks two of them:
|
|
13474
|
+
*
|
|
13475
|
+
* - the **native API** (`https://ollama.com/api`) — Ollama's own wire format,
|
|
13476
|
+
* with `/api/chat` and `/api/tags`;
|
|
13477
|
+
* - the **OpenAI-compatible API** (`https://ollama.com/v1`) — a documented
|
|
13478
|
+
* subset of the OpenAI API, which is what a coding agent wants by default
|
|
13479
|
+
* because its tool-calling and streaming shapes are the familiar ones.
|
|
13480
|
+
*
|
|
13481
|
+
* A third surface, the Anthropic-compatible `/v1/messages`, exists and is not
|
|
13482
|
+
* used here: the Claude line already transcribes the Anthropic wire format
|
|
13483
|
+
* against a snapshot it can verify, and routing Ollama's subset of it through
|
|
13484
|
+
* the same mapper would claim capabilities Ollama does not document.
|
|
13485
|
+
*
|
|
13486
|
+
* DOCUMENTED CLOUD LIMITS (docs/api/openai-compatibility, docs/cloud):
|
|
13487
|
+
*
|
|
13488
|
+
* - No stateful Responses. Only the stateless form works.
|
|
13489
|
+
* - No built-in web search through `/v1/responses`.
|
|
13490
|
+
* - No custom/freeform tool-call replay.
|
|
13491
|
+
*
|
|
13492
|
+
* Those are server-side capability limits, not choices made here, so this line
|
|
13493
|
+
* sends ordinary `tools` arrays and streams ordinary `tool_calls` back, and
|
|
13494
|
+
* makes no promise about replaying a custom tool call across turns. Saying so
|
|
13495
|
+
* in the settings card is the honest rendering of a documented boundary.
|
|
13496
|
+
*
|
|
13497
|
+
* Authentication is a static API key in `Authorization: Bearer`, not an OAuth
|
|
13498
|
+
* flow: keys do not expire and are revoked from the account settings page.
|
|
13499
|
+
*/
|
|
13500
|
+
const PROVIDER_ID$4 = "ollama";
|
|
13501
|
+
const PROVIDER_NAME$4 = "Ollama";
|
|
13502
|
+
/** Direct cloud access, per docs/api/introduction. */
|
|
13503
|
+
const CLOUD_BASE_URL = "https://ollama.com";
|
|
13504
|
+
/** Native surface. */
|
|
13505
|
+
const NATIVE_CHAT_PATH = "/api/chat";
|
|
13506
|
+
const NATIVE_TAGS_PATH = "/api/tags";
|
|
13507
|
+
const OPENAI_CHAT_PATH = "/chat/completions";
|
|
13508
|
+
/**
|
|
13509
|
+
* Default context window for a model whose catalog entry states none.
|
|
13510
|
+
*
|
|
13511
|
+
* Ollama's `/api/tags` returns names and families, not context windows, so a
|
|
13512
|
+
* fixed conservative value is the honest number here. It is deliberately small
|
|
13513
|
+
* enough to be true of every cloud model rather than flattering to one.
|
|
13514
|
+
*/
|
|
13515
|
+
const DEFAULT_CONTEXT_WINDOW$4 = 128e3;
|
|
13516
|
+
/**
|
|
13517
|
+
* Output cap used when neither the caller nor the catalog states one.
|
|
13518
|
+
*
|
|
13519
|
+
* Ollama does not document a per-model maximum output length, and unlike the
|
|
13520
|
+
* Claude line there is no snapshot to read one from, so this is a request-level
|
|
13521
|
+
* ceiling rather than a claim about what the model can do.
|
|
13522
|
+
*/
|
|
13523
|
+
const DEFAULT_MAX_OUTPUT_TOKENS = 8192;
|
|
13524
|
+
/** How long a catalog sync may take before the cached list is used instead. */
|
|
13525
|
+
const CATALOG_TIMEOUT_MS = 15e3;
|
|
13526
|
+
/**
|
|
13527
|
+
* Cooldown one account takes when Ollama refuses it without stating a delay.
|
|
13528
|
+
*
|
|
13529
|
+
* A 429 here is a real quota or rate limit, not a blip, so the account is parked
|
|
13530
|
+
* for long enough that the next request goes to a different one.
|
|
13531
|
+
*/
|
|
13532
|
+
const POOL_COOLDOWN_MS$3 = 15 * 6e4;
|
|
13533
|
+
/**
|
|
13534
|
+
* NO fallback model names.
|
|
13535
|
+
*
|
|
13536
|
+
* An earlier revision hard-coded `gpt-oss:120b-cloud` and `gpt-oss:20b-cloud`
|
|
13537
|
+
* here, and they reached the model picker. That was wrong twice over: this line
|
|
13538
|
+
* has no model table of its own, so every name in it would be a guess about what
|
|
13539
|
+
* some account is entitled to, and a guess that lands in the picker is a model a
|
|
13540
|
+
* user can select and then fail on. The other lines can fall back to hard-coded
|
|
13541
|
+
* names because theirs are transcribed from a real table - Claude's is its own
|
|
13542
|
+
* frozen catalog. Ollama's truth is `/api/tags`, and there is nothing honest to
|
|
13543
|
+
* stand in for it before the first sync.
|
|
13544
|
+
*
|
|
13545
|
+
* So the catalog starts empty and the card says the list has not been synced yet,
|
|
13546
|
+
* with the sync button right beside it. A user who has a key is one click from a
|
|
13547
|
+
* list that is actually theirs.
|
|
13548
|
+
*/
|
|
13549
|
+
const FALLBACK_MODELS$3 = Object.freeze([]);
|
|
13550
|
+
/**
|
|
13551
|
+
* Resolve the wire shape for a model.
|
|
13552
|
+
*
|
|
13553
|
+
* `openai` first, always: the OpenAI-compatible surface is the one whose tool
|
|
13554
|
+
* and streaming semantics this plugin's mapper already implements faithfully,
|
|
13555
|
+
* and it is what an agent wants. `native` is the fallback for the documented
|
|
13556
|
+
* cases where the OpenAI surface refuses a request, so a model the service
|
|
13557
|
+
* accepts natively still works instead of failing outright.
|
|
13558
|
+
*/
|
|
13559
|
+
function wireForModel$1(_model) {
|
|
13560
|
+
return "openai";
|
|
13561
|
+
}
|
|
13562
|
+
/** Context window for a model, falling back to the conservative constant. */
|
|
13563
|
+
function contextWindowFor(model) {
|
|
13564
|
+
const stated = model?.contextWindow;
|
|
13565
|
+
return typeof stated === "number" && Number.isFinite(stated) && stated > 0 ? Math.floor(stated) : DEFAULT_CONTEXT_WINDOW$4;
|
|
13566
|
+
}
|
|
13567
|
+
/** Bearer headers for a stored key. */
|
|
13568
|
+
function ollamaHeaders(apiKey) {
|
|
13569
|
+
return {
|
|
13570
|
+
authorization: `Bearer ${apiKey}`,
|
|
13571
|
+
"content-type": "application/json"
|
|
13572
|
+
};
|
|
13573
|
+
}
|
|
13574
|
+
//#endregion
|
|
13575
|
+
//#region src/host/ollama/token-store.ts
|
|
13576
|
+
function credentialPath$4() {
|
|
13577
|
+
return path.join(dshHomeDir$2(), "storages", "ollama-credentials.json");
|
|
13578
|
+
}
|
|
13579
|
+
function modelSettingsPath$6() {
|
|
13580
|
+
return path.join(dshHomeDir$2(), "storages", "ollama-models.json");
|
|
13581
|
+
}
|
|
13582
|
+
/** Stable keyring account name for one credential file. */
|
|
13583
|
+
function credentialAccount$2(filePath) {
|
|
13584
|
+
return createHash("sha256").update(path.resolve(filePath)).digest("hex");
|
|
13585
|
+
}
|
|
13586
|
+
function optionalString$6(record, key) {
|
|
13587
|
+
const value = record[key];
|
|
13588
|
+
if (value === void 0 || value === null) return void 0;
|
|
13589
|
+
if (typeof value !== "string") throw new Error("Ollama credential payload is invalid");
|
|
13590
|
+
return value;
|
|
13591
|
+
}
|
|
13592
|
+
function parseOllamaCredentials(value) {
|
|
13593
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Ollama credential payload is invalid");
|
|
13594
|
+
const record = value;
|
|
13595
|
+
const apiKey = record.apiKey;
|
|
13596
|
+
if (typeof apiKey !== "string" || apiKey.trim() === "") throw new Error("Ollama credential is missing its API key");
|
|
13597
|
+
const credentials = { apiKey };
|
|
13598
|
+
const alias = optionalString$6(record, "alias");
|
|
13599
|
+
if (alias !== void 0) credentials.alias = alias;
|
|
13600
|
+
const addedAt = record.addedAt;
|
|
13601
|
+
if (typeof addedAt === "number" && Number.isFinite(addedAt)) credentials.addedAt = addedAt;
|
|
13602
|
+
return credentials;
|
|
13603
|
+
}
|
|
13604
|
+
/**
|
|
13605
|
+
* The OS credential backend for this platform.
|
|
13606
|
+
*
|
|
13607
|
+
* Same matrix as every other line here: DPAPI on Windows, Keychain on macOS,
|
|
13608
|
+
* Secret Service on Linux. The plaintext JSON file is a migration source only,
|
|
13609
|
+
* never the live store.
|
|
13610
|
+
*/
|
|
13611
|
+
function createCredentialBackend$2(filePath) {
|
|
13612
|
+
if (process.platform === "win32") return new WindowsDpapiCredentialStore(`${filePath}.dpapi`, parseOllamaCredentials);
|
|
13613
|
+
if (process.platform === "darwin") return new MacKeychainCredentialStore(PROVIDER_ID$4, credentialAccount$2(filePath), parseOllamaCredentials);
|
|
13614
|
+
if (process.platform === "linux") return new SecretServiceCredentialStore(PROVIDER_ID$4, credentialAccount$2(filePath), parseOllamaCredentials);
|
|
13615
|
+
throw new Error("Ollama credential storage requires Windows, macOS, or Linux.");
|
|
13616
|
+
}
|
|
13617
|
+
/** Encrypted credential store for the single-key (no pool) configuration. */
|
|
13618
|
+
var FileCredentialStore$5 = class {
|
|
13619
|
+
filePath;
|
|
13620
|
+
backend;
|
|
13621
|
+
constructor(filePath = credentialPath$4(), backend = createCredentialBackend$2(filePath)) {
|
|
13622
|
+
this.filePath = filePath;
|
|
13623
|
+
this.backend = backend;
|
|
13624
|
+
}
|
|
13625
|
+
path() {
|
|
13626
|
+
return this.filePath;
|
|
13627
|
+
}
|
|
13628
|
+
async read() {
|
|
13629
|
+
return this.backend.load();
|
|
13630
|
+
}
|
|
13631
|
+
async write(credentials) {
|
|
13632
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
13633
|
+
await this.backend.save(credentials);
|
|
13634
|
+
}
|
|
13635
|
+
async clear() {
|
|
13636
|
+
await this.backend.clear();
|
|
13637
|
+
}
|
|
13638
|
+
};
|
|
13639
|
+
/**
|
|
13640
|
+
* Model settings, bound to the DSH settings document when the harness offers one
|
|
13641
|
+
* and to a JSON file beside it otherwise.
|
|
13642
|
+
*/
|
|
13643
|
+
var FileModelSettingsStore$5 = class {
|
|
13644
|
+
filePath;
|
|
13645
|
+
cached = null;
|
|
13646
|
+
loadPromise = null;
|
|
13647
|
+
constructor(filePath = modelSettingsPath$6()) {
|
|
13648
|
+
this.filePath = filePath;
|
|
13649
|
+
}
|
|
13650
|
+
path() {
|
|
13651
|
+
return this.filePath;
|
|
13652
|
+
}
|
|
13653
|
+
status() {
|
|
13654
|
+
return this.cached ?? {
|
|
13655
|
+
enabled: true,
|
|
13656
|
+
enabledModelIds: [],
|
|
13657
|
+
catalogModels: [],
|
|
13658
|
+
defaultReasoningEffort: null
|
|
13659
|
+
};
|
|
13660
|
+
}
|
|
13661
|
+
async read() {
|
|
13662
|
+
if (this.cached !== null) return this.cached;
|
|
13663
|
+
if (this.loadPromise !== null) return this.loadPromise;
|
|
13664
|
+
this.loadPromise = this.load().finally(() => {
|
|
13665
|
+
this.loadPromise = null;
|
|
13666
|
+
});
|
|
13667
|
+
return this.loadPromise;
|
|
13668
|
+
}
|
|
13669
|
+
async load() {
|
|
13670
|
+
try {
|
|
13671
|
+
const content = await fs.readFile(this.filePath, "utf8");
|
|
13672
|
+
const parsed = JSON.parse(content);
|
|
13673
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
13674
|
+
const record = parsed;
|
|
13675
|
+
const enabledModels = Array.isArray(record.enabledModelIds) ? record.enabledModelIds.filter((id) => typeof id === "string") : [];
|
|
13676
|
+
const catalogModels = Array.isArray(record.catalogModels) ? record.catalogModels.filter((entry) => typeof entry === "object" && entry !== null && typeof entry.id === "string") : [];
|
|
13677
|
+
this.cached = {
|
|
13678
|
+
enabled: record.enabled !== false,
|
|
13679
|
+
enabledModelIds: enabledModels,
|
|
13680
|
+
catalogModels,
|
|
13681
|
+
defaultReasoningEffort: typeof record.defaultReasoningEffort === "string" ? record.defaultReasoningEffort : null
|
|
13682
|
+
};
|
|
13683
|
+
return this.cached;
|
|
13684
|
+
}
|
|
13685
|
+
} catch {}
|
|
13686
|
+
this.cached = {
|
|
13687
|
+
enabled: true,
|
|
13688
|
+
enabledModelIds: [],
|
|
13689
|
+
catalogModels: [],
|
|
13690
|
+
defaultReasoningEffort: null
|
|
13691
|
+
};
|
|
13692
|
+
return this.cached;
|
|
13693
|
+
}
|
|
13694
|
+
async write(settings) {
|
|
13695
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
13696
|
+
const tmp = `${this.filePath}.tmp.${Date.now()}`;
|
|
13697
|
+
await fs.writeFile(tmp, JSON.stringify(settings, null, 2), "utf8");
|
|
13698
|
+
await fs.rename(tmp, this.filePath);
|
|
13699
|
+
this.cached = settings;
|
|
13700
|
+
}
|
|
13701
|
+
async update(patch) {
|
|
13702
|
+
const current = await this.read();
|
|
13703
|
+
const next = {
|
|
13704
|
+
enabled: patch.enabled ?? current.enabled,
|
|
13705
|
+
enabledModelIds: patch.enabledModelIds ?? current.enabledModelIds,
|
|
13706
|
+
catalogModels: current.catalogModels,
|
|
13707
|
+
defaultReasoningEffort: patch.defaultReasoningEffort === void 0 ? current.defaultReasoningEffort : patch.defaultReasoningEffort
|
|
13708
|
+
};
|
|
13709
|
+
await this.write(next);
|
|
13710
|
+
return next;
|
|
13711
|
+
}
|
|
13712
|
+
/** Record a catalog sync without touching the user's enabled selection. */
|
|
13713
|
+
async storeCatalog(models) {
|
|
13714
|
+
const next = {
|
|
13715
|
+
...await this.read(),
|
|
13716
|
+
catalogModels: models
|
|
13717
|
+
};
|
|
13718
|
+
await this.write(next);
|
|
13719
|
+
return next;
|
|
13720
|
+
}
|
|
13721
|
+
};
|
|
13722
|
+
//#endregion
|
|
13723
|
+
//#region src/host/ollama/client.ts
|
|
13724
|
+
/** Headers both surfaces require, per docs/api/authentication. */
|
|
13725
|
+
function headersFor(credentials, accept) {
|
|
13726
|
+
return {
|
|
13727
|
+
...ollamaHeaders(credentials.apiKey),
|
|
13728
|
+
accept
|
|
13729
|
+
};
|
|
13730
|
+
}
|
|
13731
|
+
/**
|
|
13732
|
+
* Build the request body for one wire shape.
|
|
13733
|
+
*
|
|
13734
|
+
* The two surfaces are not translations of each other — the native API nests the
|
|
13735
|
+
* assistant's tool calls under `message.tool_calls` and expects results under a
|
|
13736
|
+
* `tool` role without ids, while the OpenAI surface uses `tool_calls` and
|
|
13737
|
+
* `tool_call_id`. Mapping them through one shared builder would have to guess at
|
|
13738
|
+
* the fields the other surface does not carry, so each is written out where its
|
|
13739
|
+
* own rules stay visible.
|
|
13740
|
+
*/
|
|
13741
|
+
function buildBody(wire, request) {
|
|
13742
|
+
if (wire === "openai") return buildOpenAIBody(request);
|
|
13743
|
+
return buildNativeBody(request);
|
|
13744
|
+
}
|
|
13745
|
+
function buildOpenAIBody(request) {
|
|
13746
|
+
const messages = request.messages.map((message) => {
|
|
13747
|
+
if (message.role === "tool") return {
|
|
13748
|
+
role: "tool",
|
|
13749
|
+
tool_call_id: message.toolCallId ?? "",
|
|
13750
|
+
content: message.content
|
|
13751
|
+
};
|
|
13752
|
+
if (message.role === "assistant" && message.toolCalls !== void 0 && message.toolCalls.length > 0) return {
|
|
13753
|
+
role: "assistant",
|
|
13754
|
+
content: message.content,
|
|
13755
|
+
tool_calls: message.toolCalls.map((call) => ({
|
|
13756
|
+
id: call.id,
|
|
13757
|
+
type: "function",
|
|
13758
|
+
function: {
|
|
13759
|
+
name: call.name,
|
|
13760
|
+
arguments: call.arguments
|
|
13761
|
+
}
|
|
13762
|
+
}))
|
|
13763
|
+
};
|
|
13764
|
+
return {
|
|
13765
|
+
role: message.role,
|
|
13766
|
+
content: message.content
|
|
13767
|
+
};
|
|
13768
|
+
});
|
|
13769
|
+
const body = {
|
|
13770
|
+
model: request.model,
|
|
13771
|
+
messages,
|
|
13772
|
+
stream: true,
|
|
13773
|
+
max_tokens: request.maxOutputTokens ?? 8192
|
|
13774
|
+
};
|
|
13775
|
+
if (request.tools !== void 0 && request.tools.length > 0) body.tools = request.tools.map((tool) => ({
|
|
13776
|
+
type: "function",
|
|
13777
|
+
function: {
|
|
13778
|
+
name: tool.name,
|
|
13779
|
+
description: tool.description ?? "",
|
|
13780
|
+
parameters: tool.parameters
|
|
13781
|
+
}
|
|
13782
|
+
}));
|
|
13783
|
+
if (request.temperature !== void 0) body.temperature = request.temperature;
|
|
13784
|
+
return body;
|
|
13785
|
+
}
|
|
13786
|
+
function buildNativeBody(request) {
|
|
13787
|
+
const messages = request.messages.map((message) => {
|
|
13788
|
+
if (message.role === "tool") return {
|
|
13789
|
+
role: "tool",
|
|
13790
|
+
content: message.content
|
|
13791
|
+
};
|
|
13792
|
+
if (message.role === "assistant" && message.toolCalls !== void 0 && message.toolCalls.length > 0) return {
|
|
13793
|
+
role: "assistant",
|
|
13794
|
+
content: message.content,
|
|
13795
|
+
tool_calls: message.toolCalls.map((call) => ({ function: {
|
|
13796
|
+
name: call.name,
|
|
13797
|
+
arguments: call.arguments
|
|
13798
|
+
} }))
|
|
13799
|
+
};
|
|
13800
|
+
return {
|
|
13801
|
+
role: message.role,
|
|
13802
|
+
content: message.content
|
|
13803
|
+
};
|
|
13804
|
+
});
|
|
13805
|
+
const body = {
|
|
13806
|
+
model: request.model,
|
|
13807
|
+
messages,
|
|
13808
|
+
stream: true
|
|
13809
|
+
};
|
|
13810
|
+
const options = { num_predict: request.maxOutputTokens ?? 8192 };
|
|
13811
|
+
if (request.temperature !== void 0) options.temperature = request.temperature;
|
|
13812
|
+
body.options = options;
|
|
13813
|
+
if (request.tools !== void 0 && request.tools.length > 0) body.tools = request.tools.map((tool) => ({
|
|
13814
|
+
type: "function",
|
|
13815
|
+
function: {
|
|
13816
|
+
name: tool.name,
|
|
13817
|
+
description: tool.description ?? "",
|
|
13818
|
+
parameters: tool.parameters
|
|
13819
|
+
}
|
|
13820
|
+
}));
|
|
13821
|
+
return body;
|
|
13822
|
+
}
|
|
13823
|
+
/** The endpoint one request goes to. */
|
|
13824
|
+
function chatUrl(wire) {
|
|
13825
|
+
return wire === "openai" ? `${CLOUD_BASE_URL}${OPENAI_CHAT_PATH}` : `${CLOUD_BASE_URL}${NATIVE_CHAT_PATH}`;
|
|
13826
|
+
}
|
|
13827
|
+
/**
|
|
13828
|
+
* Start one streaming chat call.
|
|
13829
|
+
*
|
|
13830
|
+
* The HTTP status is surfaced through a promise rather than by handing back the
|
|
13831
|
+
* Response, because the caller has to decide between retrying this account and
|
|
13832
|
+
* failing the turn, and both need the status before any body is read.
|
|
13833
|
+
*/
|
|
13834
|
+
function startChat(fetchFn, credentials, wire, request) {
|
|
13835
|
+
let resolveStatus = () => void 0;
|
|
13836
|
+
let rejectStatus = () => void 0;
|
|
13837
|
+
const status = new Promise((resolve, reject) => {
|
|
13838
|
+
resolveStatus = resolve;
|
|
13839
|
+
rejectStatus = reject;
|
|
13840
|
+
});
|
|
13841
|
+
const opened = fetchFn(chatUrl(wire), {
|
|
13842
|
+
method: "POST",
|
|
13843
|
+
headers: headersFor(credentials, "text/event-stream"),
|
|
13844
|
+
body: JSON.stringify(buildBody(wire, request)),
|
|
13845
|
+
signal: request.signal
|
|
13846
|
+
}).then((response) => {
|
|
13847
|
+
resolveStatus(response.status);
|
|
13848
|
+
return response;
|
|
13849
|
+
}, (error) => {
|
|
13850
|
+
rejectStatus(error);
|
|
13851
|
+
throw error;
|
|
13852
|
+
});
|
|
13853
|
+
return {
|
|
13854
|
+
events: (async function* run() {
|
|
13855
|
+
const response = await opened;
|
|
13856
|
+
if (!response.ok) {
|
|
13857
|
+
const detail = await safeErrorText(response);
|
|
13858
|
+
yield {
|
|
13859
|
+
type: "error",
|
|
13860
|
+
message: `Ollama request failed (${response.status}): ${detail}`
|
|
13861
|
+
};
|
|
13862
|
+
return;
|
|
13863
|
+
}
|
|
13864
|
+
yield* readStream(response, wire);
|
|
13865
|
+
})(),
|
|
13866
|
+
status
|
|
13867
|
+
};
|
|
13868
|
+
}
|
|
13869
|
+
async function safeErrorText(response) {
|
|
13870
|
+
try {
|
|
13871
|
+
return (await response.text()).slice(0, 500);
|
|
13872
|
+
} catch {
|
|
13873
|
+
return "";
|
|
13874
|
+
}
|
|
13875
|
+
}
|
|
13876
|
+
/** Parse the SSE stream of either surface. */
|
|
13877
|
+
async function* readStream(response, wire) {
|
|
13878
|
+
const body = response.body;
|
|
13879
|
+
if (body === null) return;
|
|
13880
|
+
const decoder = new TextDecoder();
|
|
13881
|
+
const reader = body.getReader();
|
|
13882
|
+
let buffer = "";
|
|
13883
|
+
const pending = /* @__PURE__ */ new Map();
|
|
13884
|
+
try {
|
|
13885
|
+
for (;;) {
|
|
13886
|
+
const { done, value } = await reader.read();
|
|
13887
|
+
if (done) break;
|
|
13888
|
+
buffer += decoder.decode(value, { stream: true });
|
|
13889
|
+
let newline = buffer.indexOf("\n");
|
|
13890
|
+
while (newline !== -1) {
|
|
13891
|
+
const line = buffer.slice(0, newline).trim();
|
|
13892
|
+
buffer = buffer.slice(newline + 1);
|
|
13893
|
+
newline = buffer.indexOf("\n");
|
|
13894
|
+
if (line === "" || !line.startsWith("data:")) continue;
|
|
13895
|
+
const payload = line.slice(5).trim();
|
|
13896
|
+
if (payload === "" || payload === "[DONE]") continue;
|
|
13897
|
+
for (const event of parseEvent(payload, wire, pending)) yield event;
|
|
13898
|
+
}
|
|
13899
|
+
}
|
|
13900
|
+
} finally {
|
|
13901
|
+
reader.releaseLock?.();
|
|
13902
|
+
}
|
|
13903
|
+
for (const call of pending.values()) yield {
|
|
13904
|
+
type: "tool_call",
|
|
13905
|
+
call: call.arguments === "" ? {
|
|
13906
|
+
...call,
|
|
13907
|
+
arguments: "{}"
|
|
13908
|
+
} : call
|
|
13909
|
+
};
|
|
13910
|
+
}
|
|
13911
|
+
function parseEvent(payload, wire, pending) {
|
|
13912
|
+
let parsed;
|
|
13913
|
+
try {
|
|
13914
|
+
parsed = JSON.parse(payload);
|
|
13915
|
+
} catch {
|
|
13916
|
+
return [];
|
|
13917
|
+
}
|
|
13918
|
+
if (typeof parsed !== "object" || parsed === null) return [];
|
|
13919
|
+
const record = parsed;
|
|
13920
|
+
if (typeof record.error === "string") return [{
|
|
13921
|
+
type: "error",
|
|
13922
|
+
message: record.error
|
|
13923
|
+
}];
|
|
13924
|
+
return wire === "openai" ? parseOpenAIChunk(record, pending) : parseNativeChunk(record, pending);
|
|
13925
|
+
}
|
|
13926
|
+
function parseOpenAIChunk(record, pending) {
|
|
13927
|
+
const events = [];
|
|
13928
|
+
const choices = Array.isArray(record.choices) ? record.choices : [];
|
|
13929
|
+
for (const choice of choices) {
|
|
13930
|
+
if (typeof choice !== "object" || choice === null) continue;
|
|
13931
|
+
const entry = choice;
|
|
13932
|
+
const delta = entry.delta;
|
|
13933
|
+
if (typeof delta === "object" && delta !== null) {
|
|
13934
|
+
const fields = delta;
|
|
13935
|
+
if (typeof fields.content === "string" && fields.content !== "") events.push({
|
|
13936
|
+
type: "text",
|
|
13937
|
+
text: fields.content
|
|
13938
|
+
});
|
|
13939
|
+
if (Array.isArray(fields.tool_calls)) for (const raw of fields.tool_calls) accumulateToolCall(raw, pending, true);
|
|
13940
|
+
}
|
|
13941
|
+
if (typeof entry.finish_reason === "string" && entry.finish_reason !== "") events.push({
|
|
13942
|
+
type: "done",
|
|
13943
|
+
finishReason: entry.finish_reason
|
|
13944
|
+
});
|
|
13945
|
+
}
|
|
13946
|
+
const usage = readUsage$1(record);
|
|
13947
|
+
if (usage !== null) events.push({
|
|
13948
|
+
type: "usage",
|
|
13949
|
+
usage
|
|
13950
|
+
});
|
|
13951
|
+
return events;
|
|
13952
|
+
}
|
|
13953
|
+
function parseNativeChunk(record, pending) {
|
|
13954
|
+
const events = [];
|
|
13955
|
+
const message = record.message;
|
|
13956
|
+
if (typeof message === "object" && message !== null) {
|
|
13957
|
+
const fields = message;
|
|
13958
|
+
if (typeof fields.content === "string" && fields.content !== "") events.push({
|
|
13959
|
+
type: "text",
|
|
13960
|
+
text: fields.content
|
|
13961
|
+
});
|
|
13962
|
+
if (Array.isArray(fields.tool_calls)) for (const raw of fields.tool_calls) accumulateToolCall(raw, pending, false);
|
|
13963
|
+
}
|
|
13964
|
+
if (record.done === true) events.push({
|
|
13965
|
+
type: "done",
|
|
13966
|
+
finishReason: "stop"
|
|
13967
|
+
});
|
|
13968
|
+
const usage = readNativeUsage(record);
|
|
13969
|
+
if (usage !== null) events.push({
|
|
13970
|
+
type: "usage",
|
|
13971
|
+
usage
|
|
13972
|
+
});
|
|
13973
|
+
return events;
|
|
13974
|
+
}
|
|
13975
|
+
/**
|
|
13976
|
+
* Read token counts off an OpenAI-compatible chunk.
|
|
13977
|
+
*
|
|
13978
|
+
* Ollama names them the OpenAI way here (`prompt_tokens` / `completion_tokens`),
|
|
13979
|
+
* while the native surface uses `prompt_eval_count` / `eval_count`. Both are
|
|
13980
|
+
* accepted on both paths because the service documents the native names in its
|
|
13981
|
+
* own OpenAPI and an OpenAI-compatible alias in the compatibility layer, and a
|
|
13982
|
+
* counter that silently read zero on the wrong surface would be worse than none.
|
|
13983
|
+
*
|
|
13984
|
+
* Returns null when the chunk carries neither pair, which is every content chunk.
|
|
13985
|
+
*/
|
|
13986
|
+
function readUsage$1(record) {
|
|
13987
|
+
const container = typeof record.usage === "object" && record.usage !== null ? record.usage : record;
|
|
13988
|
+
const input = firstNumber$1(container, [
|
|
13989
|
+
"prompt_tokens",
|
|
13990
|
+
"prompt_eval_count",
|
|
13991
|
+
"input_tokens"
|
|
13992
|
+
]);
|
|
13993
|
+
const output = firstNumber$1(container, [
|
|
13994
|
+
"completion_tokens",
|
|
13995
|
+
"eval_count",
|
|
13996
|
+
"output_tokens"
|
|
13997
|
+
]);
|
|
13998
|
+
if (input === void 0 && output === void 0) return null;
|
|
13999
|
+
return {
|
|
14000
|
+
inputTokens: input ?? 0,
|
|
14001
|
+
outputTokens: output ?? 0
|
|
14002
|
+
};
|
|
14003
|
+
}
|
|
14004
|
+
/** The native surface's names, read from the chunk itself. */
|
|
14005
|
+
function readNativeUsage(record) {
|
|
14006
|
+
const input = firstNumber$1(record, [
|
|
14007
|
+
"prompt_eval_count",
|
|
14008
|
+
"prompt_tokens",
|
|
14009
|
+
"input_tokens"
|
|
14010
|
+
]);
|
|
14011
|
+
const output = firstNumber$1(record, [
|
|
14012
|
+
"eval_count",
|
|
14013
|
+
"completion_tokens",
|
|
14014
|
+
"output_tokens"
|
|
14015
|
+
]);
|
|
14016
|
+
if (input === void 0 && output === void 0) return null;
|
|
14017
|
+
return {
|
|
14018
|
+
inputTokens: input ?? 0,
|
|
14019
|
+
outputTokens: output ?? 0
|
|
14020
|
+
};
|
|
14021
|
+
}
|
|
14022
|
+
function firstNumber$1(record, keys) {
|
|
14023
|
+
for (const key of keys) {
|
|
14024
|
+
const value = record[key];
|
|
14025
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return Math.floor(value);
|
|
14026
|
+
}
|
|
14027
|
+
}
|
|
14028
|
+
/**
|
|
14029
|
+
* Fold one streamed tool-call delta into the pending set.
|
|
14030
|
+
*
|
|
14031
|
+
* Nothing is emitted here: a call is only complete once the stream has ended, and
|
|
14032
|
+
* emitting a half-received argument string would have the caller act on a tool
|
|
14033
|
+
* invocation that does not exist yet. The native surface sends no id, so a stable
|
|
14034
|
+
* synthetic one is minted from the delta index — the only handle both surfaces
|
|
14035
|
+
* agree on for pairing a call with its result.
|
|
14036
|
+
*/
|
|
14037
|
+
function accumulateToolCall(raw, pending, openaiShape) {
|
|
14038
|
+
if (typeof raw !== "object" || raw === null) return;
|
|
14039
|
+
const record = raw;
|
|
14040
|
+
const index = typeof record.index === "number" ? record.index : 0;
|
|
14041
|
+
const holder = typeof record.function === "object" && record.function !== null ? record.function : record;
|
|
14042
|
+
const name = typeof holder.name === "string" ? holder.name : void 0;
|
|
14043
|
+
const args = typeof holder.arguments === "string" ? holder.arguments : void 0;
|
|
14044
|
+
const id = openaiShape && typeof record.id === "string" ? record.id : void 0;
|
|
14045
|
+
if (name === void 0 && args === void 0 && id === void 0) return;
|
|
14046
|
+
const call = pending.get(index) ?? {
|
|
14047
|
+
id: id ?? `call_${index}`,
|
|
14048
|
+
name: "",
|
|
14049
|
+
arguments: ""
|
|
14050
|
+
};
|
|
14051
|
+
if (id !== void 0 && id !== "") call.id = id;
|
|
14052
|
+
if (name !== void 0 && name !== "") call.name = name;
|
|
14053
|
+
if (args !== void 0) call.arguments += args;
|
|
14054
|
+
pending.set(index, call);
|
|
14055
|
+
}
|
|
14056
|
+
/**
|
|
14057
|
+
* Read the model catalog.
|
|
14058
|
+
*
|
|
14059
|
+
* `/api/tags` is the native surface's own list and needs no translation; the
|
|
14060
|
+
* OpenAI `/models` endpoint would be a subset of it. Returns an empty list rather
|
|
14061
|
+
* than throwing, so a failed sync degrades to the cached selection instead of
|
|
14062
|
+
* making the line unusable.
|
|
14063
|
+
*/
|
|
14064
|
+
async function loadCatalog$1(fetchFn, credentials) {
|
|
14065
|
+
const response = await fetchFn(`${CLOUD_BASE_URL}${NATIVE_TAGS_PATH}`, {
|
|
14066
|
+
method: "GET",
|
|
14067
|
+
headers: headersFor(credentials, "application/json"),
|
|
14068
|
+
signal: AbortSignal.timeout(CATALOG_TIMEOUT_MS)
|
|
14069
|
+
});
|
|
14070
|
+
if (!response.ok) return [];
|
|
14071
|
+
const data = await response.json();
|
|
14072
|
+
if (typeof data !== "object" || data === null) return [];
|
|
14073
|
+
const record = data;
|
|
14074
|
+
const models = Array.isArray(record.models) ? record.models : [];
|
|
14075
|
+
const result = [];
|
|
14076
|
+
for (const entry of models) {
|
|
14077
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
14078
|
+
const fields = entry;
|
|
14079
|
+
const name = typeof fields.name === "string" ? fields.name : void 0;
|
|
14080
|
+
const model = typeof fields.model === "string" ? fields.model : void 0;
|
|
14081
|
+
const id = name ?? model;
|
|
14082
|
+
if (id === void 0 || id === "") continue;
|
|
14083
|
+
result.push({
|
|
14084
|
+
id,
|
|
14085
|
+
name,
|
|
14086
|
+
fetchedAt: Date.now()
|
|
14087
|
+
});
|
|
14088
|
+
}
|
|
14089
|
+
return result;
|
|
14090
|
+
}
|
|
14091
|
+
//#endregion
|
|
14092
|
+
//#region src/host/ollama/mapper.ts
|
|
14093
|
+
function createStreamState$4() {
|
|
14094
|
+
return {
|
|
14095
|
+
nextBlockIndex: 0,
|
|
14096
|
+
textBlock: null,
|
|
14097
|
+
toolCalls: /* @__PURE__ */ new Map(),
|
|
14098
|
+
hasContent: false
|
|
14099
|
+
};
|
|
14100
|
+
}
|
|
14101
|
+
/** Fold one event into chunks, opening and closing blocks as the stream needs. */
|
|
14102
|
+
function applyEvent(state, event) {
|
|
14103
|
+
if (event.type === "text") return applyText(state, event.text);
|
|
14104
|
+
if (event.type === "tool_call") return applyToolCall(state, event.call);
|
|
14105
|
+
return [];
|
|
14106
|
+
}
|
|
14107
|
+
function applyText(state, text) {
|
|
14108
|
+
state.hasContent = true;
|
|
14109
|
+
if (state.textBlock === null) {
|
|
14110
|
+
const index = state.nextBlockIndex;
|
|
14111
|
+
state.nextBlockIndex += 1;
|
|
14112
|
+
state.textBlock = {
|
|
14113
|
+
index,
|
|
14114
|
+
text
|
|
14115
|
+
};
|
|
14116
|
+
return [{
|
|
14117
|
+
type: "block-start",
|
|
14118
|
+
index,
|
|
14119
|
+
blockType: "text"
|
|
14120
|
+
}, {
|
|
14121
|
+
type: "text-delta",
|
|
14122
|
+
index,
|
|
14123
|
+
text
|
|
14124
|
+
}];
|
|
14125
|
+
}
|
|
14126
|
+
state.textBlock.text += text;
|
|
14127
|
+
return [{
|
|
14128
|
+
type: "text-delta",
|
|
14129
|
+
index: state.textBlock.index,
|
|
14130
|
+
text
|
|
14131
|
+
}];
|
|
14132
|
+
}
|
|
14133
|
+
function applyToolCall(state, call) {
|
|
14134
|
+
state.hasContent = true;
|
|
14135
|
+
const out = [];
|
|
14136
|
+
out.push(...closeText(state));
|
|
14137
|
+
const index = state.nextBlockIndex;
|
|
14138
|
+
state.nextBlockIndex += 1;
|
|
14139
|
+
out.push({
|
|
14140
|
+
type: "block-start",
|
|
14141
|
+
index,
|
|
14142
|
+
blockType: "tool-call"
|
|
14143
|
+
});
|
|
14144
|
+
state.toolCalls.set(call.id, {
|
|
14145
|
+
call,
|
|
14146
|
+
blockIndex: index
|
|
14147
|
+
});
|
|
14148
|
+
out.push({
|
|
14149
|
+
type: "tool-call-delta",
|
|
14150
|
+
index,
|
|
14151
|
+
id: toToolCallId(call.id),
|
|
14152
|
+
name: call.name,
|
|
14153
|
+
argumentsDelta: call.arguments === "" ? "{}" : call.arguments
|
|
14154
|
+
});
|
|
14155
|
+
return out;
|
|
14156
|
+
}
|
|
14157
|
+
function closeText(state) {
|
|
14158
|
+
if (state.textBlock === null) return [];
|
|
14159
|
+
const { index, text } = state.textBlock;
|
|
14160
|
+
state.textBlock = null;
|
|
14161
|
+
return [{
|
|
14162
|
+
type: "block-end",
|
|
14163
|
+
index,
|
|
14164
|
+
block: {
|
|
14165
|
+
type: "text",
|
|
14166
|
+
text
|
|
14167
|
+
}
|
|
14168
|
+
}];
|
|
14169
|
+
}
|
|
14170
|
+
function closeToolCalls$3(state) {
|
|
14171
|
+
const out = [];
|
|
14172
|
+
for (const entry of [...state.toolCalls.values()]) {
|
|
14173
|
+
const { call, blockIndex } = entry;
|
|
14174
|
+
const block = {
|
|
14175
|
+
type: "tool-call",
|
|
14176
|
+
id: toToolCallId(call.id),
|
|
14177
|
+
name: call.name,
|
|
14178
|
+
arguments: call.arguments === "" ? "{}" : call.arguments
|
|
14179
|
+
};
|
|
14180
|
+
out.push({
|
|
14181
|
+
type: "block-end",
|
|
14182
|
+
index: blockIndex,
|
|
14183
|
+
block
|
|
14184
|
+
});
|
|
14185
|
+
}
|
|
14186
|
+
state.toolCalls.clear();
|
|
14187
|
+
return out;
|
|
14188
|
+
}
|
|
14189
|
+
/**
|
|
14190
|
+
* Close the turn.
|
|
14191
|
+
*
|
|
14192
|
+
* A stream that ends while a text block is still open is a real possibility - the
|
|
14193
|
+
* model can be cut off mid-sentence - so the block is closed with what arrived
|
|
14194
|
+
* rather than dropped, and the text the user sees is the text that streamed.
|
|
14195
|
+
*/
|
|
14196
|
+
function closeStream$3(state) {
|
|
14197
|
+
const sawToolCall = state.toolCalls.size > 0;
|
|
14198
|
+
const out = [...closeText(state), ...closeToolCalls$3(state)];
|
|
14199
|
+
out.push({
|
|
14200
|
+
type: "finish",
|
|
14201
|
+
reason: sawToolCall ? { kind: "tool-calls" } : { kind: "stop" }
|
|
14202
|
+
});
|
|
14203
|
+
return out;
|
|
14204
|
+
}
|
|
14205
|
+
//#endregion
|
|
14206
|
+
//#region src/host/ollama/adapter.ts
|
|
14207
|
+
/**
|
|
14208
|
+
* Retry policy for the `ollama` route.
|
|
14209
|
+
*
|
|
14210
|
+
* A 5xx from a shared inference service says nothing about this key, so it is
|
|
14211
|
+
* classified as `SERVER` and given the same bounded backoff the other lines use.
|
|
14212
|
+
* Deliberately outside the set: `INVALID_CREDENTIAL` (a rejected key fails
|
|
14213
|
+
* identically every time, and the pool rotates past it instead) and `ABORTED`.
|
|
14214
|
+
*/
|
|
14215
|
+
const RETRY_POLICY$4 = resolveRetryPolicy({
|
|
14216
|
+
mode: "normal",
|
|
14217
|
+
maxRetries: 3,
|
|
14218
|
+
retryableCodes: [
|
|
14219
|
+
"RATE_LIMIT",
|
|
14220
|
+
"SERVER",
|
|
14221
|
+
"TIMEOUT",
|
|
14222
|
+
"TRANSPORT"
|
|
14223
|
+
],
|
|
14224
|
+
backoff: {
|
|
14225
|
+
initialDelayMs: 1500,
|
|
14226
|
+
maxDelayMs: 15e3,
|
|
14227
|
+
jitterRatio: .2
|
|
14228
|
+
}
|
|
14229
|
+
}, "dsh-chatgpt-subscription.ollama.retry");
|
|
14230
|
+
var OllamaAdapter = class extends LlmAdapter {
|
|
14231
|
+
store;
|
|
14232
|
+
modelSettings;
|
|
14233
|
+
options;
|
|
14234
|
+
/**
|
|
14235
|
+
* Rotation pool, or null for the single stored key.
|
|
14236
|
+
*
|
|
14237
|
+
* Only the plugin entry installs it, because the entry owns the storage; an
|
|
14238
|
+
* adapter built without one must keep reading the one credential file rather
|
|
14239
|
+
* than minting pool state that would outlive the process.
|
|
14240
|
+
*/
|
|
14241
|
+
accountPool;
|
|
14242
|
+
constructor(store = new FileCredentialStore$5(), modelSettings = new FileModelSettingsStore$5(), options = {}, accountPool) {
|
|
14243
|
+
super();
|
|
14244
|
+
this.store = store;
|
|
14245
|
+
this.modelSettings = modelSettings;
|
|
14246
|
+
this.options = options;
|
|
14247
|
+
this.accountPool = accountPool ?? null;
|
|
14248
|
+
}
|
|
14249
|
+
providerInfo(provider) {
|
|
14250
|
+
return {
|
|
14251
|
+
id: provider,
|
|
14252
|
+
name: PROVIDER_NAME$4
|
|
14253
|
+
};
|
|
14254
|
+
}
|
|
14255
|
+
providerRetryPolicy() {
|
|
14256
|
+
return RETRY_POLICY$4;
|
|
14257
|
+
}
|
|
14258
|
+
imageRequestPricing() {}
|
|
14259
|
+
settings() {
|
|
14260
|
+
return this.modelSettings.read();
|
|
14261
|
+
}
|
|
14262
|
+
/**
|
|
14263
|
+
* Catalog for the picker: the live `/api/tags` listing when a key can reach it,
|
|
14264
|
+
* the shipped fallback otherwise, narrowed by the user's enabled selection.
|
|
14265
|
+
*
|
|
14266
|
+
* Ollama's model set moves quickly, so a stale local table would be the wrong
|
|
14267
|
+
* default; the fallback exists only so the line is usable before the first
|
|
14268
|
+
* successful sync.
|
|
14269
|
+
*/
|
|
14270
|
+
async catalog() {
|
|
14271
|
+
const load = this.options.loadCatalog ?? loadCatalog$1;
|
|
14272
|
+
const fetchFn = this.options.fetchFn ?? fetch;
|
|
14273
|
+
const credentials = await this.anyCredentials();
|
|
14274
|
+
if (credentials !== null) {
|
|
14275
|
+
const live = await load(fetchFn, credentials).catch(() => []);
|
|
14276
|
+
if (live.length > 0) {
|
|
14277
|
+
await this.modelSettings.storeCatalog(live).catch(() => void 0);
|
|
14278
|
+
return live;
|
|
14279
|
+
}
|
|
14280
|
+
}
|
|
14281
|
+
const cached = this.modelSettings.status().catalogModels;
|
|
14282
|
+
if (cached.length > 0) return cached;
|
|
14283
|
+
return [...FALLBACK_MODELS$3];
|
|
14284
|
+
}
|
|
14285
|
+
/** Any usable key, for the catalog call; the pool decides real routing later. */
|
|
14286
|
+
async anyCredentials() {
|
|
14287
|
+
if (this.accountPool !== null) return (await this.accountPool.getEffectiveCredential(/* @__PURE__ */ new Set(), this.options.fetchFn ?? fetch).catch(() => null))?.credentials ?? null;
|
|
14288
|
+
return this.store.read().catch(() => null);
|
|
14289
|
+
}
|
|
14290
|
+
async listModels(provider) {
|
|
14291
|
+
const prov = provider || "ollama";
|
|
14292
|
+
const settings = await this.settings();
|
|
14293
|
+
if (settings.enabled === false) return [];
|
|
14294
|
+
const catalog = await this.catalog();
|
|
14295
|
+
const enabled = new Set(settings.enabledModelIds);
|
|
14296
|
+
return (settings.enabledModelIds.length === 0 ? catalog : catalog.filter((model) => enabled.has(model.id))).map((model) => ({
|
|
14297
|
+
provider: prov,
|
|
14298
|
+
id: model.id,
|
|
14299
|
+
name: model.name ?? model.id,
|
|
14300
|
+
inputModalities: ["text", "image"]
|
|
14301
|
+
}));
|
|
14302
|
+
}
|
|
14303
|
+
async resolveModel(provider, modelId, signal) {
|
|
14304
|
+
if (signal?.aborted) throw new LlmError("Ollama model resolution aborted", "ABORTED");
|
|
14305
|
+
const entry = (await this.catalog()).find((model) => model.id === modelId);
|
|
14306
|
+
return {
|
|
14307
|
+
provider,
|
|
14308
|
+
id: modelId,
|
|
14309
|
+
name: entry?.name ?? modelId,
|
|
14310
|
+
inputModalities: ["text", "image"],
|
|
14311
|
+
context: { contextWindow: contextWindowFor(entry) },
|
|
14312
|
+
...outputReservation(contextWindowFor(entry), DEFAULT_MAX_OUTPUT_TOKENS)
|
|
14313
|
+
};
|
|
14314
|
+
}
|
|
14315
|
+
async prepareCall(provider, model, signal) {
|
|
14316
|
+
return {
|
|
14317
|
+
model: await this.resolveModel(provider, model, signal),
|
|
14318
|
+
stream: (options) => this.stream(options)
|
|
14319
|
+
};
|
|
14320
|
+
}
|
|
14321
|
+
async *stream(options) {
|
|
14322
|
+
yield* wrapStreamWithWatchdog((watchdogSignal) => this.requestStream(options, watchdogSignal), options.signal, STREAM_IDLE_TIMEOUT_MS$4, STREAM_IDLE_TIMEOUT_CODE$4, PROVIDER_NAME$4);
|
|
14323
|
+
}
|
|
14324
|
+
async *requestStream(options, signal) {
|
|
14325
|
+
const fetchFn = this.options.fetchFn ?? fetch;
|
|
14326
|
+
const wire = wireForModel$1(options.model);
|
|
14327
|
+
const request = toOllamaRequest(options);
|
|
14328
|
+
const pool = this.accountPool;
|
|
14329
|
+
const tried = /* @__PURE__ */ new Set();
|
|
14330
|
+
let lastStatus;
|
|
14331
|
+
let lastDetail = "";
|
|
14332
|
+
let accountId;
|
|
14333
|
+
for (;;) {
|
|
14334
|
+
let credentials;
|
|
14335
|
+
if (pool === null) {
|
|
14336
|
+
const stored = await this.store.read();
|
|
14337
|
+
if (stored === null) throw new LlmError(`No ${PROVIDER_NAME$4} API key configured. Add one from Settings > ${PROVIDER_NAME$4} (create a key at https://ollama.com/settings/keys).`, "MISSING_CREDENTIAL");
|
|
14338
|
+
credentials = stored;
|
|
14339
|
+
} else {
|
|
14340
|
+
const effective = await pool.getEffectiveCredential(tried, fetchFn);
|
|
14341
|
+
accountId = effective.account.id;
|
|
14342
|
+
tried.add(accountId);
|
|
14343
|
+
credentials = effective.credentials;
|
|
14344
|
+
}
|
|
14345
|
+
const call = startChat(fetchFn, credentials, wire, {
|
|
14346
|
+
...request,
|
|
14347
|
+
signal
|
|
14348
|
+
});
|
|
14349
|
+
const state = createStreamState$4();
|
|
14350
|
+
let openedStatus;
|
|
14351
|
+
call.status.then((value) => {
|
|
14352
|
+
openedStatus = value;
|
|
14353
|
+
});
|
|
14354
|
+
let spent = null;
|
|
14355
|
+
let failed = false;
|
|
14356
|
+
for await (const event of call.events) {
|
|
14357
|
+
if (event.type === "error") {
|
|
14358
|
+
lastStatus = openedStatus;
|
|
14359
|
+
lastDetail = event.message;
|
|
14360
|
+
failed = true;
|
|
14361
|
+
break;
|
|
14362
|
+
}
|
|
14363
|
+
if (event.type === "usage") {
|
|
14364
|
+
spent = event.usage;
|
|
14365
|
+
continue;
|
|
14366
|
+
}
|
|
14367
|
+
if (event.type === "text" || event.type === "tool_call") {
|
|
14368
|
+
for (const chunk of applyEvent(state, event)) yield chunk;
|
|
14369
|
+
continue;
|
|
14370
|
+
}
|
|
14371
|
+
}
|
|
14372
|
+
if (!failed) {
|
|
14373
|
+
for (const chunk of closeStream$3(state)) yield chunk;
|
|
14374
|
+
if (pool !== null && accountId !== void 0 && spent !== null) await pool.recordUsage(accountId, spent.inputTokens, spent.outputTokens);
|
|
14375
|
+
return;
|
|
14376
|
+
}
|
|
14377
|
+
if (pool === null || accountId === void 0) break;
|
|
14378
|
+
if (lastStatus === 401 || lastStatus === 403) {
|
|
14379
|
+
await pool.markAuthFailed(accountId, `${PROVIDER_NAME$4} rejected the API key (${lastStatus}).`, "invalid").catch(() => void 0);
|
|
14380
|
+
if (await pool.hasAnotherAvailableAccount(tried)) continue;
|
|
14381
|
+
break;
|
|
14382
|
+
}
|
|
14383
|
+
if (lastStatus === 429) {
|
|
14384
|
+
await pool.markCooldown(accountId, POOL_COOLDOWN_MS$3, `${PROVIDER_NAME$4} 429`).catch(() => void 0);
|
|
14385
|
+
if (await pool.hasAnotherAvailableAccount(tried)) continue;
|
|
14386
|
+
break;
|
|
14387
|
+
}
|
|
14388
|
+
break;
|
|
14389
|
+
}
|
|
14390
|
+
const status = lastStatus;
|
|
14391
|
+
if (status === 401 || status === 403) throw new LlmError(`${PROVIDER_NAME$4} rejected the API key (${status}). Replace it from Settings > ${PROVIDER_NAME$4}.${lastDetail ? ` ${lastDetail}` : ""}`, "INVALID_CREDENTIAL", { status });
|
|
14392
|
+
if (status === 429) throw new LlmError(`${PROVIDER_NAME$4} rate limit or plan quota reached (429). Add another key, or wait for the cooldown shown in Settings > ${PROVIDER_NAME$4}.${lastDetail ? ` ${lastDetail}` : ""}`, "RATE_LIMIT", { status: 429 });
|
|
14393
|
+
throw new LlmError(`${PROVIDER_NAME$4} request failed${status === void 0 ? "" : ` (${status})`}.${lastDetail}`, status !== void 0 && status >= 500 ? "SERVER" : "INVALID_REQUEST", status === void 0 ? {} : { status });
|
|
14394
|
+
}
|
|
14395
|
+
};
|
|
14396
|
+
/** Project DSH's request shape onto Ollama's, per surface. */
|
|
14397
|
+
function toOllamaRequest(options) {
|
|
14398
|
+
const messages = [];
|
|
14399
|
+
for (const message of options.messages) {
|
|
14400
|
+
if (message.role === "tool") {
|
|
14401
|
+
messages.push({
|
|
14402
|
+
role: "tool",
|
|
14403
|
+
content: textOf$4(message.content),
|
|
14404
|
+
toolCallId: message.toolCallId
|
|
14405
|
+
});
|
|
14406
|
+
continue;
|
|
14407
|
+
}
|
|
14408
|
+
const toolCalls = assistantToolCalls(message.content);
|
|
14409
|
+
if (toolCalls.length > 0) {
|
|
14410
|
+
messages.push({
|
|
14411
|
+
role: "assistant",
|
|
14412
|
+
content: textOf$4(message.content),
|
|
14413
|
+
toolCalls
|
|
14414
|
+
});
|
|
14415
|
+
continue;
|
|
14416
|
+
}
|
|
14417
|
+
messages.push({
|
|
14418
|
+
role: message.role === "developer" ? "system" : message.role,
|
|
14419
|
+
content: textOf$4(message.content)
|
|
14420
|
+
});
|
|
14421
|
+
}
|
|
14422
|
+
const request = {
|
|
14423
|
+
model: options.model,
|
|
14424
|
+
messages
|
|
14425
|
+
};
|
|
14426
|
+
if (options.maxTokens !== void 0) request.maxOutputTokens = options.maxTokens;
|
|
14427
|
+
if (options.temperature !== void 0) request.temperature = options.temperature;
|
|
14428
|
+
const tools = options.tools;
|
|
14429
|
+
if (Array.isArray(tools) && tools.length > 0) request.tools = tools.map((tool) => ({
|
|
14430
|
+
name: tool.name,
|
|
14431
|
+
...typeof tool.description === "string" ? { description: tool.description } : {},
|
|
14432
|
+
parameters: tool.parameters ?? {
|
|
14433
|
+
type: "object",
|
|
14434
|
+
properties: {}
|
|
14435
|
+
}
|
|
14436
|
+
}));
|
|
14437
|
+
return request;
|
|
14438
|
+
}
|
|
14439
|
+
/** Tool calls an assistant turn carries, read from its content blocks. */
|
|
14440
|
+
function assistantToolCalls(content) {
|
|
14441
|
+
if (!Array.isArray(content)) return [];
|
|
14442
|
+
const calls = [];
|
|
14443
|
+
for (const block of content) {
|
|
14444
|
+
if (typeof block !== "object" || block === null) continue;
|
|
14445
|
+
const fields = block;
|
|
14446
|
+
if (fields.type !== "tool-call") continue;
|
|
14447
|
+
const id = fields.id;
|
|
14448
|
+
const name = fields.name;
|
|
14449
|
+
if (typeof id !== "string" || typeof name !== "string") continue;
|
|
14450
|
+
calls.push({
|
|
14451
|
+
id,
|
|
14452
|
+
name,
|
|
14453
|
+
arguments: typeof fields.arguments === "string" ? fields.arguments : JSON.stringify(fields.arguments ?? {})
|
|
14454
|
+
});
|
|
14455
|
+
}
|
|
14456
|
+
return calls;
|
|
14457
|
+
}
|
|
14458
|
+
function textOf$4(content) {
|
|
14459
|
+
if (typeof content === "string") return content;
|
|
14460
|
+
if (Array.isArray(content)) return content.map((part) => {
|
|
14461
|
+
if (typeof part === "string") return part;
|
|
14462
|
+
if (typeof part === "object" && part !== null) {
|
|
14463
|
+
const text = part.text;
|
|
14464
|
+
if (typeof text === "string") return text;
|
|
14465
|
+
}
|
|
14466
|
+
return "";
|
|
14467
|
+
}).join("");
|
|
14468
|
+
return "";
|
|
14469
|
+
}
|
|
14470
|
+
//#endregion
|
|
14471
|
+
//#region src/host/ollama/routes.ts
|
|
14472
|
+
const ROUTE_PREFIX$3 = "/ollama/api";
|
|
14473
|
+
function sendJson$5(response, status, body) {
|
|
14474
|
+
const payload = JSON.stringify(body);
|
|
14475
|
+
response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
14476
|
+
response.end(payload);
|
|
14477
|
+
}
|
|
14478
|
+
function sendMethodNotAllowed$4(response) {
|
|
14479
|
+
sendJson$5(response, 405, {
|
|
14480
|
+
ok: false,
|
|
14481
|
+
error: "Method not allowed."
|
|
14482
|
+
});
|
|
14483
|
+
}
|
|
14484
|
+
async function readRequestJson$5(request) {
|
|
14485
|
+
const chunks = [];
|
|
14486
|
+
let size = 0;
|
|
14487
|
+
for await (const chunk of request) {
|
|
14488
|
+
const buffer = chunk;
|
|
14489
|
+
size += buffer.length;
|
|
14490
|
+
if (size > 8192) throw new Error("Request body is too large.");
|
|
14491
|
+
chunks.push(buffer);
|
|
14492
|
+
}
|
|
14493
|
+
if (chunks.length === 0) return {};
|
|
14494
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
14495
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("Request body must be a JSON object.");
|
|
14496
|
+
return parsed;
|
|
14497
|
+
}
|
|
14498
|
+
/** Whether one account can serve a request right now. */
|
|
14499
|
+
function isUsable(account, now) {
|
|
14500
|
+
if (account.authStatus !== void 0 && account.authStatus !== "ok") return false;
|
|
14501
|
+
if (typeof account.cooldownUntil === "number" && account.cooldownUntil > now) return false;
|
|
14502
|
+
return true;
|
|
14503
|
+
}
|
|
14504
|
+
/** Assemble the whole status payload the Ollama tab renders. */
|
|
14505
|
+
async function getOllamaWebStatus(options) {
|
|
14506
|
+
const { accountPool, modelSettings } = options;
|
|
14507
|
+
const data = await accountPool.read().catch(() => null);
|
|
14508
|
+
const accounts = await accountPool.listAccounts().catch(() => []);
|
|
14509
|
+
const now = Date.now();
|
|
14510
|
+
const pool = {
|
|
14511
|
+
accounts,
|
|
14512
|
+
rotationStrategy: data?.rotationStrategy ?? "sequential",
|
|
14513
|
+
...data?.activeAccountId === void 0 ? {} : { activeAccountId: data.activeAccountId }
|
|
14514
|
+
};
|
|
14515
|
+
const settings = await modelSettings.read().catch(() => ({
|
|
14516
|
+
enabled: true,
|
|
14517
|
+
enabledModelIds: [],
|
|
14518
|
+
catalogModels: [],
|
|
14519
|
+
defaultReasoningEffort: null
|
|
14520
|
+
}));
|
|
14521
|
+
return {
|
|
14522
|
+
pool,
|
|
14523
|
+
models: settings.catalogModels.map((model) => ({
|
|
14524
|
+
id: model.id,
|
|
14525
|
+
...model.name === void 0 ? {} : { name: model.name }
|
|
14526
|
+
})),
|
|
14527
|
+
enabledModelIds: settings.enabledModelIds,
|
|
14528
|
+
usable: accounts.some((account) => isUsable(account, now)),
|
|
14529
|
+
catalogSynced: settings.catalogModels.length > 0
|
|
14530
|
+
};
|
|
14531
|
+
}
|
|
14532
|
+
/**
|
|
14533
|
+
* Register the Ollama settings API.
|
|
14534
|
+
*
|
|
14535
|
+
* The surface is deliberately the same one every other line exposes - status,
|
|
14536
|
+
* accounts, models - so the shared settings card and the client API helper
|
|
14537
|
+
* work against it without a per-provider special case.
|
|
14538
|
+
*/
|
|
14539
|
+
function registerOllamaRoutes(ctx, options) {
|
|
14540
|
+
const { accountPool, modelSettings } = options;
|
|
14541
|
+
const fetchFn = options.fetchFn ?? fetch;
|
|
14542
|
+
return ctx.webServer.register({
|
|
14543
|
+
kind: "prefix",
|
|
14544
|
+
path: ROUTE_PREFIX$3,
|
|
14545
|
+
handler: async (request, response) => {
|
|
14546
|
+
const path = new URL(request.url || "/", "http://dsh.local").pathname.replace(/^\/ollama\/api\/?/, "");
|
|
14547
|
+
const method = request.method ?? "GET";
|
|
14548
|
+
if (path === "" || path === "status") return sendJson$5(response, 200, {
|
|
14549
|
+
ok: true,
|
|
14550
|
+
value: await getOllamaWebStatus(options)
|
|
14551
|
+
});
|
|
14552
|
+
if (path === "accounts") {
|
|
14553
|
+
if (method === "GET") return sendJson$5(response, 200, {
|
|
14554
|
+
ok: true,
|
|
14555
|
+
value: await getOllamaWebStatus(options)
|
|
14556
|
+
});
|
|
14557
|
+
if (method !== "POST") return sendMethodNotAllowed$4(response);
|
|
14558
|
+
if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
|
|
14559
|
+
ok: false,
|
|
14560
|
+
error: "Cross-origin request rejected."
|
|
14561
|
+
});
|
|
14562
|
+
let body;
|
|
14563
|
+
try {
|
|
14564
|
+
body = await readRequestJson$5(request);
|
|
14565
|
+
} catch (error) {
|
|
14566
|
+
return sendJson$5(response, 400, {
|
|
14567
|
+
ok: false,
|
|
14568
|
+
error: error instanceof Error ? error.message : String(error)
|
|
14569
|
+
});
|
|
14570
|
+
}
|
|
14571
|
+
const action = typeof body.action === "string" ? body.action : "";
|
|
14572
|
+
const accountId = typeof body.accountId === "string" ? body.accountId : void 0;
|
|
14573
|
+
try {
|
|
14574
|
+
if (action === "add") {
|
|
14575
|
+
const apiKey = typeof body.apiKey === "string" ? body.apiKey.trim() : "";
|
|
14576
|
+
if (apiKey === "") return sendJson$5(response, 400, {
|
|
14577
|
+
ok: false,
|
|
14578
|
+
error: "API key is required."
|
|
14579
|
+
});
|
|
14580
|
+
const alias = typeof body.alias === "string" && body.alias.trim() !== "" ? body.alias.trim() : void 0;
|
|
14581
|
+
const credentials = {
|
|
14582
|
+
apiKey,
|
|
14583
|
+
addedAt: Date.now()
|
|
14584
|
+
};
|
|
14585
|
+
if (alias !== void 0) credentials.alias = alias;
|
|
14586
|
+
return sendJson$5(response, 200, {
|
|
14587
|
+
ok: true,
|
|
14588
|
+
value: { id: (await accountPool.addAccount(credentials, alias)).id }
|
|
14589
|
+
});
|
|
14590
|
+
}
|
|
14591
|
+
if (accountId === void 0) return sendJson$5(response, 400, {
|
|
14592
|
+
ok: false,
|
|
14593
|
+
error: "accountId is required."
|
|
14594
|
+
});
|
|
14595
|
+
if (action === "set-primary") await accountPool.setPrimary(accountId);
|
|
14596
|
+
else if (action === "set-alias" && typeof body.alias === "string") await accountPool.setAlias(accountId, body.alias);
|
|
14597
|
+
else if (action === "delete") await accountPool.deleteAccount(accountId);
|
|
14598
|
+
else if (action === "clear-cooldown") await accountPool.clearCooldown(accountId);
|
|
14599
|
+
else if (action === "strategy" && (body.strategy === "sequential" || body.strategy === "round-robin" || body.strategy === "sticky")) await accountPool.setStrategy(body.strategy);
|
|
14600
|
+
else return sendJson$5(response, 400, {
|
|
14601
|
+
ok: false,
|
|
14602
|
+
error: "Unsupported account action."
|
|
14603
|
+
});
|
|
14604
|
+
} catch (error) {
|
|
14605
|
+
return sendJson$5(response, 400, {
|
|
14606
|
+
ok: false,
|
|
14607
|
+
error: error instanceof Error ? error.message : String(error)
|
|
14608
|
+
});
|
|
14609
|
+
}
|
|
14610
|
+
return sendJson$5(response, 200, {
|
|
14611
|
+
ok: true,
|
|
14612
|
+
value: await getOllamaWebStatus(options)
|
|
14613
|
+
});
|
|
14614
|
+
}
|
|
14615
|
+
if (path === "models") {
|
|
14616
|
+
if (method === "GET") {
|
|
14617
|
+
const settings = await modelSettings.read();
|
|
14618
|
+
return sendJson$5(response, 200, {
|
|
14619
|
+
ok: true,
|
|
14620
|
+
value: {
|
|
14621
|
+
models: settings.catalogModels,
|
|
14622
|
+
enabled: settings.enabled,
|
|
14623
|
+
enabledModelIds: settings.enabledModelIds
|
|
14624
|
+
}
|
|
14625
|
+
});
|
|
14626
|
+
}
|
|
14627
|
+
if (method !== "POST") return sendMethodNotAllowed$4(response);
|
|
14628
|
+
if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
|
|
14629
|
+
ok: false,
|
|
14630
|
+
error: "Cross-origin request rejected."
|
|
14631
|
+
});
|
|
14632
|
+
const body = await readRequestJson$5(request).catch(() => ({}));
|
|
14633
|
+
if (typeof body.enabled === "boolean") await modelSettings.update({ enabled: body.enabled });
|
|
14634
|
+
if (Array.isArray(body.enabledModelIds)) await modelSettings.update({ enabledModelIds: body.enabledModelIds.filter((id) => typeof id === "string") });
|
|
14635
|
+
return sendJson$5(response, 200, {
|
|
14636
|
+
ok: true,
|
|
14637
|
+
value: await modelSettings.read()
|
|
14638
|
+
});
|
|
14639
|
+
}
|
|
14640
|
+
if (path === "catalog/refresh") {
|
|
14641
|
+
if (method !== "POST") return sendMethodNotAllowed$4(response);
|
|
14642
|
+
if (!isSameOriginMutation(request)) return sendJson$5(response, 403, {
|
|
14643
|
+
ok: false,
|
|
14644
|
+
error: "Cross-origin request rejected."
|
|
14645
|
+
});
|
|
14646
|
+
let credentials = null;
|
|
14647
|
+
try {
|
|
14648
|
+
credentials = (await accountPool.getEffectiveCredential(void 0, fetchFn)).credentials;
|
|
14649
|
+
} catch {
|
|
14650
|
+
credentials = null;
|
|
14651
|
+
}
|
|
14652
|
+
if (credentials === null) return sendJson$5(response, 400, {
|
|
14653
|
+
ok: false,
|
|
14654
|
+
error: "Add an API key before syncing models."
|
|
14655
|
+
});
|
|
14656
|
+
const models = await loadCatalog$1(fetchFn, credentials).catch(() => []);
|
|
14657
|
+
if (models.length === 0) return sendJson$5(response, 502, {
|
|
14658
|
+
ok: false,
|
|
14659
|
+
error: "Could not read the model list from Ollama."
|
|
14660
|
+
});
|
|
14661
|
+
await modelSettings.storeCatalog(models);
|
|
14662
|
+
return sendJson$5(response, 200, {
|
|
14663
|
+
ok: true,
|
|
14664
|
+
value: { models }
|
|
14665
|
+
});
|
|
14666
|
+
}
|
|
14667
|
+
}
|
|
14668
|
+
});
|
|
14669
|
+
}
|
|
14670
|
+
//#endregion
|
|
14671
|
+
//#region src/host/ollama/account-pool.ts
|
|
14672
|
+
/** Encrypted pool file this line owns. */
|
|
14673
|
+
function ollamaPoolPath() {
|
|
14674
|
+
return path.join(dshHomeDir(), "storages", "ollama-pool.json");
|
|
14675
|
+
}
|
|
14676
|
+
/**
|
|
14677
|
+
* Stable identity of one key.
|
|
14678
|
+
*
|
|
14679
|
+
* Ollama hands the user a bare key and nothing else — no account id, no email —
|
|
14680
|
+
* so the key's own digest is the only identity available. It is never written
|
|
14681
|
+
* anywhere: it exists to tell two pasted keys apart inside the pool document,
|
|
14682
|
+
* and it is not a credential and cannot be used as one.
|
|
14683
|
+
*/
|
|
14684
|
+
function ollamaAccountKey(credentials) {
|
|
14685
|
+
return createHash("sha256").update(credentials.apiKey).digest("hex");
|
|
14686
|
+
}
|
|
14687
|
+
function optionalString$5(record, key) {
|
|
14688
|
+
const value = record[key];
|
|
14689
|
+
if (value === void 0 || value === null) return void 0;
|
|
14690
|
+
if (typeof value !== "string") throw new Error("Ollama pool account field is invalid");
|
|
14691
|
+
return value;
|
|
14692
|
+
}
|
|
14693
|
+
/** Non-negative integer, or undefined for anything else. */
|
|
14694
|
+
function counter(value) {
|
|
14695
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
14696
|
+
return Math.floor(value);
|
|
14697
|
+
}
|
|
14698
|
+
/** Read one account's counters, dropping the whole record if it is unusable. */
|
|
14699
|
+
function readUsage(value) {
|
|
14700
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
14701
|
+
const record = value;
|
|
14702
|
+
const inputTokens = counter(record.inputTokens);
|
|
14703
|
+
const outputTokens = counter(record.outputTokens);
|
|
14704
|
+
const requestCount = counter(record.requestCount);
|
|
14705
|
+
if (inputTokens === void 0 || outputTokens === void 0 || requestCount === void 0) return void 0;
|
|
14706
|
+
const lastCountedAt = counter(record.lastCountedAt);
|
|
14707
|
+
return {
|
|
14708
|
+
inputTokens,
|
|
14709
|
+
outputTokens,
|
|
14710
|
+
requestCount,
|
|
14711
|
+
...lastCountedAt === void 0 ? {} : { lastCountedAt }
|
|
14712
|
+
};
|
|
14713
|
+
}
|
|
14714
|
+
/** Validate and normalize one whole pool document. */
|
|
14715
|
+
function parseOllamaPoolData(value) {
|
|
14716
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Ollama pool payload is invalid");
|
|
14717
|
+
const record = value;
|
|
14718
|
+
const accounts = [];
|
|
14719
|
+
for (const item of Array.isArray(record.accounts) ? record.accounts : []) {
|
|
14720
|
+
if (typeof item !== "object" || item === null) continue;
|
|
14721
|
+
const raw = item;
|
|
14722
|
+
if (typeof raw.id !== "string") continue;
|
|
14723
|
+
const credentials = parseOllamaCredentials(raw.credentials);
|
|
14724
|
+
const account = {
|
|
14725
|
+
id: raw.id,
|
|
14726
|
+
alias: typeof raw.alias === "string" && raw.alias !== "" ? raw.alias : "Ollama 账号",
|
|
14727
|
+
credentials,
|
|
14728
|
+
addedAt: typeof raw.addedAt === "number" ? raw.addedAt : Date.now(),
|
|
14729
|
+
isPrimary: raw.isPrimary === true
|
|
14730
|
+
};
|
|
14731
|
+
const lastModelId = optionalString$5(raw, "lastModelId");
|
|
14732
|
+
if (lastModelId !== void 0) account.lastModelId = lastModelId;
|
|
14733
|
+
const usage = readUsage(raw.usage);
|
|
14734
|
+
if (usage !== void 0) account.usage = usage;
|
|
14735
|
+
if (typeof raw.lastUsedAt === "number") account.lastUsedAt = raw.lastUsedAt;
|
|
14736
|
+
if (typeof raw.cooldownUntil === "number") account.cooldownUntil = raw.cooldownUntil;
|
|
14737
|
+
if (typeof raw.cooldownReason === "string") account.cooldownReason = raw.cooldownReason;
|
|
14738
|
+
if (raw.authStatus === "expired" || raw.authStatus === "invalid") account.authStatus = raw.authStatus;
|
|
14739
|
+
if (typeof raw.authFailedReason === "string") account.authFailedReason = raw.authFailedReason;
|
|
14740
|
+
accounts.push(account);
|
|
14741
|
+
}
|
|
14742
|
+
const result = {
|
|
14743
|
+
version: 1,
|
|
14744
|
+
rotationStrategy: normalizeRotationStrategy(record.rotationStrategy),
|
|
14745
|
+
accounts
|
|
14746
|
+
};
|
|
14747
|
+
if (typeof record.activeAccountId === "string") result.activeAccountId = record.activeAccountId;
|
|
14748
|
+
return result;
|
|
14749
|
+
}
|
|
14750
|
+
/**
|
|
14751
|
+
* Ollama's account pool.
|
|
14752
|
+
*
|
|
14753
|
+
* An Ollama credential is a static key that never expires, so this wrapper
|
|
14754
|
+
* supplies identity, the display alias a user chose, and the mirror of the
|
|
14755
|
+
* single-credential file — no refresh logic is needed anywhere.
|
|
14756
|
+
*/
|
|
14757
|
+
var OllamaAccountPool = class extends AccountPoolCore {
|
|
14758
|
+
store;
|
|
14759
|
+
constructor(options = {}) {
|
|
14760
|
+
const store = options.store ?? new FileCredentialStore$5();
|
|
14761
|
+
const hooks = {
|
|
14762
|
+
providerId: PROVIDER_ID$4,
|
|
14763
|
+
displayName: PROVIDER_NAME$4,
|
|
14764
|
+
poolFile: ollamaPoolPath(),
|
|
14765
|
+
keychainService: "dsh-ollama-pool",
|
|
14766
|
+
parsePoolData: parseOllamaPoolData,
|
|
14767
|
+
dedupeKey: ollamaAccountKey,
|
|
14768
|
+
defaultAlias: (credentials, position) => credentials.alias ?? `Ollama 账号 ${position}`,
|
|
14769
|
+
createAccount: ({ id, alias, credentials, addedAt, isPrimary }) => ({
|
|
14770
|
+
id,
|
|
14771
|
+
alias,
|
|
14772
|
+
credentials,
|
|
14773
|
+
addedAt,
|
|
14774
|
+
isPrimary
|
|
14775
|
+
}),
|
|
14776
|
+
legacyAccount: async () => {
|
|
14777
|
+
const stored = await store.read();
|
|
14778
|
+
if (stored === null) return null;
|
|
14779
|
+
return {
|
|
14780
|
+
id: "acc_primary",
|
|
14781
|
+
alias: stored.alias ?? "Ollama 账号 1",
|
|
14782
|
+
credentials: stored,
|
|
14783
|
+
addedAt: stored.addedAt ?? Date.now(),
|
|
14784
|
+
isPrimary: true
|
|
14785
|
+
};
|
|
14786
|
+
},
|
|
14787
|
+
mirrorPrimary: async (credentials) => {
|
|
14788
|
+
if (credentials === null) {
|
|
14789
|
+
await store.clear();
|
|
14790
|
+
return;
|
|
14791
|
+
}
|
|
14792
|
+
await store.write(credentials);
|
|
14793
|
+
},
|
|
14794
|
+
extendSummary: (account, base) => {
|
|
14795
|
+
const extra = {};
|
|
14796
|
+
if (account.lastModelId !== void 0) extra.planLabel = account.lastModelId;
|
|
14797
|
+
if (account.usage !== void 0) extra.usage = { ...account.usage };
|
|
14798
|
+
return Object.keys(extra).length === 0 ? base : {
|
|
14799
|
+
...base,
|
|
14800
|
+
...extra
|
|
14801
|
+
};
|
|
14802
|
+
},
|
|
14803
|
+
...options.backend === void 0 ? {} : { backend: options.backend },
|
|
14804
|
+
...options.maxAccounts === void 0 ? {} : { maxAccounts: options.maxAccounts }
|
|
14805
|
+
};
|
|
14806
|
+
super(hooks);
|
|
14807
|
+
this.store = store;
|
|
14808
|
+
}
|
|
14809
|
+
/**
|
|
14810
|
+
* Pick the key for the next request, skipping accounts already tried.
|
|
14811
|
+
*
|
|
14812
|
+
* Ollama's credential carries no deployment dimension - there is one cloud
|
|
14813
|
+
* endpoint and every key is valid for it - so the account and its key are
|
|
14814
|
+
* returned together and nothing has to be attached to the credential.
|
|
14815
|
+
*/
|
|
14816
|
+
async getEffectiveCredential(excludeIds, fetchFn = fetch) {
|
|
14817
|
+
return this.getEffectiveAccount(excludeIds, fetchFn);
|
|
14818
|
+
}
|
|
14819
|
+
/** The pre-pool credential file this pool mirrors its primary account into. */
|
|
14820
|
+
mirrorStore() {
|
|
14821
|
+
return this.store;
|
|
14822
|
+
}
|
|
14823
|
+
async recordUsage(accountId, inputTokens, outputTokens) {
|
|
14824
|
+
if (inputTokens <= 0 && outputTokens <= 0) return;
|
|
14825
|
+
await this.updatePool((data) => {
|
|
14826
|
+
const account = data.accounts.find((entry) => entry.id === accountId);
|
|
14827
|
+
if (account === void 0) return false;
|
|
14828
|
+
const previous = account.usage;
|
|
14829
|
+
account.usage = {
|
|
14830
|
+
inputTokens: (previous?.inputTokens ?? 0) + inputTokens,
|
|
14831
|
+
outputTokens: (previous?.outputTokens ?? 0) + outputTokens,
|
|
14832
|
+
requestCount: (previous?.requestCount ?? 0) + 1,
|
|
14833
|
+
lastCountedAt: Date.now()
|
|
14834
|
+
};
|
|
14835
|
+
return true;
|
|
14836
|
+
}).catch(() => void 0);
|
|
14837
|
+
}
|
|
14838
|
+
};
|
|
14839
|
+
//#endregion
|
|
13150
14840
|
//#region src/host/kimi-code/model-catalog.ts
|
|
13151
14841
|
/**
|
|
13152
14842
|
* The four ids the subscription serves today.
|
|
@@ -17183,7 +18873,6 @@ var KimiCodeAdapter = class extends LlmAdapter {
|
|
|
17183
18873
|
name: entry?.name ?? modelId,
|
|
17184
18874
|
inputModalities: inputModalitiesForEntry(modelId, catalog),
|
|
17185
18875
|
context: { contextWindow: this.contextWindowFor(modelId, entry, settings.contextWindowOverrides) },
|
|
17186
|
-
defaultMaxTokens: maxOutputTokensFor$3(modelId, this.contextWindowFor(modelId, entry, settings.contextWindowOverrides)),
|
|
17187
18876
|
...efforts.length === 0 ? {} : { reasoning: {
|
|
17188
18877
|
efforts: efforts.map((effort) => ({
|
|
17189
18878
|
id: ReasoningEffortId(effort),
|
|
@@ -21653,9 +23342,8 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
|
|
|
21653
23342
|
* One model's resolved metadata, read through the current selection.
|
|
21654
23343
|
*
|
|
21655
23344
|
* The effective context window is the override when one is saved, and the
|
|
21656
|
-
* output cap is sized against that same number
|
|
21657
|
-
*
|
|
21658
|
-
* this single resolver exists to prevent.
|
|
23345
|
+
* wire output cap is sized against that same number in requestStream. It is
|
|
23346
|
+
* deliberately not exposed as a fixed defaultMaxTokens reservation here.
|
|
21659
23347
|
*/
|
|
21660
23348
|
async resolveModel(provider, modelId, signal) {
|
|
21661
23349
|
if (signal?.aborted === true) throw new LlmError("MiniMax Code model resolution aborted", "ABORTED");
|
|
@@ -21670,7 +23358,6 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
|
|
|
21670
23358
|
name: entry?.name ?? modelId,
|
|
21671
23359
|
inputModalities: modalitiesForModel(modelId),
|
|
21672
23360
|
context: { contextWindow },
|
|
21673
|
-
defaultMaxTokens: maxOutputTokensFor$1(modelId, contextWindow),
|
|
21674
23361
|
...efforts.length === 0 ? {} : { reasoning: {
|
|
21675
23362
|
efforts: efforts.map((effort) => ({
|
|
21676
23363
|
id: ReasoningEffortId(effort),
|
|
@@ -26373,7 +28060,7 @@ var WorkBuddyAdapter = class extends LlmAdapter {
|
|
|
26373
28060
|
name: entry.id === modelId ? entry.name : modelId,
|
|
26374
28061
|
inputModalities: entry.supportsImage ? ["text", "image"] : ["text"],
|
|
26375
28062
|
context: { contextWindow: contextWindow || 128e3 },
|
|
26376
|
-
|
|
28063
|
+
...outputReservation(contextWindow || 128e3, maxOutputTokensFor$2(modelId, catalog)),
|
|
26377
28064
|
...efforts.length === 0 ? {} : { reasoning: {
|
|
26378
28065
|
efforts: efforts.map((effort) => ({
|
|
26379
28066
|
id: ReasoningEffortId(effort),
|
|
@@ -26428,7 +28115,13 @@ var WorkBuddyAdapter = class extends LlmAdapter {
|
|
|
26428
28115
|
const fetchFn = this.options.fetchFn ?? fetch;
|
|
26429
28116
|
const requestOptions = offloadOldestRequestImages$1(normalizeGenerateOptions(options));
|
|
26430
28117
|
const images = await resolveRequestImages$1(requestOptions, this.options.attachments, signal);
|
|
26431
|
-
const
|
|
28118
|
+
const settings = await this.settings();
|
|
28119
|
+
const credentials = await this.credentials(settings);
|
|
28120
|
+
const catalog = credentials === null ? FALLBACK_MODELS$1 : await this.catalog(credentials);
|
|
28121
|
+
const body = JSON.stringify(buildChatRequest({
|
|
28122
|
+
...requestOptions,
|
|
28123
|
+
maxTokens: requestOptions.maxTokens ?? maxOutputTokensFor$2(options.model, catalog)
|
|
28124
|
+
}, images));
|
|
26432
28125
|
const pool = this.accountPool;
|
|
26433
28126
|
const tried = /* @__PURE__ */ new Set();
|
|
26434
28127
|
let response;
|
|
@@ -27399,17 +29092,29 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
|
|
|
27399
29092
|
* LOCAL ADDITIONS — the rows the reference snapshot predates
|
|
27400
29093
|
* ---------------------------------------------------------------------------
|
|
27401
29094
|
*
|
|
27402
|
-
*
|
|
27403
|
-
*
|
|
27404
|
-
*
|
|
27405
|
-
*
|
|
27406
|
-
*
|
|
27407
|
-
*
|
|
27408
|
-
*
|
|
27409
|
-
*
|
|
27410
|
-
*
|
|
27411
|
-
* row
|
|
27412
|
-
*
|
|
29095
|
+
* TWO KINDS OF ROW ARE NOT FULLY TRANSCRIBED, and they are different things.
|
|
29096
|
+
* Do not collapse them.
|
|
29097
|
+
*
|
|
29098
|
+
* 1. `claude-sonnet-5-5` — the snapshot this table was copied from has no
|
|
29099
|
+
* entry for it, so the whole row is CURATED: every value below comes from
|
|
29100
|
+
* the vendor's own documentation and no snapshot field can check it. The
|
|
29101
|
+
* mirror list in the test is `LOCALLY_CURATED_MODEL_IDS`.
|
|
29102
|
+
*
|
|
29103
|
+
* 2. `claude-opus-5-5` — a NEWER snapshot (pi-ai >= 0.87.1) does carry it, so
|
|
29104
|
+
* the row sits in the snapshot's own position and EVERY field is checked
|
|
29105
|
+
* against that snapshot. Exactly one field is not: `thinkingMode`, recorded
|
|
29106
|
+
* in the test as `SNAPSHOT_AGREES_BUT_CURATED_WINS`. The snapshot would
|
|
29107
|
+
* classify it 'mid-convo', whose form forces `output_config.effort = 'high'`
|
|
29108
|
+
* when the caller names none, while the vendor documents this model's
|
|
29109
|
+
* default effort as MEDIUM. Transcribing it would silently outrank the user
|
|
29110
|
+
* and think — and bill — harder than asked, so the documented 'adaptive'
|
|
29111
|
+
* stands. The test asserts the row still disagrees with the snapshot, so a
|
|
29112
|
+
* future snapshot that agrees fails loudly and retires the entry.
|
|
29113
|
+
*
|
|
29114
|
+
* Both must be changed together with the mirror list in the test. An invented
|
|
29115
|
+
* row that mimics the format of a checked one is worse than a missing row: it
|
|
29116
|
+
* is unverifiable and it looks verified. So each row is marked at the row
|
|
29117
|
+
* itself, the test asserts the id is on the curated list, and the test
|
|
27413
29118
|
* asserts the curated list is exactly the ids the snapshot lacks — which keeps
|
|
27414
29119
|
* the lock narrow instead of merely weaker.
|
|
27415
29120
|
*
|
|
@@ -27434,10 +29139,12 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
|
|
|
27434
29139
|
* 2. `thinkingMode: 'adaptive'`, NOT `'mid-convo'`. The two are easy to confuse
|
|
27435
29140
|
* here because every model in this line that refuses a temperature is also,
|
|
27436
29141
|
* so far, a managed-effort model. That is a coincidence of `compat`, not a
|
|
27437
|
-
* rule
|
|
29142
|
+
* rule. A newer snapshot DOES flag this row `supportsMidConvoEffort` and
|
|
29143
|
+
* would therefore classify it 'mid-convo' — which is exactly why this is the
|
|
29144
|
+
* one declared exception rather than a transcription: 'mid-convo'
|
|
27438
29145
|
* additionally forces `output_config = { effort: 'high' }` when the caller
|
|
27439
|
-
* names no effort. That forced high is correct for the rows
|
|
27440
|
-
*
|
|
29146
|
+
* names no effort. That forced high is correct for the rows whose documented
|
|
29147
|
+
* default effort is high, and wrong for this one; this model's
|
|
27441
29148
|
* documented default effort is `medium`. Labelling it 'mid-convo' would
|
|
27442
29149
|
* silently override the vendor's own default and make every request think —
|
|
27443
29150
|
* and cost — harder than the user asked for.
|
|
@@ -27454,8 +29161,8 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
|
|
|
27454
29161
|
* the user has to reach.
|
|
27455
29162
|
*/
|
|
27456
29163
|
/**
|
|
27457
|
-
* Sixteen rows: the snapshot's own
|
|
27458
|
-
* followed by the
|
|
29164
|
+
* Sixteen rows: the snapshot's own 15, in the snapshot's own declaration order,
|
|
29165
|
+
* followed by the one row the snapshot still predates (see LOCAL ADDITIONS above).
|
|
27459
29166
|
*
|
|
27460
29167
|
* The order matters and is not cosmetic. The test asserts the snapshot's ids
|
|
27461
29168
|
* appear in the table as a SUBSEQUENCE, so a curated row may be appended or
|
|
@@ -27644,6 +29351,25 @@ const CLAUDE_MODELS = Object.freeze([
|
|
|
27644
29351
|
],
|
|
27645
29352
|
canDisableThinking: false
|
|
27646
29353
|
},
|
|
29354
|
+
{
|
|
29355
|
+
id: "claude-opus-5-5",
|
|
29356
|
+
name: "Claude Opus 5.5",
|
|
29357
|
+
contextWindow: 1e6,
|
|
29358
|
+
maxTokens: 128e3,
|
|
29359
|
+
supportsImage: true,
|
|
29360
|
+
supportsTemperature: false,
|
|
29361
|
+
thinkingMode: "adaptive",
|
|
29362
|
+
reasoningEfforts: [
|
|
29363
|
+
"low",
|
|
29364
|
+
"medium",
|
|
29365
|
+
"high",
|
|
29366
|
+
"xhigh",
|
|
29367
|
+
"max"
|
|
29368
|
+
],
|
|
29369
|
+
canDisableThinking: false,
|
|
29370
|
+
minCliVersion: "2.1.280",
|
|
29371
|
+
bindsThinkingToPrefix: true
|
|
29372
|
+
},
|
|
27647
29373
|
{
|
|
27648
29374
|
id: "claude-sonnet-4-5",
|
|
27649
29375
|
name: "Claude Sonnet 4.5 (latest)",
|
|
@@ -27707,25 +29433,6 @@ const CLAUDE_MODELS = Object.freeze([
|
|
|
27707
29433
|
],
|
|
27708
29434
|
canDisableThinking: true
|
|
27709
29435
|
},
|
|
27710
|
-
{
|
|
27711
|
-
id: "claude-opus-5-5",
|
|
27712
|
-
name: "Claude Opus 5.5",
|
|
27713
|
-
contextWindow: 1e6,
|
|
27714
|
-
maxTokens: 128e3,
|
|
27715
|
-
supportsImage: true,
|
|
27716
|
-
supportsTemperature: false,
|
|
27717
|
-
thinkingMode: "adaptive",
|
|
27718
|
-
reasoningEfforts: [
|
|
27719
|
-
"low",
|
|
27720
|
-
"medium",
|
|
27721
|
-
"high",
|
|
27722
|
-
"xhigh",
|
|
27723
|
-
"max"
|
|
27724
|
-
],
|
|
27725
|
-
canDisableThinking: false,
|
|
27726
|
-
minCliVersion: "2.1.280",
|
|
27727
|
-
bindsThinkingToPrefix: true
|
|
27728
|
-
},
|
|
27729
29436
|
{
|
|
27730
29437
|
id: "claude-sonnet-5-5",
|
|
27731
29438
|
name: "Claude Sonnet 5.5",
|
|
@@ -32249,11 +33956,9 @@ function cancelLogin() {
|
|
|
32249
33956
|
* importing it before it exists would make this adapter uncompilable, and
|
|
32250
33957
|
* importing it afterwards changes nothing, because the only methods used are
|
|
32251
33958
|
* the five in {@link ClaudeAccountPoolLike}.
|
|
32252
|
-
* - A2.
|
|
32253
|
-
*
|
|
32254
|
-
*
|
|
32255
|
-
* catalog. Loading it per request would add a cached round trip that cannot
|
|
32256
|
-
* change a byte of the body.
|
|
33959
|
+
* - A2. When no fixed output default was materialized, the request path reads
|
|
33960
|
+
* the cached catalog to preserve its wire cap independently of compaction
|
|
33961
|
+
* reservation metadata. Thinking forms still come from the frozen catalog.
|
|
32257
33962
|
* - A3. A 429 with no `retry-after` and no reset instant cools the account for
|
|
32258
33963
|
* {@link POOL_COOLDOWN_MS}. That is deliberately shorter than the 5-hour
|
|
32259
33964
|
* window: an account parked for hours after a burst limit recovers would cost
|
|
@@ -32496,7 +34201,7 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
|
|
|
32496
34201
|
name: entry.name,
|
|
32497
34202
|
inputModalities: entry.supportsImage ? ["text", "image"] : ["text"],
|
|
32498
34203
|
context: { contextWindow: contextWindow || 2e5 },
|
|
32499
|
-
|
|
34204
|
+
...outputReservation(contextWindow || 2e5, maxOutputTokensFor(modelId, catalog)),
|
|
32500
34205
|
...efforts.length === 0 ? {} : { reasoning: {
|
|
32501
34206
|
efforts: efforts.map((effort) => ({
|
|
32502
34207
|
id: ReasoningEffortId(effort),
|
|
@@ -32579,7 +34284,11 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
|
|
|
32579
34284
|
}
|
|
32580
34285
|
async attemptRequest(credentials, requestOptions, images, toolNames, thinking, settings, signal, fetchFn) {
|
|
32581
34286
|
const cacheTtl = ClaudeAdapter.cacheTtlFor(settings);
|
|
32582
|
-
const
|
|
34287
|
+
const catalog = requestOptions.maxTokens === void 0 ? await this.catalog(credentials) : void 0;
|
|
34288
|
+
const payload = buildClaudeRequestBody({
|
|
34289
|
+
...requestOptions,
|
|
34290
|
+
maxTokens: requestOptions.maxTokens ?? maxOutputTokensFor(requestOptions.model, catalog)
|
|
34291
|
+
}, images, {
|
|
32583
34292
|
toolNames,
|
|
32584
34293
|
cacheControl: true,
|
|
32585
34294
|
cacheTtl
|
|
@@ -38002,12 +39711,12 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
38002
39711
|
const claimAntigravityRoute = () => {
|
|
38003
39712
|
if (antigravityRegistration !== void 0) return;
|
|
38004
39713
|
try {
|
|
38005
|
-
antigravityRegistration = ctx.llm.registerAdapter([PROVIDER_ID$
|
|
38006
|
-
if (antigravityConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] Antigravity route "${PROVIDER_ID$
|
|
39714
|
+
antigravityRegistration = ctx.llm.registerAdapter([PROVIDER_ID$6], antigravityAdapter);
|
|
39715
|
+
if (antigravityConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] Antigravity route "${PROVIDER_ID$6}" is now served by this plugin`);
|
|
38007
39716
|
antigravityConflict = null;
|
|
38008
39717
|
} catch (error) {
|
|
38009
39718
|
antigravityConflict = error instanceof Error ? error.message : String(error);
|
|
38010
|
-
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$
|
|
39719
|
+
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$6}" is already owned by another adapter; Antigravity models keep being served by that one until its configuration is removed (${antigravityConflict})`);
|
|
38011
39720
|
}
|
|
38012
39721
|
};
|
|
38013
39722
|
claimAntigravityRoute();
|
|
@@ -38026,6 +39735,12 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
38026
39735
|
}, commandCodeAccountPool);
|
|
38027
39736
|
let commandCodeRegistration;
|
|
38028
39737
|
let commandCodeConflict = null;
|
|
39738
|
+
const ollamaStore = new FileCredentialStore$5();
|
|
39739
|
+
const ollamaModelSettings = new FileModelSettingsStore$5();
|
|
39740
|
+
const ollamaAccountPool = new OllamaAccountPool({ store: ollamaStore });
|
|
39741
|
+
const ollamaAdapter = new OllamaAdapter(ollamaStore, ollamaModelSettings, { fetchFn: proxyFetch }, ollamaAccountPool);
|
|
39742
|
+
let ollamaRegistration;
|
|
39743
|
+
let ollamaConflict = null;
|
|
38029
39744
|
const kimiCodeAdapter = new KimiCodeAdapter(kimiCodeStore, kimiCodeModelSettings, kimiCodePreferences, {
|
|
38030
39745
|
fetchFn: proxyFetch,
|
|
38031
39746
|
attachments: ctx.attachments,
|
|
@@ -38051,15 +39766,32 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
38051
39766
|
const kimiCodeRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
|
|
38052
39767
|
claimKimiCodeRoute();
|
|
38053
39768
|
}) : void 0;
|
|
39769
|
+
const claimOllamaRoute = () => {
|
|
39770
|
+
if (ollamaRegistration !== void 0) return;
|
|
39771
|
+
try {
|
|
39772
|
+
ollamaRegistration = ctx.llm.registerAdapter([PROVIDER_ID$4], ollamaAdapter);
|
|
39773
|
+
if (ollamaConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$4} route "${PROVIDER_ID$4}" is now served by this plugin`);
|
|
39774
|
+
ollamaConflict = null;
|
|
39775
|
+
} catch (error) {
|
|
39776
|
+
ollamaConflict = error instanceof Error ? error.message : String(error);
|
|
39777
|
+
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$4}" is already owned by another adapter; ${PROVIDER_NAME$4} models keep being served by that one until its configuration is removed (${ollamaConflict})`);
|
|
39778
|
+
}
|
|
39779
|
+
};
|
|
39780
|
+
claimOllamaRoute();
|
|
39781
|
+
const disposeOllamaRoutes = registerOllamaRoutes(ctx, {
|
|
39782
|
+
accountPool: ollamaAccountPool,
|
|
39783
|
+
modelSettings: ollamaModelSettings,
|
|
39784
|
+
fetchFn: proxyFetch
|
|
39785
|
+
});
|
|
38054
39786
|
const claimCommandCodeRoute = () => {
|
|
38055
39787
|
if (commandCodeRegistration !== void 0) return;
|
|
38056
39788
|
try {
|
|
38057
|
-
commandCodeRegistration = ctx.llm.registerAdapter([PROVIDER_ID$
|
|
38058
|
-
if (commandCodeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$
|
|
39789
|
+
commandCodeRegistration = ctx.llm.registerAdapter([PROVIDER_ID$5], commandCodeAdapter);
|
|
39790
|
+
if (commandCodeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$5} route "${PROVIDER_ID$5}" is now served by this plugin`);
|
|
38059
39791
|
commandCodeConflict = null;
|
|
38060
39792
|
} catch (error) {
|
|
38061
39793
|
commandCodeConflict = error instanceof Error ? error.message : String(error);
|
|
38062
|
-
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$
|
|
39794
|
+
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$5}" is already owned by another adapter; ${PROVIDER_NAME$5} models keep being served by that one until its configuration is removed (${commandCodeConflict})`);
|
|
38063
39795
|
}
|
|
38064
39796
|
};
|
|
38065
39797
|
claimCommandCodeRoute();
|
|
@@ -38242,7 +39974,7 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
38242
39974
|
});
|
|
38243
39975
|
};
|
|
38244
39976
|
const disposeRoutes = registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwitcher, readRouteAudit, codexAccountPool);
|
|
38245
|
-
const disposeAdapter = ctx.llm.registerAdapter([PROVIDER_ID$
|
|
39977
|
+
const disposeAdapter = ctx.llm.registerAdapter([PROVIDER_ID$7], adapter);
|
|
38246
39978
|
const disposeImageTool = ctx.tools.register(createCodexImageTool(oauth, ctx.attachments, { fetchFn: proxyFetch }));
|
|
38247
39979
|
const disposeVideoTool = ctx.tools.register(createKimiVideoTool(ctx, { fetchFn: proxyFetch }));
|
|
38248
39980
|
ctx.inject(["web"], (ctx) => {
|
|
@@ -38268,6 +40000,9 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
38268
40000
|
releaseHandle(antigravityRouteWatch);
|
|
38269
40001
|
antigravityRegistration?.();
|
|
38270
40002
|
antigravityRegistration = void 0;
|
|
40003
|
+
disposeOllamaRoutes();
|
|
40004
|
+
ollamaRegistration?.();
|
|
40005
|
+
ollamaRegistration = void 0;
|
|
38271
40006
|
disposeCommandCodeRoutes();
|
|
38272
40007
|
releaseHandle(commandCodeRouteWatch);
|
|
38273
40008
|
commandCodeRegistration?.();
|