@eddyskywalker/dsh-chatgpt-subscription 0.10.12 → 0.10.13
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 +25 -0
- package/README.md +176 -42
- package/lib/client.js +133 -26
- package/lib/client.js.map +1 -1
- package/lib/index.js +710 -280
- package/lib/types/client/claude/ClaudeSection.d.ts.map +1 -1
- package/lib/types/client/claude/locales.d.ts +17 -2
- package/lib/types/client/claude/locales.d.ts.map +1 -1
- package/lib/types/client/kimi-code/KimiCodeSection.d.ts.map +1 -1
- package/lib/types/client/kimi-code/locales.d.ts +17 -2
- package/lib/types/client/kimi-code/locales.d.ts.map +1 -1
- package/lib/types/compat.d.ts +35 -2
- package/lib/types/compat.d.ts.map +1 -1
- package/lib/types/host/adapter.d.ts +20 -1
- package/lib/types/host/adapter.d.ts.map +1 -1
- package/lib/types/host/claude/adapter.d.ts +11 -0
- package/lib/types/host/claude/adapter.d.ts.map +1 -1
- package/lib/types/host/claude/client.d.ts +7 -2
- package/lib/types/host/claude/client.d.ts.map +1 -1
- package/lib/types/host/claude/mapper.d.ts +13 -1
- package/lib/types/host/claude/mapper.d.ts.map +1 -1
- package/lib/types/host/claude/routes.d.ts.map +1 -1
- package/lib/types/host/claude/token-store.d.ts +9 -0
- package/lib/types/host/claude/token-store.d.ts.map +1 -1
- package/lib/types/host/claude/types.d.ts +35 -0
- package/lib/types/host/claude/types.d.ts.map +1 -1
- package/lib/types/host/codex-catalog.d.ts +54 -0
- package/lib/types/host/codex-catalog.d.ts.map +1 -0
- package/lib/types/host/codex-images.d.ts.map +1 -1
- package/lib/types/host/codex-search.d.ts.map +1 -1
- package/lib/types/host/kimi-code/adapter.d.ts.map +1 -1
- package/lib/types/host/kimi-code/mapper.d.ts +13 -7
- package/lib/types/host/kimi-code/mapper.d.ts.map +1 -1
- package/lib/types/host/kimi-code/routes.d.ts.map +1 -1
- package/lib/types/host/kimi-code/token-store.d.ts +11 -1
- package/lib/types/host/kimi-code/token-store.d.ts.map +1 -1
- package/lib/types/host/kimi-code/types.d.ts +13 -0
- package/lib/types/host/kimi-code/types.d.ts.map +1 -1
- package/lib/types/host/model-catalog.d.ts +3 -2
- package/lib/types/host/model-catalog.d.ts.map +1 -1
- package/lib/types/host/responses-client.d.ts +24 -0
- package/lib/types/host/responses-client.d.ts.map +1 -1
- package/lib/types/host/responses-mapper.d.ts +5 -1
- package/lib/types/host/responses-mapper.d.ts.map +1 -1
- package/lib/types/host/wire-auth.d.ts +14 -1
- package/lib/types/host/wire-auth.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/shared/claude-contracts.d.ts +19 -0
- package/lib/types/shared/claude-contracts.d.ts.map +1 -1
- package/lib/types/shared/kimi-code-contracts.d.ts +16 -0
- package/lib/types/shared/kimi-code-contracts.d.ts.map +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -2,13 +2,13 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import * as LlmModule from "@deepseek-ai/dsh-llm";
|
|
4
4
|
import { HarnessError, LlmAdapter, LlmError, LlmError as LlmError$1, ProviderRequestId, ReasoningEffortId, attributionHeaders, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
5
|
+
import fs, { chmod, lstat, mkdir, open, readFile, rename, stat, unlink } from "node:fs/promises";
|
|
5
6
|
import path, { basename, dirname, join, relative, resolve } from "node:path";
|
|
6
7
|
import os, { homedir } from "node:os";
|
|
7
8
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
8
9
|
import { isDeepStrictEqual } from "node:util";
|
|
9
10
|
import { execSync, spawn } from "node:child_process";
|
|
10
|
-
import
|
|
11
|
-
import fs, { appendFileSync, constants, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
|
|
11
|
+
import fs$1, { appendFileSync, constants, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
|
|
12
12
|
import http, { createServer } from "node:http";
|
|
13
13
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
14
14
|
import { lookup } from "node:dns/promises";
|
|
@@ -60,7 +60,23 @@ const OAUTH_CALLBACK_HOST = "localhost";
|
|
|
60
60
|
const OAUTH_CALLBACK_PORT = 1455;
|
|
61
61
|
const OAUTH_REDIRECT_URI = `http://${OAUTH_CALLBACK_HOST}:${OAUTH_CALLBACK_PORT}/auth/callback`;
|
|
62
62
|
const OAUTH_SCOPE = "openid profile email offline_access";
|
|
63
|
-
|
|
63
|
+
/**
|
|
64
|
+
* The single `originator` this line presents to the Codex backend on every
|
|
65
|
+
* request, and the one OAuth sends during sign-in.
|
|
66
|
+
*
|
|
67
|
+
* This used to be three different values - `opencode` for chat and sign-in,
|
|
68
|
+
* `pi` for the image and search endpoints - which was an archaeological record
|
|
69
|
+
* of when each endpoint was reverse-engineered, not a decision. The Codex
|
|
70
|
+
* backend keys behaviour off this value (`codex_cli_rs` is what the official
|
|
71
|
+
* CLI sends) and treats it as one client identity, so splitting it across three
|
|
72
|
+
* files meant three unrelated failure signatures for one account. Both spellings
|
|
73
|
+
* are known-good against the backend; `opencode` is kept because it is the value
|
|
74
|
+
* the OAuth flow has always presented, and changing the sign-in originator is a
|
|
75
|
+
* different risk from changing the request originator.
|
|
76
|
+
*/
|
|
77
|
+
const CODEX_ORIGINATOR = "opencode";
|
|
78
|
+
/** OAuth presents the same identity the request path does (see above). */
|
|
79
|
+
const OAUTH_ORIGINATOR = CODEX_ORIGINATOR;
|
|
64
80
|
const TOKEN_REFRESH_MARGIN_MS = 6e4;
|
|
65
81
|
const ROUTE_PREFIX$4 = "/api/dsh-chatgpt-subscription";
|
|
66
82
|
const PLUGIN_VERSION = "0.1.0-alpha.0";
|
|
@@ -70,9 +86,18 @@ const CODEX_RESPONSES_URL = `${CODEX_API_BASE}/responses`;
|
|
|
70
86
|
const CODEX_IMAGE_GENERATION_URL = `${CODEX_API_BASE}/images/generations`;
|
|
71
87
|
const CODEX_SEARCH_URL = `${CODEX_API_BASE}/alpha/search`;
|
|
72
88
|
const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
89
|
+
const CODEX_MODELS_URL = `${CODEX_API_BASE}/models?client_version=0.99.0`;
|
|
90
|
+
/**
|
|
91
|
+
* Beta gate the Codex backend expects on every responses request.
|
|
92
|
+
*
|
|
93
|
+
* The subscription backend is served under a beta flag, not as stable API:
|
|
94
|
+
* without this header the endpoint is a different (and, on older builds,
|
|
95
|
+
* rejected) surface. Kept as a single constant so the value is reviewable in
|
|
96
|
+
* one place with the rest of the wire contract in `wire-auth.ts`.
|
|
97
|
+
*/
|
|
98
|
+
const CODEX_OPENAI_BETA = "responses=experimental";
|
|
73
99
|
const CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
|
|
74
100
|
const CODEX_RESET_CREDITS_CONSUME_URL = `${CODEX_RESET_CREDITS_URL}/consume`;
|
|
75
|
-
const CODEX_ORIGINATOR = "opencode";
|
|
76
101
|
const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
|
|
77
102
|
const CODEX_IMAGE_MODEL = "gpt-image-2";
|
|
78
103
|
const CODEX_SEARCH_PROVIDER_ID = "codex-subscription";
|
|
@@ -227,6 +252,9 @@ function reasoningEffortsForModel(model) {
|
|
|
227
252
|
if (profile === "gpt-6") return GPT_6_REASONING_EFFORTS;
|
|
228
253
|
return profile === "gpt-5.6" ? GPT_56_REASONING_EFFORTS : STANDARD_REASONING_EFFORTS;
|
|
229
254
|
}
|
|
255
|
+
function isCodexReasoningEffort(value) {
|
|
256
|
+
return typeof value === "string" && GPT_56_REASONING_EFFORTS.some((effort) => effort === value);
|
|
257
|
+
}
|
|
230
258
|
function isCodexModelId(model) {
|
|
231
259
|
return typeof model === "string" && CODEX_MODEL_CATALOG.some((entry) => entry.id === model);
|
|
232
260
|
}
|
|
@@ -270,28 +298,40 @@ function resolveCodexFallbackModel(model) {
|
|
|
270
298
|
//#region src/host/model-catalog.ts
|
|
271
299
|
const PROVIDER_ID$6 = CODEX_CHATGPT_PROVIDER_ID;
|
|
272
300
|
const PROVIDER_NAME$6 = "Codex(ChatGPT 订阅)";
|
|
273
|
-
function listCodexModels(preferences) {
|
|
301
|
+
function listCodexModels(preferences, live) {
|
|
274
302
|
const status = preferences?.status();
|
|
275
303
|
if (status?.enabled === false) return [];
|
|
276
|
-
const
|
|
277
|
-
|
|
304
|
+
const source = live !== void 0 && live.length > 0 ? live.map((entry) => ({
|
|
305
|
+
id: entry.id,
|
|
306
|
+
name: entry.name,
|
|
307
|
+
inputModalities: entry.inputModalities
|
|
308
|
+
})) : CODEX_MODEL_CATALOG.map((entry) => ({
|
|
309
|
+
id: entry.id,
|
|
310
|
+
name: entry.name,
|
|
311
|
+
inputModalities: entry.inputModalities
|
|
312
|
+
}));
|
|
313
|
+
const visible = new Set(status?.visibleModelIds ?? source.map((entry) => entry.id));
|
|
314
|
+
return source.filter((entry) => visible.has(entry.id)).map((entry) => ({
|
|
278
315
|
provider: PROVIDER_ID$6,
|
|
279
316
|
id: entry.id,
|
|
280
317
|
name: entry.name,
|
|
281
318
|
inputModalities: [...entry.inputModalities]
|
|
282
319
|
}));
|
|
283
320
|
}
|
|
284
|
-
function resolveCodexModel(model, preferences) {
|
|
321
|
+
function resolveCodexModel(model, preferences, live) {
|
|
285
322
|
const entry = resolveCodexCatalogEntry(model);
|
|
323
|
+
const liveEntry = live?.find((candidate) => candidate.id === model);
|
|
286
324
|
const configuredContextWindow = (preferences?.status())?.contextWindowOverrides[model];
|
|
287
|
-
const
|
|
288
|
-
const
|
|
325
|
+
const listedEfforts = liveEntry?.reasoningEfforts?.filter(isCodexReasoningEffort);
|
|
326
|
+
const efforts = listedEfforts !== void 0 && listedEfforts.length > 0 ? listedEfforts : reasoningEffortsForModel(model);
|
|
327
|
+
const statedDefault = liveEntry?.defaultReasoningEffort ?? entry.defaultReasoningEffort;
|
|
328
|
+
const defaultEffort = isCodexReasoningEffort(statedDefault) && efforts.includes(statedDefault) ? statedDefault : efforts[0];
|
|
289
329
|
return {
|
|
290
330
|
provider: PROVIDER_ID$6,
|
|
291
331
|
id: model,
|
|
292
|
-
name: entry.id === model ? entry.name : model,
|
|
293
|
-
inputModalities: [...entry.inputModalities],
|
|
294
|
-
context: { contextWindow: configuredContextWindow ?? entry.contextWindow },
|
|
332
|
+
name: liveEntry?.name ?? (entry.id === model ? entry.name : model),
|
|
333
|
+
inputModalities: [...liveEntry?.inputModalities ?? entry.inputModalities],
|
|
334
|
+
context: { contextWindow: configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow },
|
|
295
335
|
defaultMaxTokens: codexModelMaxTokens(model),
|
|
296
336
|
reasoning: {
|
|
297
337
|
efforts: efforts.map((effort) => ({
|
|
@@ -324,10 +364,12 @@ const RETRY_POLICY$5 = resolveRetryPolicy({
|
|
|
324
364
|
var CodexChatGptAdapter = class extends LlmAdapter {
|
|
325
365
|
client;
|
|
326
366
|
preferences;
|
|
327
|
-
|
|
367
|
+
loadCatalog;
|
|
368
|
+
constructor(client, preferences, loadCatalog) {
|
|
328
369
|
super();
|
|
329
370
|
this.client = client;
|
|
330
371
|
this.preferences = preferences;
|
|
372
|
+
this.loadCatalog = loadCatalog;
|
|
331
373
|
}
|
|
332
374
|
providerInfo(provider) {
|
|
333
375
|
return {
|
|
@@ -340,10 +382,21 @@ var CodexChatGptAdapter = class extends LlmAdapter {
|
|
|
340
382
|
}
|
|
341
383
|
imageRequestPricing(_provider, _model) {}
|
|
342
384
|
async listModels() {
|
|
343
|
-
return listCodexModels(this.preferences);
|
|
385
|
+
return listCodexModels(this.preferences, await this.catalog());
|
|
344
386
|
}
|
|
345
387
|
async resolveModel(_provider, model, signal) {
|
|
346
|
-
return resolveCodexModel(model, this.preferences);
|
|
388
|
+
return resolveCodexModel(model, this.preferences, await this.catalog(signal));
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* The live listing, or undefined when this line has no way to fetch one.
|
|
392
|
+
*
|
|
393
|
+
* Never throws: a listing failure must not become a failed picker or a
|
|
394
|
+
* rejected model resolution. `loadCodexCatalog` already answers with the
|
|
395
|
+
* shipped table on failure, so the catch here only covers a throwing loader.
|
|
396
|
+
*/
|
|
397
|
+
async catalog(signal) {
|
|
398
|
+
if (this.loadCatalog === void 0) return void 0;
|
|
399
|
+
return this.loadCatalog({ signal }).catch(() => void 0);
|
|
347
400
|
}
|
|
348
401
|
async prepareCall(provider, model, signal) {
|
|
349
402
|
return {
|
|
@@ -367,6 +420,347 @@ function dshHomeDir() {
|
|
|
367
420
|
return process.env.DSH_HOME?.trim() || path.join(os.homedir(), ".dsh");
|
|
368
421
|
}
|
|
369
422
|
//#endregion
|
|
423
|
+
//#region src/host/common/catalog-snapshot.ts
|
|
424
|
+
/**
|
|
425
|
+
* Persistent snapshot for one provider's live model catalog.
|
|
426
|
+
*
|
|
427
|
+
* The in-memory catalog caches keep a process alive between requests, but they
|
|
428
|
+
* start cold: on the first call after a restart (or a plugin reinstall) the
|
|
429
|
+
* picker and the settings page both wait on the upstream listing before they
|
|
430
|
+
* can render anything, and an unreachable endpoint turns that wait into the
|
|
431
|
+
* full discovery timeout — "the model server is unavailable" on a fresh
|
|
432
|
+
* install. The catalog is not fast-moving data, so the last successful listing
|
|
433
|
+
* is persisted to `<DSH_HOME>/storages/` and rehydrated into the in-memory
|
|
434
|
+
* cache on first use. A snapshot the caller can read is therefore available
|
|
435
|
+
* from the very first call, with no network in the path.
|
|
436
|
+
*
|
|
437
|
+
* Writes are best-effort: persistence is an optimization, never a capability.
|
|
438
|
+
* A read-only home or a locked file must not fail the fetch that produced the
|
|
439
|
+
* snapshot.
|
|
440
|
+
*/
|
|
441
|
+
/** File name of the persisted snapshot for one catalog. */
|
|
442
|
+
function catalogSnapshotPath(name) {
|
|
443
|
+
return path.join(dshHomeDir(), "storages", `${name}-catalog.json`);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Snapshot name for one catalog *and* one credential scope.
|
|
447
|
+
*
|
|
448
|
+
* A persisted snapshot answers before any credential is read — that is the whole
|
|
449
|
+
* point of it — so the file itself has to carry the scope it was fetched under.
|
|
450
|
+
* Otherwise the first call after a restart, for any account, is answered by
|
|
451
|
+
* whatever listing some other scope happened to persist last: a Kimi listing
|
|
452
|
+
* fetched for the mainland-cn endpoint would answer a global caller with models
|
|
453
|
+
* and context windows that region does not serve, and a Command Code listing
|
|
454
|
+
* fetched against one API environment would answer the other.
|
|
455
|
+
*
|
|
456
|
+
* The scope is part of the file name rather than of the payload because a caller
|
|
457
|
+
* that cannot name the account — the harness resolves this catalog with no token
|
|
458
|
+
* at all — still has to find the snapshot its own scope owns.
|
|
459
|
+
*
|
|
460
|
+
* Everything outside `[a-z0-9]` is folded to `_`, so no scope can escape the
|
|
461
|
+
* `storages` directory.
|
|
462
|
+
*/
|
|
463
|
+
function catalogSnapshotName(base, scope) {
|
|
464
|
+
const normalized = scope.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
465
|
+
return normalized === "" ? base : `${base}-${normalized}`;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Read the persisted snapshot for one catalog.
|
|
469
|
+
*
|
|
470
|
+
* Shape errors and unreadable files both return `undefined` — the caller keeps
|
|
471
|
+
* its in-memory state, and the next successful fetch overwrites the file.
|
|
472
|
+
*/
|
|
473
|
+
async function readCatalogSnapshot(name, parseModels) {
|
|
474
|
+
try {
|
|
475
|
+
const content = await fs.readFile(catalogSnapshotPath(name), "utf8");
|
|
476
|
+
const parsed = JSON.parse(content);
|
|
477
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
|
|
478
|
+
const record = parsed;
|
|
479
|
+
const fetchedAt = record.fetchedAt;
|
|
480
|
+
if (typeof fetchedAt !== "number" || !Number.isFinite(fetchedAt) || fetchedAt <= 0) return void 0;
|
|
481
|
+
const models = parseModels(record.models);
|
|
482
|
+
if (models === void 0 || models.length === 0) return void 0;
|
|
483
|
+
return {
|
|
484
|
+
fetchedAt,
|
|
485
|
+
models
|
|
486
|
+
};
|
|
487
|
+
} catch {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Snapshot writes still in flight.
|
|
493
|
+
*
|
|
494
|
+
* Callers fire-and-forget these writes, so nothing in production ever awaits
|
|
495
|
+
* one. A test that deletes its private `storages` directory between cases
|
|
496
|
+
* cannot know when that is safe, and a write that lands after the delete
|
|
497
|
+
* recreates the file the next case asserts is absent — which is how a stale
|
|
498
|
+
* listing leaked into a later test and failed it on CI. Registering each write
|
|
499
|
+
* gives {@link flushCatalogSnapshots} a deterministic point to wait on.
|
|
500
|
+
*/
|
|
501
|
+
const pendingSnapshotWrites = /* @__PURE__ */ new Set();
|
|
502
|
+
/** Persist one snapshot; a failure is logged nowhere and never thrown. */
|
|
503
|
+
function writeCatalogSnapshot(name, models, fetchedAt) {
|
|
504
|
+
const write = (async () => {
|
|
505
|
+
try {
|
|
506
|
+
const filePath = catalogSnapshotPath(name);
|
|
507
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
508
|
+
const tmp = `${filePath}.tmp.${Date.now()}`;
|
|
509
|
+
await fs.writeFile(tmp, JSON.stringify({
|
|
510
|
+
fetchedAt,
|
|
511
|
+
models
|
|
512
|
+
}, null, 2), "utf8");
|
|
513
|
+
await fs.rename(tmp, filePath);
|
|
514
|
+
} catch {}
|
|
515
|
+
})();
|
|
516
|
+
pendingSnapshotWrites.add(write);
|
|
517
|
+
write.finally(() => {
|
|
518
|
+
pendingSnapshotWrites.delete(write);
|
|
519
|
+
});
|
|
520
|
+
return write;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Warm an in-memory cache from the persisted snapshot when it is fresher than
|
|
524
|
+
* what the cache currently holds. Called lazily on the first cache miss of a
|
|
525
|
+
* process, so a restart serves the previous listing without waiting on the
|
|
526
|
+
* network.
|
|
527
|
+
*
|
|
528
|
+
* @returns the snapshot's `fetchedAt`, or `0` when no usable snapshot exists —
|
|
529
|
+
* the caller then fetches as before.
|
|
530
|
+
*/
|
|
531
|
+
async function rehydrateCatalogCache(name, parseModels, accept, currentFetchedAt) {
|
|
532
|
+
const snapshot = await readCatalogSnapshot(name, parseModels);
|
|
533
|
+
if (snapshot === void 0 || snapshot.fetchedAt <= currentFetchedAt) return 0;
|
|
534
|
+
accept(snapshot.fetchedAt, snapshot.models);
|
|
535
|
+
return snapshot.fetchedAt;
|
|
536
|
+
}
|
|
537
|
+
//#endregion
|
|
538
|
+
//#region src/host/wire-auth.ts
|
|
539
|
+
/**
|
|
540
|
+
* Turn-state header the Codex backend hands back on a responses response.
|
|
541
|
+
*
|
|
542
|
+
* The official CLI reads it and echoes it on the next request of the same
|
|
543
|
+
* conversation; the backend uses it to resume instead of re-ingesting the
|
|
544
|
+
* whole history. It is opaque, short-lived and account-scoped, so it is
|
|
545
|
+
* threaded through rather than computed (see `responses-client.ts`).
|
|
546
|
+
*/
|
|
547
|
+
const CODEX_TURN_STATE_HEADER = "x-codex-turn-state";
|
|
548
|
+
function codexHeaders(credentials, sessionId, options = {}) {
|
|
549
|
+
const dshAgent = attributionHeaders()["user-agent"] ?? "dsh/unknown";
|
|
550
|
+
return {
|
|
551
|
+
authorization: `Bearer ${credentials.accessToken}`,
|
|
552
|
+
...credentials.accountId ? { "chatgpt-account-id": credentials.accountId } : {},
|
|
553
|
+
"openai-beta": CODEX_OPENAI_BETA,
|
|
554
|
+
originator: CODEX_ORIGINATOR,
|
|
555
|
+
"user-agent": `dsh-chatgpt-subscription/${PLUGIN_VERSION} (${dshAgent})`,
|
|
556
|
+
...sessionId ? { "session-id": sessionId } : {},
|
|
557
|
+
...options.turnState ? { [CODEX_TURN_STATE_HEADER]: options.turnState } : {}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
function stableSessionId(value) {
|
|
561
|
+
const source = value === void 0 || value === "" ? randomUUID() : value;
|
|
562
|
+
return `dsh-${createHash("sha256").update(source).digest("hex").slice(0, 32)}`;
|
|
563
|
+
}
|
|
564
|
+
function retryAfterMs$1(headers) {
|
|
565
|
+
const raw = headers.get("retry-after");
|
|
566
|
+
if (raw === null) return void 0;
|
|
567
|
+
const seconds = Number(raw);
|
|
568
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.min(seconds * 1e3, 10 * 6e4);
|
|
569
|
+
const timestamp = Date.parse(raw);
|
|
570
|
+
if (!Number.isFinite(timestamp)) return void 0;
|
|
571
|
+
return Math.min(Math.max(0, timestamp - Date.now()), 10 * 6e4);
|
|
572
|
+
}
|
|
573
|
+
//#endregion
|
|
574
|
+
//#region src/host/codex-catalog.ts
|
|
575
|
+
/** How long a live listing is reused before the server is asked again. */
|
|
576
|
+
const CATALOG_TTL_MS = 15 * 6e4;
|
|
577
|
+
/**
|
|
578
|
+
* Snapshot scope.
|
|
579
|
+
*
|
|
580
|
+
* The Codex listing is account-scoped (each plan sees its own models), so the
|
|
581
|
+
* persisted snapshot is filed under the account it was fetched for. Without the
|
|
582
|
+
* scope a rehydrated listing from one account would answer another's picker.
|
|
583
|
+
*/
|
|
584
|
+
const SNAPSHOT_SCOPE = "chatgpt-codex";
|
|
585
|
+
let catalogCache$2 = null;
|
|
586
|
+
const catalogInFlight$3 = /* @__PURE__ */ new Map();
|
|
587
|
+
let snapshotRehydrated = null;
|
|
588
|
+
/**
|
|
589
|
+
* The listing currently held, or undefined before the first load.
|
|
590
|
+
*
|
|
591
|
+
* Callers that need a catalog with no credential read the shipped table
|
|
592
|
+
* directly; returning it here under a different entry type would force every
|
|
593
|
+
* one of them to convert it.
|
|
594
|
+
*/
|
|
595
|
+
function getCachedCatalog() {
|
|
596
|
+
return catalogCache$2?.models;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Read the models this subscription can call.
|
|
600
|
+
*
|
|
601
|
+
* The shipped table in `shared/model-catalog.ts` stays the floor: it is what
|
|
602
|
+
* answers a picker before the first sign-in, and what a failed listing falls
|
|
603
|
+
* back to. The subscription listing is the authority once one is available,
|
|
604
|
+
* because only the backend knows which models a plan currently serves and at
|
|
605
|
+
* what context window — the reason subscription-only models exist at all.
|
|
606
|
+
*
|
|
607
|
+
* Single-flighted per account, because the harness resolves every model of every
|
|
608
|
+
* provider when it builds the picker; without that, one picker build is one
|
|
609
|
+
* round trip per model.
|
|
610
|
+
*/
|
|
611
|
+
function loadCodexCatalog(credentials, options = {}) {
|
|
612
|
+
const key = accountKeyFor$1(credentials);
|
|
613
|
+
if (options.force !== true) {
|
|
614
|
+
const cached = catalogCache$2;
|
|
615
|
+
if (cached !== null && cached.key === key && Date.now() - cached.at < CATALOG_TTL_MS) return Promise.resolve(cached.models);
|
|
616
|
+
}
|
|
617
|
+
const pending = catalogInFlight$3.get(key);
|
|
618
|
+
if (pending !== void 0) return pending;
|
|
619
|
+
const run = performCatalogLoad$1(credentials, options).then((models) => {
|
|
620
|
+
catalogInFlight$3.delete(key);
|
|
621
|
+
return models;
|
|
622
|
+
}, (error) => {
|
|
623
|
+
catalogInFlight$3.delete(key);
|
|
624
|
+
throw error;
|
|
625
|
+
});
|
|
626
|
+
catalogInFlight$3.set(key, run);
|
|
627
|
+
return run;
|
|
628
|
+
}
|
|
629
|
+
async function performCatalogLoad$1(credentials, options) {
|
|
630
|
+
const key = accountKeyFor$1(credentials);
|
|
631
|
+
const fetchFn = options.fetchFn ?? fetch;
|
|
632
|
+
snapshotRehydrated ??= rehydrateSnapshot(key);
|
|
633
|
+
await snapshotRehydrated;
|
|
634
|
+
const cached = catalogCache$2;
|
|
635
|
+
if (cached !== null && cached.key === key && Date.now() - cached.at < CATALOG_TTL_MS) return cached.models;
|
|
636
|
+
let models;
|
|
637
|
+
let live = false;
|
|
638
|
+
try {
|
|
639
|
+
const response = await fetchFn(CODEX_MODELS_URL, {
|
|
640
|
+
method: "GET",
|
|
641
|
+
headers: {
|
|
642
|
+
...codexHeaders(credentials),
|
|
643
|
+
accept: "application/json"
|
|
644
|
+
},
|
|
645
|
+
signal: options.signal
|
|
646
|
+
});
|
|
647
|
+
if (!response.ok) throw new Error(`Codex model listing failed (${response.status}).`);
|
|
648
|
+
const listing = parseListing(await response.json().catch(() => void 0));
|
|
649
|
+
if (listing.length === 0) throw new Error("Codex model listing named no models this line understands.");
|
|
650
|
+
models = listing;
|
|
651
|
+
live = true;
|
|
652
|
+
writeCatalogSnapshot(catalogSnapshotName(SNAPSHOT_SCOPE, key), [...models], Date.now());
|
|
653
|
+
} catch {
|
|
654
|
+
models = getCachedCatalog() ?? [];
|
|
655
|
+
live = false;
|
|
656
|
+
}
|
|
657
|
+
catalogCache$2 = {
|
|
658
|
+
at: Date.now(),
|
|
659
|
+
models,
|
|
660
|
+
key,
|
|
661
|
+
live
|
|
662
|
+
};
|
|
663
|
+
return models;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Seed the in-memory cache from the last persisted listing.
|
|
667
|
+
*
|
|
668
|
+
* Best effort by construction: a missing or malformed snapshot leaves the cache
|
|
669
|
+
* cold, and the caller then simply makes the network call.
|
|
670
|
+
*/
|
|
671
|
+
async function rehydrateSnapshot(key) {
|
|
672
|
+
const snapshot = await readCatalogSnapshot(catalogSnapshotName(SNAPSHOT_SCOPE, key), (value) => Array.isArray(value) ? value.flatMap((item) => parseListingEntry$1(item)) : void 0).catch(() => void 0);
|
|
673
|
+
if (snapshot === void 0 || snapshot.models.length === 0) return;
|
|
674
|
+
if (catalogCache$2 !== null) return;
|
|
675
|
+
catalogCache$2 = {
|
|
676
|
+
at: snapshot.fetchedAt,
|
|
677
|
+
models: snapshot.models,
|
|
678
|
+
key,
|
|
679
|
+
live: true
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Read the listing payload.
|
|
684
|
+
*
|
|
685
|
+
* The documented shape is `{ models: [...] }`, but a bare array is accepted too
|
|
686
|
+
* so a backend that flattens the envelope does not read as an empty catalog.
|
|
687
|
+
*/
|
|
688
|
+
function parseListing(payload) {
|
|
689
|
+
const record = asRecord$10(payload);
|
|
690
|
+
const models = Array.isArray(payload) ? payload : record?.models;
|
|
691
|
+
if (!Array.isArray(models)) return [];
|
|
692
|
+
return models.flatMap((item) => parseListingEntry$1(item));
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* One listing entry.
|
|
696
|
+
*
|
|
697
|
+
* Only fields the backend actually documents are read. A model id this line has
|
|
698
|
+
* never heard of still produces an entry, because the listing is the authority
|
|
699
|
+
* on what the account may call — but every capability it does not state falls
|
|
700
|
+
* back to a conservative answer rather than to a guess.
|
|
701
|
+
*/
|
|
702
|
+
function parseListingEntry$1(value) {
|
|
703
|
+
const record = asRecord$10(value);
|
|
704
|
+
if (record === void 0) return [];
|
|
705
|
+
const id = asString$14(record.slug) ?? asString$14(record.id);
|
|
706
|
+
if (id === void 0) return [];
|
|
707
|
+
const contextWindow = asNumber$4(record.context_window) ?? asNumber$4(record.contextWindow);
|
|
708
|
+
const modalities = parseModalities(record.input_modalities);
|
|
709
|
+
const efforts = parseEfforts(record.supported_reasoning_levels);
|
|
710
|
+
const fallback = CODEX_MODEL_CATALOG.find((entry) => entry.id === id);
|
|
711
|
+
return [{
|
|
712
|
+
id,
|
|
713
|
+
name: asString$14(record.display_name) ?? asString$14(record.displayName) ?? fallback?.name ?? id,
|
|
714
|
+
contextWindow: contextWindow !== void 0 && contextWindow > 0 ? contextWindow : null,
|
|
715
|
+
inputModalities: modalities ?? (fallback ? [...fallback.inputModalities] : ["text"]),
|
|
716
|
+
...efforts !== null ? { reasoningEfforts: efforts } : {},
|
|
717
|
+
defaultReasoningEffort: asString$14(record.default_reasoning_level) ?? fallback?.defaultReasoningEffort ?? null
|
|
718
|
+
}];
|
|
719
|
+
}
|
|
720
|
+
function parseModalities(value) {
|
|
721
|
+
if (!Array.isArray(value)) return null;
|
|
722
|
+
const out = [];
|
|
723
|
+
for (const item of value) {
|
|
724
|
+
const name = asString$14(item);
|
|
725
|
+
if (name === "text" || name === "image") out.push(name);
|
|
726
|
+
}
|
|
727
|
+
return out.length > 0 ? out : null;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Reasoning levels the listing states, in the order it stated them.
|
|
731
|
+
*
|
|
732
|
+
* The backend writes each level as `{ "effort": "high" }`; a bare string is
|
|
733
|
+
* accepted too, since that is the other spelling seen in the wild.
|
|
734
|
+
*/
|
|
735
|
+
function parseEfforts(value) {
|
|
736
|
+
if (!Array.isArray(value)) return null;
|
|
737
|
+
const out = [];
|
|
738
|
+
for (const item of value) {
|
|
739
|
+
const effort = asString$14(item) ?? asString$14(asRecord$10(item)?.effort);
|
|
740
|
+
if (effort !== void 0 && !out.includes(effort)) out.push(effort);
|
|
741
|
+
}
|
|
742
|
+
return out.length > 0 ? out : null;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Cache identity for one credential.
|
|
746
|
+
*
|
|
747
|
+
* The account id is the entitlement boundary: two accounts of the same user can
|
|
748
|
+
* see different listings, and a pool rotates between them, so the key has to
|
|
749
|
+
* follow the account rather than the person.
|
|
750
|
+
*/
|
|
751
|
+
function accountKeyFor$1(credentials) {
|
|
752
|
+
return credentials.accountId || "default";
|
|
753
|
+
}
|
|
754
|
+
function asRecord$10(value) {
|
|
755
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
756
|
+
}
|
|
757
|
+
function asString$14(value) {
|
|
758
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
759
|
+
}
|
|
760
|
+
function asNumber$4(value) {
|
|
761
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
762
|
+
}
|
|
763
|
+
//#endregion
|
|
370
764
|
//#region src/host/common/credential-read-cache.ts
|
|
371
765
|
/**
|
|
372
766
|
* Identity a file-backed credential store reports when nothing is stored.
|
|
@@ -2752,31 +3146,6 @@ function normalizeGenerateOptions(options) {
|
|
|
2752
3146
|
};
|
|
2753
3147
|
}
|
|
2754
3148
|
//#endregion
|
|
2755
|
-
//#region src/host/wire-auth.ts
|
|
2756
|
-
function codexHeaders(credentials, sessionId) {
|
|
2757
|
-
const dshAgent = attributionHeaders()["user-agent"] ?? "dsh/unknown";
|
|
2758
|
-
return {
|
|
2759
|
-
authorization: `Bearer ${credentials.accessToken}`,
|
|
2760
|
-
...credentials.accountId ? { "chatgpt-account-id": credentials.accountId } : {},
|
|
2761
|
-
originator: CODEX_ORIGINATOR,
|
|
2762
|
-
"user-agent": `dsh-chatgpt-subscription/${PLUGIN_VERSION} (${dshAgent})`,
|
|
2763
|
-
...sessionId ? { "session-id": sessionId } : {}
|
|
2764
|
-
};
|
|
2765
|
-
}
|
|
2766
|
-
function stableSessionId(value) {
|
|
2767
|
-
const source = value === void 0 || value === "" ? randomUUID() : value;
|
|
2768
|
-
return `dsh-${createHash("sha256").update(source).digest("hex").slice(0, 32)}`;
|
|
2769
|
-
}
|
|
2770
|
-
function retryAfterMs$1(headers) {
|
|
2771
|
-
const raw = headers.get("retry-after");
|
|
2772
|
-
if (raw === null) return void 0;
|
|
2773
|
-
const seconds = Number(raw);
|
|
2774
|
-
if (Number.isFinite(seconds) && seconds >= 0) return Math.min(seconds * 1e3, 10 * 6e4);
|
|
2775
|
-
const timestamp = Date.parse(raw);
|
|
2776
|
-
if (!Number.isFinite(timestamp)) return void 0;
|
|
2777
|
-
return Math.min(Math.max(0, timestamp - Date.now()), 10 * 6e4);
|
|
2778
|
-
}
|
|
2779
|
-
//#endregion
|
|
2780
3149
|
//#region src/host/codex-images.ts
|
|
2781
3150
|
const PNG_SIGNATURE = [
|
|
2782
3151
|
137,
|
|
@@ -2930,7 +3299,6 @@ function requestImage(fetchFn, credentials, prompt, turnId, signal) {
|
|
|
2930
3299
|
method: "POST",
|
|
2931
3300
|
headers: {
|
|
2932
3301
|
...codexHeaders(credentials),
|
|
2933
|
-
originator: "pi",
|
|
2934
3302
|
accept: "application/json",
|
|
2935
3303
|
"content-type": "application/json",
|
|
2936
3304
|
"x-codex-image-turn-id": turnId
|
|
@@ -3030,7 +3398,6 @@ function sendSearch(fetchFn, credentials, query, model, id, signal) {
|
|
|
3030
3398
|
method: "POST",
|
|
3031
3399
|
headers: {
|
|
3032
3400
|
...codexHeaders(credentials),
|
|
3033
|
-
originator: "pi",
|
|
3034
3401
|
accept: "application/json",
|
|
3035
3402
|
"content-type": "application/json"
|
|
3036
3403
|
},
|
|
@@ -3183,8 +3550,8 @@ function parseEnvProxy(env = process.env, options = {}) {
|
|
|
3183
3550
|
try {
|
|
3184
3551
|
const dshHome = env.DSH_HOME || path.join(os.homedir(), ".dsh");
|
|
3185
3552
|
const envFile = options.envFile ?? path.join(dshHome, ".env");
|
|
3186
|
-
if (fs.existsSync(envFile)) {
|
|
3187
|
-
const match = fs.readFileSync(envFile, "utf8").match(/^(?:export\s+)?(?:HTTPS_PROXY|https_proxy|HTTP_PROXY|http_proxy|ALL_PROXY|all_proxy)\s*=\s*["']?([^"'\r\n]+)["']?/m);
|
|
3553
|
+
if (fs$1.existsSync(envFile)) {
|
|
3554
|
+
const match = fs$1.readFileSync(envFile, "utf8").match(/^(?:export\s+)?(?:HTTPS_PROXY|https_proxy|HTTP_PROXY|http_proxy|ALL_PROXY|all_proxy)\s*=\s*["']?([^"'\r\n]+)["']?/m);
|
|
3188
3555
|
if (match && match[1]?.trim()) return normalizeProxyUrl(match[1].trim());
|
|
3189
3556
|
}
|
|
3190
3557
|
} catch {}
|
|
@@ -3805,7 +4172,7 @@ var FileCredentialStore$2 = class {
|
|
|
3805
4172
|
}
|
|
3806
4173
|
async removeLegacy() {
|
|
3807
4174
|
try {
|
|
3808
|
-
await
|
|
4175
|
+
await fs.unlink(this.filePath);
|
|
3809
4176
|
} catch (error) {
|
|
3810
4177
|
if (error.code !== "ENOENT") throw new Error("Antigravity legacy credential removal failed");
|
|
3811
4178
|
}
|
|
@@ -3824,11 +4191,11 @@ var FileCredentialStore$2 = class {
|
|
|
3824
4191
|
}
|
|
3825
4192
|
let legacy;
|
|
3826
4193
|
try {
|
|
3827
|
-
const stats = await
|
|
4194
|
+
const stats = await fs.lstat(this.filePath);
|
|
3828
4195
|
if (!stats.isFile() || stats.isSymbolicLink()) throw new Error("Invalid credential file");
|
|
3829
4196
|
if (process.getuid && stats.uid !== process.getuid()) throw new Error("Invalid credential owner");
|
|
3830
|
-
if (process.platform !== "win32") await
|
|
3831
|
-
legacy = await
|
|
4197
|
+
if (process.platform !== "win32") await fs.chmod(this.filePath, 384);
|
|
4198
|
+
legacy = await fs.readFile(this.filePath, "utf8");
|
|
3832
4199
|
} catch (error) {
|
|
3833
4200
|
if (error.code === "ENOENT") return null;
|
|
3834
4201
|
throw new Error("Antigravity legacy credential read failed");
|
|
@@ -3863,7 +4230,7 @@ var FileModelSettingsStore$2 = class {
|
|
|
3863
4230
|
}
|
|
3864
4231
|
async read() {
|
|
3865
4232
|
try {
|
|
3866
|
-
const content = await
|
|
4233
|
+
const content = await fs.readFile(this.filePath, "utf8");
|
|
3867
4234
|
const parsed = JSON.parse(content);
|
|
3868
4235
|
if (typeof parsed === "object" && parsed !== null) {
|
|
3869
4236
|
const record = parsed;
|
|
@@ -3889,10 +4256,10 @@ var FileModelSettingsStore$2 = class {
|
|
|
3889
4256
|
};
|
|
3890
4257
|
}
|
|
3891
4258
|
async write(settings) {
|
|
3892
|
-
await
|
|
4259
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
3893
4260
|
const tmp = `${this.filePath}.tmp.${Date.now()}`;
|
|
3894
|
-
await
|
|
3895
|
-
await
|
|
4261
|
+
await fs.writeFile(tmp, JSON.stringify(settings, null, 2), "utf8");
|
|
4262
|
+
await fs.rename(tmp, this.filePath);
|
|
3896
4263
|
}
|
|
3897
4264
|
async updateSettings(patch) {
|
|
3898
4265
|
const current = await this.read();
|
|
@@ -4018,7 +4385,7 @@ var FilePreferencesStore = class {
|
|
|
4018
4385
|
async readDocument() {
|
|
4019
4386
|
let parsed;
|
|
4020
4387
|
try {
|
|
4021
|
-
parsed = JSON.parse(await
|
|
4388
|
+
parsed = JSON.parse(await fs.readFile(this.filePath, "utf8"));
|
|
4022
4389
|
} catch {
|
|
4023
4390
|
return;
|
|
4024
4391
|
}
|
|
@@ -4039,17 +4406,17 @@ var FilePreferencesStore = class {
|
|
|
4039
4406
|
for (const listener of [...this.listeners]) listener(next, prev);
|
|
4040
4407
|
}
|
|
4041
4408
|
async write(value) {
|
|
4042
|
-
await
|
|
4409
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
4043
4410
|
writeSequence += 1;
|
|
4044
4411
|
const tmp = `${this.filePath}.tmp.${process.pid}.${writeSequence}`;
|
|
4045
4412
|
try {
|
|
4046
|
-
await
|
|
4413
|
+
await fs.writeFile(tmp, JSON.stringify(value, null, 2), {
|
|
4047
4414
|
encoding: "utf8",
|
|
4048
4415
|
mode: 384
|
|
4049
4416
|
});
|
|
4050
|
-
await
|
|
4417
|
+
await fs.rename(tmp, this.filePath);
|
|
4051
4418
|
} finally {
|
|
4052
|
-
await
|
|
4419
|
+
await fs.unlink(tmp).catch(() => void 0);
|
|
4053
4420
|
}
|
|
4054
4421
|
}
|
|
4055
4422
|
};
|
|
@@ -4114,7 +4481,7 @@ const LEGACY_DOCUMENTS = ["settings.yaml", "settings.yaml.imported"];
|
|
|
4114
4481
|
*/
|
|
4115
4482
|
async function readLegacyPreferences(homeDir = dshHomeDir$2()) {
|
|
4116
4483
|
for (const name of LEGACY_DOCUMENTS) {
|
|
4117
|
-
const document = await
|
|
4484
|
+
const document = await fs.readFile(path.join(homeDir, name), "utf8").catch(() => void 0);
|
|
4118
4485
|
if (document === void 0) continue;
|
|
4119
4486
|
const section = sectionOf(document, PREFERENCES_NAMESPACE);
|
|
4120
4487
|
if (section !== void 0 && Object.keys(section).length > 0) return section;
|
|
@@ -4422,7 +4789,7 @@ function normalizeReplayCallIds(items, normalizeCallId) {
|
|
|
4422
4789
|
call_id: normalizeCallId(item.call_id)
|
|
4423
4790
|
} : item);
|
|
4424
4791
|
}
|
|
4425
|
-
async function buildResponsesPayload(options, attachments, localRawImages = {}, outputVerbosity = null, fastMode = false, reasoningSummary = null) {
|
|
4792
|
+
async function buildResponsesPayload(options, attachments, localRawImages = {}, outputVerbosity = null, fastMode = false, reasoningSummary = null, promptCacheKey) {
|
|
4426
4793
|
const sandboxRetryTools = recentSandboxRetryToolNames(options.messages);
|
|
4427
4794
|
const resolveLocalRawImages = supportsImageInput(options);
|
|
4428
4795
|
const normalizeCallId = createCallIdNormalizer();
|
|
@@ -4490,7 +4857,8 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
|
|
|
4490
4857
|
input,
|
|
4491
4858
|
stream: true,
|
|
4492
4859
|
store: false,
|
|
4493
|
-
include: ["reasoning.encrypted_content"]
|
|
4860
|
+
include: ["reasoning.encrypted_content"],
|
|
4861
|
+
...promptCacheKey ? { prompt_cache_key: promptCacheKey } : {}
|
|
4494
4862
|
};
|
|
4495
4863
|
const instructions = [...instructionParts, localRawImageInstruction(localImageStats)].filter((value) => Boolean(value));
|
|
4496
4864
|
if (instructions.length > 0) payload.instructions = instructions.join("\n\n");
|
|
@@ -4506,6 +4874,8 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
|
|
|
4506
4874
|
}
|
|
4507
4875
|
if (outputVerbosity !== null) payload.text = { verbosity: outputVerbosity };
|
|
4508
4876
|
if (fastMode) payload.service_tier = "priority";
|
|
4877
|
+
const modelMaxTokens = codexModelMaxTokens(options.model);
|
|
4878
|
+
payload.max_output_tokens = options.maxTokens === void 0 ? modelMaxTokens : Math.min(options.maxTokens, modelMaxTokens);
|
|
4509
4879
|
if (options.reasoningEffort !== void 0) {
|
|
4510
4880
|
const effort = codexWireReasoningEffort(options.model, options.reasoningEffort);
|
|
4511
4881
|
payload.reasoning = codexModelSupportsReasoningSummary(options.model) ? {
|
|
@@ -4823,6 +5193,8 @@ const MAX_VISIBLE_REASONING_CHARS = 12e3;
|
|
|
4823
5193
|
const DEFAULT_POOL_COOLDOWN_MS = 15 * 6e4;
|
|
4824
5194
|
const REASONING_DELTA_FLUSH_CHARS = 768;
|
|
4825
5195
|
const REASONING_TRUNCATED_NOTICE = "\n\n[Reasoning summary truncated to keep the DSH web UI responsive.]";
|
|
5196
|
+
/** Cap on conversations whose turn state is remembered at once. */
|
|
5197
|
+
const MAX_TRACKED_TURN_STATES = 200;
|
|
4826
5198
|
var ResponsesClient = class {
|
|
4827
5199
|
oauth;
|
|
4828
5200
|
attachments;
|
|
@@ -4832,6 +5204,16 @@ var ResponsesClient = class {
|
|
|
4832
5204
|
fastMode;
|
|
4833
5205
|
reasoningSummary;
|
|
4834
5206
|
accountPool;
|
|
5207
|
+
/**
|
|
5208
|
+
* Opaque backend turn state, keyed by conversation session id.
|
|
5209
|
+
*
|
|
5210
|
+
* The Codex backend hands `x-codex-turn-state` back on each response and
|
|
5211
|
+
* expects it on the next request of the same conversation, which lets it
|
|
5212
|
+
* resume the turn instead of re-ingesting the whole history. The value is
|
|
5213
|
+
* short-lived and account-scoped, so it is stored per session and simply
|
|
5214
|
+
* dropped when the backend stops sending it — never a correctness input.
|
|
5215
|
+
*/
|
|
5216
|
+
turnStates = /* @__PURE__ */ new Map();
|
|
4835
5217
|
constructor(oauth, attachments, options = {}) {
|
|
4836
5218
|
this.oauth = oauth;
|
|
4837
5219
|
this.attachments = attachments;
|
|
@@ -4852,7 +5234,7 @@ var ResponsesClient = class {
|
|
|
4852
5234
|
let attemptOptions = options;
|
|
4853
5235
|
let response;
|
|
4854
5236
|
while (true) {
|
|
4855
|
-
const payload = await buildResponsesPayload(attemptOptions, this.attachments, this.localRawImages, this.outputVerbosity(), this.fastMode(), this.reasoningSummary());
|
|
5237
|
+
const payload = await buildResponsesPayload(attemptOptions, this.attachments, this.localRawImages, this.outputVerbosity(), this.fastMode(), this.reasoningSummary(), promptCacheKeyFor(sessionId));
|
|
4856
5238
|
try {
|
|
4857
5239
|
response = await this.send(payload, sessionId, options.signal);
|
|
4858
5240
|
break;
|
|
@@ -4887,9 +5269,36 @@ var ResponsesClient = class {
|
|
|
4887
5269
|
response = await this.request(payload, credentials, sessionId, signal);
|
|
4888
5270
|
}
|
|
4889
5271
|
if (!response.ok) throw await responseError(response);
|
|
5272
|
+
this.rememberTurnState(response, sessionId);
|
|
4890
5273
|
return response;
|
|
4891
5274
|
}
|
|
4892
5275
|
/**
|
|
5276
|
+
* Record the turn state a response carried, for the next turn of this session.
|
|
5277
|
+
*
|
|
5278
|
+
* A response that carries none clears any stored value: a backend that stops
|
|
5279
|
+
* sending the header has stopped honouring it, and replaying a stale value
|
|
5280
|
+
* would be guessing. An empty header is treated the same as absent.
|
|
5281
|
+
*/
|
|
5282
|
+
rememberTurnState(response, sessionId) {
|
|
5283
|
+
const next = response.headers.get(CODEX_TURN_STATE_HEADER)?.trim();
|
|
5284
|
+
if (next === void 0 || next === "") this.turnStates.delete(sessionId);
|
|
5285
|
+
else this.storeTurnState(sessionId, next);
|
|
5286
|
+
}
|
|
5287
|
+
/**
|
|
5288
|
+
* Bounded store: a long-lived host serves many sessions, and nothing here ever
|
|
5289
|
+
* signals that a session ended, so the map is capped and evicts the oldest
|
|
5290
|
+
* entry. The value is an optimization, so a miss only costs a full resend.
|
|
5291
|
+
*/
|
|
5292
|
+
storeTurnState(sessionId, value) {
|
|
5293
|
+
this.turnStates.delete(sessionId);
|
|
5294
|
+
this.turnStates.set(sessionId, value);
|
|
5295
|
+
while (this.turnStates.size > MAX_TRACKED_TURN_STATES) {
|
|
5296
|
+
const oldest = this.turnStates.keys().next();
|
|
5297
|
+
if (oldest.done === true) break;
|
|
5298
|
+
this.turnStates.delete(oldest.value);
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
5301
|
+
/**
|
|
4893
5302
|
* Send one payload with account rotation.
|
|
4894
5303
|
*
|
|
4895
5304
|
* The pool picks the account — skipping cooling, quota-exhausted and failed
|
|
@@ -4931,6 +5340,7 @@ var ResponsesClient = class {
|
|
|
4931
5340
|
});
|
|
4932
5341
|
}
|
|
4933
5342
|
if (!response.ok) throw await responseError(response);
|
|
5343
|
+
this.rememberTurnState(response, sessionId);
|
|
4934
5344
|
return response;
|
|
4935
5345
|
}
|
|
4936
5346
|
}
|
|
@@ -4939,7 +5349,7 @@ var ResponsesClient = class {
|
|
|
4939
5349
|
return await this.fetchFn(CODEX_RESPONSES_URL, {
|
|
4940
5350
|
method: "POST",
|
|
4941
5351
|
headers: {
|
|
4942
|
-
...codexHeaders(credentials, sessionId),
|
|
5352
|
+
...codexHeaders(credentials, sessionId, { turnState: this.turnStates.get(sessionId) }),
|
|
4943
5353
|
"content-type": "application/json",
|
|
4944
5354
|
accept: "text/event-stream"
|
|
4945
5355
|
},
|
|
@@ -4952,6 +5362,17 @@ var ResponsesClient = class {
|
|
|
4952
5362
|
}
|
|
4953
5363
|
}
|
|
4954
5364
|
};
|
|
5365
|
+
/**
|
|
5366
|
+
* Prompt cache key for one conversation.
|
|
5367
|
+
*
|
|
5368
|
+
* The backend keys its prefix cache on this string, so it must be stable for a
|
|
5369
|
+
* conversation and distinct between conversations — otherwise one session would
|
|
5370
|
+
* be served another session's cached prefix. The session id is already a hashed
|
|
5371
|
+
* opaque string, so it is reused directly rather than hashed again.
|
|
5372
|
+
*/
|
|
5373
|
+
function promptCacheKeyFor(sessionId) {
|
|
5374
|
+
return sessionId;
|
|
5375
|
+
}
|
|
4955
5376
|
async function* parseResponsesStream(response, signal, hiddenSandboxControls = /* @__PURE__ */ new Set()) {
|
|
4956
5377
|
if (response.body === null) throw new LlmError("Codex returned no response stream.", "PROVIDER_ERROR");
|
|
4957
5378
|
const reader = response.body.getReader();
|
|
@@ -5943,7 +6364,7 @@ function registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwit
|
|
|
5943
6364
|
"Content-Type": "application/javascript; charset=utf-8",
|
|
5944
6365
|
"Cache-Control": "public, max-age=86400"
|
|
5945
6366
|
});
|
|
5946
|
-
fs.createReadStream(mermaidPath).pipe(response);
|
|
6367
|
+
fs$1.createReadStream(mermaidPath).pipe(response);
|
|
5947
6368
|
} catch {
|
|
5948
6369
|
response.writeHead(404, { "Content-Type": "text/plain" });
|
|
5949
6370
|
response.end("Not found");
|
|
@@ -9509,7 +9930,7 @@ var FileCredentialStore$1 = class {
|
|
|
9509
9930
|
}
|
|
9510
9931
|
async removeLegacy() {
|
|
9511
9932
|
try {
|
|
9512
|
-
await
|
|
9933
|
+
await fs.unlink(this.filePath);
|
|
9513
9934
|
} catch (error) {
|
|
9514
9935
|
if (error.code !== "ENOENT") throw new Error("Command Code legacy credential removal failed");
|
|
9515
9936
|
}
|
|
@@ -9528,11 +9949,11 @@ var FileCredentialStore$1 = class {
|
|
|
9528
9949
|
}
|
|
9529
9950
|
let legacy;
|
|
9530
9951
|
try {
|
|
9531
|
-
const stats = await
|
|
9952
|
+
const stats = await fs.lstat(this.filePath);
|
|
9532
9953
|
if (!stats.isFile() || stats.isSymbolicLink()) throw new Error("Invalid credential file");
|
|
9533
9954
|
if (process.getuid && stats.uid !== process.getuid()) throw new Error("Invalid credential owner");
|
|
9534
|
-
if (process.platform !== "win32") await
|
|
9535
|
-
legacy = await
|
|
9955
|
+
if (process.platform !== "win32") await fs.chmod(this.filePath, 384);
|
|
9956
|
+
legacy = await fs.readFile(this.filePath, "utf8");
|
|
9536
9957
|
} catch (error) {
|
|
9537
9958
|
if (error.code === "ENOENT") return null;
|
|
9538
9959
|
throw new Error("Command Code legacy credential read failed");
|
|
@@ -9568,7 +9989,7 @@ var FileModelSettingsStore$1 = class {
|
|
|
9568
9989
|
}
|
|
9569
9990
|
async read() {
|
|
9570
9991
|
try {
|
|
9571
|
-
const content = await
|
|
9992
|
+
const content = await fs.readFile(this.filePath, "utf8");
|
|
9572
9993
|
const parsed = JSON.parse(content);
|
|
9573
9994
|
if (typeof parsed === "object" && parsed !== null) {
|
|
9574
9995
|
const record = parsed;
|
|
@@ -9594,10 +10015,10 @@ var FileModelSettingsStore$1 = class {
|
|
|
9594
10015
|
};
|
|
9595
10016
|
}
|
|
9596
10017
|
async write(settings) {
|
|
9597
|
-
await
|
|
10018
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
9598
10019
|
const tmp = `${this.filePath}.tmp.${Date.now()}`;
|
|
9599
|
-
await
|
|
9600
|
-
await
|
|
10020
|
+
await fs.writeFile(tmp, JSON.stringify(settings, null, 2), "utf8");
|
|
10021
|
+
await fs.rename(tmp, this.filePath);
|
|
9601
10022
|
}
|
|
9602
10023
|
async updateSettings(patch) {
|
|
9603
10024
|
const current = await this.read();
|
|
@@ -9696,121 +10117,6 @@ function commandCodePlanLabel(planId) {
|
|
|
9696
10117
|
return resolveCommandCodePlan(planId)?.name ?? planId;
|
|
9697
10118
|
}
|
|
9698
10119
|
//#endregion
|
|
9699
|
-
//#region src/host/common/catalog-snapshot.ts
|
|
9700
|
-
/**
|
|
9701
|
-
* Persistent snapshot for one provider's live model catalog.
|
|
9702
|
-
*
|
|
9703
|
-
* The in-memory catalog caches keep a process alive between requests, but they
|
|
9704
|
-
* start cold: on the first call after a restart (or a plugin reinstall) the
|
|
9705
|
-
* picker and the settings page both wait on the upstream listing before they
|
|
9706
|
-
* can render anything, and an unreachable endpoint turns that wait into the
|
|
9707
|
-
* full discovery timeout — "the model server is unavailable" on a fresh
|
|
9708
|
-
* install. The catalog is not fast-moving data, so the last successful listing
|
|
9709
|
-
* is persisted to `<DSH_HOME>/storages/` and rehydrated into the in-memory
|
|
9710
|
-
* cache on first use. A snapshot the caller can read is therefore available
|
|
9711
|
-
* from the very first call, with no network in the path.
|
|
9712
|
-
*
|
|
9713
|
-
* Writes are best-effort: persistence is an optimization, never a capability.
|
|
9714
|
-
* A read-only home or a locked file must not fail the fetch that produced the
|
|
9715
|
-
* snapshot.
|
|
9716
|
-
*/
|
|
9717
|
-
/** File name of the persisted snapshot for one catalog. */
|
|
9718
|
-
function catalogSnapshotPath(name) {
|
|
9719
|
-
return path.join(dshHomeDir(), "storages", `${name}-catalog.json`);
|
|
9720
|
-
}
|
|
9721
|
-
/**
|
|
9722
|
-
* Snapshot name for one catalog *and* one credential scope.
|
|
9723
|
-
*
|
|
9724
|
-
* A persisted snapshot answers before any credential is read — that is the whole
|
|
9725
|
-
* point of it — so the file itself has to carry the scope it was fetched under.
|
|
9726
|
-
* Otherwise the first call after a restart, for any account, is answered by
|
|
9727
|
-
* whatever listing some other scope happened to persist last: a Kimi listing
|
|
9728
|
-
* fetched for the mainland-cn endpoint would answer a global caller with models
|
|
9729
|
-
* and context windows that region does not serve, and a Command Code listing
|
|
9730
|
-
* fetched against one API environment would answer the other.
|
|
9731
|
-
*
|
|
9732
|
-
* The scope is part of the file name rather than of the payload because a caller
|
|
9733
|
-
* that cannot name the account — the harness resolves this catalog with no token
|
|
9734
|
-
* at all — still has to find the snapshot its own scope owns.
|
|
9735
|
-
*
|
|
9736
|
-
* Everything outside `[a-z0-9]` is folded to `_`, so no scope can escape the
|
|
9737
|
-
* `storages` directory.
|
|
9738
|
-
*/
|
|
9739
|
-
function catalogSnapshotName(base, scope) {
|
|
9740
|
-
const normalized = scope.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
9741
|
-
return normalized === "" ? base : `${base}-${normalized}`;
|
|
9742
|
-
}
|
|
9743
|
-
/**
|
|
9744
|
-
* Read the persisted snapshot for one catalog.
|
|
9745
|
-
*
|
|
9746
|
-
* Shape errors and unreadable files both return `undefined` — the caller keeps
|
|
9747
|
-
* its in-memory state, and the next successful fetch overwrites the file.
|
|
9748
|
-
*/
|
|
9749
|
-
async function readCatalogSnapshot(name, parseModels) {
|
|
9750
|
-
try {
|
|
9751
|
-
const content = await fsPromises.readFile(catalogSnapshotPath(name), "utf8");
|
|
9752
|
-
const parsed = JSON.parse(content);
|
|
9753
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
|
|
9754
|
-
const record = parsed;
|
|
9755
|
-
const fetchedAt = record.fetchedAt;
|
|
9756
|
-
if (typeof fetchedAt !== "number" || !Number.isFinite(fetchedAt) || fetchedAt <= 0) return void 0;
|
|
9757
|
-
const models = parseModels(record.models);
|
|
9758
|
-
if (models === void 0 || models.length === 0) return void 0;
|
|
9759
|
-
return {
|
|
9760
|
-
fetchedAt,
|
|
9761
|
-
models
|
|
9762
|
-
};
|
|
9763
|
-
} catch {
|
|
9764
|
-
return;
|
|
9765
|
-
}
|
|
9766
|
-
}
|
|
9767
|
-
/**
|
|
9768
|
-
* Snapshot writes still in flight.
|
|
9769
|
-
*
|
|
9770
|
-
* Callers fire-and-forget these writes, so nothing in production ever awaits
|
|
9771
|
-
* one. A test that deletes its private `storages` directory between cases
|
|
9772
|
-
* cannot know when that is safe, and a write that lands after the delete
|
|
9773
|
-
* recreates the file the next case asserts is absent — which is how a stale
|
|
9774
|
-
* listing leaked into a later test and failed it on CI. Registering each write
|
|
9775
|
-
* gives {@link flushCatalogSnapshots} a deterministic point to wait on.
|
|
9776
|
-
*/
|
|
9777
|
-
const pendingSnapshotWrites = /* @__PURE__ */ new Set();
|
|
9778
|
-
/** Persist one snapshot; a failure is logged nowhere and never thrown. */
|
|
9779
|
-
function writeCatalogSnapshot(name, models, fetchedAt) {
|
|
9780
|
-
const write = (async () => {
|
|
9781
|
-
try {
|
|
9782
|
-
const filePath = catalogSnapshotPath(name);
|
|
9783
|
-
await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
|
|
9784
|
-
const tmp = `${filePath}.tmp.${Date.now()}`;
|
|
9785
|
-
await fsPromises.writeFile(tmp, JSON.stringify({
|
|
9786
|
-
fetchedAt,
|
|
9787
|
-
models
|
|
9788
|
-
}, null, 2), "utf8");
|
|
9789
|
-
await fsPromises.rename(tmp, filePath);
|
|
9790
|
-
} catch {}
|
|
9791
|
-
})();
|
|
9792
|
-
pendingSnapshotWrites.add(write);
|
|
9793
|
-
write.finally(() => {
|
|
9794
|
-
pendingSnapshotWrites.delete(write);
|
|
9795
|
-
});
|
|
9796
|
-
return write;
|
|
9797
|
-
}
|
|
9798
|
-
/**
|
|
9799
|
-
* Warm an in-memory cache from the persisted snapshot when it is fresher than
|
|
9800
|
-
* what the cache currently holds. Called lazily on the first cache miss of a
|
|
9801
|
-
* process, so a restart serves the previous listing without waiting on the
|
|
9802
|
-
* network.
|
|
9803
|
-
*
|
|
9804
|
-
* @returns the snapshot's `fetchedAt`, or `0` when no usable snapshot exists —
|
|
9805
|
-
* the caller then fetches as before.
|
|
9806
|
-
*/
|
|
9807
|
-
async function rehydrateCatalogCache(name, parseModels, accept, currentFetchedAt) {
|
|
9808
|
-
const snapshot = await readCatalogSnapshot(name, parseModels);
|
|
9809
|
-
if (snapshot === void 0 || snapshot.fetchedAt <= currentFetchedAt) return 0;
|
|
9810
|
-
accept(snapshot.fetchedAt, snapshot.models);
|
|
9811
|
-
return snapshot.fetchedAt;
|
|
9812
|
-
}
|
|
9813
|
-
//#endregion
|
|
9814
10120
|
//#region src/host/command-code/client.ts
|
|
9815
10121
|
const CLI_VERSION = "1.0.0";
|
|
9816
10122
|
/**
|
|
@@ -13080,6 +13386,10 @@ function maxOutputTokensFor$3(modelId, contextWindow) {
|
|
|
13080
13386
|
const capped = (contextWindow ?? kimiCodeModelDef(modelId)?.contextWindow ?? 262144) - CONTEXT_HEADROOM_TOKENS$1;
|
|
13081
13387
|
return Math.max(declared, capped);
|
|
13082
13388
|
}
|
|
13389
|
+
/** Whether a stored preference names a tier this line can send. */
|
|
13390
|
+
function isKimiCacheTtl(value) {
|
|
13391
|
+
return value === "5m" || value === "1h";
|
|
13392
|
+
}
|
|
13083
13393
|
/**
|
|
13084
13394
|
* Reduce the requested output cap so prompt + output fit the window.
|
|
13085
13395
|
*
|
|
@@ -13129,7 +13439,8 @@ function registerKimiCodePreferenceStore(settings, fallbackStore = new FileModel
|
|
|
13129
13439
|
enabledModelIds: [...DEFAULT_ENABLED_MODEL_IDS$3],
|
|
13130
13440
|
catalogModels: [],
|
|
13131
13441
|
contextWindowOverrides: {},
|
|
13132
|
-
defaultReasoningEffort: null
|
|
13442
|
+
defaultReasoningEffort: null,
|
|
13443
|
+
cacheTtl: null
|
|
13133
13444
|
};
|
|
13134
13445
|
fallbackStore.read().then((stored) => {
|
|
13135
13446
|
snapshot = stored;
|
|
@@ -13146,7 +13457,12 @@ function registerKimiCodePreferenceStore(settings, fallbackStore = new FileModel
|
|
|
13146
13457
|
enabled: z.boolean().default(true),
|
|
13147
13458
|
enabledModelIds: z.array(z.string()).default([...DEFAULT_ENABLED_MODEL_IDS$3]),
|
|
13148
13459
|
contextWindowOverrides: z.dict(z.number()).default({}),
|
|
13149
|
-
defaultReasoningEffort: z.union([...KIMI_CODE_REASONING_EFFORTS.map((effort) => z.const(effort)), z.const(null)]).default(null)
|
|
13460
|
+
defaultReasoningEffort: z.union([...KIMI_CODE_REASONING_EFFORTS.map((effort) => z.const(effort)), z.const(null)]).default(null),
|
|
13461
|
+
cacheTtl: z.union([
|
|
13462
|
+
z.const("5m"),
|
|
13463
|
+
z.const("1h"),
|
|
13464
|
+
z.const(null)
|
|
13465
|
+
]).default(null)
|
|
13150
13466
|
}));
|
|
13151
13467
|
return {
|
|
13152
13468
|
status: () => {
|
|
@@ -13156,7 +13472,8 @@ function registerKimiCodePreferenceStore(settings, fallbackStore = new FileModel
|
|
|
13156
13472
|
enabledModelIds: value.enabledModelIds,
|
|
13157
13473
|
catalogModels: [],
|
|
13158
13474
|
contextWindowOverrides: value.contextWindowOverrides,
|
|
13159
|
-
defaultReasoningEffort: value.defaultReasoningEffort
|
|
13475
|
+
defaultReasoningEffort: value.defaultReasoningEffort,
|
|
13476
|
+
cacheTtl: isKimiCacheTtl(value.cacheTtl) ? value.cacheTtl : null
|
|
13160
13477
|
};
|
|
13161
13478
|
},
|
|
13162
13479
|
update: async (patch) => {
|
|
@@ -13165,7 +13482,8 @@ function registerKimiCodePreferenceStore(settings, fallbackStore = new FileModel
|
|
|
13165
13482
|
enabled: patch.enabled !== void 0 ? patch.enabled : current.enabled !== false,
|
|
13166
13483
|
enabledModelIds: patch.enabledModelIds ?? current.enabledModelIds,
|
|
13167
13484
|
contextWindowOverrides: patch.contextWindowOverrides ? mergeContextWindowOverrides(current.contextWindowOverrides, patch.contextWindowOverrides) : current.contextWindowOverrides,
|
|
13168
|
-
defaultReasoningEffort: patch.defaultReasoningEffort !== void 0 ? patch.defaultReasoningEffort : current.defaultReasoningEffort
|
|
13485
|
+
defaultReasoningEffort: patch.defaultReasoningEffort !== void 0 ? patch.defaultReasoningEffort : current.defaultReasoningEffort,
|
|
13486
|
+
cacheTtl: patch.cacheTtl !== void 0 ? patch.cacheTtl : current.cacheTtl
|
|
13169
13487
|
};
|
|
13170
13488
|
await scope.update(normalized);
|
|
13171
13489
|
fallbackStore.updateSettings(patch).catch(() => void 0);
|
|
@@ -13287,7 +13605,7 @@ var FileCredentialStore$3 = class {
|
|
|
13287
13605
|
}
|
|
13288
13606
|
async removeLegacy() {
|
|
13289
13607
|
try {
|
|
13290
|
-
await
|
|
13608
|
+
await fs.unlink(this.filePath);
|
|
13291
13609
|
} catch (error) {
|
|
13292
13610
|
if (error.code !== "ENOENT") throw new Error("Kimi Code legacy credential removal failed");
|
|
13293
13611
|
}
|
|
@@ -13306,11 +13624,11 @@ var FileCredentialStore$3 = class {
|
|
|
13306
13624
|
}
|
|
13307
13625
|
let legacy;
|
|
13308
13626
|
try {
|
|
13309
|
-
const stats = await
|
|
13627
|
+
const stats = await fs.lstat(this.filePath);
|
|
13310
13628
|
if (!stats.isFile() || stats.isSymbolicLink()) throw new Error("Invalid credential file");
|
|
13311
13629
|
if (process.getuid && stats.uid !== process.getuid()) throw new Error("Invalid credential owner");
|
|
13312
|
-
if (process.platform !== "win32") await
|
|
13313
|
-
legacy = await
|
|
13630
|
+
if (process.platform !== "win32") await fs.chmod(this.filePath, 384);
|
|
13631
|
+
legacy = await fs.readFile(this.filePath, "utf8");
|
|
13314
13632
|
} catch (error) {
|
|
13315
13633
|
if (error.code === "ENOENT") return null;
|
|
13316
13634
|
throw new Error("Kimi Code legacy credential read failed");
|
|
@@ -13346,7 +13664,7 @@ var FileModelSettingsStore$3 = class {
|
|
|
13346
13664
|
}
|
|
13347
13665
|
async read() {
|
|
13348
13666
|
try {
|
|
13349
|
-
const content = await
|
|
13667
|
+
const content = await fs.readFile(this.filePath, "utf8");
|
|
13350
13668
|
const parsed = JSON.parse(content);
|
|
13351
13669
|
if (typeof parsed === "object" && parsed !== null) {
|
|
13352
13670
|
const record = parsed;
|
|
@@ -13359,7 +13677,8 @@ var FileModelSettingsStore$3 = class {
|
|
|
13359
13677
|
enabledModelIds,
|
|
13360
13678
|
catalogModels,
|
|
13361
13679
|
contextWindowOverrides,
|
|
13362
|
-
defaultReasoningEffort
|
|
13680
|
+
defaultReasoningEffort,
|
|
13681
|
+
cacheTtl: isKimiCacheTtl(record.cacheTtl) ? record.cacheTtl : null
|
|
13363
13682
|
};
|
|
13364
13683
|
}
|
|
13365
13684
|
} catch {}
|
|
@@ -13368,14 +13687,15 @@ var FileModelSettingsStore$3 = class {
|
|
|
13368
13687
|
enabledModelIds: [...DEFAULT_ENABLED_MODEL_IDS$3],
|
|
13369
13688
|
catalogModels: [],
|
|
13370
13689
|
contextWindowOverrides: {},
|
|
13371
|
-
defaultReasoningEffort: null
|
|
13690
|
+
defaultReasoningEffort: null,
|
|
13691
|
+
cacheTtl: null
|
|
13372
13692
|
};
|
|
13373
13693
|
}
|
|
13374
13694
|
async write(settings) {
|
|
13375
|
-
await
|
|
13695
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
13376
13696
|
const tmp = `${this.filePath}.tmp.${Date.now()}`;
|
|
13377
|
-
await
|
|
13378
|
-
await
|
|
13697
|
+
await fs.writeFile(tmp, JSON.stringify(settings, null, 2), "utf8");
|
|
13698
|
+
await fs.rename(tmp, this.filePath);
|
|
13379
13699
|
}
|
|
13380
13700
|
async updateSettings(patch) {
|
|
13381
13701
|
const current = await this.read();
|
|
@@ -13384,7 +13704,8 @@ var FileModelSettingsStore$3 = class {
|
|
|
13384
13704
|
...patch.enabled !== void 0 ? { enabled: patch.enabled } : {},
|
|
13385
13705
|
...patch.enabledModelIds !== void 0 ? { enabledModelIds: patch.enabledModelIds } : {},
|
|
13386
13706
|
...patch.contextWindowOverrides !== void 0 ? { contextWindowOverrides: mergeContextWindowOverrides(current.contextWindowOverrides, patch.contextWindowOverrides) } : {},
|
|
13387
|
-
...patch.defaultReasoningEffort !== void 0 ? { defaultReasoningEffort: patch.defaultReasoningEffort } : {}
|
|
13707
|
+
...patch.defaultReasoningEffort !== void 0 ? { defaultReasoningEffort: patch.defaultReasoningEffort } : {},
|
|
13708
|
+
...patch.cacheTtl !== void 0 && patch.cacheTtl !== null ? { cacheTtl: isKimiCacheTtl(patch.cacheTtl) ? patch.cacheTtl : current.cacheTtl } : patch.cacheTtl === null ? { cacheTtl: null } : {}
|
|
13388
13709
|
};
|
|
13389
13710
|
await this.write(next);
|
|
13390
13711
|
return next;
|
|
@@ -13414,7 +13735,7 @@ async function resolveRegion(env = process.env) {
|
|
|
13414
13735
|
return pinned.includes(".ai") ? "global" : "mainland-cn";
|
|
13415
13736
|
}
|
|
13416
13737
|
try {
|
|
13417
|
-
const marker = (await
|
|
13738
|
+
const marker = (await fs.readFile(regionMarkerPath(), "utf8")).trim();
|
|
13418
13739
|
if (isRegion$4(marker)) return marker;
|
|
13419
13740
|
} catch {}
|
|
13420
13741
|
return "mainland-cn";
|
|
@@ -13423,8 +13744,8 @@ async function resolveRegion(env = process.env) {
|
|
|
13423
13744
|
async function persistRegion(region) {
|
|
13424
13745
|
try {
|
|
13425
13746
|
const file = regionMarkerPath();
|
|
13426
|
-
await
|
|
13427
|
-
await
|
|
13747
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
13748
|
+
await fs.writeFile(file, region, "utf8");
|
|
13428
13749
|
} catch {}
|
|
13429
13750
|
}
|
|
13430
13751
|
//#endregion
|
|
@@ -13526,13 +13847,13 @@ function deviceModel() {
|
|
|
13526
13847
|
async function getDeviceId() {
|
|
13527
13848
|
const file = deviceIdPath();
|
|
13528
13849
|
try {
|
|
13529
|
-
const existing = (await
|
|
13850
|
+
const existing = (await fs.readFile(file, "utf8")).trim();
|
|
13530
13851
|
if (existing !== "") return existing;
|
|
13531
13852
|
} catch {}
|
|
13532
13853
|
const created = randomUUID();
|
|
13533
13854
|
try {
|
|
13534
|
-
await
|
|
13535
|
-
await
|
|
13855
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
13856
|
+
await fs.writeFile(file, created, {
|
|
13536
13857
|
encoding: "utf8",
|
|
13537
13858
|
mode: 384
|
|
13538
13859
|
});
|
|
@@ -15348,6 +15669,39 @@ function imageBlockToInline$2(block, images) {
|
|
|
15348
15669
|
data: resolved.data
|
|
15349
15670
|
} : void 0;
|
|
15350
15671
|
}
|
|
15672
|
+
/**
|
|
15673
|
+
* Media a single request build may carry.
|
|
15674
|
+
*
|
|
15675
|
+
* Passed as one object so adding a media kind never grows a positional
|
|
15676
|
+
* signature the existing callers already bind.
|
|
15677
|
+
*/
|
|
15678
|
+
/**
|
|
15679
|
+
* The cache field for the Anthropic-compatible wire.
|
|
15680
|
+
*
|
|
15681
|
+
* TOP LEVEL, not per block: the service documents that a `cache_control` inside
|
|
15682
|
+
* a message is ignored, and omitting the field means "read a 5m entry but do
|
|
15683
|
+
* not write one" — which is why this is only attached when the caller asked
|
|
15684
|
+
* for a tier.
|
|
15685
|
+
*/
|
|
15686
|
+
function kimiAnthropicCacheControl(ttl) {
|
|
15687
|
+
return { cache_control: {
|
|
15688
|
+
type: "ephemeral",
|
|
15689
|
+
ttl
|
|
15690
|
+
} };
|
|
15691
|
+
}
|
|
15692
|
+
/**
|
|
15693
|
+
* The cache field for the OpenAI-compatible wire.
|
|
15694
|
+
*
|
|
15695
|
+
* `mode: 'implicit'` is the only mode the service supports — it identifies the
|
|
15696
|
+
* prefix itself, so this is not a request to break the cache but a request to
|
|
15697
|
+
* write it at a chosen TTL.
|
|
15698
|
+
*/
|
|
15699
|
+
function kimiOpenAICacheOptions(ttl) {
|
|
15700
|
+
return { prompt_cache_options: {
|
|
15701
|
+
mode: "implicit",
|
|
15702
|
+
ttl
|
|
15703
|
+
} };
|
|
15704
|
+
}
|
|
15351
15705
|
function textOf$3(content) {
|
|
15352
15706
|
if (typeof content === "string") return sanitizeText$3(content);
|
|
15353
15707
|
if (!Array.isArray(content)) return "";
|
|
@@ -15883,6 +16237,7 @@ function buildOpenAIRequest(options, images = NO_RESOLVED_IMAGES$3, preserveThin
|
|
|
15883
16237
|
stream: true,
|
|
15884
16238
|
stream_options: { include_usage: true },
|
|
15885
16239
|
max_completion_tokens: maxTokens,
|
|
16240
|
+
...media.cacheTtl === null || media.cacheTtl === void 0 ? {} : kimiOpenAICacheOptions(media.cacheTtl),
|
|
15886
16241
|
...stopSequences(options.stop).length > 0 ? { stop: stopSequences(options.stop) } : {},
|
|
15887
16242
|
...options.tools && options.tools.length > 0 ? {
|
|
15888
16243
|
tools: options.tools.map((tool) => ({
|
|
@@ -16016,7 +16371,8 @@ function mergeAnthropicMessages$1(entries) {
|
|
|
16016
16371
|
return merged;
|
|
16017
16372
|
}
|
|
16018
16373
|
/** Build one `/v1/messages` body. */
|
|
16019
|
-
function buildAnthropicRequest(options, images = NO_RESOLVED_IMAGES$3,
|
|
16374
|
+
function buildAnthropicRequest(options, images = NO_RESOLVED_IMAGES$3, media = {}) {
|
|
16375
|
+
const cacheTtl = media.cacheTtl ?? null;
|
|
16020
16376
|
const carriers = options.messages.filter((message) => message.role === "system" && messageToolsOf(message) !== void 0);
|
|
16021
16377
|
const unsentDeclarations = carriers.reduce((total, message) => total + (messageToolsOf(message)?.length ?? 0), 0);
|
|
16022
16378
|
const carrierText = carriers.map((message) => textOf$3(message.content)).filter((text) => text !== "");
|
|
@@ -16047,6 +16403,7 @@ function buildAnthropicRequest(options, images = NO_RESOLVED_IMAGES$3, _media =
|
|
|
16047
16403
|
model: options.model,
|
|
16048
16404
|
max_tokens: maxTokens,
|
|
16049
16405
|
messages: mergeAnthropicMessages$1(entries),
|
|
16406
|
+
...cacheTtl === null || cacheTtl === void 0 ? {} : kimiAnthropicCacheControl(cacheTtl),
|
|
16050
16407
|
...system === void 0 ? {} : { system },
|
|
16051
16408
|
...options.temperature === void 0 || budget !== void 0 ? {} : { temperature: options.temperature },
|
|
16052
16409
|
...options.stop && options.stop.length > 0 ? { stop_sequences: options.stop } : {},
|
|
@@ -16849,12 +17206,13 @@ var KimiCodeAdapter = class extends LlmAdapter {
|
|
|
16849
17206
|
const entry = catalog.find((model) => model.id === options.model);
|
|
16850
17207
|
const requestOptions = offloadOldestRequestVideos(offloadOldestRequestImages$2(normalizeGenerateOptions(options)));
|
|
16851
17208
|
const [images, videos] = await Promise.all([resolveRequestImages$2(requestOptions, this.options.attachments, signal), resolveRequestVideos(requestOptions, this.options.videos, signal)]);
|
|
17209
|
+
const settings = await this.settings();
|
|
16852
17210
|
const media = {
|
|
16853
17211
|
videos,
|
|
16854
17212
|
videoAccepted: inputModalitiesForEntry(options.model, catalog).includes("video"),
|
|
16855
|
-
messageTools: dynamicToolsForEntry(options.model, catalog)
|
|
17213
|
+
messageTools: dynamicToolsForEntry(options.model, catalog),
|
|
17214
|
+
cacheTtl: settings.cacheTtl
|
|
16856
17215
|
};
|
|
16857
|
-
const settings = await this.settings();
|
|
16858
17216
|
const contextWindow = this.contextWindowFor(options.model, entry, settings.contextWindowOverrides);
|
|
16859
17217
|
const requestedMax = options.maxTokens ?? maxOutputTokensFor$3(options.model, contextWindow);
|
|
16860
17218
|
const body = assertRequestBodyFits$1(buildRequest({
|
|
@@ -17233,6 +17591,7 @@ async function getKimiCodeWebStatus(store, modelSettings, preferences, options =
|
|
|
17233
17591
|
models,
|
|
17234
17592
|
contextWindowOverrides: settings.contextWindowOverrides,
|
|
17235
17593
|
defaultReasoningEffort: settings.defaultReasoningEffort,
|
|
17594
|
+
cacheTtl: settings.cacheTtl,
|
|
17236
17595
|
loginRegion: region,
|
|
17237
17596
|
serving: readOption$3(options.serving, true),
|
|
17238
17597
|
conflict: readOption$3(options.conflict, null)
|
|
@@ -17458,6 +17817,10 @@ function registerKimiCodeRoutes(ctx, store, modelSettings, preferences, options
|
|
|
17458
17817
|
const effort = body.defaultReasoningEffort;
|
|
17459
17818
|
if (effort === null || isKimiCodeEffort(effort)) patch.defaultReasoningEffort = effort;
|
|
17460
17819
|
}
|
|
17820
|
+
if (body.cacheTtl !== void 0) {
|
|
17821
|
+
const ttl = body.cacheTtl;
|
|
17822
|
+
if (ttl === null || isKimiCacheTtl(ttl)) patch.cacheTtl = ttl;
|
|
17823
|
+
}
|
|
17461
17824
|
if (preferences) await preferences.update(patch);
|
|
17462
17825
|
else await modelSettings.updateSettings(patch);
|
|
17463
17826
|
if (patch.enabledModelIds !== void 0 || patch.enabled !== void 0) ctx.emit?.("llm/adapters-updated");
|
|
@@ -17667,15 +18030,15 @@ async function saveVideo(input) {
|
|
|
17667
18030
|
const digest = createHash("sha256").update(input.data).digest("hex");
|
|
17668
18031
|
const attachmentId = `sha256:${digest}`;
|
|
17669
18032
|
const dir = videoStoreDir();
|
|
17670
|
-
await
|
|
18033
|
+
await fs.mkdir(dir, {
|
|
17671
18034
|
recursive: true,
|
|
17672
18035
|
mode: 448
|
|
17673
18036
|
});
|
|
17674
18037
|
const target = path.join(dir, digest + extensionFor(input.declaredType));
|
|
17675
|
-
if (!await
|
|
18038
|
+
if (!await fs.stat(target).then(() => true).catch(() => false)) {
|
|
17676
18039
|
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
17677
|
-
await
|
|
17678
|
-
await
|
|
18040
|
+
await fs.writeFile(tmp, input.data);
|
|
18041
|
+
await fs.rename(tmp, target);
|
|
17679
18042
|
}
|
|
17680
18043
|
pruneVideoStore().catch(() => void 0);
|
|
17681
18044
|
return {
|
|
@@ -17705,14 +18068,14 @@ async function pruneVideoStore(budgetBytes = VIDEO_STORE_BUDGET_BYTES) {
|
|
|
17705
18068
|
const dir = videoStoreDir();
|
|
17706
18069
|
let entries;
|
|
17707
18070
|
try {
|
|
17708
|
-
const names = await
|
|
18071
|
+
const names = await fs.readdir(dir);
|
|
17709
18072
|
entries = [];
|
|
17710
18073
|
for (const name of names) {
|
|
17711
18074
|
if (name.includes(".tmp.")) {
|
|
17712
|
-
await
|
|
18075
|
+
await fs.rm(path.join(dir, name), { force: true }).catch(() => void 0);
|
|
17713
18076
|
continue;
|
|
17714
18077
|
}
|
|
17715
|
-
const stats = await
|
|
18078
|
+
const stats = await fs.stat(path.join(dir, name)).catch(() => void 0);
|
|
17716
18079
|
if (stats === void 0 || !stats.isFile()) continue;
|
|
17717
18080
|
entries.push({
|
|
17718
18081
|
path: path.join(dir, name),
|
|
@@ -17729,7 +18092,7 @@ async function pruneVideoStore(budgetBytes = VIDEO_STORE_BUDGET_BYTES) {
|
|
|
17729
18092
|
let removed = 0;
|
|
17730
18093
|
for (const entry of entries) {
|
|
17731
18094
|
if (total <= budgetBytes) break;
|
|
17732
|
-
await
|
|
18095
|
+
await fs.rm(entry.path, { force: true }).catch(() => void 0);
|
|
17733
18096
|
total -= entry.size;
|
|
17734
18097
|
removed += 1;
|
|
17735
18098
|
}
|
|
@@ -17753,7 +18116,7 @@ async function readVideoBytes(ref) {
|
|
|
17753
18116
|
if (stored === void 0) throw new VideoIngestError("video reference is not one this store issued", "VIDEO_REFERENCE_INVALID");
|
|
17754
18117
|
let data;
|
|
17755
18118
|
try {
|
|
17756
|
-
data = await
|
|
18119
|
+
data = await fs.readFile(stored);
|
|
17757
18120
|
} catch {
|
|
17758
18121
|
throw new VideoIngestError("stored video is missing; attach it again", "VIDEO_MISSING");
|
|
17759
18122
|
}
|
|
@@ -17814,14 +18177,14 @@ function humanBytes(bytes) {
|
|
|
17814
18177
|
/** Read one local file, refusing anything that is not a regular file. */
|
|
17815
18178
|
async function readLocalFile(input) {
|
|
17816
18179
|
if (!path.isAbsolute(input)) throw new HarnessError(`video path must be absolute: ${input}`, "KIMI_VIDEO_PATH_NOT_ABSOLUTE");
|
|
17817
|
-
const stats = await
|
|
18180
|
+
const stats = await fs.stat(input).catch(() => void 0);
|
|
17818
18181
|
if (stats === void 0) throw new HarnessError(`video file not found: ${input}`, "KIMI_VIDEO_NOT_FOUND");
|
|
17819
18182
|
if (!stats.isFile()) throw new HarnessError(`video path is not a regular file: ${input}`, "KIMI_VIDEO_NOT_A_FILE");
|
|
17820
18183
|
if (stats.size > 31457280) throw new HarnessError(`video is ${humanBytes(stats.size)}, above the ${humanBytes(MAX_VIDEO_FILE_BYTES)} limit this route sends`, "KIMI_VIDEO_TOO_LARGE");
|
|
17821
18184
|
const type = mediaTypeForPath(input);
|
|
17822
18185
|
if (type === void 0) throw new HarnessError(`unsupported video extension "${path.extname(input)}"; supported: .mp4 .m4v .webm .mov .avi .mpeg .mpg .flv .wmv .3gp`, "KIMI_VIDEO_TYPE_UNSUPPORTED");
|
|
17823
18186
|
return {
|
|
17824
|
-
data: await
|
|
18187
|
+
data: await fs.readFile(input),
|
|
17825
18188
|
name: path.basename(input),
|
|
17826
18189
|
type
|
|
17827
18190
|
};
|
|
@@ -19302,16 +19665,16 @@ function credentialNeedsRefresh(credentials, now = Date.now()) {
|
|
|
19302
19665
|
* leaves the destination untouched.
|
|
19303
19666
|
*/
|
|
19304
19667
|
async function writeFileAtomic(filePath, contents) {
|
|
19305
|
-
await
|
|
19668
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
19306
19669
|
const temporary = filePath + ".dsh-tmp-" + process.pid + "-" + Date.now();
|
|
19307
19670
|
try {
|
|
19308
|
-
await
|
|
19671
|
+
await fs.writeFile(temporary, contents, {
|
|
19309
19672
|
encoding: "utf8",
|
|
19310
19673
|
mode: 384
|
|
19311
19674
|
});
|
|
19312
|
-
await
|
|
19675
|
+
await fs.rename(temporary, filePath);
|
|
19313
19676
|
} catch (error) {
|
|
19314
|
-
await
|
|
19677
|
+
await fs.rm(temporary, { force: true }).catch(() => void 0);
|
|
19315
19678
|
throw new Error("MiniMax Code credential write failed; the previous file was left untouched (" + (error instanceof Error ? error.message : String(error)) + ")");
|
|
19316
19679
|
}
|
|
19317
19680
|
}
|
|
@@ -19521,7 +19884,7 @@ async function readNativeDocument(region) {
|
|
|
19521
19884
|
const filePath = authJsonPath(region);
|
|
19522
19885
|
let raw;
|
|
19523
19886
|
try {
|
|
19524
|
-
raw = await
|
|
19887
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
19525
19888
|
} catch {
|
|
19526
19889
|
return null;
|
|
19527
19890
|
}
|
|
@@ -19558,7 +19921,7 @@ async function syncAuthState(region, options) {
|
|
|
19558
19921
|
const filePath = authStateJsonPath(region);
|
|
19559
19922
|
let parsed;
|
|
19560
19923
|
try {
|
|
19561
|
-
parsed = JSON.parse(await
|
|
19924
|
+
parsed = JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
19562
19925
|
} catch {
|
|
19563
19926
|
return;
|
|
19564
19927
|
}
|
|
@@ -19606,7 +19969,7 @@ var MinimaxCodeCredentialStore = class {
|
|
|
19606
19969
|
async readPluginFile() {
|
|
19607
19970
|
let raw;
|
|
19608
19971
|
try {
|
|
19609
|
-
raw = await
|
|
19972
|
+
raw = await fs.readFile(pluginCredentialPath(), "utf8");
|
|
19610
19973
|
} catch {
|
|
19611
19974
|
return null;
|
|
19612
19975
|
}
|
|
@@ -19748,7 +20111,7 @@ var MinimaxCodeCredentialStore = class {
|
|
|
19748
20111
|
*/
|
|
19749
20112
|
delete() {
|
|
19750
20113
|
return serialize(pluginCredentialPath(), async () => {
|
|
19751
|
-
await
|
|
20114
|
+
await fs.rm(pluginCredentialPath(), { force: true });
|
|
19752
20115
|
recentRead = null;
|
|
19753
20116
|
});
|
|
19754
20117
|
}
|
|
@@ -19847,17 +20210,17 @@ var MinimaxCodeModelSettingsStore = class {
|
|
|
19847
20210
|
}
|
|
19848
20211
|
async read() {
|
|
19849
20212
|
try {
|
|
19850
|
-
return parseMinimaxCodeModelSettings(JSON.parse(await
|
|
20213
|
+
return parseMinimaxCodeModelSettings(JSON.parse(await fs.readFile(this.filePath, "utf8")));
|
|
19851
20214
|
} catch {
|
|
19852
20215
|
return defaultMinimaxCodeSettings();
|
|
19853
20216
|
}
|
|
19854
20217
|
}
|
|
19855
20218
|
async write(settings) {
|
|
19856
|
-
await
|
|
20219
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
19857
20220
|
const temporary = this.filePath + ".tmp." + Date.now();
|
|
19858
|
-
await
|
|
19859
|
-
await
|
|
19860
|
-
if (!isDeepStrictEqual(parseMinimaxCodeModelSettings(JSON.parse(await
|
|
20221
|
+
await fs.writeFile(temporary, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
20222
|
+
await fs.rename(temporary, this.filePath);
|
|
20223
|
+
if (!isDeepStrictEqual(parseMinimaxCodeModelSettings(JSON.parse(await fs.readFile(this.filePath, "utf8"))), settings)) throw new Error("MiniMax Code model settings write failed; the stored selection does not match what was written");
|
|
19861
20224
|
}
|
|
19862
20225
|
/**
|
|
19863
20226
|
* Read-modify-write under the same per-path lock the credential writes use.
|
|
@@ -23572,7 +23935,7 @@ function isCanonicalFile(name) {
|
|
|
23572
23935
|
async function scanCredentials(dir = codeBuddyAuthDir()) {
|
|
23573
23936
|
let names;
|
|
23574
23937
|
try {
|
|
23575
|
-
names = await
|
|
23938
|
+
names = await fs.readdir(dir);
|
|
23576
23939
|
} catch (error) {
|
|
23577
23940
|
if (error.code === "ENOENT") return [];
|
|
23578
23941
|
throw new Error(`WorkBuddy credential directory could not be read: ${dir}`);
|
|
@@ -23582,9 +23945,9 @@ async function scanCredentials(dir = codeBuddyAuthDir()) {
|
|
|
23582
23945
|
if (!name.endsWith(".info")) continue;
|
|
23583
23946
|
const file = path.join(dir, name);
|
|
23584
23947
|
try {
|
|
23585
|
-
const stats = await
|
|
23948
|
+
const stats = await fs.stat(file);
|
|
23586
23949
|
if (!stats.isFile()) continue;
|
|
23587
|
-
const raw = await
|
|
23950
|
+
const raw = await fs.readFile(file, "utf8");
|
|
23588
23951
|
found.push(parseCredentialFile(JSON.parse(raw), file, stats.mtimeMs));
|
|
23589
23952
|
} catch {
|
|
23590
23953
|
continue;
|
|
@@ -23721,7 +24084,7 @@ var FileCredentialStore$4 = class {
|
|
|
23721
24084
|
async isStillCurrent(credentials) {
|
|
23722
24085
|
if (credentials.source === "managed") return true;
|
|
23723
24086
|
try {
|
|
23724
|
-
return (await
|
|
24087
|
+
return (await fs.stat(credentials.sourceFile)).mtimeMs === credentials.sourceMtimeMs;
|
|
23725
24088
|
} catch {
|
|
23726
24089
|
return false;
|
|
23727
24090
|
}
|
|
@@ -23761,9 +24124,9 @@ var FileCredentialStore$4 = class {
|
|
|
23761
24124
|
*/
|
|
23762
24125
|
async readDesktopFile(filePath) {
|
|
23763
24126
|
try {
|
|
23764
|
-
const stats = await
|
|
24127
|
+
const stats = await fs.stat(filePath);
|
|
23765
24128
|
if (!stats.isFile()) return null;
|
|
23766
|
-
const raw = await
|
|
24129
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
23767
24130
|
return parseCredentialFile(JSON.parse(raw), filePath, stats.mtimeMs);
|
|
23768
24131
|
} catch {
|
|
23769
24132
|
return null;
|
|
@@ -23822,25 +24185,25 @@ var FileCredentialStore$4 = class {
|
|
|
23822
24185
|
*/
|
|
23823
24186
|
async writeBack(credentials) {
|
|
23824
24187
|
try {
|
|
23825
|
-
const raw = await
|
|
24188
|
+
const raw = await fs.readFile(credentials.sourceFile, "utf8");
|
|
23826
24189
|
const parsed = JSON.parse(raw);
|
|
23827
24190
|
if (!isRecord$11(parsed) || !isRecord$11(parsed.auth)) return;
|
|
23828
24191
|
parsed.auth.accessToken = credentials.accessToken;
|
|
23829
24192
|
if (credentials.refreshToken !== "") parsed.auth.refreshToken = credentials.refreshToken;
|
|
23830
24193
|
parsed.auth.expiresAt = credentials.expiresAt;
|
|
23831
24194
|
parsed.auth.lastRefreshTime = Date.now();
|
|
23832
|
-
const sourceMode = process.platform === "win32" ? void 0 : (await
|
|
24195
|
+
const sourceMode = process.platform === "win32" ? void 0 : (await fs.stat(credentials.sourceFile)).mode & 511;
|
|
23833
24196
|
const tmp = `${credentials.sourceFile}.tmp.${process.pid}`;
|
|
23834
24197
|
try {
|
|
23835
|
-
await
|
|
24198
|
+
await fs.writeFile(tmp, JSON.stringify(parsed, null, 2), {
|
|
23836
24199
|
encoding: "utf8",
|
|
23837
24200
|
...sourceMode === void 0 ? {} : { mode: sourceMode }
|
|
23838
24201
|
});
|
|
23839
|
-
await
|
|
24202
|
+
await fs.rename(tmp, credentials.sourceFile);
|
|
23840
24203
|
} finally {
|
|
23841
|
-
await
|
|
24204
|
+
await fs.unlink(tmp).catch(() => void 0);
|
|
23842
24205
|
}
|
|
23843
|
-
credentials.sourceMtimeMs = (await
|
|
24206
|
+
credentials.sourceMtimeMs = (await fs.stat(credentials.sourceFile)).mtimeMs;
|
|
23844
24207
|
} catch {}
|
|
23845
24208
|
}
|
|
23846
24209
|
};
|
|
@@ -23855,7 +24218,7 @@ var FileModelSettingsStore$4 = class {
|
|
|
23855
24218
|
}
|
|
23856
24219
|
async read() {
|
|
23857
24220
|
try {
|
|
23858
|
-
const content = await
|
|
24221
|
+
const content = await fs.readFile(this.filePath, "utf8");
|
|
23859
24222
|
const parsed = JSON.parse(content);
|
|
23860
24223
|
if (isRecord$11(parsed)) {
|
|
23861
24224
|
const enabledModelIds = Array.isArray(parsed.enabledModelIds) ? parsed.enabledModelIds.filter((id) => typeof id === "string") : [...DEFAULT_ENABLED_MODEL_IDS$1];
|
|
@@ -23883,10 +24246,10 @@ var FileModelSettingsStore$4 = class {
|
|
|
23883
24246
|
};
|
|
23884
24247
|
}
|
|
23885
24248
|
async write(settings) {
|
|
23886
|
-
await
|
|
24249
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
23887
24250
|
const tmp = `${this.filePath}.tmp.${Date.now()}`;
|
|
23888
|
-
await
|
|
23889
|
-
await
|
|
24251
|
+
await fs.writeFile(tmp, JSON.stringify(settings, null, 2), "utf8");
|
|
24252
|
+
await fs.rename(tmp, this.filePath);
|
|
23890
24253
|
}
|
|
23891
24254
|
async updateSettings(patch) {
|
|
23892
24255
|
const current = await this.read();
|
|
@@ -26510,6 +26873,25 @@ const CLAUDE_CODE_BETA = "claude-code-20250219";
|
|
|
26510
26873
|
/** Lets thinking blocks interleave with tool use across a multi-turn loop. */
|
|
26511
26874
|
const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
|
|
26512
26875
|
/**
|
|
26876
|
+
* Licenses the extended one-hour prompt-cache TTL.
|
|
26877
|
+
*
|
|
26878
|
+
* `cache_control: { type: 'ephemeral', ttl: '1h' }` is NOT accepted without this
|
|
26879
|
+
* beta: the one-hour tier is a separate licensed capability, and a body that
|
|
26880
|
+
* asks for it without the marker is refused the same way `block_binding` is.
|
|
26881
|
+
*/
|
|
26882
|
+
const EXTENDED_CACHE_TTL_BETA = "extended-cache-ttl-2025-04-11";
|
|
26883
|
+
/** A cache marker for one tier. Absent `ttl` means the five-minute default. */
|
|
26884
|
+
function cacheControlFor(ttl) {
|
|
26885
|
+
return {
|
|
26886
|
+
type: "ephemeral",
|
|
26887
|
+
ttl
|
|
26888
|
+
};
|
|
26889
|
+
}
|
|
26890
|
+
/** Whether a stored preference names a tier this line can send. */
|
|
26891
|
+
function isClaudeCacheTtl$1(value) {
|
|
26892
|
+
return value === "5m" || value === "1h";
|
|
26893
|
+
}
|
|
26894
|
+
/**
|
|
26513
26895
|
* Licenses `thinking.block_binding` in a request body.
|
|
26514
26896
|
*
|
|
26515
26897
|
* The field is not optional to license: the vendor documents that a body
|
|
@@ -27478,6 +27860,7 @@ function defaultClaudeSettings() {
|
|
|
27478
27860
|
enabledModelIds: [...DEFAULT_ENABLED_MODEL_IDS],
|
|
27479
27861
|
contextWindowOverrides: {},
|
|
27480
27862
|
defaultReasoningEffort: null,
|
|
27863
|
+
cacheTtl: null,
|
|
27481
27864
|
selectedAccountId: null
|
|
27482
27865
|
};
|
|
27483
27866
|
}
|
|
@@ -27713,12 +28096,14 @@ function parseClaudeModelSettings(value) {
|
|
|
27713
28096
|
for (const [model, window] of Object.entries(record.contextWindowOverrides)) if (typeof window === "number" && Number.isFinite(window) && window > 0) contextWindowOverrides[model] = window;
|
|
27714
28097
|
}
|
|
27715
28098
|
const defaultReasoningEffort = nonEmptyString$3(record.defaultReasoningEffort) ?? null;
|
|
28099
|
+
const cacheTtl = isClaudeCacheTtl$1(record.cacheTtl) ? record.cacheTtl : null;
|
|
27716
28100
|
const selectedAccountId = nonEmptyString$3(record.selectedAccountId) ?? null;
|
|
27717
28101
|
return {
|
|
27718
28102
|
enabled: record.enabled !== false,
|
|
27719
28103
|
enabledModelIds,
|
|
27720
28104
|
contextWindowOverrides,
|
|
27721
28105
|
defaultReasoningEffort,
|
|
28106
|
+
cacheTtl,
|
|
27722
28107
|
selectedAccountId
|
|
27723
28108
|
};
|
|
27724
28109
|
}
|
|
@@ -27729,6 +28114,7 @@ function mergeClaudeSettings(current, patch) {
|
|
|
27729
28114
|
enabledModelIds: patch.enabledModelIds !== void 0 ? [...patch.enabledModelIds] : current.enabledModelIds,
|
|
27730
28115
|
contextWindowOverrides: patch.contextWindowOverrides !== void 0 ? mergeContextWindowOverrides(current.contextWindowOverrides, patch.contextWindowOverrides) : current.contextWindowOverrides,
|
|
27731
28116
|
defaultReasoningEffort: patch.defaultReasoningEffort !== void 0 ? patch.defaultReasoningEffort : current.defaultReasoningEffort,
|
|
28117
|
+
cacheTtl: patch.cacheTtl !== void 0 ? patch.cacheTtl : current.cacheTtl,
|
|
27732
28118
|
selectedAccountId: patch.selectedAccountId !== void 0 ? patch.selectedAccountId : current.selectedAccountId
|
|
27733
28119
|
};
|
|
27734
28120
|
}
|
|
@@ -27801,7 +28187,7 @@ var FileCredentialStore = class {
|
|
|
27801
28187
|
}
|
|
27802
28188
|
async removeLegacy() {
|
|
27803
28189
|
try {
|
|
27804
|
-
await
|
|
28190
|
+
await fs.unlink(this.filePath);
|
|
27805
28191
|
} catch (error) {
|
|
27806
28192
|
if (error.code !== "ENOENT") throw new Error("Claude legacy credential removal failed");
|
|
27807
28193
|
}
|
|
@@ -27822,11 +28208,11 @@ var FileCredentialStore = class {
|
|
|
27822
28208
|
}
|
|
27823
28209
|
let legacy;
|
|
27824
28210
|
try {
|
|
27825
|
-
const stats = await
|
|
28211
|
+
const stats = await fs.lstat(this.filePath);
|
|
27826
28212
|
if (!stats.isFile() || stats.isSymbolicLink()) throw new Error("Invalid credential file");
|
|
27827
28213
|
if (process.getuid && stats.uid !== process.getuid()) throw new Error("Invalid credential owner");
|
|
27828
|
-
if (process.platform !== "win32") await
|
|
27829
|
-
legacy = await
|
|
28214
|
+
if (process.platform !== "win32") await fs.chmod(this.filePath, 384);
|
|
28215
|
+
legacy = await fs.readFile(this.filePath, "utf8");
|
|
27830
28216
|
} catch (error) {
|
|
27831
28217
|
if (error.code === "ENOENT") return null;
|
|
27832
28218
|
throw new Error("Claude legacy credential read failed");
|
|
@@ -27934,17 +28320,17 @@ var FileModelSettingsStore = class {
|
|
|
27934
28320
|
}
|
|
27935
28321
|
async read() {
|
|
27936
28322
|
try {
|
|
27937
|
-
const content = await
|
|
28323
|
+
const content = await fs.readFile(this.filePath, "utf8");
|
|
27938
28324
|
return parseClaudeModelSettings(JSON.parse(content));
|
|
27939
28325
|
} catch {
|
|
27940
28326
|
return defaultClaudeSettings();
|
|
27941
28327
|
}
|
|
27942
28328
|
}
|
|
27943
28329
|
async write(settings) {
|
|
27944
|
-
await
|
|
28330
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
27945
28331
|
const tmp = this.filePath + ".tmp." + Date.now();
|
|
27946
|
-
await
|
|
27947
|
-
await
|
|
28332
|
+
await fs.writeFile(tmp, JSON.stringify(settings, null, 2), "utf8");
|
|
28333
|
+
await fs.rename(tmp, this.filePath);
|
|
27948
28334
|
}
|
|
27949
28335
|
/**
|
|
27950
28336
|
* Read-modify-write under the same per-path lock the credentials use.
|
|
@@ -27986,6 +28372,11 @@ function registerClaudePreferenceStore(settings, fallbackStore = new FileModelSe
|
|
|
27986
28372
|
enabledModelIds: z.array(z.string()).default([...DEFAULT_ENABLED_MODEL_IDS]),
|
|
27987
28373
|
contextWindowOverrides: z.dict(z.number()).default({}),
|
|
27988
28374
|
defaultReasoningEffort: z.union([z.string(), z.const(null)]).default(null),
|
|
28375
|
+
cacheTtl: z.union([
|
|
28376
|
+
z.const("5m"),
|
|
28377
|
+
z.const("1h"),
|
|
28378
|
+
z.const(null)
|
|
28379
|
+
]).default(null),
|
|
27989
28380
|
selectedAccountId: z.union([z.string(), z.const(null)]).default(null)
|
|
27990
28381
|
}));
|
|
27991
28382
|
return {
|
|
@@ -27996,6 +28387,7 @@ function registerClaudePreferenceStore(settings, fallbackStore = new FileModelSe
|
|
|
27996
28387
|
enabledModelIds: value.enabledModelIds,
|
|
27997
28388
|
contextWindowOverrides: value.contextWindowOverrides,
|
|
27998
28389
|
defaultReasoningEffort: value.defaultReasoningEffort,
|
|
28390
|
+
cacheTtl: isClaudeCacheTtl$1(value.cacheTtl) ? value.cacheTtl : null,
|
|
27999
28391
|
selectedAccountId: value.selectedAccountId ?? null
|
|
28000
28392
|
};
|
|
28001
28393
|
},
|
|
@@ -28191,6 +28583,7 @@ function claudeBetas(options = {}) {
|
|
|
28191
28583
|
betas.push(OAUTH_BETA);
|
|
28192
28584
|
if (options.thinking === true) betas.push(INTERLEAVED_THINKING_BETA);
|
|
28193
28585
|
if (options.thinkingBinding === true) betas.push(THINKING_BINDING_CONTROLS_BETA);
|
|
28586
|
+
if (options.cacheTtl === "1h") betas.push(EXTENDED_CACHE_TTL_BETA);
|
|
28194
28587
|
return betas;
|
|
28195
28588
|
}
|
|
28196
28589
|
/** The `User-Agent` value this line reports for one surface. */
|
|
@@ -29788,7 +30181,7 @@ function leadingSystemText(options) {
|
|
|
29788
30181
|
*
|
|
29789
30182
|
* @param cacheControl - false is the opt-OUT; the default requests caching.
|
|
29790
30183
|
*/
|
|
29791
|
-
function buildClaudeSystemBlocks(options, cacheControl = true) {
|
|
30184
|
+
function buildClaudeSystemBlocks(options, cacheControl = true, ttl = "5m") {
|
|
29792
30185
|
const blocks = [{
|
|
29793
30186
|
type: "text",
|
|
29794
30187
|
text: CLAUDE_CODE_IDENTITY_TEXT
|
|
@@ -29800,7 +30193,7 @@ function buildClaudeSystemBlocks(options, cacheControl = true) {
|
|
|
29800
30193
|
});
|
|
29801
30194
|
if (cacheControl) {
|
|
29802
30195
|
const last = blocks[blocks.length - 1];
|
|
29803
|
-
last.cache_control =
|
|
30196
|
+
last.cache_control = cacheControlFor(ttl);
|
|
29804
30197
|
}
|
|
29805
30198
|
return blocks;
|
|
29806
30199
|
}
|
|
@@ -29838,13 +30231,14 @@ function lastCacheableUserBlock(messages) {
|
|
|
29838
30231
|
* and has not been handed to a caller yet.
|
|
29839
30232
|
* @returns the number of breakpoints written.
|
|
29840
30233
|
*/
|
|
29841
|
-
function markClaudeCacheBreakpoints(body) {
|
|
30234
|
+
function markClaudeCacheBreakpoints(body, ttl = "5m") {
|
|
29842
30235
|
let marked = 0;
|
|
30236
|
+
const marker = cacheControlFor(ttl);
|
|
29843
30237
|
const system = body.system;
|
|
29844
30238
|
if (Array.isArray(system)) {
|
|
29845
30239
|
const last = system[system.length - 1];
|
|
29846
30240
|
if (last !== void 0) {
|
|
29847
|
-
last.cache_control = {
|
|
30241
|
+
last.cache_control = { ...marker };
|
|
29848
30242
|
marked += 1;
|
|
29849
30243
|
}
|
|
29850
30244
|
}
|
|
@@ -29852,7 +30246,7 @@ function markClaudeCacheBreakpoints(body) {
|
|
|
29852
30246
|
if (Array.isArray(messages)) {
|
|
29853
30247
|
const target = lastCacheableUserBlock(messages);
|
|
29854
30248
|
if (target !== void 0) {
|
|
29855
|
-
target.cache_control = {
|
|
30249
|
+
target.cache_control = { ...marker };
|
|
29856
30250
|
marked += 1;
|
|
29857
30251
|
}
|
|
29858
30252
|
}
|
|
@@ -29860,7 +30254,7 @@ function markClaudeCacheBreakpoints(body) {
|
|
|
29860
30254
|
if (Array.isArray(tools) && tools.length > 0) {
|
|
29861
30255
|
const last = tools[tools.length - 1];
|
|
29862
30256
|
if (last !== void 0) {
|
|
29863
|
-
last.cache_control = {
|
|
30257
|
+
last.cache_control = { ...marker };
|
|
29864
30258
|
marked += 1;
|
|
29865
30259
|
}
|
|
29866
30260
|
}
|
|
@@ -30094,11 +30488,12 @@ function buildClaudeRequestBody(options, images = NO_RESOLVED_IMAGES, request =
|
|
|
30094
30488
|
const thinkingEnabled = thinking !== void 0 && thinking.type !== "disabled";
|
|
30095
30489
|
const temperatureAllowed = claudeModelSupportsTemperature(options.model) && !thinkingEnabled;
|
|
30096
30490
|
const caching = request.cacheControl !== false;
|
|
30491
|
+
const cacheTtl = request.cacheTtl ?? "5m";
|
|
30097
30492
|
const body = {
|
|
30098
30493
|
model: options.model,
|
|
30099
30494
|
max_tokens: maxTokens,
|
|
30100
30495
|
stream: true,
|
|
30101
|
-
system: buildClaudeSystemBlocks(options, caching),
|
|
30496
|
+
system: buildClaudeSystemBlocks(options, caching, cacheTtl),
|
|
30102
30497
|
messages: mergeClaudeMessages(entries),
|
|
30103
30498
|
...options.tools !== void 0 && options.tools.length > 0 ? { tools: options.tools.map((tool) => wireTool(tool, names)) } : {},
|
|
30104
30499
|
...thinking === void 0 ? {} : { thinking },
|
|
@@ -30106,7 +30501,7 @@ function buildClaudeRequestBody(options, images = NO_RESOLVED_IMAGES, request =
|
|
|
30106
30501
|
...temperatureAllowed && options.temperature !== void 0 ? { temperature: options.temperature } : {},
|
|
30107
30502
|
...options.stop !== void 0 && options.stop.length > 0 ? { stop_sequences: [...options.stop] } : {}
|
|
30108
30503
|
};
|
|
30109
|
-
if (caching) markClaudeCacheBreakpoints(body);
|
|
30504
|
+
if (caching) markClaudeCacheBreakpoints(body, cacheTtl);
|
|
30110
30505
|
return body;
|
|
30111
30506
|
}
|
|
30112
30507
|
/**
|
|
@@ -31888,7 +32283,7 @@ var ClaudeAccountTokenStore = class {
|
|
|
31888
32283
|
await this.store.saveAccount(credentials, { internalId: this.internalId });
|
|
31889
32284
|
}
|
|
31890
32285
|
};
|
|
31891
|
-
var ClaudeAdapter = class extends LlmAdapter {
|
|
32286
|
+
var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
|
|
31892
32287
|
store;
|
|
31893
32288
|
modelSettings;
|
|
31894
32289
|
preferences;
|
|
@@ -32070,10 +32465,25 @@ var ClaudeAdapter = class extends LlmAdapter {
|
|
|
32070
32465
|
* driven by {@link shouldRotateAccount}, which bypasses the pool ENTIRELY when
|
|
32071
32466
|
* no credentials were resolved for it.
|
|
32072
32467
|
*/
|
|
32073
|
-
|
|
32468
|
+
/**
|
|
32469
|
+
* The prompt-cache tier this line requests.
|
|
32470
|
+
*
|
|
32471
|
+
* A stored value wins; nothing stored falls back to the tier the OFFICIAL
|
|
32472
|
+
* client uses for a subscription's main conversation, so a user comparing
|
|
32473
|
+
* this plugin with Claude Code sees the same cache lifetime rather than half
|
|
32474
|
+
* of it.
|
|
32475
|
+
*
|
|
32476
|
+
* @param settings - the document the caller already read for this turn.
|
|
32477
|
+
*/
|
|
32478
|
+
static cacheTtlFor(settings) {
|
|
32479
|
+
return settings?.cacheTtl ?? "1h";
|
|
32480
|
+
}
|
|
32481
|
+
async attemptRequest(credentials, requestOptions, images, toolNames, thinking, settings, signal, fetchFn) {
|
|
32482
|
+
const cacheTtl = ClaudeAdapter.cacheTtlFor(settings);
|
|
32074
32483
|
const payload = buildClaudeRequestBody(requestOptions, images, {
|
|
32075
32484
|
toolNames,
|
|
32076
|
-
cacheControl: true
|
|
32485
|
+
cacheControl: true,
|
|
32486
|
+
cacheTtl
|
|
32077
32487
|
});
|
|
32078
32488
|
try {
|
|
32079
32489
|
return await fetchFn("https://api.anthropic.com/v1/messages?beta=true", {
|
|
@@ -32082,6 +32492,7 @@ var ClaudeAdapter = class extends LlmAdapter {
|
|
|
32082
32492
|
model: requestOptions.model,
|
|
32083
32493
|
thinking,
|
|
32084
32494
|
thinkingBinding: claudeBodyBindsThinking(payload),
|
|
32495
|
+
cacheTtl,
|
|
32085
32496
|
method: "POST"
|
|
32086
32497
|
}),
|
|
32087
32498
|
body: JSON.stringify(payload),
|
|
@@ -32100,6 +32511,7 @@ var ClaudeAdapter = class extends LlmAdapter {
|
|
|
32100
32511
|
assertClaudeCliVersionMeetsFloor(requestOptions.model, await this.catalogForRequest());
|
|
32101
32512
|
const effort = requestOptions.reasoningEffort === void 0 || requestOptions.reasoningEffort === null ? void 0 : String(requestOptions.reasoningEffort);
|
|
32102
32513
|
const thinking = claudeRequestThinks(requestOptions.model, effort);
|
|
32514
|
+
const settings = await this.settings().catch(() => null);
|
|
32103
32515
|
const pool = this.accountPool;
|
|
32104
32516
|
const tried = /* @__PURE__ */ new Set();
|
|
32105
32517
|
let response;
|
|
@@ -32119,7 +32531,7 @@ var ClaudeAdapter = class extends LlmAdapter {
|
|
|
32119
32531
|
let outputStarted = false;
|
|
32120
32532
|
while (true) {
|
|
32121
32533
|
const { credentials, accountId } = await this.resolveCredential(pool, tried, fetchFn, signal);
|
|
32122
|
-
response = await this.attemptRequest(credentials, requestOptions, images, toolNames, thinking, signal, fetchFn);
|
|
32534
|
+
response = await this.attemptRequest(credentials, requestOptions, images, toolNames, thinking, settings, signal, fetchFn);
|
|
32123
32535
|
if (response.ok) break;
|
|
32124
32536
|
const detail = (await response.text().catch(() => "")).slice(0, 2e3);
|
|
32125
32537
|
const failure = classifyFailure(response.status, detail, response.headers);
|
|
@@ -33589,6 +34001,16 @@ const CLAUDE_REASONING_EFFORTS = [
|
|
|
33589
34001
|
function isClaudeReasoningEffort(value) {
|
|
33590
34002
|
return typeof value === "string" && CLAUDE_REASONING_EFFORTS.includes(value);
|
|
33591
34003
|
}
|
|
34004
|
+
/**
|
|
34005
|
+
* Whether a posted value names a tier this route can send.
|
|
34006
|
+
*
|
|
34007
|
+
* Membership is checked at the wire boundary for the same reason effort levels
|
|
34008
|
+
* are: the one-hour tier is a LICENSED capability, so a value that reached the
|
|
34009
|
+
* body without the matching beta would be refused by the service.
|
|
34010
|
+
*/
|
|
34011
|
+
function isClaudeCacheTtl(value) {
|
|
34012
|
+
return value === "5m" || value === "1h";
|
|
34013
|
+
}
|
|
33592
34014
|
//#endregion
|
|
33593
34015
|
//#region src/host/claude/routes.ts
|
|
33594
34016
|
/** Ceiling on one request body. Identical to every sibling line's. */
|
|
@@ -33780,6 +34202,7 @@ async function getClaudeWebStatus(store, modelSettings, preferences, options = {
|
|
|
33780
34202
|
models,
|
|
33781
34203
|
contextWindowOverrides: settings.contextWindowOverrides,
|
|
33782
34204
|
defaultReasoningEffort: settings.defaultReasoningEffort,
|
|
34205
|
+
cacheTtl: settings.cacheTtl,
|
|
33783
34206
|
selectedAccountId: settings.selectedAccountId,
|
|
33784
34207
|
accounts: poolAccounts.map((summary) => summary),
|
|
33785
34208
|
rotationStrategy: poolData?.rotationStrategy ?? "sequential",
|
|
@@ -34058,6 +34481,10 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
|
|
|
34058
34481
|
const effort = body.defaultReasoningEffort;
|
|
34059
34482
|
if (effort === null || isClaudeReasoningEffort(effort)) patch.defaultReasoningEffort = effort;
|
|
34060
34483
|
}
|
|
34484
|
+
if (body.cacheTtl !== void 0) {
|
|
34485
|
+
const ttl = body.cacheTtl;
|
|
34486
|
+
if (ttl === null || isClaudeCacheTtl(ttl)) patch.cacheTtl = ttl;
|
|
34487
|
+
}
|
|
34061
34488
|
if (body.selectedAccountId !== void 0) {
|
|
34062
34489
|
const selected = body.selectedAccountId;
|
|
34063
34490
|
if (selected !== null && typeof selected !== "string") return sendJson(response, 400, {
|
|
@@ -34593,7 +35020,7 @@ var WorkBuddyCheckinService = class {
|
|
|
34593
35020
|
if (this.loaded) return;
|
|
34594
35021
|
this.loaded = true;
|
|
34595
35022
|
try {
|
|
34596
|
-
const parsed = JSON.parse(await
|
|
35023
|
+
const parsed = JSON.parse(await fs.readFile(this.statePath, "utf8"));
|
|
34597
35024
|
if (isRecord$2(parsed)) {
|
|
34598
35025
|
const lastRunAt = asCount$1(parsed.lastRunAt);
|
|
34599
35026
|
if (lastRunAt !== void 0) this.lastRunAt = lastRunAt;
|
|
@@ -34606,15 +35033,15 @@ var WorkBuddyCheckinService = class {
|
|
|
34606
35033
|
}
|
|
34607
35034
|
async save() {
|
|
34608
35035
|
try {
|
|
34609
|
-
await
|
|
35036
|
+
await fs.mkdir(path.dirname(this.statePath), { recursive: true });
|
|
34610
35037
|
const tmp = `${this.statePath}.tmp.${process.pid}`;
|
|
34611
35038
|
const document = {
|
|
34612
35039
|
version: 2,
|
|
34613
35040
|
lastRunAt: this.lastRunAt,
|
|
34614
35041
|
accounts: this.state
|
|
34615
35042
|
};
|
|
34616
|
-
await
|
|
34617
|
-
await
|
|
35043
|
+
await fs.writeFile(tmp, JSON.stringify(document, null, 2), "utf8");
|
|
35044
|
+
await fs.rename(tmp, this.statePath);
|
|
34618
35045
|
} catch {}
|
|
34619
35046
|
}
|
|
34620
35047
|
async post(url, headers) {
|
|
@@ -35262,7 +35689,7 @@ var MinimaxCodeCheckinService = class {
|
|
|
35262
35689
|
if (this.loaded) return;
|
|
35263
35690
|
this.loaded = true;
|
|
35264
35691
|
try {
|
|
35265
|
-
const parsed = JSON.parse(await
|
|
35692
|
+
const parsed = JSON.parse(await fs.readFile(this.statePath, "utf8"));
|
|
35266
35693
|
if (isRecord(parsed)) {
|
|
35267
35694
|
const lastRunAt = asCount(parsed.lastRunAt);
|
|
35268
35695
|
if (lastRunAt !== void 0) this.lastRunAt = lastRunAt;
|
|
@@ -35281,7 +35708,7 @@ var MinimaxCodeCheckinService = class {
|
|
|
35281
35708
|
}
|
|
35282
35709
|
async save() {
|
|
35283
35710
|
try {
|
|
35284
|
-
await
|
|
35711
|
+
await fs.mkdir(path.dirname(this.statePath), { recursive: true });
|
|
35285
35712
|
const tmp = `${this.statePath}.tmp.${process.pid}`;
|
|
35286
35713
|
const document = {
|
|
35287
35714
|
version: 1,
|
|
@@ -35289,8 +35716,8 @@ var MinimaxCodeCheckinService = class {
|
|
|
35289
35716
|
accounts: this.state,
|
|
35290
35717
|
identities: this.identities
|
|
35291
35718
|
};
|
|
35292
|
-
await
|
|
35293
|
-
await
|
|
35719
|
+
await fs.writeFile(tmp, JSON.stringify(document, null, 2), "utf8");
|
|
35720
|
+
await fs.rename(tmp, this.statePath);
|
|
35294
35721
|
} catch {}
|
|
35295
35722
|
}
|
|
35296
35723
|
};
|
|
@@ -37682,7 +38109,10 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
37682
38109
|
outputVerbosity: () => preferences.status().outputVerbosity,
|
|
37683
38110
|
fastMode: () => preferences.status().fastMode,
|
|
37684
38111
|
reasoningSummary: () => preferences.status().reasoningSummary
|
|
37685
|
-
}), preferences)
|
|
38112
|
+
}), preferences, (options) => oauth.credentials(void 0, { purpose: "tool" }).then((credentials) => loadCodexCatalog(credentials, {
|
|
38113
|
+
fetchFn: proxyFetch,
|
|
38114
|
+
signal: options?.signal
|
|
38115
|
+
})).catch(() => []));
|
|
37686
38116
|
const searchSwitcher = new SearchProviderSwitcher(ctx.loader);
|
|
37687
38117
|
const applyWebProviders = (current = preferences.status()) => {
|
|
37688
38118
|
const pluginFetch = proxyManager.resolveActiveProxyUrl() !== null;
|