@eddyskywalker/dsh-chatgpt-subscription 0.10.15 → 0.10.21
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 +45 -1
- package/README.md +8 -4
- package/lib/client.js +641 -121
- package/lib/client.js.map +1 -1
- package/lib/index.js +2086 -306
- 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/reasoning-collapse-guard/index.d.ts.map +1 -1
- 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
|
@@ -13,12 +13,27 @@ import http, { createServer } from "node:http";
|
|
|
13
13
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
14
14
|
import { lookup } from "node:dns/promises";
|
|
15
15
|
import { isIP } from "node:net";
|
|
16
|
-
import { createUserMessage } from "@deepseek-ai/dsh-llm/message";
|
|
16
|
+
import { boundContextSummary, createUserMessage } from "@deepseek-ai/dsh-llm/message";
|
|
17
17
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
18
18
|
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,8 +364,8 @@ 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 [];
|
|
@@ -309,7 +377,7 @@ function listCodexModels(preferences, live) {
|
|
|
309
377
|
const visible = new Set(status?.visibleModelIds ?? (listed ?? shippedModels()).map((entry) => entry.id));
|
|
310
378
|
const selected = (listed ?? shippedModels()).filter((entry) => visible.has(entry.id));
|
|
311
379
|
return (listed === void 0 || selected.length > 0 ? selected : shippedModels().filter((entry) => visible.has(entry.id))).map((entry) => ({
|
|
312
|
-
provider: PROVIDER_ID$
|
|
380
|
+
provider: PROVIDER_ID$7,
|
|
313
381
|
id: entry.id,
|
|
314
382
|
name: entry.name,
|
|
315
383
|
inputModalities: [...entry.inputModalities]
|
|
@@ -332,12 +400,12 @@ function resolveCodexModel(model, preferences, live) {
|
|
|
332
400
|
const statedDefault = liveEntry?.defaultReasoningEffort ?? entry.defaultReasoningEffort;
|
|
333
401
|
const defaultEffort = isCodexReasoningEffort(statedDefault) && efforts.includes(statedDefault) ? statedDefault : efforts[0];
|
|
334
402
|
return {
|
|
335
|
-
provider: PROVIDER_ID$
|
|
403
|
+
provider: PROVIDER_ID$7,
|
|
336
404
|
id: model,
|
|
337
405
|
name: liveEntry?.name ?? (entry.id === model ? entry.name : model),
|
|
338
406
|
inputModalities: [...liveEntry?.inputModalities ?? entry.inputModalities],
|
|
339
407
|
context: { contextWindow: configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow },
|
|
340
|
-
|
|
408
|
+
...outputReservation(configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow, codexModelMaxTokens(model)),
|
|
341
409
|
reasoning: {
|
|
342
410
|
efforts: efforts.map((effort) => ({
|
|
343
411
|
id: ReasoningEffortId(effort),
|
|
@@ -349,7 +417,7 @@ function resolveCodexModel(model, preferences, live) {
|
|
|
349
417
|
}
|
|
350
418
|
//#endregion
|
|
351
419
|
//#region src/host/adapter.ts
|
|
352
|
-
const RETRY_POLICY$
|
|
420
|
+
const RETRY_POLICY$6 = resolveRetryPolicy({
|
|
353
421
|
mode: "normal",
|
|
354
422
|
maxRetries: 3,
|
|
355
423
|
retryableCodes: [
|
|
@@ -379,11 +447,11 @@ var CodexChatGptAdapter = class extends LlmAdapter {
|
|
|
379
447
|
providerInfo(provider) {
|
|
380
448
|
return {
|
|
381
449
|
id: provider,
|
|
382
|
-
name: PROVIDER_NAME$
|
|
450
|
+
name: PROVIDER_NAME$7
|
|
383
451
|
};
|
|
384
452
|
}
|
|
385
453
|
providerRetryPolicy() {
|
|
386
|
-
return RETRY_POLICY$
|
|
454
|
+
return RETRY_POLICY$6;
|
|
387
455
|
}
|
|
388
456
|
imageRequestPricing(_provider, _model) {}
|
|
389
457
|
async listModels() {
|
|
@@ -2630,7 +2698,7 @@ async function oauthErrorIdentifier(response) {
|
|
|
2630
2698
|
function codexPoolPath() {
|
|
2631
2699
|
return path.join(dshHomeDir(), "storages", "codex-pool.json");
|
|
2632
2700
|
}
|
|
2633
|
-
function optionalString$
|
|
2701
|
+
function optionalString$9(record, key) {
|
|
2634
2702
|
const value = record[key];
|
|
2635
2703
|
if (value === void 0 || value === null) return void 0;
|
|
2636
2704
|
if (typeof value !== "string") throw new Error("ChatGPT pool account field is invalid");
|
|
@@ -2653,11 +2721,11 @@ function parseCodexPoolData(value) {
|
|
|
2653
2721
|
addedAt: typeof raw.addedAt === "number" ? raw.addedAt : Date.now(),
|
|
2654
2722
|
isPrimary: raw.isPrimary === true
|
|
2655
2723
|
};
|
|
2656
|
-
const email = optionalString$
|
|
2724
|
+
const email = optionalString$9(raw, "email") ?? credentials.email;
|
|
2657
2725
|
if (email !== void 0) account.email = email;
|
|
2658
|
-
const planType = optionalString$
|
|
2726
|
+
const planType = optionalString$9(raw, "planType") ?? credentials.planType;
|
|
2659
2727
|
if (planType !== void 0) account.planType = planType;
|
|
2660
|
-
const accountId = optionalString$
|
|
2728
|
+
const accountId = optionalString$9(raw, "accountId") ?? credentials.accountId;
|
|
2661
2729
|
if (accountId !== void 0) account.accountId = accountId;
|
|
2662
2730
|
if (typeof raw.lastUsedAt === "number") account.lastUsedAt = raw.lastUsedAt;
|
|
2663
2731
|
if (typeof raw.cooldownUntil === "number") account.cooldownUntil = raw.cooldownUntil;
|
|
@@ -3414,8 +3482,7 @@ function sendSearch(fetchFn, credentials, query, model, id, signal) {
|
|
|
3414
3482
|
settings: {
|
|
3415
3483
|
allowed_callers: ["direct"],
|
|
3416
3484
|
external_web_access: true
|
|
3417
|
-
}
|
|
3418
|
-
max_output_tokens: 4096
|
|
3485
|
+
}
|
|
3419
3486
|
}),
|
|
3420
3487
|
signal
|
|
3421
3488
|
});
|
|
@@ -3678,8 +3745,8 @@ var ProxyManager = class {
|
|
|
3678
3745
|
};
|
|
3679
3746
|
//#endregion
|
|
3680
3747
|
//#region src/host/antigravity/types.ts
|
|
3681
|
-
const PROVIDER_NAME$
|
|
3682
|
-
const PROVIDER_ID$
|
|
3748
|
+
const PROVIDER_NAME$6 = "Antigravity";
|
|
3749
|
+
const PROVIDER_ID$6 = "antigravity";
|
|
3683
3750
|
const STREAM_IDLE_TIMEOUT_MS$5 = 3e5;
|
|
3684
3751
|
const STREAM_IDLE_TIMEOUT_CODE$6 = "LLM_STREAM_IDLE_TIMEOUT";
|
|
3685
3752
|
const DISCOVERY_TIMEOUT_MS$4 = 8e3;
|
|
@@ -4143,13 +4210,13 @@ function parseAntigravityCredentials(value) {
|
|
|
4143
4210
|
if (!(credentials.access || credentials.access_token || credentials.refresh || credentials.refresh_token)) throw new Error("Antigravity credential tokens are missing");
|
|
4144
4211
|
return credentials;
|
|
4145
4212
|
}
|
|
4146
|
-
function credentialAccount$
|
|
4213
|
+
function credentialAccount$4(filePath) {
|
|
4147
4214
|
return createHash("sha256").update(path.resolve(filePath)).digest("hex");
|
|
4148
4215
|
}
|
|
4149
|
-
function createCredentialBackend$
|
|
4216
|
+
function createCredentialBackend$4(filePath) {
|
|
4150
4217
|
if (process.platform === "win32") return new WindowsDpapiCredentialStore(`${filePath}.dpapi`, parseAntigravityCredentials);
|
|
4151
|
-
if (process.platform === "darwin") return new MacKeychainCredentialStore("dsh-antigravity", credentialAccount$
|
|
4152
|
-
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);
|
|
4153
4220
|
throw new Error("Antigravity encrypted credential storage requires Windows, macOS, or Linux.");
|
|
4154
4221
|
}
|
|
4155
4222
|
const credentialOperations$3 = /* @__PURE__ */ new Map();
|
|
@@ -4157,13 +4224,13 @@ const credentialOperations$3 = /* @__PURE__ */ new Map();
|
|
|
4157
4224
|
var FileCredentialStore$2 = class {
|
|
4158
4225
|
filePath;
|
|
4159
4226
|
backend;
|
|
4160
|
-
constructor(filePath = credentialPath$2(), backend = createCredentialBackend$
|
|
4227
|
+
constructor(filePath = credentialPath$2(), backend = createCredentialBackend$4(filePath)) {
|
|
4161
4228
|
this.filePath = filePath;
|
|
4162
4229
|
this.backend = backend;
|
|
4163
4230
|
}
|
|
4164
4231
|
path() {
|
|
4165
4232
|
if (process.platform === "win32") return `${this.filePath}.dpapi`;
|
|
4166
|
-
return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: dsh-antigravity/${credentialAccount$
|
|
4233
|
+
return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: dsh-antigravity/${credentialAccount$4(this.filePath)}`;
|
|
4167
4234
|
}
|
|
4168
4235
|
serialize(operation) {
|
|
4169
4236
|
const key = path.resolve(this.filePath);
|
|
@@ -4877,15 +4944,15 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
|
|
|
4877
4944
|
payload.tool_choice = "auto";
|
|
4878
4945
|
payload.parallel_tool_calls = true;
|
|
4879
4946
|
}
|
|
4880
|
-
|
|
4947
|
+
const providerVerbosity = codexDefaultOutputVerbosity(options.model);
|
|
4948
|
+
if (providerVerbosity !== void 0) payload.text = { verbosity: outputVerbosity ?? providerVerbosity };
|
|
4881
4949
|
if (fastMode) payload.service_tier = "priority";
|
|
4882
|
-
const modelMaxTokens = codexModelMaxTokens(options.model);
|
|
4883
|
-
payload.max_output_tokens = options.maxTokens === void 0 ? modelMaxTokens : Math.min(options.maxTokens, modelMaxTokens);
|
|
4884
4950
|
if (options.reasoningEffort !== void 0) {
|
|
4885
4951
|
const effort = codexWireReasoningEffort(options.model, options.reasoningEffort);
|
|
4886
|
-
|
|
4952
|
+
const summary = reasoningSummary === null || reasoningSummary === "none" ? void 0 : reasoningSummary;
|
|
4953
|
+
payload.reasoning = summary !== void 0 && codexModelSupportsReasoningSummary(options.model) ? {
|
|
4887
4954
|
effort,
|
|
4888
|
-
summary
|
|
4955
|
+
summary
|
|
4889
4956
|
} : { effort };
|
|
4890
4957
|
}
|
|
4891
4958
|
return payload;
|
|
@@ -6408,13 +6475,13 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
6408
6475
|
}
|
|
6409
6476
|
try {
|
|
6410
6477
|
switch (url.pathname) {
|
|
6411
|
-
case `${ROUTE_PREFIX$
|
|
6478
|
+
case `${ROUTE_PREFIX$5}/login/start`:
|
|
6412
6479
|
json(response, {
|
|
6413
6480
|
ok: true,
|
|
6414
6481
|
value: await oauth.startLogin()
|
|
6415
6482
|
});
|
|
6416
6483
|
return;
|
|
6417
|
-
case `${ROUTE_PREFIX$
|
|
6484
|
+
case `${ROUTE_PREFIX$5}/login/cancel`: {
|
|
6418
6485
|
const loginId = field(body, "loginId");
|
|
6419
6486
|
if (loginId === null) throw new Error("missing loginId");
|
|
6420
6487
|
oauth.cancelLogin(loginId);
|
|
@@ -6424,7 +6491,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
6424
6491
|
});
|
|
6425
6492
|
return;
|
|
6426
6493
|
}
|
|
6427
|
-
case `${ROUTE_PREFIX$
|
|
6494
|
+
case `${ROUTE_PREFIX$5}/accounts`: {
|
|
6428
6495
|
if (accountPool === void 0) throw new Error("The ChatGPT account pool is not installed.");
|
|
6429
6496
|
const action = field(body, "action");
|
|
6430
6497
|
const accountId = field(body, "accountId");
|
|
@@ -6446,7 +6513,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
6446
6513
|
});
|
|
6447
6514
|
return;
|
|
6448
6515
|
}
|
|
6449
|
-
case `${ROUTE_PREFIX$
|
|
6516
|
+
case `${ROUTE_PREFIX$5}/logout`:
|
|
6450
6517
|
await oauth.logout(field(body, "accountId") ?? void 0);
|
|
6451
6518
|
usage.clear();
|
|
6452
6519
|
json(response, {
|
|
@@ -6454,7 +6521,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
6454
6521
|
value: { authenticated: false }
|
|
6455
6522
|
});
|
|
6456
6523
|
return;
|
|
6457
|
-
case `${ROUTE_PREFIX$
|
|
6524
|
+
case `${ROUTE_PREFIX$5}/token/refresh`: {
|
|
6458
6525
|
const oauthStatus = await oauth.refresh();
|
|
6459
6526
|
json(response, {
|
|
6460
6527
|
ok: true,
|
|
@@ -6466,27 +6533,27 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
6466
6533
|
});
|
|
6467
6534
|
return;
|
|
6468
6535
|
}
|
|
6469
|
-
case `${ROUTE_PREFIX$
|
|
6536
|
+
case `${ROUTE_PREFIX$5}/quota/refresh`:
|
|
6470
6537
|
if (!(await oauth.status()).authenticated) throw new Error("not authenticated");
|
|
6471
6538
|
json(response, {
|
|
6472
6539
|
ok: true,
|
|
6473
6540
|
value: await usage.status(true, true)
|
|
6474
6541
|
});
|
|
6475
6542
|
return;
|
|
6476
|
-
case `${ROUTE_PREFIX$
|
|
6543
|
+
case `${ROUTE_PREFIX$5}/quota/reset-credit/use`:
|
|
6477
6544
|
if (!(await oauth.status()).authenticated) throw new Error("not authenticated");
|
|
6478
6545
|
json(response, {
|
|
6479
6546
|
ok: true,
|
|
6480
6547
|
value: await usage.consumeResetCredit()
|
|
6481
6548
|
});
|
|
6482
6549
|
return;
|
|
6483
|
-
case `${ROUTE_PREFIX$
|
|
6550
|
+
case `${ROUTE_PREFIX$5}/connection/test`:
|
|
6484
6551
|
json(response, {
|
|
6485
6552
|
ok: true,
|
|
6486
6553
|
value: await usage.testConnection()
|
|
6487
6554
|
});
|
|
6488
6555
|
return;
|
|
6489
|
-
case `${ROUTE_PREFIX$
|
|
6556
|
+
case `${ROUTE_PREFIX$5}/preferences/update`: {
|
|
6490
6557
|
const patch = readPreferencesUpdate(body, preferences.status());
|
|
6491
6558
|
const value = await preferences.update(patch);
|
|
6492
6559
|
if (patch.visibleModelIds !== void 0 || patch.enabled !== void 0 || patch.contextWindowOverrides !== void 0) ctx.emit?.("llm/adapters-updated");
|
|
@@ -6563,11 +6630,11 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
6563
6630
|
};
|
|
6564
6631
|
const disposers = [ctx.webServer.register({
|
|
6565
6632
|
kind: "prefix",
|
|
6566
|
-
path: ROUTE_PREFIX$
|
|
6633
|
+
path: ROUTE_PREFIX$5,
|
|
6567
6634
|
handler
|
|
6568
6635
|
}), ctx.webServer.register({
|
|
6569
6636
|
kind: "exact",
|
|
6570
|
-
path: `${ROUTE_PREFIX$
|
|
6637
|
+
path: `${ROUTE_PREFIX$5}/login/events`,
|
|
6571
6638
|
handler: events
|
|
6572
6639
|
})];
|
|
6573
6640
|
return () => {
|
|
@@ -6675,6 +6742,27 @@ function statusFor(error) {
|
|
|
6675
6742
|
}
|
|
6676
6743
|
//#endregion
|
|
6677
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
|
+
}
|
|
6678
6766
|
var SearchProviderSwitcher = class {
|
|
6679
6767
|
loader;
|
|
6680
6768
|
originalSearchProvider;
|
|
@@ -6733,15 +6821,40 @@ var SearchProviderSwitcher = class {
|
|
|
6733
6821
|
else nextConfig.fetchProvider = nextFetch;
|
|
6734
6822
|
this.state = "applying";
|
|
6735
6823
|
try {
|
|
6736
|
-
if (configured && entry.fiber)
|
|
6824
|
+
if (configured && entry.fiber) entry.fiber.update(nextConfig, true);
|
|
6737
6825
|
else await entry.update({ config: nextConfig });
|
|
6738
|
-
await
|
|
6826
|
+
await this.awaitSettled(entry);
|
|
6739
6827
|
this.state = "applied";
|
|
6740
6828
|
} catch (error) {
|
|
6741
6829
|
this.state = "failed";
|
|
6742
6830
|
throw error;
|
|
6743
6831
|
}
|
|
6744
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
|
+
}
|
|
6745
6858
|
findWebEntry() {
|
|
6746
6859
|
for (const entry of this.loader.entries()) if (entry.options.id === "web" || entry.options.name === "@deepseek-ai/dsh-web") return entry;
|
|
6747
6860
|
return null;
|
|
@@ -7971,7 +8084,7 @@ function buildRequest$2(options, model, projectId, runtimeModel, effort, images
|
|
|
7971
8084
|
requestId: `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
7972
8085
|
};
|
|
7973
8086
|
}
|
|
7974
|
-
function createStreamState$
|
|
8087
|
+
function createStreamState$6() {
|
|
7975
8088
|
return {
|
|
7976
8089
|
blocks: [],
|
|
7977
8090
|
replayBlocks: [],
|
|
@@ -8032,7 +8145,7 @@ function processStreamLine$2(line, state) {
|
|
|
8032
8145
|
const json = line.slice(5).trim();
|
|
8033
8146
|
if (json === "[DONE]") {
|
|
8034
8147
|
state.done = true;
|
|
8035
|
-
return closeStream$
|
|
8148
|
+
return closeStream$5(state);
|
|
8036
8149
|
}
|
|
8037
8150
|
if (!json) return [];
|
|
8038
8151
|
const chunk = safeJsonParse$5(json);
|
|
@@ -8149,7 +8262,7 @@ function processStreamLine$2(line, state) {
|
|
|
8149
8262
|
}
|
|
8150
8263
|
return out;
|
|
8151
8264
|
}
|
|
8152
|
-
function closeStream$
|
|
8265
|
+
function closeStream$5(state) {
|
|
8153
8266
|
if (state.finished) return [];
|
|
8154
8267
|
if (!state.finishReason && !state.done) throw new LlmError$1("Antigravity stream ended before its terminal response", "PROVIDER_ERROR");
|
|
8155
8268
|
state.finished = true;
|
|
@@ -8163,7 +8276,7 @@ function closeStream$4(state) {
|
|
|
8163
8276
|
type: "finish",
|
|
8164
8277
|
reason,
|
|
8165
8278
|
replayState: {
|
|
8166
|
-
response: { provider: PROVIDER_ID$
|
|
8279
|
+
response: { provider: PROVIDER_ID$6 },
|
|
8167
8280
|
blocks: state.replayBlocks
|
|
8168
8281
|
}
|
|
8169
8282
|
});
|
|
@@ -8371,7 +8484,7 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
8371
8484
|
providerInfo(provider) {
|
|
8372
8485
|
return {
|
|
8373
8486
|
id: provider,
|
|
8374
|
-
name: PROVIDER_NAME$
|
|
8487
|
+
name: PROVIDER_NAME$6
|
|
8375
8488
|
};
|
|
8376
8489
|
}
|
|
8377
8490
|
providerRetryPolicy() {}
|
|
@@ -8389,7 +8502,7 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
8389
8502
|
name: model.name,
|
|
8390
8503
|
inputModalities: model.inputModalities,
|
|
8391
8504
|
context: { contextWindow: overrides[model.id] || model.contextWindow },
|
|
8392
|
-
|
|
8505
|
+
...outputReservation(overrides[model.id] || model.contextWindow, model.maxTokens),
|
|
8393
8506
|
...model.reasoningEfforts ? { reasoningEfforts: model.reasoningEfforts } : {}
|
|
8394
8507
|
}));
|
|
8395
8508
|
}
|
|
@@ -8416,7 +8529,7 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
8416
8529
|
name: model.name,
|
|
8417
8530
|
inputModalities: model.inputModalities,
|
|
8418
8531
|
context: { contextWindow: overrides[model.id] || model.contextWindow },
|
|
8419
|
-
|
|
8532
|
+
...outputReservation(overrides[model.id] || model.contextWindow, model.maxTokens),
|
|
8420
8533
|
...model.reasoningEfforts ? { reasoning: {
|
|
8421
8534
|
efforts: efforts.map((effort) => ({
|
|
8422
8535
|
id: ReasoningEffortId(effort),
|
|
@@ -8446,7 +8559,10 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
8446
8559
|
...options,
|
|
8447
8560
|
reasoningEffort: effectiveEffort
|
|
8448
8561
|
} : options;
|
|
8449
|
-
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");
|
|
8450
8566
|
}
|
|
8451
8567
|
async *requestStream(options, model, signal) {
|
|
8452
8568
|
const fetchFn = this.options.fetchFn ?? fetch;
|
|
@@ -8519,7 +8635,7 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
8519
8635
|
if (!response.body) throw new LlmError("Antigravity returned empty response body", "PROVIDER_ERROR");
|
|
8520
8636
|
const reader = response.body.getReader();
|
|
8521
8637
|
const decoder = new TextDecoder();
|
|
8522
|
-
const state = createStreamState$
|
|
8638
|
+
const state = createStreamState$6();
|
|
8523
8639
|
let buffer = "";
|
|
8524
8640
|
try {
|
|
8525
8641
|
while (true) {
|
|
@@ -8541,7 +8657,7 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
8541
8657
|
const chunks = processStreamLine$2(buffer.trim(), state);
|
|
8542
8658
|
for (const chunk of chunks) yield chunk;
|
|
8543
8659
|
}
|
|
8544
|
-
for (const chunk of closeStream$
|
|
8660
|
+
for (const chunk of closeStream$5(state)) yield chunk;
|
|
8545
8661
|
} finally {
|
|
8546
8662
|
reader.cancel().catch(() => void 0);
|
|
8547
8663
|
}
|
|
@@ -8606,17 +8722,17 @@ var QuotaRefresh = class {
|
|
|
8606
8722
|
};
|
|
8607
8723
|
//#endregion
|
|
8608
8724
|
//#region src/host/antigravity/routes.ts
|
|
8609
|
-
function sendJson$
|
|
8725
|
+
function sendJson$8(response, status, body) {
|
|
8610
8726
|
response.writeHead(status, { "Content-Type": "application/json" });
|
|
8611
8727
|
response.end(JSON.stringify(body));
|
|
8612
8728
|
}
|
|
8613
|
-
function sendMethodNotAllowed$
|
|
8614
|
-
sendJson$
|
|
8729
|
+
function sendMethodNotAllowed$6(response) {
|
|
8730
|
+
sendJson$8(response, 405, {
|
|
8615
8731
|
ok: false,
|
|
8616
8732
|
error: "Method Not Allowed"
|
|
8617
8733
|
});
|
|
8618
8734
|
}
|
|
8619
|
-
async function readRequestJson$
|
|
8735
|
+
async function readRequestJson$7(request) {
|
|
8620
8736
|
return new Promise((resolve, reject) => {
|
|
8621
8737
|
let raw = "";
|
|
8622
8738
|
request.on("data", (chunk) => {
|
|
@@ -8676,7 +8792,7 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8676
8792
|
const path = new URL(request.url || "/", "http://dsh.local").pathname.replace(/^\/antigravity\/api\/?/, "");
|
|
8677
8793
|
try {
|
|
8678
8794
|
if (path === "status" || path === "") {
|
|
8679
|
-
if (request.method !== "GET") return sendMethodNotAllowed$
|
|
8795
|
+
if (request.method !== "GET") return sendMethodNotAllowed$6(response);
|
|
8680
8796
|
const credentials = await store.read();
|
|
8681
8797
|
const authenticated = !!(credentials?.access || credentials?.access_token);
|
|
8682
8798
|
const cached = getCachedQuota();
|
|
@@ -8685,7 +8801,7 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8685
8801
|
if (cached === void 0) await quotaRefresh.run(refresh);
|
|
8686
8802
|
else quotaRefresh.start(refresh);
|
|
8687
8803
|
}
|
|
8688
|
-
return sendJson$
|
|
8804
|
+
return sendJson$8(response, 200, {
|
|
8689
8805
|
ok: true,
|
|
8690
8806
|
value: {
|
|
8691
8807
|
...await getAntigravityWebStatus(store, modelSettings, preferences, accountPool),
|
|
@@ -8694,8 +8810,8 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8694
8810
|
});
|
|
8695
8811
|
}
|
|
8696
8812
|
if (path === "login" || path === "accounts/login") {
|
|
8697
|
-
if (request.method !== "POST") return sendMethodNotAllowed$
|
|
8698
|
-
return sendJson$
|
|
8813
|
+
if (request.method !== "POST") return sendMethodNotAllowed$6(response);
|
|
8814
|
+
return sendJson$8(response, 200, {
|
|
8699
8815
|
ok: true,
|
|
8700
8816
|
value: await beginWebLogin$2(store, fetchFn, void 0, async (creds) => {
|
|
8701
8817
|
await accountPool.addAccount(creds);
|
|
@@ -8703,19 +8819,19 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8703
8819
|
});
|
|
8704
8820
|
}
|
|
8705
8821
|
if (path === "login/status") {
|
|
8706
|
-
if (request.method !== "GET") return sendMethodNotAllowed$
|
|
8707
|
-
return sendJson$
|
|
8822
|
+
if (request.method !== "GET") return sendMethodNotAllowed$6(response);
|
|
8823
|
+
return sendJson$8(response, 200, {
|
|
8708
8824
|
ok: true,
|
|
8709
8825
|
value: getWebLoginStatus$3()
|
|
8710
8826
|
});
|
|
8711
8827
|
}
|
|
8712
8828
|
if (path === "accounts") {
|
|
8713
|
-
if (request.method === "GET") return sendJson$
|
|
8829
|
+
if (request.method === "GET") return sendJson$8(response, 200, {
|
|
8714
8830
|
ok: true,
|
|
8715
8831
|
value: await accountPool.listAccounts()
|
|
8716
8832
|
});
|
|
8717
8833
|
if (request.method === "POST") {
|
|
8718
|
-
const body = await readRequestJson$
|
|
8834
|
+
const body = await readRequestJson$7(request);
|
|
8719
8835
|
const action = String(body.action || "");
|
|
8720
8836
|
if (action === "set-primary" && typeof body.accountId === "string") await accountPool.setPrimary(body.accountId);
|
|
8721
8837
|
else if (action === "set-alias" && typeof body.accountId === "string" && typeof body.alias === "string") await accountPool.setAlias(body.accountId, body.alias);
|
|
@@ -8723,17 +8839,17 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8723
8839
|
else if (action === "strategy" && (body.strategy === "sequential" || body.strategy === "round-robin" || body.strategy === "sticky")) await accountPool.setStrategy(body.strategy);
|
|
8724
8840
|
else if (action === "clear-cooldown" && typeof body.accountId === "string") await accountPool.clearCooldown(body.accountId);
|
|
8725
8841
|
clearCachedQuota();
|
|
8726
|
-
return sendJson$
|
|
8842
|
+
return sendJson$8(response, 200, {
|
|
8727
8843
|
ok: true,
|
|
8728
8844
|
value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
|
|
8729
8845
|
});
|
|
8730
8846
|
}
|
|
8731
|
-
return sendMethodNotAllowed$
|
|
8847
|
+
return sendMethodNotAllowed$6(response);
|
|
8732
8848
|
}
|
|
8733
8849
|
if (path === "quota") {
|
|
8734
|
-
if (request.method !== "GET" && request.method !== "POST") return sendMethodNotAllowed$
|
|
8850
|
+
if (request.method !== "GET" && request.method !== "POST") return sendMethodNotAllowed$6(response);
|
|
8735
8851
|
const quota = await fetchAccountQuota(store, modelSettings, fetchFn, true);
|
|
8736
|
-
return sendJson$
|
|
8852
|
+
return sendJson$8(response, 200, {
|
|
8737
8853
|
ok: true,
|
|
8738
8854
|
value: {
|
|
8739
8855
|
...await getAntigravityWebStatus(store, modelSettings, preferences, accountPool),
|
|
@@ -8742,38 +8858,38 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8742
8858
|
});
|
|
8743
8859
|
}
|
|
8744
8860
|
if (path === "settings") {
|
|
8745
|
-
if (request.method !== "POST") return sendMethodNotAllowed$
|
|
8746
|
-
const body = await readRequestJson$
|
|
8861
|
+
if (request.method !== "POST") return sendMethodNotAllowed$6(response);
|
|
8862
|
+
const body = await readRequestJson$7(request);
|
|
8747
8863
|
if (preferences) await preferences.update(body);
|
|
8748
8864
|
else await modelSettings.updateSettings(body);
|
|
8749
8865
|
if (body.enabledModelIds !== void 0 || body.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
|
|
8750
|
-
return sendJson$
|
|
8866
|
+
return sendJson$8(response, 200, {
|
|
8751
8867
|
ok: true,
|
|
8752
8868
|
value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
|
|
8753
8869
|
});
|
|
8754
8870
|
}
|
|
8755
8871
|
if (path === "models") {
|
|
8756
|
-
if (request.method === "GET") return sendJson$
|
|
8872
|
+
if (request.method === "GET") return sendJson$8(response, 200, {
|
|
8757
8873
|
ok: true,
|
|
8758
8874
|
value: (await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)).models
|
|
8759
8875
|
});
|
|
8760
8876
|
if (request.method === "POST") {
|
|
8761
|
-
const body = await readRequestJson$
|
|
8877
|
+
const body = await readRequestJson$7(request);
|
|
8762
8878
|
if (Array.isArray(body.enabledModelIds) || body.enabled !== void 0 || body.contextWindowOverrides || body.defaultReasoningEffort !== void 0) {
|
|
8763
8879
|
if (preferences) await preferences.update(body);
|
|
8764
8880
|
else await modelSettings.updateSettings(body);
|
|
8765
8881
|
if (body.enabledModelIds !== void 0 || body.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
|
|
8766
8882
|
}
|
|
8767
|
-
return sendJson$
|
|
8883
|
+
return sendJson$8(response, 200, {
|
|
8768
8884
|
ok: true,
|
|
8769
8885
|
value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
|
|
8770
8886
|
});
|
|
8771
8887
|
}
|
|
8772
|
-
return sendMethodNotAllowed$
|
|
8888
|
+
return sendMethodNotAllowed$6(response);
|
|
8773
8889
|
}
|
|
8774
8890
|
if (path === "logout") {
|
|
8775
|
-
if (request.method !== "POST") return sendMethodNotAllowed$
|
|
8776
|
-
const body = await readRequestJson$
|
|
8891
|
+
if (request.method !== "POST") return sendMethodNotAllowed$6(response);
|
|
8892
|
+
const body = await readRequestJson$7(request).catch(() => ({}));
|
|
8777
8893
|
const targetId = typeof body.accountId === "string" ? body.accountId : void 0;
|
|
8778
8894
|
if (targetId) await accountPool.deleteAccount(targetId);
|
|
8779
8895
|
else {
|
|
@@ -8782,17 +8898,17 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8782
8898
|
else await store.delete();
|
|
8783
8899
|
}
|
|
8784
8900
|
clearCachedQuota();
|
|
8785
|
-
return sendJson$
|
|
8901
|
+
return sendJson$8(response, 200, {
|
|
8786
8902
|
ok: true,
|
|
8787
8903
|
value: await getAntigravityWebStatus(store, modelSettings, preferences, accountPool)
|
|
8788
8904
|
});
|
|
8789
8905
|
}
|
|
8790
|
-
return sendJson$
|
|
8906
|
+
return sendJson$8(response, 404, {
|
|
8791
8907
|
ok: false,
|
|
8792
8908
|
error: "not-found"
|
|
8793
8909
|
});
|
|
8794
8910
|
} catch (err) {
|
|
8795
|
-
return sendJson$
|
|
8911
|
+
return sendJson$8(response, 500, {
|
|
8796
8912
|
ok: false,
|
|
8797
8913
|
error: err instanceof Error ? err.message : String(err)
|
|
8798
8914
|
});
|
|
@@ -8804,6 +8920,20 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
8804
8920
|
//#region src/host/command-code/model-catalog.ts
|
|
8805
8921
|
/** Every model the Command Code registry describes, in registry order. */
|
|
8806
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
|
+
},
|
|
8807
8937
|
{
|
|
8808
8938
|
id: "claude-sonnet-5",
|
|
8809
8939
|
name: "Claude Sonnet 5",
|
|
@@ -8860,6 +8990,20 @@ const COMMAND_CODE_MODELS = [
|
|
|
8860
8990
|
contextWindow: 1e6,
|
|
8861
8991
|
maxTokens: null
|
|
8862
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
|
+
},
|
|
8863
9007
|
{
|
|
8864
9008
|
id: "claude-opus-5",
|
|
8865
9009
|
name: "Claude Opus 5",
|
|
@@ -8924,6 +9068,48 @@ const COMMAND_CODE_MODELS = [
|
|
|
8924
9068
|
contextWindow: 105e4,
|
|
8925
9069
|
maxTokens: null
|
|
8926
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
|
+
},
|
|
8927
9113
|
{
|
|
8928
9114
|
id: "gpt-5.6-sol",
|
|
8929
9115
|
name: "GPT-5.6 Sol",
|
|
@@ -9021,7 +9207,11 @@ const COMMAND_CODE_MODELS = [
|
|
|
9021
9207
|
id: "deepseek/deepseek-v4-pro",
|
|
9022
9208
|
name: "DeepSeek V4 Pro (latest)",
|
|
9023
9209
|
inputModalities: ["text"],
|
|
9024
|
-
reasoningEfforts: [
|
|
9210
|
+
reasoningEfforts: [
|
|
9211
|
+
"off",
|
|
9212
|
+
"high",
|
|
9213
|
+
"max"
|
|
9214
|
+
],
|
|
9025
9215
|
contextWindow: 1e6,
|
|
9026
9216
|
maxTokens: null
|
|
9027
9217
|
},
|
|
@@ -9029,7 +9219,11 @@ const COMMAND_CODE_MODELS = [
|
|
|
9029
9219
|
id: "deepseek/deepseek-v4-flash",
|
|
9030
9220
|
name: "DeepSeek V4 Flash (latest)",
|
|
9031
9221
|
inputModalities: ["text"],
|
|
9032
|
-
reasoningEfforts: [
|
|
9222
|
+
reasoningEfforts: [
|
|
9223
|
+
"off",
|
|
9224
|
+
"high",
|
|
9225
|
+
"max"
|
|
9226
|
+
],
|
|
9033
9227
|
contextWindow: 1e6,
|
|
9034
9228
|
maxTokens: null
|
|
9035
9229
|
},
|
|
@@ -9037,7 +9231,11 @@ const COMMAND_CODE_MODELS = [
|
|
|
9037
9231
|
id: "deepseek/deepseek-v4-flash-vision-exp",
|
|
9038
9232
|
name: "DeepSeek V4 Flash Vision (exp)",
|
|
9039
9233
|
inputModalities: ["text", "image"],
|
|
9040
|
-
reasoningEfforts: [
|
|
9234
|
+
reasoningEfforts: [
|
|
9235
|
+
"off",
|
|
9236
|
+
"high",
|
|
9237
|
+
"max"
|
|
9238
|
+
],
|
|
9041
9239
|
contextWindow: 1e6,
|
|
9042
9240
|
maxTokens: null
|
|
9043
9241
|
},
|
|
@@ -9058,6 +9256,20 @@ const COMMAND_CODE_MODELS = [
|
|
|
9058
9256
|
name: "DeepSeek V4.1 Flash",
|
|
9059
9257
|
inputModalities: ["text", "image"],
|
|
9060
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",
|
|
9061
9273
|
"low",
|
|
9062
9274
|
"high",
|
|
9063
9275
|
"max"
|
|
@@ -9121,6 +9333,18 @@ const COMMAND_CODE_MODELS = [
|
|
|
9121
9333
|
contextWindow: 1048576,
|
|
9122
9334
|
maxTokens: 131072
|
|
9123
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
|
+
},
|
|
9124
9348
|
{
|
|
9125
9349
|
id: "zai-org/GLM-5.3",
|
|
9126
9350
|
name: "GLM-5.3",
|
|
@@ -9154,7 +9378,7 @@ const COMMAND_CODE_MODELS = [
|
|
|
9154
9378
|
name: "GLM-5.1",
|
|
9155
9379
|
inputModalities: ["text"],
|
|
9156
9380
|
reasoningEfforts: [],
|
|
9157
|
-
contextWindow:
|
|
9381
|
+
contextWindow: 2e5,
|
|
9158
9382
|
maxTokens: null
|
|
9159
9383
|
},
|
|
9160
9384
|
{
|
|
@@ -9182,35 +9406,39 @@ const COMMAND_CODE_MODELS = [
|
|
|
9182
9406
|
name: "MiniMax M2.7",
|
|
9183
9407
|
inputModalities: ["text"],
|
|
9184
9408
|
reasoningEfforts: [],
|
|
9185
|
-
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,
|
|
9186
9418
|
maxTokens: null
|
|
9187
9419
|
},
|
|
9188
9420
|
{
|
|
9189
|
-
id: "
|
|
9190
|
-
name: "
|
|
9421
|
+
id: "xiaomi/mimo-v2.6-pro",
|
|
9422
|
+
name: "MiMo V2.6 Pro",
|
|
9191
9423
|
inputModalities: ["text", "image"],
|
|
9192
|
-
reasoningEfforts: [
|
|
9193
|
-
|
|
9194
|
-
"medium",
|
|
9195
|
-
"high"
|
|
9196
|
-
],
|
|
9197
|
-
contextWindow: 1e6,
|
|
9424
|
+
reasoningEfforts: [],
|
|
9425
|
+
contextWindow: 1048576,
|
|
9198
9426
|
maxTokens: null
|
|
9199
9427
|
},
|
|
9200
9428
|
{
|
|
9201
|
-
id: "
|
|
9202
|
-
name: "
|
|
9203
|
-
inputModalities: ["text"],
|
|
9429
|
+
id: "xiaomi/mimo-v2.6-pro-ultraspeed",
|
|
9430
|
+
name: "MiMo V2.6 Pro UltraSpeed",
|
|
9431
|
+
inputModalities: ["text", "image"],
|
|
9204
9432
|
reasoningEfforts: [],
|
|
9205
|
-
contextWindow:
|
|
9433
|
+
contextWindow: 1048576,
|
|
9206
9434
|
maxTokens: null
|
|
9207
9435
|
},
|
|
9208
9436
|
{
|
|
9209
|
-
id: "
|
|
9210
|
-
name: "
|
|
9211
|
-
inputModalities: ["text"],
|
|
9437
|
+
id: "xiaomi/mimo-v2.6-flash",
|
|
9438
|
+
name: "MiMo V2.6 Flash",
|
|
9439
|
+
inputModalities: ["text", "image"],
|
|
9212
9440
|
reasoningEfforts: [],
|
|
9213
|
-
contextWindow:
|
|
9441
|
+
contextWindow: 1048576,
|
|
9214
9442
|
maxTokens: null
|
|
9215
9443
|
},
|
|
9216
9444
|
{
|
|
@@ -9229,6 +9457,18 @@ const COMMAND_CODE_MODELS = [
|
|
|
9229
9457
|
contextWindow: 1e6,
|
|
9230
9458
|
maxTokens: null
|
|
9231
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
|
+
},
|
|
9232
9472
|
{
|
|
9233
9473
|
id: "Qwen/Qwen3.8-Max-0902",
|
|
9234
9474
|
name: "Qwen 3.8 Max 0902",
|
|
@@ -9306,7 +9546,7 @@ const COMMAND_CODE_MODELS = [
|
|
|
9306
9546
|
name: "Qwen 3.6 Max Preview",
|
|
9307
9547
|
inputModalities: ["text"],
|
|
9308
9548
|
reasoningEfforts: [],
|
|
9309
|
-
contextWindow:
|
|
9549
|
+
contextWindow: 2e5,
|
|
9310
9550
|
maxTokens: null
|
|
9311
9551
|
},
|
|
9312
9552
|
{
|
|
@@ -9314,17 +9554,29 @@ const COMMAND_CODE_MODELS = [
|
|
|
9314
9554
|
name: "Qwen 3.6 Plus",
|
|
9315
9555
|
inputModalities: ["text", "image"],
|
|
9316
9556
|
reasoningEfforts: [],
|
|
9317
|
-
contextWindow:
|
|
9557
|
+
contextWindow: 2e5,
|
|
9318
9558
|
maxTokens: null
|
|
9319
9559
|
},
|
|
9320
9560
|
{
|
|
9321
|
-
id: "meituan/LongCat-2.0
|
|
9561
|
+
id: "meituan/LongCat-2.0",
|
|
9322
9562
|
name: "LongCat 2.0",
|
|
9323
9563
|
inputModalities: ["text"],
|
|
9324
9564
|
reasoningEfforts: [],
|
|
9325
9565
|
contextWindow: 1048576,
|
|
9326
9566
|
maxTokens: null
|
|
9327
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
|
+
},
|
|
9328
9580
|
{
|
|
9329
9581
|
id: "stepfun/Step-3.7-Flash",
|
|
9330
9582
|
name: "Step 3.7 Flash",
|
|
@@ -9338,14 +9590,6 @@ const COMMAND_CODE_MODELS = [
|
|
|
9338
9590
|
name: "Step 3.5 Flash",
|
|
9339
9591
|
inputModalities: ["text"],
|
|
9340
9592
|
reasoningEfforts: [],
|
|
9341
|
-
contextWindow: 1e6,
|
|
9342
|
-
maxTokens: null
|
|
9343
|
-
},
|
|
9344
|
-
{
|
|
9345
|
-
id: "tencent/Hy3",
|
|
9346
|
-
name: "Tencent Hy3 (Free)",
|
|
9347
|
-
inputModalities: ["text"],
|
|
9348
|
-
reasoningEfforts: [],
|
|
9349
9593
|
contextWindow: 262144,
|
|
9350
9594
|
maxTokens: null
|
|
9351
9595
|
},
|
|
@@ -9473,6 +9717,19 @@ const COMMAND_CODE_MODELS = [
|
|
|
9473
9717
|
contextWindow: 1e6,
|
|
9474
9718
|
maxTokens: null
|
|
9475
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
|
+
},
|
|
9476
9733
|
{
|
|
9477
9734
|
id: "poolside/laguna-s-2.1-free",
|
|
9478
9735
|
name: "Laguna S 2.1",
|
|
@@ -9482,18 +9739,22 @@ const COMMAND_CODE_MODELS = [
|
|
|
9482
9739
|
maxTokens: 32768
|
|
9483
9740
|
},
|
|
9484
9741
|
{
|
|
9485
|
-
id: "inclusionai/ling-3.0-flash-free",
|
|
9486
|
-
name: "Ling 3.0 Flash",
|
|
9742
|
+
id: "inclusionai/ling-3.0-flash-sante:free",
|
|
9743
|
+
name: "Ling 3.0 Flash Sante",
|
|
9487
9744
|
inputModalities: ["text"],
|
|
9488
9745
|
reasoningEfforts: [],
|
|
9489
|
-
contextWindow:
|
|
9746
|
+
contextWindow: 262144,
|
|
9490
9747
|
maxTokens: 32768
|
|
9491
9748
|
},
|
|
9492
9749
|
{
|
|
9493
|
-
id: "inclusionai/ling-3.
|
|
9494
|
-
name: "Ling 3.
|
|
9750
|
+
id: "inclusionai/ling-3.1-flash:free",
|
|
9751
|
+
name: "Ling 3.1 Flash",
|
|
9495
9752
|
inputModalities: ["text"],
|
|
9496
|
-
reasoningEfforts: [
|
|
9753
|
+
reasoningEfforts: [
|
|
9754
|
+
"low",
|
|
9755
|
+
"medium",
|
|
9756
|
+
"high"
|
|
9757
|
+
],
|
|
9497
9758
|
contextWindow: 262144,
|
|
9498
9759
|
maxTokens: 32768
|
|
9499
9760
|
},
|
|
@@ -9587,6 +9848,19 @@ const COMMAND_CODE_MODELS = [
|
|
|
9587
9848
|
],
|
|
9588
9849
|
contextWindow: 5e5,
|
|
9589
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
|
|
9590
9864
|
}
|
|
9591
9865
|
];
|
|
9592
9866
|
/** Registry entry for one model id, or undefined when it describes no such model. */
|
|
@@ -9595,8 +9869,8 @@ function commandCodeModelDef(modelId) {
|
|
|
9595
9869
|
}
|
|
9596
9870
|
//#endregion
|
|
9597
9871
|
//#region src/host/command-code/types.ts
|
|
9598
|
-
const PROVIDER_ID$
|
|
9599
|
-
const PROVIDER_NAME$
|
|
9872
|
+
const PROVIDER_ID$5 = "command-code";
|
|
9873
|
+
const PROVIDER_NAME$5 = "Command Code";
|
|
9600
9874
|
/** Browser sign-in page the CLI opens; it redirects back to our loopback server. */
|
|
9601
9875
|
const STUDIO_PATH = "/studio/auth/cli";
|
|
9602
9876
|
/** Query parameter that carries the loopback callback URL to the studio page. */
|
|
@@ -9674,11 +9948,11 @@ function providerUrl(env = resolveApiEnv()) {
|
|
|
9674
9948
|
* @param modelId - exact model id from the live catalog or a caller.
|
|
9675
9949
|
* @returns the wire dialect to build the request for.
|
|
9676
9950
|
*/
|
|
9677
|
-
function wireForModel$
|
|
9951
|
+
function wireForModel$2(modelId) {
|
|
9678
9952
|
return /^claude[-/]/i.test(modelId.trim()) ? "anthropic" : "openai";
|
|
9679
9953
|
}
|
|
9680
9954
|
/**
|
|
9681
|
-
* Reasoning levels one model advertises.
|
|
9955
|
+
* Reasoning levels one model advertises, in DSH's vocabulary.
|
|
9682
9956
|
*
|
|
9683
9957
|
* The registry is the authority: a model it describes but gives no
|
|
9684
9958
|
* `reasoningEfforts` is a non-reasoning model, which is why that answers with an
|
|
@@ -9686,9 +9960,42 @@ function wireForModel$1(modelId) {
|
|
|
9686
9960
|
* describe declares nothing either — guessing from the family name is exactly
|
|
9687
9961
|
* how `deepseek-v4-flash` got treated as a reasoner with image input when it is
|
|
9688
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.
|
|
9689
9975
|
*/
|
|
9690
9976
|
function reasoningEffortsFor$1(modelId) {
|
|
9691
|
-
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;
|
|
9692
9999
|
}
|
|
9693
10000
|
/**
|
|
9694
10001
|
* Accepted request modalities for one model.
|
|
@@ -9776,7 +10083,7 @@ function maxOutputTokensFor$4(modelId) {
|
|
|
9776
10083
|
* from the registry rather than hand-listed, so a model cannot drift between the
|
|
9777
10084
|
* offline fallback and its real capability entry.
|
|
9778
10085
|
*/
|
|
9779
|
-
const FALLBACK_MODELS$
|
|
10086
|
+
const FALLBACK_MODELS$4 = COMMAND_CODE_MODELS.filter((model) => model.contextWindow !== null).map((model) => ({
|
|
9780
10087
|
id: model.id,
|
|
9781
10088
|
name: model.name,
|
|
9782
10089
|
contextWindow: model.contextWindow
|
|
@@ -9785,6 +10092,7 @@ const FALLBACK_MODELS$3 = COMMAND_CODE_MODELS.filter((model) => model.contextWin
|
|
|
9785
10092
|
//#region src/shared/command-code-contracts.ts
|
|
9786
10093
|
/** Every level, in escalating order; the settings card renders exactly these. */
|
|
9787
10094
|
const COMMAND_CODE_REASONING_EFFORTS = [
|
|
10095
|
+
"none",
|
|
9788
10096
|
"minimal",
|
|
9789
10097
|
"low",
|
|
9790
10098
|
"medium",
|
|
@@ -9799,7 +10107,7 @@ const COMMAND_CODE_PREFERENCES_NAMESPACE = "dsh-command-code";
|
|
|
9799
10107
|
function isReasoningEffort$3(value) {
|
|
9800
10108
|
return typeof value === "string" && COMMAND_CODE_REASONING_EFFORTS.includes(value);
|
|
9801
10109
|
}
|
|
9802
|
-
const DEFAULT_ENABLED_MODEL_IDS$4 = FALLBACK_MODELS$
|
|
10110
|
+
const DEFAULT_ENABLED_MODEL_IDS$4 = FALLBACK_MODELS$4.map((model) => model.id);
|
|
9803
10111
|
/**
|
|
9804
10112
|
* Bind the model selection to the DSH settings document when the harness still
|
|
9805
10113
|
* offers one, and to the JSON file beside it otherwise: a harness without the
|
|
@@ -9865,7 +10173,7 @@ function credentialPath$1() {
|
|
|
9865
10173
|
function modelSettingsPath$1() {
|
|
9866
10174
|
return path.join(dshHomeDir$2(), "storages", "command-code-models.json");
|
|
9867
10175
|
}
|
|
9868
|
-
function optionalString$
|
|
10176
|
+
function optionalString$8(record, key) {
|
|
9869
10177
|
const value = record[key];
|
|
9870
10178
|
if (value === void 0 || value === null) return void 0;
|
|
9871
10179
|
if (typeof value !== "string") throw new Error("Command Code credential payload is invalid");
|
|
@@ -9886,7 +10194,7 @@ function parseCommandCodeCredentials(value) {
|
|
|
9886
10194
|
"planLabel",
|
|
9887
10195
|
"planId"
|
|
9888
10196
|
]) {
|
|
9889
|
-
const parsed = optionalString$
|
|
10197
|
+
const parsed = optionalString$8(record, key);
|
|
9890
10198
|
if (parsed !== void 0) credentials[key] = parsed;
|
|
9891
10199
|
}
|
|
9892
10200
|
const authenticatedAt = record.authenticatedAt;
|
|
@@ -9901,13 +10209,13 @@ function parseCommandCodeCredentials(value) {
|
|
|
9901
10209
|
}
|
|
9902
10210
|
return credentials;
|
|
9903
10211
|
}
|
|
9904
|
-
function credentialAccount$
|
|
10212
|
+
function credentialAccount$3(filePath) {
|
|
9905
10213
|
return createHash("sha256").update(path.resolve(filePath)).digest("hex");
|
|
9906
10214
|
}
|
|
9907
|
-
function createCredentialBackend$
|
|
10215
|
+
function createCredentialBackend$3(filePath) {
|
|
9908
10216
|
if (process.platform === "win32") return new WindowsDpapiCredentialStore(`${filePath}.dpapi`, parseCommandCodeCredentials);
|
|
9909
|
-
if (process.platform === "darwin") return new MacKeychainCredentialStore(PROVIDER_ID$
|
|
9910
|
-
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);
|
|
9911
10219
|
throw new Error("Command Code credential storage requires Windows, macOS, or Linux.");
|
|
9912
10220
|
}
|
|
9913
10221
|
const credentialOperations$2 = /* @__PURE__ */ new Map();
|
|
@@ -9915,13 +10223,13 @@ const credentialOperations$2 = /* @__PURE__ */ new Map();
|
|
|
9915
10223
|
var FileCredentialStore$1 = class {
|
|
9916
10224
|
filePath;
|
|
9917
10225
|
backend;
|
|
9918
|
-
constructor(filePath = credentialPath$1(), backend = createCredentialBackend$
|
|
10226
|
+
constructor(filePath = credentialPath$1(), backend = createCredentialBackend$3(filePath)) {
|
|
9919
10227
|
this.filePath = filePath;
|
|
9920
10228
|
this.backend = backend;
|
|
9921
10229
|
}
|
|
9922
10230
|
path() {
|
|
9923
10231
|
if (process.platform === "win32") return `${this.filePath}.dpapi`;
|
|
9924
|
-
return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: ${PROVIDER_ID$
|
|
10232
|
+
return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: ${PROVIDER_ID$5}/${credentialAccount$3(this.filePath)}`;
|
|
9925
10233
|
}
|
|
9926
10234
|
serialize(operation) {
|
|
9927
10235
|
const key = path.resolve(this.filePath);
|
|
@@ -10136,7 +10444,7 @@ function commandCodeHeaders(apiKey, extra = {}) {
|
|
|
10136
10444
|
authorization: `Bearer ${apiKey}`,
|
|
10137
10445
|
"content-type": "application/json",
|
|
10138
10446
|
accept: "application/json",
|
|
10139
|
-
"user-agent": `${PROVIDER_ID$
|
|
10447
|
+
"user-agent": `${PROVIDER_ID$5}/${CLI_VERSION}`,
|
|
10140
10448
|
[HEADER_CLI_VERSION]: CLI_VERSION,
|
|
10141
10449
|
[HEADER_CLI_ENVIRONMENT]: process.platform === "win32" ? "windows" : process.platform === "darwin" ? "macos" : "linux",
|
|
10142
10450
|
[HEADER_PROJECT_SLUG]: "dsh-chatgpt-subscription",
|
|
@@ -10172,7 +10480,7 @@ function firstString$1(record, keys) {
|
|
|
10172
10480
|
if (value !== void 0) return value;
|
|
10173
10481
|
}
|
|
10174
10482
|
}
|
|
10175
|
-
function firstNumber$
|
|
10483
|
+
function firstNumber$2(record, keys) {
|
|
10176
10484
|
for (const key of keys) {
|
|
10177
10485
|
const value = asNumber$3(record[key]);
|
|
10178
10486
|
if (value !== void 0) return value;
|
|
@@ -10344,12 +10652,12 @@ function parseWindowLimits(payload, consumed = []) {
|
|
|
10344
10652
|
if (key === "limited" || key === "exceeded") continue;
|
|
10345
10653
|
const record = asRecord$9(limits[key]);
|
|
10346
10654
|
if (record === void 0) continue;
|
|
10347
|
-
const cap = firstNumber$
|
|
10655
|
+
const cap = firstNumber$2(record, [
|
|
10348
10656
|
"cap",
|
|
10349
10657
|
"limit",
|
|
10350
10658
|
"total"
|
|
10351
10659
|
]);
|
|
10352
|
-
const used = firstNumber$
|
|
10660
|
+
const used = firstNumber$2(record, ["used", "consumed"]);
|
|
10353
10661
|
if (cap === void 0 && used === void 0) continue;
|
|
10354
10662
|
consumed.push(record);
|
|
10355
10663
|
const usedFraction = cap !== void 0 && cap > 0 && used !== void 0 ? clamp01$2(used / cap) : null;
|
|
@@ -10379,9 +10687,9 @@ function parseCreditBalances(payload, consumed = []) {
|
|
|
10379
10687
|
const root = asRecord$9(payload) ?? {};
|
|
10380
10688
|
const credits = asRecord$9(deepValue(asRecord$9(root.data) ?? root, ["credits"]));
|
|
10381
10689
|
if (credits === void 0) return [];
|
|
10382
|
-
const monthly = firstNumber$
|
|
10383
|
-
const purchased = firstNumber$
|
|
10384
|
-
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"]);
|
|
10385
10693
|
if (monthly === void 0 && purchased === void 0 && free === void 0) return [];
|
|
10386
10694
|
consumed.push(credits);
|
|
10387
10695
|
const meters = [];
|
|
@@ -10460,27 +10768,27 @@ function sweepMeters(payload, skipIds, consumed) {
|
|
|
10460
10768
|
let anonymized = 0;
|
|
10461
10769
|
for (const record of records) {
|
|
10462
10770
|
if (consumed.has(record)) continue;
|
|
10463
|
-
const limit = firstNumber$
|
|
10771
|
+
const limit = firstNumber$2(record, [
|
|
10464
10772
|
"limit",
|
|
10465
10773
|
"quota",
|
|
10466
10774
|
"allowance",
|
|
10467
10775
|
"cap",
|
|
10468
10776
|
"total"
|
|
10469
10777
|
]);
|
|
10470
|
-
const used = firstNumber$
|
|
10778
|
+
const used = firstNumber$2(record, [
|
|
10471
10779
|
"used",
|
|
10472
10780
|
"consumed",
|
|
10473
10781
|
"spent"
|
|
10474
10782
|
]);
|
|
10475
|
-
const remainingRaw = firstNumber$
|
|
10476
|
-
const balance = firstNumber$
|
|
10477
|
-
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, [
|
|
10478
10786
|
"usedPercent",
|
|
10479
10787
|
"used_percent",
|
|
10480
10788
|
"usagePercent",
|
|
10481
10789
|
"percentUsed"
|
|
10482
10790
|
]);
|
|
10483
|
-
const remainingPercentRaw = firstNumber$
|
|
10791
|
+
const remainingPercentRaw = firstNumber$2(record, [
|
|
10484
10792
|
"remainingPercent",
|
|
10485
10793
|
"remaining_percent",
|
|
10486
10794
|
"percentRemaining"
|
|
@@ -10604,7 +10912,7 @@ function parseUsageWindows$1(payload) {
|
|
|
10604
10912
|
const windows = [];
|
|
10605
10913
|
const seen = /* @__PURE__ */ new Set();
|
|
10606
10914
|
for (const record of records) {
|
|
10607
|
-
const percent = firstNumber$
|
|
10915
|
+
const percent = firstNumber$2(record, [
|
|
10608
10916
|
"usedPercent",
|
|
10609
10917
|
"used_percent",
|
|
10610
10918
|
"usagePercent",
|
|
@@ -10631,7 +10939,7 @@ function parseUsageWindows$1(payload) {
|
|
|
10631
10939
|
"window"
|
|
10632
10940
|
]) ?? id,
|
|
10633
10941
|
usedPercent: Math.round(clamp01$2(normalizePercent(percent) ?? 0) * 100),
|
|
10634
|
-
windowDurationMins: firstNumber$
|
|
10942
|
+
windowDurationMins: firstNumber$2(record, [
|
|
10635
10943
|
"windowDurationMins",
|
|
10636
10944
|
"window_minutes",
|
|
10637
10945
|
"durationMins",
|
|
@@ -10707,7 +11015,7 @@ function parseProviderModels(payload) {
|
|
|
10707
11015
|
models.push({
|
|
10708
11016
|
id,
|
|
10709
11017
|
name: firstString$1(record, ["displayName", "name"]) ?? id,
|
|
10710
|
-
contextWindow: firstNumber$
|
|
11018
|
+
contextWindow: firstNumber$2(record, [
|
|
10711
11019
|
"context_length",
|
|
10712
11020
|
"contextLength",
|
|
10713
11021
|
"context_window",
|
|
@@ -10725,7 +11033,7 @@ async function fetchProviderModels(options = {}) {
|
|
|
10725
11033
|
const response = await (options.fetchFn ?? fetch)(`${providerUrl(options.apiEnv ?? resolveApiEnv())}/models`, {
|
|
10726
11034
|
headers: {
|
|
10727
11035
|
accept: "application/json",
|
|
10728
|
-
"user-agent": PROVIDER_NAME$
|
|
11036
|
+
"user-agent": PROVIDER_NAME$5
|
|
10729
11037
|
},
|
|
10730
11038
|
signal: timeoutSignal$3(options.signal, DISCOVERY_TIMEOUT_MS$3)
|
|
10731
11039
|
});
|
|
@@ -10948,7 +11256,7 @@ function buildModelOptions$2(catalog, enabledModelIds, overrides) {
|
|
|
10948
11256
|
contextWindow: overrides[model.id] && overrides[model.id] > 0 ? overrides[model.id] : contextWindow,
|
|
10949
11257
|
defaultMaxTokens: maxOutputTokensFor$4(model.id),
|
|
10950
11258
|
...efforts.length > 0 ? { reasoningEfforts: efforts } : {},
|
|
10951
|
-
wire: wireForModel$
|
|
11259
|
+
wire: wireForModel$2(model.id)
|
|
10952
11260
|
};
|
|
10953
11261
|
});
|
|
10954
11262
|
}
|
|
@@ -11137,14 +11445,14 @@ function imageBlockToInline$3(block, images) {
|
|
|
11137
11445
|
data: resolved.data
|
|
11138
11446
|
} : void 0;
|
|
11139
11447
|
}
|
|
11140
|
-
function textOf$
|
|
11448
|
+
function textOf$5(content) {
|
|
11141
11449
|
if (typeof content === "string") return sanitizeText$4(content);
|
|
11142
11450
|
if (!Array.isArray(content)) return "";
|
|
11143
11451
|
const parts = [];
|
|
11144
11452
|
for (const block of content) {
|
|
11145
11453
|
if (!isRecord$18(block)) continue;
|
|
11146
11454
|
if (block.type === "text" && typeof block.text === "string") parts.push(sanitizeText$4(block.text));
|
|
11147
|
-
else if (block.type === "tool-result") parts.push(textOf$
|
|
11455
|
+
else if (block.type === "tool-result") parts.push(textOf$5(block.content));
|
|
11148
11456
|
}
|
|
11149
11457
|
return parts.join("");
|
|
11150
11458
|
}
|
|
@@ -11252,7 +11560,7 @@ function leadingSystemText$4(options) {
|
|
|
11252
11560
|
if (typeof options.system === "string" && options.system.trim() !== "") parts.push(options.system);
|
|
11253
11561
|
for (const message of options.messages) {
|
|
11254
11562
|
if (message.role !== "system") continue;
|
|
11255
|
-
const text = textOf$
|
|
11563
|
+
const text = textOf$5(message.content);
|
|
11256
11564
|
if (text !== "") parts.push(text);
|
|
11257
11565
|
}
|
|
11258
11566
|
return parts.length === 0 ? void 0 : parts.join("\n\n");
|
|
@@ -11372,7 +11680,7 @@ function buildOpenAIRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
|
|
|
11372
11680
|
content
|
|
11373
11681
|
});
|
|
11374
11682
|
}
|
|
11375
|
-
const effort = options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort);
|
|
11683
|
+
const effort = wireReasoningEffort(options.reasoningEffort === void 0 ? void 0 : String(options.reasoningEffort));
|
|
11376
11684
|
const maxTokens = options.maxTokens ?? maxOutputTokensFor$4(options.model);
|
|
11377
11685
|
return {
|
|
11378
11686
|
model: options.model,
|
|
@@ -11506,7 +11814,7 @@ function buildAnthropicRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
|
|
|
11506
11814
|
}
|
|
11507
11815
|
const system = leadingSystemText$4(options);
|
|
11508
11816
|
const maxTokens = options.maxTokens ?? maxOutputTokensFor$4(options.model);
|
|
11509
|
-
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);
|
|
11510
11818
|
return {
|
|
11511
11819
|
model: options.model,
|
|
11512
11820
|
max_tokens: maxTokens,
|
|
@@ -11529,7 +11837,7 @@ function buildAnthropicRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
|
|
|
11529
11837
|
function buildRequest$1(options, wire, images = NO_RESOLVED_IMAGES$4) {
|
|
11530
11838
|
return wire === "anthropic" ? buildAnthropicRequest$1(options, images) : buildOpenAIRequest$1(options, images);
|
|
11531
11839
|
}
|
|
11532
|
-
function createStreamState$
|
|
11840
|
+
function createStreamState$5(wire) {
|
|
11533
11841
|
return {
|
|
11534
11842
|
wire,
|
|
11535
11843
|
blocks: [],
|
|
@@ -11565,7 +11873,7 @@ function closeCurrent$3(state) {
|
|
|
11565
11873
|
block
|
|
11566
11874
|
}];
|
|
11567
11875
|
}
|
|
11568
|
-
function closeToolCalls$
|
|
11876
|
+
function closeToolCalls$4(state) {
|
|
11569
11877
|
const out = [];
|
|
11570
11878
|
for (const [wireIndex, call] of [...state.toolCalls.entries()].sort((a, b) => a[0] - b[0])) {
|
|
11571
11879
|
const block = {
|
|
@@ -11610,7 +11918,7 @@ function processOpenAIStreamLine$1(line, state) {
|
|
|
11610
11918
|
const payload = trimmed.slice(5).trim();
|
|
11611
11919
|
if (payload === "[DONE]") {
|
|
11612
11920
|
state.done = true;
|
|
11613
|
-
return closeStream$
|
|
11921
|
+
return closeStream$4(state);
|
|
11614
11922
|
}
|
|
11615
11923
|
if (payload === "") return [];
|
|
11616
11924
|
const chunk = safeJsonParse$4(payload);
|
|
@@ -11633,7 +11941,7 @@ function processOpenAIStreamLine$1(line, state) {
|
|
|
11633
11941
|
if (delta) {
|
|
11634
11942
|
const reasoning = asString$11(delta.reasoning_content) ?? asString$11(delta.reasoning);
|
|
11635
11943
|
if (reasoning !== void 0 && reasoning !== "") {
|
|
11636
|
-
out.push(...closeToolCalls$
|
|
11944
|
+
out.push(...closeToolCalls$4(state));
|
|
11637
11945
|
if (state.current === null || state.current.type !== "reasoning") out.push(...openTextBlock$3(state, "reasoning"));
|
|
11638
11946
|
state.current.text += sanitizeText$4(reasoning);
|
|
11639
11947
|
state.hasContent = true;
|
|
@@ -11645,7 +11953,7 @@ function processOpenAIStreamLine$1(line, state) {
|
|
|
11645
11953
|
}
|
|
11646
11954
|
const content = asString$11(delta.content);
|
|
11647
11955
|
if (content !== void 0 && content !== "") {
|
|
11648
|
-
out.push(...closeToolCalls$
|
|
11956
|
+
out.push(...closeToolCalls$4(state));
|
|
11649
11957
|
if (state.current === null || state.current.type !== "text") out.push(...openTextBlock$3(state, "text"));
|
|
11650
11958
|
state.current.text += sanitizeText$4(content);
|
|
11651
11959
|
state.hasContent = true;
|
|
@@ -11665,7 +11973,7 @@ function processOpenAIStreamLine$1(line, state) {
|
|
|
11665
11973
|
if (finish !== void 0 && finish !== "") {
|
|
11666
11974
|
state.finishReason = finish;
|
|
11667
11975
|
out.push(...closeCurrent$3(state));
|
|
11668
|
-
out.push(...closeToolCalls$
|
|
11976
|
+
out.push(...closeToolCalls$4(state));
|
|
11669
11977
|
}
|
|
11670
11978
|
return out;
|
|
11671
11979
|
}
|
|
@@ -11883,7 +12191,7 @@ function processAnthropicStreamLine$1(line, state) {
|
|
|
11883
12191
|
}
|
|
11884
12192
|
if (type === "message_stop") {
|
|
11885
12193
|
state.done = true;
|
|
11886
|
-
return closeStream$
|
|
12194
|
+
return closeStream$4(state);
|
|
11887
12195
|
}
|
|
11888
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");
|
|
11889
12197
|
return out;
|
|
@@ -11904,10 +12212,10 @@ function finishReasonFor$4(state) {
|
|
|
11904
12212
|
return { kind: "stop" };
|
|
11905
12213
|
}
|
|
11906
12214
|
/** Flush every open block, then emit usage and the terminal finish. */
|
|
11907
|
-
function closeStream$
|
|
12215
|
+
function closeStream$4(state) {
|
|
11908
12216
|
if (state.finished) return [];
|
|
11909
12217
|
state.finished = true;
|
|
11910
|
-
const out = [...closeCurrent$3(state), ...closeToolCalls$
|
|
12218
|
+
const out = [...closeCurrent$3(state), ...closeToolCalls$4(state)];
|
|
11911
12219
|
if (state.sawUsage) out.push({
|
|
11912
12220
|
type: "usage",
|
|
11913
12221
|
usage: tokenUsage$4(state)
|
|
@@ -11937,7 +12245,7 @@ function assertStreamComplete$4(state) {
|
|
|
11937
12245
|
* the set: `INVALID_CREDENTIAL` (a rejected key fails identically on every
|
|
11938
12246
|
* attempt) and `ABORTED` (the caller already cancelled).
|
|
11939
12247
|
*/
|
|
11940
|
-
const RETRY_POLICY$
|
|
12248
|
+
const RETRY_POLICY$5 = resolveRetryPolicy({
|
|
11941
12249
|
mode: "normal",
|
|
11942
12250
|
maxRetries: 3,
|
|
11943
12251
|
retryableCodes: [
|
|
@@ -11957,7 +12265,7 @@ function resolveDefaultReasoningEffort$3(efforts, configuredEffort) {
|
|
|
11957
12265
|
if (configuredEffort && efforts.includes(configuredEffort)) return ReasoningEffortId(configuredEffort);
|
|
11958
12266
|
}
|
|
11959
12267
|
/** Cooldown one rate-limited key takes when the provider states no delay. */
|
|
11960
|
-
const POOL_COOLDOWN_MS$
|
|
12268
|
+
const POOL_COOLDOWN_MS$4 = 15 * 6e4;
|
|
11961
12269
|
var CommandCodeAdapter = class extends LlmAdapter {
|
|
11962
12270
|
store;
|
|
11963
12271
|
modelSettings;
|
|
@@ -11983,11 +12291,11 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
11983
12291
|
providerInfo(provider) {
|
|
11984
12292
|
return {
|
|
11985
12293
|
id: provider,
|
|
11986
|
-
name: PROVIDER_NAME$
|
|
12294
|
+
name: PROVIDER_NAME$5
|
|
11987
12295
|
};
|
|
11988
12296
|
}
|
|
11989
12297
|
providerRetryPolicy() {
|
|
11990
|
-
return RETRY_POLICY$
|
|
12298
|
+
return RETRY_POLICY$5;
|
|
11991
12299
|
}
|
|
11992
12300
|
imageRequestPricing() {}
|
|
11993
12301
|
settings() {
|
|
@@ -12003,7 +12311,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12003
12311
|
apiEnv: resolveApiEnv()
|
|
12004
12312
|
})))().catch(() => []);
|
|
12005
12313
|
if (live.length > 0) return live;
|
|
12006
|
-
return FALLBACK_MODELS$
|
|
12314
|
+
return FALLBACK_MODELS$4.map((model) => ({
|
|
12007
12315
|
id: model.id,
|
|
12008
12316
|
name: model.name,
|
|
12009
12317
|
contextWindow: model.contextWindow
|
|
@@ -12039,7 +12347,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12039
12347
|
name: entry?.name ?? modelId,
|
|
12040
12348
|
inputModalities: inputModalitiesFor$1(modelId),
|
|
12041
12349
|
context: { contextWindow: this.contextWindowFor(modelId, entry, settings.contextWindowOverrides) },
|
|
12042
|
-
|
|
12350
|
+
...outputReservation(this.contextWindowFor(modelId, entry, settings.contextWindowOverrides), maxOutputTokensFor$4(modelId)),
|
|
12043
12351
|
...efforts.length === 0 ? {} : { reasoning: {
|
|
12044
12352
|
efforts: efforts.map((effort) => ({
|
|
12045
12353
|
id: ReasoningEffortId(effort),
|
|
@@ -12062,11 +12370,17 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12062
12370
|
...options,
|
|
12063
12371
|
reasoningEffort: ReasoningEffortId(String(effort))
|
|
12064
12372
|
};
|
|
12065
|
-
|
|
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);
|
|
12066
12380
|
}
|
|
12067
12381
|
async *requestStream(options, signal) {
|
|
12068
12382
|
const fetchFn = this.options.fetchFn ?? fetch;
|
|
12069
|
-
const wire = wireForModel$
|
|
12383
|
+
const wire = wireForModel$2(options.model);
|
|
12070
12384
|
const requestOptions = offloadOldestRequestImages$3(normalizeGenerateOptions(options));
|
|
12071
12385
|
const images = await resolveRequestImages$3(requestOptions, this.options.attachments, signal);
|
|
12072
12386
|
const body = JSON.stringify(buildRequest$1(requestOptions, wire, images));
|
|
@@ -12080,7 +12394,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12080
12394
|
let apiEnv;
|
|
12081
12395
|
if (pool === null) {
|
|
12082
12396
|
const credentials = await this.store.read();
|
|
12083
|
-
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");
|
|
12084
12398
|
apiKey = credentials.apiKey;
|
|
12085
12399
|
apiEnv = credentials.apiEnv ?? resolveApiEnv();
|
|
12086
12400
|
} else {
|
|
@@ -12116,13 +12430,13 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12116
12430
|
detail = (await response.text().catch(() => "")).slice(0, 600);
|
|
12117
12431
|
if (pool === null || accountId === void 0) break;
|
|
12118
12432
|
if (response.status === 401 || response.status === 403) {
|
|
12119
|
-
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);
|
|
12120
12434
|
if (await pool.hasAnotherAvailableAccount(tried)) continue;
|
|
12121
12435
|
break;
|
|
12122
12436
|
}
|
|
12123
12437
|
if (response.status === 429) {
|
|
12124
12438
|
const after = retryAfterMs$1(response.headers);
|
|
12125
|
-
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);
|
|
12126
12440
|
if (await pool.hasAnotherAvailableAccount(tried)) continue;
|
|
12127
12441
|
break;
|
|
12128
12442
|
}
|
|
@@ -12130,21 +12444,21 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12130
12444
|
}
|
|
12131
12445
|
if (response === void 0 || !response.ok) {
|
|
12132
12446
|
const status = response?.status ?? 500;
|
|
12133
|
-
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 });
|
|
12134
12448
|
if (status === 429) {
|
|
12135
12449
|
const after = response === void 0 ? void 0 : retryAfterMs$1(response.headers);
|
|
12136
|
-
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", {
|
|
12137
12451
|
status: 429,
|
|
12138
12452
|
...after === void 0 ? {} : { providerRetryAfterMs: after }
|
|
12139
12453
|
});
|
|
12140
12454
|
}
|
|
12141
|
-
if (status >= 500) throw new LlmError(`${PROVIDER_NAME$
|
|
12142
|
-
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 });
|
|
12143
12457
|
}
|
|
12144
12458
|
if (response.body === null) throw new LlmError("Command Code returned an empty response body", "PROVIDER_ERROR");
|
|
12145
12459
|
const reader = response.body.getReader();
|
|
12146
12460
|
const decoder = new TextDecoder();
|
|
12147
|
-
const state = createStreamState$
|
|
12461
|
+
const state = createStreamState$5(wire);
|
|
12148
12462
|
let buffer = "";
|
|
12149
12463
|
try {
|
|
12150
12464
|
while (true) {
|
|
@@ -12162,7 +12476,7 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
12162
12476
|
if (buffer.trim() !== "") for (const line of buffer.split("\n")) for (const chunk of processLine$1(line, state, wire)) yield chunk;
|
|
12163
12477
|
if (state.finished) return;
|
|
12164
12478
|
assertStreamComplete$4(state);
|
|
12165
|
-
for (const chunk of closeStream$
|
|
12479
|
+
for (const chunk of closeStream$4(state)) yield chunk;
|
|
12166
12480
|
} finally {
|
|
12167
12481
|
reader.cancel().catch(() => void 0);
|
|
12168
12482
|
}
|
|
@@ -12188,7 +12502,7 @@ function commandCodeAccountKey(credentials) {
|
|
|
12188
12502
|
if (credentials.userId !== void 0 && credentials.userId !== "") return `${credentials.userId}:${credentials.keyName ?? ""}`;
|
|
12189
12503
|
return createHash("sha256").update(credentials.apiKey).digest("hex");
|
|
12190
12504
|
}
|
|
12191
|
-
function optionalString$
|
|
12505
|
+
function optionalString$7(record, key) {
|
|
12192
12506
|
const value = record[key];
|
|
12193
12507
|
if (value === void 0 || value === null) return void 0;
|
|
12194
12508
|
if (typeof value !== "string") throw new Error("Command Code pool account field is invalid");
|
|
@@ -12220,7 +12534,7 @@ function parseCommandCodePoolData(value) {
|
|
|
12220
12534
|
"planId",
|
|
12221
12535
|
"userId"
|
|
12222
12536
|
]) {
|
|
12223
|
-
const parsed = optionalString$
|
|
12537
|
+
const parsed = optionalString$7(raw, key);
|
|
12224
12538
|
if (parsed !== void 0) account[key] = parsed;
|
|
12225
12539
|
}
|
|
12226
12540
|
if (account.email === void 0 && credentials.email !== void 0) account.email = credentials.email;
|
|
@@ -12252,7 +12566,7 @@ var CommandCodeAccountPool = class extends AccountPoolCore {
|
|
|
12252
12566
|
constructor(options = {}) {
|
|
12253
12567
|
const store = options.store ?? new FileCredentialStore$1();
|
|
12254
12568
|
const hooks = {
|
|
12255
|
-
providerId: PROVIDER_ID$
|
|
12569
|
+
providerId: PROVIDER_ID$5,
|
|
12256
12570
|
displayName: "Command Code",
|
|
12257
12571
|
poolFile: commandCodePoolPath(),
|
|
12258
12572
|
keychainService: "dsh-command-code-pool",
|
|
@@ -12418,7 +12732,7 @@ function applyCors(request, response) {
|
|
|
12418
12732
|
response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
12419
12733
|
response.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
12420
12734
|
}
|
|
12421
|
-
function sendJson$
|
|
12735
|
+
function sendJson$7(response, status, body) {
|
|
12422
12736
|
response.writeHead(status, { "content-type": "application/json" });
|
|
12423
12737
|
response.end(JSON.stringify(body));
|
|
12424
12738
|
}
|
|
@@ -12611,7 +12925,7 @@ function createAuthServer(port, expectedState, options = {}) {
|
|
|
12611
12925
|
try {
|
|
12612
12926
|
fields = fieldsFromPayload(raw, contentType);
|
|
12613
12927
|
} catch {
|
|
12614
|
-
sendJson$
|
|
12928
|
+
sendJson$7(response, 400, {
|
|
12615
12929
|
success: false,
|
|
12616
12930
|
error: "Invalid JSON"
|
|
12617
12931
|
});
|
|
@@ -12619,7 +12933,7 @@ function createAuthServer(port, expectedState, options = {}) {
|
|
|
12619
12933
|
}
|
|
12620
12934
|
if (fields.error) {
|
|
12621
12935
|
if (fields.state !== expectedState) {
|
|
12622
|
-
sendJson$
|
|
12936
|
+
sendJson$7(response, 403, {
|
|
12623
12937
|
success: false,
|
|
12624
12938
|
error: "Invalid state token"
|
|
12625
12939
|
});
|
|
@@ -12631,14 +12945,14 @@ function createAuthServer(port, expectedState, options = {}) {
|
|
|
12631
12945
|
}
|
|
12632
12946
|
const apiKey = fields.apiKey;
|
|
12633
12947
|
if (!apiKey) {
|
|
12634
|
-
sendJson$
|
|
12948
|
+
sendJson$7(response, 400, {
|
|
12635
12949
|
success: false,
|
|
12636
12950
|
error: "Missing required fields"
|
|
12637
12951
|
});
|
|
12638
12952
|
return;
|
|
12639
12953
|
}
|
|
12640
12954
|
if (fields.state !== expectedState) {
|
|
12641
|
-
sendJson$
|
|
12955
|
+
sendJson$7(response, 403, {
|
|
12642
12956
|
success: false,
|
|
12643
12957
|
error: "Invalid state token"
|
|
12644
12958
|
});
|
|
@@ -12788,18 +13102,18 @@ function isCommandCodeReasoningEffort(value) {
|
|
|
12788
13102
|
return typeof value === "string" && COMMAND_CODE_REASONING_EFFORTS.includes(value);
|
|
12789
13103
|
}
|
|
12790
13104
|
const MAX_BODY_BYTES$5 = 64 * 1024;
|
|
12791
|
-
const ROUTE_PREFIX$
|
|
12792
|
-
function sendJson$
|
|
13105
|
+
const ROUTE_PREFIX$4 = "/command-code/api";
|
|
13106
|
+
function sendJson$6(response, status, body) {
|
|
12793
13107
|
response.writeHead(status, { "Content-Type": "application/json" });
|
|
12794
13108
|
response.end(JSON.stringify(body));
|
|
12795
13109
|
}
|
|
12796
|
-
function sendMethodNotAllowed$
|
|
12797
|
-
sendJson$
|
|
13110
|
+
function sendMethodNotAllowed$5(response) {
|
|
13111
|
+
sendJson$6(response, 405, {
|
|
12798
13112
|
ok: false,
|
|
12799
13113
|
error: "Method Not Allowed"
|
|
12800
13114
|
});
|
|
12801
13115
|
}
|
|
12802
|
-
async function readRequestJson$
|
|
13116
|
+
async function readRequestJson$6(request) {
|
|
12803
13117
|
return new Promise((resolve, reject) => {
|
|
12804
13118
|
const chunks = [];
|
|
12805
13119
|
let total = 0;
|
|
@@ -12824,7 +13138,7 @@ async function readRequestJson$5(request) {
|
|
|
12824
13138
|
});
|
|
12825
13139
|
}
|
|
12826
13140
|
function fallbackCatalog$1() {
|
|
12827
|
-
return FALLBACK_MODELS$
|
|
13141
|
+
return FALLBACK_MODELS$4.map((model) => ({
|
|
12828
13142
|
id: model.id,
|
|
12829
13143
|
name: model.name,
|
|
12830
13144
|
contextWindow: model.contextWindow
|
|
@@ -12841,7 +13155,7 @@ function fallbackCatalog$1() {
|
|
|
12841
13155
|
function resolveEnabledModelIds$4(stored, catalog, enabled = true) {
|
|
12842
13156
|
if (!enabled) return [];
|
|
12843
13157
|
const catalogIds = catalog.map((model) => model.id);
|
|
12844
|
-
const shippedDefaults = new Set(FALLBACK_MODELS$
|
|
13158
|
+
const shippedDefaults = new Set(FALLBACK_MODELS$4.map((model) => model.id));
|
|
12845
13159
|
if (stored.length > 0 && stored.length === shippedDefaults.size && stored.every((id) => shippedDefaults.has(id))) return catalogIds;
|
|
12846
13160
|
const known = new Set(catalogIds);
|
|
12847
13161
|
return stored.filter((id) => known.has(id));
|
|
@@ -12926,13 +13240,13 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
12926
13240
|
const quotaRefresh = new QuotaRefresh();
|
|
12927
13241
|
return ctx.webServer.register({
|
|
12928
13242
|
kind: "prefix",
|
|
12929
|
-
path: ROUTE_PREFIX$
|
|
13243
|
+
path: ROUTE_PREFIX$4,
|
|
12930
13244
|
handler: async (request, response) => {
|
|
12931
13245
|
const path = new URL(request.url || "/", "http://dsh.local").pathname.replace(/^\/command-code\/api\/?/, "");
|
|
12932
13246
|
const method = request.method ?? "GET";
|
|
12933
13247
|
try {
|
|
12934
13248
|
if (path === "" || path === "status") {
|
|
12935
|
-
if (method !== "GET") return sendMethodNotAllowed$
|
|
13249
|
+
if (method !== "GET") return sendMethodNotAllowed$5(response);
|
|
12936
13250
|
const credentials = await store.read();
|
|
12937
13251
|
const targetId = await activeAccountId();
|
|
12938
13252
|
const cached = getCachedQuotaFor$1(targetId);
|
|
@@ -12941,7 +13255,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
12941
13255
|
if (cached === void 0) await quotaRefresh.run(refresh);
|
|
12942
13256
|
else quotaRefresh.start(refresh);
|
|
12943
13257
|
}
|
|
12944
|
-
return sendJson$
|
|
13258
|
+
return sendJson$6(response, 200, {
|
|
12945
13259
|
ok: true,
|
|
12946
13260
|
value: {
|
|
12947
13261
|
...await readStatus(),
|
|
@@ -12950,12 +13264,12 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
12950
13264
|
});
|
|
12951
13265
|
}
|
|
12952
13266
|
if (path === "login") {
|
|
12953
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
12954
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13267
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13268
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
12955
13269
|
ok: false,
|
|
12956
13270
|
error: "Cross-origin request rejected."
|
|
12957
13271
|
});
|
|
12958
|
-
return sendJson$
|
|
13272
|
+
return sendJson$6(response, 200, {
|
|
12959
13273
|
ok: true,
|
|
12960
13274
|
value: await beginWebLogin$3(store, {
|
|
12961
13275
|
fetchFn,
|
|
@@ -12964,25 +13278,25 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
12964
13278
|
});
|
|
12965
13279
|
}
|
|
12966
13280
|
if (path === "login/status") {
|
|
12967
|
-
if (method !== "GET") return sendMethodNotAllowed$
|
|
12968
|
-
return sendJson$
|
|
13281
|
+
if (method !== "GET") return sendMethodNotAllowed$5(response);
|
|
13282
|
+
return sendJson$6(response, 200, {
|
|
12969
13283
|
ok: true,
|
|
12970
13284
|
value: getWebLoginStatus()
|
|
12971
13285
|
});
|
|
12972
13286
|
}
|
|
12973
13287
|
if (path === "login/apikey") {
|
|
12974
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
12975
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13288
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13289
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
12976
13290
|
ok: false,
|
|
12977
13291
|
error: "Cross-origin request rejected."
|
|
12978
13292
|
});
|
|
12979
|
-
const body = await readRequestJson$
|
|
13293
|
+
const body = await readRequestJson$6(request);
|
|
12980
13294
|
const account = await saveApiKey(store, typeof body.apiKey === "string" ? body.apiKey : "", {
|
|
12981
13295
|
fetchFn,
|
|
12982
13296
|
...accountPool === void 0 ? {} : { onSave: (credentials) => accountPool.addAccount(credentials) }
|
|
12983
13297
|
});
|
|
12984
13298
|
clearCachedQuota$2();
|
|
12985
|
-
return sendJson$
|
|
13299
|
+
return sendJson$6(response, 200, {
|
|
12986
13300
|
ok: true,
|
|
12987
13301
|
value: {
|
|
12988
13302
|
...await readStatus(),
|
|
@@ -12991,13 +13305,13 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
12991
13305
|
});
|
|
12992
13306
|
}
|
|
12993
13307
|
if (path === "connection/test") {
|
|
12994
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
12995
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13308
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13309
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
12996
13310
|
ok: false,
|
|
12997
13311
|
error: "Cross-origin request rejected."
|
|
12998
13312
|
});
|
|
12999
13313
|
const credentials = await store.read();
|
|
13000
|
-
if (credentials === null) return sendJson$
|
|
13314
|
+
if (credentials === null) return sendJson$6(response, 400, {
|
|
13001
13315
|
ok: false,
|
|
13002
13316
|
error: "Not signed in."
|
|
13003
13317
|
});
|
|
@@ -13006,7 +13320,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13006
13320
|
fetchFn,
|
|
13007
13321
|
apiEnv: credentials.apiEnv ?? resolveApiEnv()
|
|
13008
13322
|
});
|
|
13009
|
-
return sendJson$
|
|
13323
|
+
return sendJson$6(response, 200, {
|
|
13010
13324
|
ok: true,
|
|
13011
13325
|
value: {
|
|
13012
13326
|
connected: true,
|
|
@@ -13024,24 +13338,24 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13024
13338
|
});
|
|
13025
13339
|
}
|
|
13026
13340
|
if (path === "quota") {
|
|
13027
|
-
if (method !== "GET" && method !== "POST") return sendMethodNotAllowed$
|
|
13341
|
+
if (method !== "GET" && method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13028
13342
|
await fetchAccountQuota$2(quotaStore, fetchFn, true, await activeAccountId());
|
|
13029
|
-
return sendJson$
|
|
13343
|
+
return sendJson$6(response, 200, {
|
|
13030
13344
|
ok: true,
|
|
13031
13345
|
value: await readStatus()
|
|
13032
13346
|
});
|
|
13033
13347
|
}
|
|
13034
13348
|
if (path === "models" || path === "settings") {
|
|
13035
|
-
if (method === "GET") return sendJson$
|
|
13349
|
+
if (method === "GET") return sendJson$6(response, 200, {
|
|
13036
13350
|
ok: true,
|
|
13037
13351
|
value: await readStatus()
|
|
13038
13352
|
});
|
|
13039
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
13040
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13353
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13354
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
13041
13355
|
ok: false,
|
|
13042
13356
|
error: "Cross-origin request rejected."
|
|
13043
13357
|
});
|
|
13044
|
-
const body = await readRequestJson$
|
|
13358
|
+
const body = await readRequestJson$6(request);
|
|
13045
13359
|
const patch = {};
|
|
13046
13360
|
if (typeof body.enabled === "boolean") patch.enabled = body.enabled;
|
|
13047
13361
|
if (Array.isArray(body.enabledModelIds)) patch.enabledModelIds = body.enabledModelIds.filter((id) => typeof id === "string");
|
|
@@ -13058,14 +13372,14 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13058
13372
|
if (preferences) await preferences.update(patch);
|
|
13059
13373
|
else await modelSettings.updateSettings(patch);
|
|
13060
13374
|
if (patch.enabledModelIds !== void 0 || patch.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
|
|
13061
|
-
return sendJson$
|
|
13375
|
+
return sendJson$6(response, 200, {
|
|
13062
13376
|
ok: true,
|
|
13063
13377
|
value: await readStatus()
|
|
13064
13378
|
});
|
|
13065
13379
|
}
|
|
13066
13380
|
if (path === "catalog/refresh") {
|
|
13067
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
13068
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13381
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13382
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
13069
13383
|
ok: false,
|
|
13070
13384
|
error: "Cross-origin request rejected."
|
|
13071
13385
|
});
|
|
@@ -13075,7 +13389,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13075
13389
|
force: true,
|
|
13076
13390
|
apiEnv: resolveApiEnv()
|
|
13077
13391
|
});
|
|
13078
|
-
return sendJson$
|
|
13392
|
+
return sendJson$6(response, 200, {
|
|
13079
13393
|
ok: true,
|
|
13080
13394
|
value: await readStatus()
|
|
13081
13395
|
});
|
|
@@ -13083,7 +13397,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13083
13397
|
if (path === "accounts") {
|
|
13084
13398
|
if (method === "GET") {
|
|
13085
13399
|
const data = accountPool === void 0 ? null : await accountPool.read().catch(() => null);
|
|
13086
|
-
return sendJson$
|
|
13400
|
+
return sendJson$6(response, 200, {
|
|
13087
13401
|
ok: true,
|
|
13088
13402
|
value: {
|
|
13089
13403
|
accounts: accountPool === void 0 ? [] : await accountPool.listAccounts().catch(() => []),
|
|
@@ -13092,16 +13406,16 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13092
13406
|
}
|
|
13093
13407
|
});
|
|
13094
13408
|
}
|
|
13095
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
13096
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13409
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13410
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
13097
13411
|
ok: false,
|
|
13098
13412
|
error: "Cross-origin request rejected."
|
|
13099
13413
|
});
|
|
13100
|
-
if (accountPool === void 0) return sendJson$
|
|
13414
|
+
if (accountPool === void 0) return sendJson$6(response, 400, {
|
|
13101
13415
|
ok: false,
|
|
13102
13416
|
error: "Account pool is not installed."
|
|
13103
13417
|
});
|
|
13104
|
-
const body = await readRequestJson$
|
|
13418
|
+
const body = await readRequestJson$6(request);
|
|
13105
13419
|
const action = typeof body.action === "string" ? body.action : "";
|
|
13106
13420
|
const accountId = typeof body.accountId === "string" ? body.accountId : void 0;
|
|
13107
13421
|
if (action === "set-primary" && accountId !== void 0) await accountPool.setPrimary(accountId);
|
|
@@ -13112,20 +13426,20 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13112
13426
|
else if (action === "relogin" && accountId !== void 0) await accountPool.clearAuthFailed(accountId);
|
|
13113
13427
|
clearCachedQuota$2();
|
|
13114
13428
|
clearCachedCatalog$3();
|
|
13115
|
-
return sendJson$
|
|
13429
|
+
return sendJson$6(response, 200, {
|
|
13116
13430
|
ok: true,
|
|
13117
13431
|
value: await readStatus()
|
|
13118
13432
|
});
|
|
13119
13433
|
}
|
|
13120
13434
|
if (path === "logout") {
|
|
13121
|
-
if (method !== "POST") return sendMethodNotAllowed$
|
|
13122
|
-
if (!isSameOriginMutation(request)) return sendJson$
|
|
13435
|
+
if (method !== "POST") return sendMethodNotAllowed$5(response);
|
|
13436
|
+
if (!isSameOriginMutation(request)) return sendJson$6(response, 403, {
|
|
13123
13437
|
ok: false,
|
|
13124
13438
|
error: "Cross-origin request rejected."
|
|
13125
13439
|
});
|
|
13126
13440
|
if (accountPool === void 0) await store.delete();
|
|
13127
13441
|
else {
|
|
13128
|
-
const body = await readRequestJson$
|
|
13442
|
+
const body = await readRequestJson$6(request).catch(() => ({}));
|
|
13129
13443
|
const data = await accountPool.read().catch(() => null);
|
|
13130
13444
|
const target = typeof body.accountId === "string" ? body.accountId : data?.activeAccountId ?? data?.accounts.find((account) => account.isPrimary)?.id ?? data?.accounts[0]?.id;
|
|
13131
13445
|
if (target === void 0) await store.delete();
|
|
@@ -13133,17 +13447,17 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13133
13447
|
}
|
|
13134
13448
|
clearCachedQuota$2();
|
|
13135
13449
|
clearCachedCatalog$3();
|
|
13136
|
-
return sendJson$
|
|
13450
|
+
return sendJson$6(response, 200, {
|
|
13137
13451
|
ok: true,
|
|
13138
13452
|
value: await readStatus()
|
|
13139
13453
|
});
|
|
13140
13454
|
}
|
|
13141
|
-
return sendJson$
|
|
13455
|
+
return sendJson$6(response, 404, {
|
|
13142
13456
|
ok: false,
|
|
13143
13457
|
error: "not-found"
|
|
13144
13458
|
});
|
|
13145
13459
|
} catch (error) {
|
|
13146
|
-
return sendJson$
|
|
13460
|
+
return sendJson$6(response, 500, {
|
|
13147
13461
|
ok: false,
|
|
13148
13462
|
error: error instanceof Error ? error.message : String(error)
|
|
13149
13463
|
});
|
|
@@ -13152,6 +13466,1377 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
13152
13466
|
});
|
|
13153
13467
|
}
|
|
13154
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
|
|
13155
14840
|
//#region src/host/kimi-code/model-catalog.ts
|
|
13156
14841
|
/**
|
|
13157
14842
|
* The four ids the subscription serves today.
|
|
@@ -17188,7 +18873,6 @@ var KimiCodeAdapter = class extends LlmAdapter {
|
|
|
17188
18873
|
name: entry?.name ?? modelId,
|
|
17189
18874
|
inputModalities: inputModalitiesForEntry(modelId, catalog),
|
|
17190
18875
|
context: { contextWindow: this.contextWindowFor(modelId, entry, settings.contextWindowOverrides) },
|
|
17191
|
-
defaultMaxTokens: maxOutputTokensFor$3(modelId, this.contextWindowFor(modelId, entry, settings.contextWindowOverrides)),
|
|
17192
18876
|
...efforts.length === 0 ? {} : { reasoning: {
|
|
17193
18877
|
efforts: efforts.map((effort) => ({
|
|
17194
18878
|
id: ReasoningEffortId(effort),
|
|
@@ -21658,9 +23342,8 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
|
|
|
21658
23342
|
* One model's resolved metadata, read through the current selection.
|
|
21659
23343
|
*
|
|
21660
23344
|
* The effective context window is the override when one is saved, and the
|
|
21661
|
-
* output cap is sized against that same number
|
|
21662
|
-
*
|
|
21663
|
-
* 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.
|
|
21664
23347
|
*/
|
|
21665
23348
|
async resolveModel(provider, modelId, signal) {
|
|
21666
23349
|
if (signal?.aborted === true) throw new LlmError("MiniMax Code model resolution aborted", "ABORTED");
|
|
@@ -21675,7 +23358,6 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
|
|
|
21675
23358
|
name: entry?.name ?? modelId,
|
|
21676
23359
|
inputModalities: modalitiesForModel(modelId),
|
|
21677
23360
|
context: { contextWindow },
|
|
21678
|
-
defaultMaxTokens: maxOutputTokensFor$1(modelId, contextWindow),
|
|
21679
23361
|
...efforts.length === 0 ? {} : { reasoning: {
|
|
21680
23362
|
efforts: efforts.map((effort) => ({
|
|
21681
23363
|
id: ReasoningEffortId(effort),
|
|
@@ -26378,7 +28060,7 @@ var WorkBuddyAdapter = class extends LlmAdapter {
|
|
|
26378
28060
|
name: entry.id === modelId ? entry.name : modelId,
|
|
26379
28061
|
inputModalities: entry.supportsImage ? ["text", "image"] : ["text"],
|
|
26380
28062
|
context: { contextWindow: contextWindow || 128e3 },
|
|
26381
|
-
|
|
28063
|
+
...outputReservation(contextWindow || 128e3, maxOutputTokensFor$2(modelId, catalog)),
|
|
26382
28064
|
...efforts.length === 0 ? {} : { reasoning: {
|
|
26383
28065
|
efforts: efforts.map((effort) => ({
|
|
26384
28066
|
id: ReasoningEffortId(effort),
|
|
@@ -26433,7 +28115,13 @@ var WorkBuddyAdapter = class extends LlmAdapter {
|
|
|
26433
28115
|
const fetchFn = this.options.fetchFn ?? fetch;
|
|
26434
28116
|
const requestOptions = offloadOldestRequestImages$1(normalizeGenerateOptions(options));
|
|
26435
28117
|
const images = await resolveRequestImages$1(requestOptions, this.options.attachments, signal);
|
|
26436
|
-
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));
|
|
26437
28125
|
const pool = this.accountPool;
|
|
26438
28126
|
const tried = /* @__PURE__ */ new Set();
|
|
26439
28127
|
let response;
|
|
@@ -27404,17 +29092,29 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
|
|
|
27404
29092
|
* LOCAL ADDITIONS — the rows the reference snapshot predates
|
|
27405
29093
|
* ---------------------------------------------------------------------------
|
|
27406
29094
|
*
|
|
27407
|
-
*
|
|
27408
|
-
*
|
|
27409
|
-
*
|
|
27410
|
-
*
|
|
27411
|
-
*
|
|
27412
|
-
*
|
|
27413
|
-
*
|
|
27414
|
-
*
|
|
27415
|
-
*
|
|
27416
|
-
* row
|
|
27417
|
-
*
|
|
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
|
|
27418
29118
|
* asserts the curated list is exactly the ids the snapshot lacks — which keeps
|
|
27419
29119
|
* the lock narrow instead of merely weaker.
|
|
27420
29120
|
*
|
|
@@ -27439,10 +29139,12 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
|
|
|
27439
29139
|
* 2. `thinkingMode: 'adaptive'`, NOT `'mid-convo'`. The two are easy to confuse
|
|
27440
29140
|
* here because every model in this line that refuses a temperature is also,
|
|
27441
29141
|
* so far, a managed-effort model. That is a coincidence of `compat`, not a
|
|
27442
|
-
* 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'
|
|
27443
29145
|
* additionally forces `output_config = { effort: 'high' }` when the caller
|
|
27444
|
-
* names no effort. That forced high is correct for the rows
|
|
27445
|
-
*
|
|
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
|
|
27446
29148
|
* documented default effort is `medium`. Labelling it 'mid-convo' would
|
|
27447
29149
|
* silently override the vendor's own default and make every request think —
|
|
27448
29150
|
* and cost — harder than the user asked for.
|
|
@@ -27459,8 +29161,8 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
|
|
|
27459
29161
|
* the user has to reach.
|
|
27460
29162
|
*/
|
|
27461
29163
|
/**
|
|
27462
|
-
* Sixteen rows: the snapshot's own
|
|
27463
|
-
* 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).
|
|
27464
29166
|
*
|
|
27465
29167
|
* The order matters and is not cosmetic. The test asserts the snapshot's ids
|
|
27466
29168
|
* appear in the table as a SUBSEQUENCE, so a curated row may be appended or
|
|
@@ -27649,6 +29351,25 @@ const CLAUDE_MODELS = Object.freeze([
|
|
|
27649
29351
|
],
|
|
27650
29352
|
canDisableThinking: false
|
|
27651
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
|
+
},
|
|
27652
29373
|
{
|
|
27653
29374
|
id: "claude-sonnet-4-5",
|
|
27654
29375
|
name: "Claude Sonnet 4.5 (latest)",
|
|
@@ -27712,25 +29433,6 @@ const CLAUDE_MODELS = Object.freeze([
|
|
|
27712
29433
|
],
|
|
27713
29434
|
canDisableThinking: true
|
|
27714
29435
|
},
|
|
27715
|
-
{
|
|
27716
|
-
id: "claude-opus-5-5",
|
|
27717
|
-
name: "Claude Opus 5.5",
|
|
27718
|
-
contextWindow: 1e6,
|
|
27719
|
-
maxTokens: 128e3,
|
|
27720
|
-
supportsImage: true,
|
|
27721
|
-
supportsTemperature: false,
|
|
27722
|
-
thinkingMode: "adaptive",
|
|
27723
|
-
reasoningEfforts: [
|
|
27724
|
-
"low",
|
|
27725
|
-
"medium",
|
|
27726
|
-
"high",
|
|
27727
|
-
"xhigh",
|
|
27728
|
-
"max"
|
|
27729
|
-
],
|
|
27730
|
-
canDisableThinking: false,
|
|
27731
|
-
minCliVersion: "2.1.280",
|
|
27732
|
-
bindsThinkingToPrefix: true
|
|
27733
|
-
},
|
|
27734
29436
|
{
|
|
27735
29437
|
id: "claude-sonnet-5-5",
|
|
27736
29438
|
name: "Claude Sonnet 5.5",
|
|
@@ -32254,11 +33956,9 @@ function cancelLogin() {
|
|
|
32254
33956
|
* importing it before it exists would make this adapter uncompilable, and
|
|
32255
33957
|
* importing it afterwards changes nothing, because the only methods used are
|
|
32256
33958
|
* the five in {@link ClaudeAccountPoolLike}.
|
|
32257
|
-
* - A2.
|
|
32258
|
-
*
|
|
32259
|
-
*
|
|
32260
|
-
* catalog. Loading it per request would add a cached round trip that cannot
|
|
32261
|
-
* 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.
|
|
32262
33962
|
* - A3. A 429 with no `retry-after` and no reset instant cools the account for
|
|
32263
33963
|
* {@link POOL_COOLDOWN_MS}. That is deliberately shorter than the 5-hour
|
|
32264
33964
|
* window: an account parked for hours after a burst limit recovers would cost
|
|
@@ -32501,7 +34201,7 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
|
|
|
32501
34201
|
name: entry.name,
|
|
32502
34202
|
inputModalities: entry.supportsImage ? ["text", "image"] : ["text"],
|
|
32503
34203
|
context: { contextWindow: contextWindow || 2e5 },
|
|
32504
|
-
|
|
34204
|
+
...outputReservation(contextWindow || 2e5, maxOutputTokensFor(modelId, catalog)),
|
|
32505
34205
|
...efforts.length === 0 ? {} : { reasoning: {
|
|
32506
34206
|
efforts: efforts.map((effort) => ({
|
|
32507
34207
|
id: ReasoningEffortId(effort),
|
|
@@ -32584,7 +34284,11 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
|
|
|
32584
34284
|
}
|
|
32585
34285
|
async attemptRequest(credentials, requestOptions, images, toolNames, thinking, settings, signal, fetchFn) {
|
|
32586
34286
|
const cacheTtl = ClaudeAdapter.cacheTtlFor(settings);
|
|
32587
|
-
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, {
|
|
32588
34292
|
toolNames,
|
|
32589
34293
|
cacheControl: true,
|
|
32590
34294
|
cacheTtl
|
|
@@ -37709,6 +39413,38 @@ function installRelayProbe(ctx, options) {
|
|
|
37709
39413
|
}
|
|
37710
39414
|
//#endregion
|
|
37711
39415
|
//#region src/host/reasoning-collapse-guard/index.ts
|
|
39416
|
+
/**
|
|
39417
|
+
* Reasoning-collapse guard.
|
|
39418
|
+
*
|
|
39419
|
+
* A long reasoning stream can degenerate into repetition: it cycles through a
|
|
39420
|
+
* handful of short phrases ("Let me call. Go. Calling. Go.") without emitting a
|
|
39421
|
+
* tool call or concluding anything, and runs until the output-token ceiling
|
|
39422
|
+
* truncates it. One archived session burned 128,000 output tokens that way and
|
|
39423
|
+
* returned an empty answer.
|
|
39424
|
+
*
|
|
39425
|
+
* The guard scores n-gram uniqueness over a trailing window of reasoning text.
|
|
39426
|
+
* Healthy deliberation keeps nearly every n-gram unique and scores near zero; a
|
|
39427
|
+
* degenerate stream scores near one. On a hit it stops yielding chunks, so the
|
|
39428
|
+
* in-flight request ends, and lets the turn resume on a fresh step.
|
|
39429
|
+
*
|
|
39430
|
+
* Seam choice: the guard wraps `llm/stream`, not `agent/assistant-stream`.
|
|
39431
|
+
* `agent/assistant-stream` is emit-mode — it can observe but cannot stop a
|
|
39432
|
+
* stream — and it does not exist before harness 0.1.5, which would make the
|
|
39433
|
+
* guard a silent no-op on every generation this plugin supports below that.
|
|
39434
|
+
* `llm/stream` is a waterfall whose `(options, next) => AsyncIterable<StreamChunk>`
|
|
39435
|
+
* signature is byte-identical from 0.1.2-alpha.5 through 0.2.0-rc.1, and
|
|
39436
|
+
* returning early from the listener is what actually ends the stream.
|
|
39437
|
+
*
|
|
39438
|
+
* The guard subscribes to that waterfall and never reassigns `ctx.llm.stream`.
|
|
39439
|
+
* The harness publishes `stream` as a one-argument method that enters the
|
|
39440
|
+
* waterfall itself, so a property rewrite carrying a second `next` parameter
|
|
39441
|
+
* breaks every caller dispatching it as a method, and misses the prepared
|
|
39442
|
+
* call that skips the published method entirely.
|
|
39443
|
+
*
|
|
39444
|
+
* The guard rewrites nothing and appends nothing to the aborted attempt. Its
|
|
39445
|
+
* only model-visible input is the single resume message it queues afterwards,
|
|
39446
|
+
* on a fresh turn.
|
|
39447
|
+
*/
|
|
37712
39448
|
const DEFAULT_GUARD_OPTIONS = {
|
|
37713
39449
|
windowChars: 4096,
|
|
37714
39450
|
minWindowChars: 1500,
|
|
@@ -37778,17 +39514,35 @@ function createBreakerState() {
|
|
|
37778
39514
|
};
|
|
37779
39515
|
}
|
|
37780
39516
|
/**
|
|
37781
|
-
* The resume message
|
|
37782
|
-
*
|
|
39517
|
+
* The resume message the guard queues onto the kept inbox.
|
|
39518
|
+
*
|
|
39519
|
+
* It is built with the harness message factory and carries this package's own
|
|
39520
|
+
* source kind, because the harness rejects a message that is not fully
|
|
39521
|
+
* identified: a steer payload with no `id` is refused by
|
|
39522
|
+
* `assertMessageEventShape` (`session event … lacks an identified message`)
|
|
39523
|
+
* and one with no `source` by the same check (`… has invalid source`). Either
|
|
39524
|
+
* refusal is thrown from `session.append` at the very moment the guard's
|
|
39525
|
+
* microtask steers — after the abort has already been taken — so the failure
|
|
39526
|
+
* surfaced as a repairable-splice violation, the attempt was already cancelled,
|
|
39527
|
+
* and the turn ended with the user told nothing.
|
|
39528
|
+
*
|
|
39529
|
+
* `createUserMessage` is available on every supported generation, and the
|
|
39530
|
+
* `dsh-chatgpt-subscription` kind is this package's own entry in the
|
|
39531
|
+
* merge-extensible `MessageSourceMap`, so the resume carries real provenance
|
|
39532
|
+
* instead of impersonating a human turn.
|
|
37783
39533
|
*/
|
|
37784
39534
|
function createResumeMessage(text) {
|
|
37785
|
-
return {
|
|
37786
|
-
role: "user",
|
|
39535
|
+
return createUserMessage({
|
|
37787
39536
|
content: [{
|
|
37788
39537
|
type: "text",
|
|
37789
39538
|
text
|
|
37790
|
-
}]
|
|
37791
|
-
|
|
39539
|
+
}],
|
|
39540
|
+
source: {
|
|
39541
|
+
kind: PLUGIN_MESSAGE_SOURCE_KIND,
|
|
39542
|
+
form: "notice",
|
|
39543
|
+
summary: boundContextSummary("Reasoning collapsed; the attempt was stopped and resumed.")
|
|
39544
|
+
}
|
|
39545
|
+
});
|
|
37792
39546
|
}
|
|
37793
39547
|
function appendWindow(current, addition, limit) {
|
|
37794
39548
|
const combined = current + addition;
|
|
@@ -38007,12 +39761,12 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
38007
39761
|
const claimAntigravityRoute = () => {
|
|
38008
39762
|
if (antigravityRegistration !== void 0) return;
|
|
38009
39763
|
try {
|
|
38010
|
-
antigravityRegistration = ctx.llm.registerAdapter([PROVIDER_ID$
|
|
38011
|
-
if (antigravityConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] Antigravity route "${PROVIDER_ID$
|
|
39764
|
+
antigravityRegistration = ctx.llm.registerAdapter([PROVIDER_ID$6], antigravityAdapter);
|
|
39765
|
+
if (antigravityConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] Antigravity route "${PROVIDER_ID$6}" is now served by this plugin`);
|
|
38012
39766
|
antigravityConflict = null;
|
|
38013
39767
|
} catch (error) {
|
|
38014
39768
|
antigravityConflict = error instanceof Error ? error.message : String(error);
|
|
38015
|
-
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$
|
|
39769
|
+
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})`);
|
|
38016
39770
|
}
|
|
38017
39771
|
};
|
|
38018
39772
|
claimAntigravityRoute();
|
|
@@ -38031,6 +39785,12 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
38031
39785
|
}, commandCodeAccountPool);
|
|
38032
39786
|
let commandCodeRegistration;
|
|
38033
39787
|
let commandCodeConflict = null;
|
|
39788
|
+
const ollamaStore = new FileCredentialStore$5();
|
|
39789
|
+
const ollamaModelSettings = new FileModelSettingsStore$5();
|
|
39790
|
+
const ollamaAccountPool = new OllamaAccountPool({ store: ollamaStore });
|
|
39791
|
+
const ollamaAdapter = new OllamaAdapter(ollamaStore, ollamaModelSettings, { fetchFn: proxyFetch }, ollamaAccountPool);
|
|
39792
|
+
let ollamaRegistration;
|
|
39793
|
+
let ollamaConflict = null;
|
|
38034
39794
|
const kimiCodeAdapter = new KimiCodeAdapter(kimiCodeStore, kimiCodeModelSettings, kimiCodePreferences, {
|
|
38035
39795
|
fetchFn: proxyFetch,
|
|
38036
39796
|
attachments: ctx.attachments,
|
|
@@ -38056,15 +39816,32 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
38056
39816
|
const kimiCodeRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
|
|
38057
39817
|
claimKimiCodeRoute();
|
|
38058
39818
|
}) : void 0;
|
|
39819
|
+
const claimOllamaRoute = () => {
|
|
39820
|
+
if (ollamaRegistration !== void 0) return;
|
|
39821
|
+
try {
|
|
39822
|
+
ollamaRegistration = ctx.llm.registerAdapter([PROVIDER_ID$4], ollamaAdapter);
|
|
39823
|
+
if (ollamaConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$4} route "${PROVIDER_ID$4}" is now served by this plugin`);
|
|
39824
|
+
ollamaConflict = null;
|
|
39825
|
+
} catch (error) {
|
|
39826
|
+
ollamaConflict = error instanceof Error ? error.message : String(error);
|
|
39827
|
+
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})`);
|
|
39828
|
+
}
|
|
39829
|
+
};
|
|
39830
|
+
claimOllamaRoute();
|
|
39831
|
+
const disposeOllamaRoutes = registerOllamaRoutes(ctx, {
|
|
39832
|
+
accountPool: ollamaAccountPool,
|
|
39833
|
+
modelSettings: ollamaModelSettings,
|
|
39834
|
+
fetchFn: proxyFetch
|
|
39835
|
+
});
|
|
38059
39836
|
const claimCommandCodeRoute = () => {
|
|
38060
39837
|
if (commandCodeRegistration !== void 0) return;
|
|
38061
39838
|
try {
|
|
38062
|
-
commandCodeRegistration = ctx.llm.registerAdapter([PROVIDER_ID$
|
|
38063
|
-
if (commandCodeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$
|
|
39839
|
+
commandCodeRegistration = ctx.llm.registerAdapter([PROVIDER_ID$5], commandCodeAdapter);
|
|
39840
|
+
if (commandCodeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$5} route "${PROVIDER_ID$5}" is now served by this plugin`);
|
|
38064
39841
|
commandCodeConflict = null;
|
|
38065
39842
|
} catch (error) {
|
|
38066
39843
|
commandCodeConflict = error instanceof Error ? error.message : String(error);
|
|
38067
|
-
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$
|
|
39844
|
+
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})`);
|
|
38068
39845
|
}
|
|
38069
39846
|
};
|
|
38070
39847
|
claimCommandCodeRoute();
|
|
@@ -38247,7 +40024,7 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
38247
40024
|
});
|
|
38248
40025
|
};
|
|
38249
40026
|
const disposeRoutes = registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwitcher, readRouteAudit, codexAccountPool);
|
|
38250
|
-
const disposeAdapter = ctx.llm.registerAdapter([PROVIDER_ID$
|
|
40027
|
+
const disposeAdapter = ctx.llm.registerAdapter([PROVIDER_ID$7], adapter);
|
|
38251
40028
|
const disposeImageTool = ctx.tools.register(createCodexImageTool(oauth, ctx.attachments, { fetchFn: proxyFetch }));
|
|
38252
40029
|
const disposeVideoTool = ctx.tools.register(createKimiVideoTool(ctx, { fetchFn: proxyFetch }));
|
|
38253
40030
|
ctx.inject(["web"], (ctx) => {
|
|
@@ -38273,6 +40050,9 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
38273
40050
|
releaseHandle(antigravityRouteWatch);
|
|
38274
40051
|
antigravityRegistration?.();
|
|
38275
40052
|
antigravityRegistration = void 0;
|
|
40053
|
+
disposeOllamaRoutes();
|
|
40054
|
+
ollamaRegistration?.();
|
|
40055
|
+
ollamaRegistration = void 0;
|
|
38276
40056
|
disposeCommandCodeRoutes();
|
|
38277
40057
|
releaseHandle(commandCodeRouteWatch);
|
|
38278
40058
|
commandCodeRegistration?.();
|