dsh-lcx-codex 0.4.2 → 0.4.3-pre.2

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.
Files changed (47) hide show
  1. package/README.md +116 -202
  2. package/cordis.patch.yml +3 -20
  3. package/lib/client.js +273 -146
  4. package/lib/compact-v2.js +218 -199
  5. package/lib/dsh-compat.js +227 -100
  6. package/lib/dsh-responses.js +445 -277
  7. package/lib/index.js +851 -757
  8. package/lib/json-store.js +50 -31
  9. package/lib/native-checkpoint.js +520 -194
  10. package/lib/responses-request.js +106 -121
  11. package/lib/responses-stream.js +972 -443
  12. package/lib/route.js +229 -355
  13. package/lib/service-mutex.js +73 -64
  14. package/lib/token-budget.js +176 -108
  15. package/lib/transport.js +277 -67
  16. package/lib/types/client/index.d.ts +6 -0
  17. package/lib/types/compact-v2.d.ts +104 -0
  18. package/lib/types/dsh-compat.d.ts +78 -0
  19. package/lib/types/dsh-responses.d.ts +82 -0
  20. package/lib/types/index.d.ts +83 -0
  21. package/lib/types/json-store.d.ts +10 -0
  22. package/lib/types/native-checkpoint.d.ts +213 -0
  23. package/lib/types/responses-request.d.ts +58 -0
  24. package/lib/types/responses-stream.d.ts +51 -0
  25. package/lib/types/route.d.ts +132 -0
  26. package/lib/types/service-mutex.d.ts +14 -0
  27. package/lib/types/token-budget.d.ts +50 -0
  28. package/lib/types/transport.d.ts +20 -0
  29. package/lib/types/web-run-output.d.ts +29 -0
  30. package/lib/types/web-search-alpha.d.ts +286 -0
  31. package/lib/types/web-search-capability.d.ts +26 -0
  32. package/lib/types/web-search-hosted.d.ts +246 -0
  33. package/lib/types/web-search-ref-store.d.ts +22 -0
  34. package/lib/web-run-output.js +167 -18
  35. package/lib/web-search-alpha.js +865 -163
  36. package/lib/web-search-capability.js +55 -65
  37. package/lib/web-search-hosted.js +210 -32
  38. package/lib/web-search-ref-store.js +63 -59
  39. package/package.json +79 -27
  40. package/ARCHITECTURE.md +0 -117
  41. package/CHANGELOG.md +0 -224
  42. package/README_EN.md +0 -277
  43. package/assets/dsh-lcx-codex-banner.jpg +0 -0
  44. package/lib/legacy-v3.js +0 -20
  45. package/lib/responses-replay.js +0 -68
  46. package/scripts/probe-alpha.mjs +0 -43
  47. package/scripts/validate-dsh-schema.mjs +0 -31
package/lib/route.js CHANGED
@@ -1,399 +1,273 @@
1
- // @ts-check
2
-
3
- import { createHash, randomUUID } from 'node:crypto'
4
- import { attributionHeaders, resolveRetryPolicy } from '@deepseek-ai/dsh-llm'
5
- import { settingsNamespace } from '@deepseek-ai/dsh-settings'
6
- import { getBuiltinModels } from '@earendil-works/pi-ai/providers/all'
7
-
8
- /** @typedef {Record<string, string>} HeaderMap */
9
- /** @typedef {Parameters<typeof resolveRetryPolicy>[0]} RetryPolicyConfig */
10
- /** @typedef {'none' | 'short' | 'long'} CacheRetention */
11
- /** @typedef {{ supportsDeveloperRole?: boolean, sessionAffinityFormat?: 'openai' | 'openai-nosession' | 'openrouter', supportsStrictMode?: boolean, supportsLongCacheRetention?: boolean, supportsOpenAIGrammarTools?: boolean, supportsAdditionalTools?: boolean, supportsToolSearch?: boolean, supportsExplicitPromptCacheMode?: boolean }} ResponsesCompat */
12
- /** @typedef {{ provider: string, model: string, baseURL: string, sessionId: string }} RouteIdentity */
13
- /** @typedef {{ provider?: unknown, model?: unknown, sessionId?: unknown }} RouteOptions */
14
- /** @typedef {{ id?: unknown, compat?: unknown }} ProviderModelProfile */
15
- /**
16
- * @typedef {object} ProviderProfile
17
- * @property {string} [api]
18
- * @property {string} [baseURL]
19
- * @property {string} [apiKeyEnv]
20
- * @property {HeaderMap} [headers]
21
- * @property {unknown} [cacheRetention]
22
- * @property {number} [timeoutMs]
23
- * @property {number} [maxRequestImageBytes]
24
- * @property {number} [requestImagePixelBudget]
25
- * @property {number} [requestImageMaxBytes]
26
- * @property {RetryPolicyConfig} [retryPolicy]
27
- * @property {unknown} [compat]
28
- * @property {ProviderModelProfile[]} [models]
29
- * @property {Record<string, ProviderModelProfile>} [modelOverrides]
30
- */
31
- /** @typedef {{ providers?: Record<string, ProviderProfile> }} LlmSettingsSection */
32
- /** @typedef {{ get?: (namespace: unknown) => LlmSettingsSection | undefined }} SettingsService */
33
- /** @typedef {{ resolve?: (name: string) => Promise<{ value?: unknown } | undefined> }} CredentialsService */
34
- /** @typedef {{ id: string, header?: { parentSession?: string }, requestHeader?: () => RequestHeader | undefined }} RouteSession */
35
- /** @typedef {{ get?: (id: string) => RouteSession | undefined }} SessionsService */
36
- /**
37
- * @typedef {object} RouteContext
38
- * @property {((name: string) => unknown)} [get]
39
- * @property {SettingsService} [settings]
40
- * @property {CredentialsService} [credentials]
41
- * @property {SessionsService} [sessions]
42
- */
43
- /**
44
- * Raw fallback values are not a resolved Responses route. cacheRetention is
45
- * intentionally unknown until resolveResponsesRouteConfig normalizes it.
46
- * @typedef {object} UnresolvedRouteConfig
47
- * @property {string} provider
48
- * @property {string} model
49
- * @property {string} baseURL
50
- * @property {string} apiKeyEnv
51
- * @property {HeaderMap} [headers]
52
- * @property {unknown} [cacheRetention]
53
- * @property {unknown} [supportsLongCacheRetention]
54
- * @property {unknown} [supportsExplicitPromptCacheMode]
55
- * @property {unknown} [responsesCompat]
56
- * @property {number} [timeoutMs]
57
- * @property {number} [maxAttempts]
58
- * @property {number} [maxRequestImageBytes]
59
- * @property {number} [requestImagePixelBudget]
60
- * @property {number} [requestImageMaxBytes]
61
- */
62
- /**
63
- * @typedef {UnresolvedRouteConfig & {
64
- * api: 'openai-responses',
65
- * cacheRetention: CacheRetention,
66
- * supportsLongCacheRetention: boolean,
67
- * responsesCompat?: ResponsesCompat
68
- * }} ResolvedResponsesRoute
69
- */
70
- /** @typedef {{ provider?: unknown, model?: unknown, reasoningEffort?: unknown, temperature?: unknown, maxTokens?: unknown }} RequestHeaderConfig */
71
- /** @typedef {{ config?: RequestHeaderConfig }} RequestHeader */
72
- /** @typedef {{ reasoningEffort?: unknown, temperature?: unknown, maxTokens?: unknown }} GenerationControls */
73
- /** @typedef {{ version: number, provider: unknown, model: unknown, baseURLFingerprint: unknown, sourceSessionId: unknown }} CheckpointRouteRecord */
74
- /** @typedef {Error & { code?: string }} LcxError */
75
-
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { credentialRef } from "@deepseek-ai/dsh-credentials";
3
+ import { SessionId } from "@deepseek-ai/dsh-session";
4
+ import "@deepseek-ai/dsh-settings";
5
+ import { attributionHeaders, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
6
+ import { getBuiltinModels, getBuiltinProviders, } from "@earendil-works/pi-ai/providers/all";
7
+ /** @param {unknown} value */
8
+ function isRecord(value) {
9
+ return value !== null && typeof value === "object" && !Array.isArray(value);
10
+ }
11
+ /** @param {unknown} value */
12
+ function isPositiveInteger(value) {
13
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
14
+ }
15
+ function asLlmSettingsSection(value) {
16
+ if (!isRecord(value))
17
+ return undefined;
18
+ const providers = value.providers;
19
+ if (providers === undefined)
20
+ return {};
21
+ if (!isRecord(providers))
22
+ return undefined;
23
+ return { providers: providers };
24
+ }
76
25
  /** @param {unknown} value */
77
26
  export function normalizeBaseURL(value) {
78
- return String(value ?? '').trim().replace(/\/+$/u, '')
27
+ return String(value ?? "").trim().replace(/\/+$/u, "");
79
28
  }
80
-
81
29
  /** @param {unknown} baseURL */
82
30
  export function baseURLFingerprint(baseURL) {
83
- return createHash('sha256').update(normalizeBaseURL(baseURL), 'utf8').digest('hex')
31
+ return createHash("sha256").update(normalizeBaseURL(baseURL), "utf8").digest("hex");
84
32
  }
85
-
86
- /**
87
- * @param {Partial<RouteIdentity> | null | undefined} route
88
- * @param {{ includeSession?: boolean }} [options]
89
- */
33
+ /** @param {Partial<RouteIdentity> | null | undefined} route @param {{ includeSession?: boolean }} [options] */
90
34
  export function routeFingerprint(route, options = {}) {
91
- const includeSession = options.includeSession !== false
92
- const fields = [route?.provider ?? '', route?.model ?? '', normalizeBaseURL(route?.baseURL), includeSession ? route?.sessionId ?? '' : '']
93
- return createHash('sha256').update(fields.join('\u001f'), 'utf8').digest('hex')
35
+ const includeSession = options.includeSession !== false;
36
+ return createHash("sha256").update([
37
+ route?.provider ?? "", route?.model ?? "", normalizeBaseURL(route?.baseURL), includeSession ? (route?.sessionId ?? "") : "",
38
+ ].join("\u001f"), "utf8").digest("hex");
94
39
  }
95
-
96
40
  /** @param {unknown} value */
97
41
  function clampPromptCacheKey(value) {
98
- if (value === undefined) return undefined
99
- const chars = Array.from(String(value))
100
- return chars.length <= 64 ? String(value) : chars.slice(0, 64).join('')
42
+ if (value === undefined)
43
+ return undefined;
44
+ const chars = Array.from(String(value));
45
+ return chars.length <= 64 ? String(value) : chars.slice(0, 64).join("");
101
46
  }
102
-
103
- /**
104
- * @param {Partial<RouteIdentity> | null | undefined} route
105
- * @param {Partial<Pick<ResolvedResponsesRoute, 'cacheRetention'>>} [config]
106
- */
107
- export function promptCacheSessionId(route, config = {}) {
108
- if (config?.cacheRetention === 'none') return undefined
109
- return route?.sessionId ? String(route.sessionId) : undefined
47
+ /** @param {Partial<RouteIdentity> | null | undefined} route @param {Partial<Pick<ResolvedResponsesRoute, 'cacheRetention'>>} [config] @param {RouteContext | null | undefined} [ctx] */
48
+ export function promptCacheSessionId(route, config = {}, ctx = undefined) {
49
+ if (config.cacheRetention === "none")
50
+ return undefined;
51
+ const sessionId = route?.sessionId ? String(route.sessionId) : undefined;
52
+ if (!sessionId)
53
+ return undefined;
54
+ const sessions = ctx?.sessions;
55
+ let current = sessions?.get(SessionId(sessionId));
56
+ if (current?.header?.origin !== "subagent")
57
+ return sessionId;
58
+ const seen = new Set([sessionId]);
59
+ while (current?.header?.origin === "subagent" && current.header.parentSession) {
60
+ const parentId = current.header.parentSession;
61
+ if (seen.has(parentId))
62
+ return sessionId;
63
+ const parent = sessions?.get(SessionId(parentId));
64
+ if (!parent)
65
+ return sessionId;
66
+ seen.add(parentId);
67
+ current = parent;
68
+ }
69
+ return current?.id ?? sessionId;
110
70
  }
111
-
112
- /**
113
- * @param {Partial<RouteIdentity> | null | undefined} route
114
- * @param {Partial<Pick<ResolvedResponsesRoute, 'cacheRetention'>>} [config]
115
- */
116
- export function promptCacheKey(route, config = {}) {
117
- return clampPromptCacheKey(promptCacheSessionId(route, config))
71
+ /** @param {Partial<RouteIdentity> | null | undefined} route @param {Partial<Pick<ResolvedResponsesRoute, 'cacheRetention'>>} [config] @param {RouteContext | null | undefined} [ctx] */
72
+ export function promptCacheKey(route, config = {}, ctx = undefined) {
73
+ return clampPromptCacheKey(promptCacheSessionId(route, config, ctx));
118
74
  }
119
-
120
- /** @param {Partial<Pick<ResolvedResponsesRoute, 'cacheRetention' | 'supportsLongCacheRetention'>>} [config] */
75
+ /** @param {Partial<Pick<ResolvedResponsesRoute, 'cacheRetention' | 'supportsLongCacheRetention' | 'responsesCompat'>>} [config] */
121
76
  export function promptCacheRetention(config = {}) {
122
- return config?.cacheRetention === 'long' && config?.supportsLongCacheRetention !== false ? '24h' : undefined
77
+ return config.cacheRetention === "long" && config.supportsLongCacheRetention === true && config.responsesCompat?.supportsExplicitPromptCacheMode !== true ? "24h" : undefined;
123
78
  }
124
-
125
- /**
126
- * @param {RetryPolicyConfig | null | undefined} policy
127
- * @param {number} [fallback]
128
- */
79
+ /** @param {RetryPolicyConfig | null | undefined} policy @param {number} [fallback] */
129
80
  function retryAttempts(policy, fallback = 3) {
130
- if (!policy || typeof policy !== 'object') return fallback
131
- try {
132
- const resolved = resolveRetryPolicy(policy, 'llm-pi-ai provider retryPolicy')
133
- if (resolved.mode === 'normal' && Number.isSafeInteger(resolved.maxRetries)) return Math.min(resolved.maxRetries + 1, 6)
134
- } catch { /* keep fallback */ }
135
- return fallback
81
+ if (!policy)
82
+ return fallback;
83
+ try {
84
+ const resolved = resolveRetryPolicy(policy, "llm-pi-ai provider retryPolicy");
85
+ if (resolved.mode === "normal" && isPositiveInteger(resolved.maxRetries))
86
+ return Math.min(resolved.maxRetries + 1, 6);
87
+ }
88
+ catch { }
89
+ return fallback;
136
90
  }
137
-
138
- /**
139
- * @param {RouteContext | null | undefined} ctx
140
- * @param {string} namespace
141
- */
91
+ /** @param {RouteContext | null | undefined} ctx @param {string} namespace */
142
92
  export function settingsValue(ctx, namespace) {
143
- const settings = /** @type {SettingsService | undefined} */ (ctx?.get?.('settings') ?? ctx?.settings)
144
- return settings?.get?.(settingsNamespace(namespace))
93
+ return asLlmSettingsSection(ctx?.settings?.get(namespace));
145
94
  }
146
-
147
95
  /** @type {Set<keyof ResponsesCompat>} */
148
- const RESPONSES_COMPAT_FIELDS = new Set(['supportsDeveloperRole', 'sessionAffinityFormat', 'supportsStrictMode', 'supportsLongCacheRetention', 'supportsOpenAIGrammarTools', 'supportsAdditionalTools', 'supportsToolSearch', 'supportsExplicitPromptCacheMode'])
149
-
150
- /**
151
- * @param {ResponsesCompat} target
152
- * @param {unknown} source
153
- */
96
+ const RESPONSES_COMPAT_FIELDS = new Set(["supportsDeveloperRole", "sessionAffinityFormat", "supportsStrictMode", "supportsLongCacheRetention", "supportsOpenAIGrammarTools", "supportsAdditionalTools", "supportsToolSearch", "supportsExplicitPromptCacheMode", "supportsMaxOutputTokens"]);
97
+ /** @param {ResponsesCompat} target @param {unknown} source */
154
98
  function copyResponsesCompat(target, source) {
155
- if (!source || typeof source !== 'object') return
156
- const values = /** @type {Record<string, unknown>} */ (source)
157
- const output = /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (target))
158
- for (const field of RESPONSES_COMPAT_FIELDS) {
159
- const value = values[field]
160
- if (field === 'sessionAffinityFormat') {
161
- if (['openai', 'openai-nosession', 'openrouter'].includes(String(value))) output[field] = value
162
- } else if (typeof value === 'boolean') output[field] = value
163
- }
99
+ if (!isRecord(source))
100
+ return;
101
+ for (const field of RESPONSES_COMPAT_FIELDS) {
102
+ const value = source[field];
103
+ if (field === "sessionAffinityFormat") {
104
+ if (value === "openai" || value === "openai-nosession" || value === "openrouter")
105
+ target.sessionAffinityFormat = value;
106
+ }
107
+ else if (typeof value === "boolean")
108
+ Object.assign(target, { [field]: value });
109
+ }
164
110
  }
165
-
166
111
  /** @param {unknown} provider @param {unknown} modelId */
167
112
  function builtinResponsesModel(provider, modelId) {
168
- try {
169
- return getBuiltinModels(/** @type {any} */ (String(provider ?? '')))
170
- .find((model) => model?.id === String(modelId ?? '') && model?.api === 'openai-responses')
171
- } catch { return undefined }
113
+ const providerName = String(provider ?? "");
114
+ const builtinProvider = getBuiltinProviders().find((candidate) => candidate === providerName);
115
+ if (builtinProvider === undefined)
116
+ return undefined;
117
+ return getBuiltinModels(builtinProvider).find((model) => model.id === String(modelId ?? "") && model.api === "openai-responses");
172
118
  }
173
-
174
- /**
175
- * @param {ProviderProfile | null | undefined} profile
176
- * @param {unknown} modelId
177
- * @returns {ResponsesCompat | undefined}
178
- */
119
+ /** @param {ProviderProfile | null | undefined} profile @param {unknown} modelId @returns {ResponsesCompat | undefined} */
179
120
  function configuredResponsesCompat(profile, modelId) {
180
- /** @type {ResponsesCompat} */
181
- const compat = {}
182
- copyResponsesCompat(compat, profile?.compat)
183
- const configuredModels = Array.isArray(profile?.models) ? profile.models : []
184
- const modelEntry = configuredModels.length > 0
185
- ? configuredModels.find((entry) => String(entry?.id ?? '') === String(modelId))
186
- : profile?.modelOverrides?.[String(modelId)]
187
- copyResponsesCompat(compat, modelEntry?.compat)
188
- return Object.keys(compat).length > 0 ? compat : undefined
121
+ const compat = {};
122
+ copyResponsesCompat(compat, profile?.compat);
123
+ const configuredModels = Array.isArray(profile?.models) ? profile.models : [];
124
+ const modelEntry = configuredModels.length > 0
125
+ ? configuredModels.find((entry) => String(entry?.id ?? "") === String(modelId))
126
+ : profile?.modelOverrides?.[String(modelId)];
127
+ copyResponsesCompat(compat, modelEntry?.compat);
128
+ return Object.keys(compat).length ? compat : undefined;
129
+ }
130
+ function isLcxCapabilityRoute(provider, model) {
131
+ return provider === "lcx" &&
132
+ /^gpt-5\.6-(?:sol|luna|terra)$/iu.test(model);
189
133
  }
190
-
191
- /**
192
- * @param {RouteContext | null | undefined} ctx
193
- * @param {RouteOptions} options
194
- * @param {UnresolvedRouteConfig} fallbackConfig
195
- * @returns {ResolvedResponsesRoute | undefined}
196
- */
197
- export function resolveResponsesRouteConfig(ctx, options, fallbackConfig) {
198
- const provider = String(options?.provider ?? '')
199
- const model = String(options?.model ?? '')
200
- const section = settingsValue(ctx, 'llm-pi-ai')
201
- const profile = section?.providers?.[provider]
202
- const fallbackOwned = provider === fallbackConfig.provider
203
- if (profile === undefined && !fallbackOwned) return undefined
204
- const configured = /** @type {ProviderProfile} */ (profile ?? {})
205
-
206
- const builtin = builtinResponsesModel(provider, model)
207
- const api = configured.api ?? builtin?.api ?? (fallbackOwned ? 'openai-responses' : undefined)
208
- if (api !== 'openai-responses') return undefined
209
-
210
- const baseURL = configured.baseURL ?? builtin?.baseUrl ?? (fallbackOwned ? fallbackConfig.baseURL : undefined)
211
- const apiKeyEnv = configured.apiKeyEnv ?? (fallbackOwned ? fallbackConfig.apiKeyEnv : undefined)
212
- if (!baseURL || !apiKeyEnv) return undefined
213
-
214
- /** @type {ResponsesCompat} */
215
- const responsesCompat = {}
216
- copyResponsesCompat(responsesCompat, builtin?.compat)
217
- copyResponsesCompat(responsesCompat, fallbackOwned ? fallbackConfig.responsesCompat : undefined)
218
- copyResponsesCompat(responsesCompat, configuredResponsesCompat(configured, model))
219
- // Host Pi rc.2 withholds this field; the plugin opt-in still requires Pi's exact model capability.
220
- const promptCacheModel = fallbackOwned && fallbackConfig.supportsExplicitPromptCacheMode === true
221
- ? builtinResponsesModel('openai', model)
222
- : undefined
223
- const promptCacheCompat = /** @type {ResponsesCompat | undefined} */ (promptCacheModel?.compat)
224
- if (promptCacheCompat?.supportsExplicitPromptCacheMode === true) responsesCompat.supportsExplicitPromptCacheMode = true
225
- const resolvedCompat = Object.keys(responsesCompat).length > 0 ? responsesCompat : undefined
226
-
227
- return {
228
- ...fallbackConfig,
229
- provider,
230
- model,
231
- api: 'openai-responses',
232
- baseURL: normalizeBaseURL(baseURL),
233
- apiKeyEnv,
234
- headers: configured.headers && typeof configured.headers === 'object' ? { ...configured.headers } : { ...(fallbackConfig.headers ?? {}) },
235
- cacheRetention: /** @type {CacheRetention} */ (['none', 'short', 'long'].includes(/** @type {string} */ (configured.cacheRetention)) ? configured.cacheRetention : (['none', 'short', 'long'].includes(/** @type {string} */ (fallbackConfig.cacheRetention)) ? fallbackConfig.cacheRetention : 'short')),
236
- supportsLongCacheRetention: resolvedCompat?.supportsLongCacheRetention ?? /** @type {boolean | undefined} */ (fallbackConfig.supportsLongCacheRetention) ?? true,
237
- responsesCompat: resolvedCompat,
238
- timeoutMs: Number.isInteger(configured.timeoutMs) && /** @type {number} */ (configured.timeoutMs) > 0 ? /** @type {number} */ (configured.timeoutMs) : fallbackConfig.timeoutMs,
239
- maxAttempts: retryAttempts(configured.retryPolicy, fallbackConfig.maxAttempts),
240
- maxRequestImageBytes: Number.isSafeInteger(configured.maxRequestImageBytes) && /** @type {number} */ (configured.maxRequestImageBytes) > 0 ? /** @type {number} */ (configured.maxRequestImageBytes) : fallbackConfig.maxRequestImageBytes,
241
- requestImagePixelBudget: Number.isSafeInteger(configured.requestImagePixelBudget) && /** @type {number} */ (configured.requestImagePixelBudget) > 0 ? /** @type {number} */ (configured.requestImagePixelBudget) : fallbackConfig.requestImagePixelBudget,
242
- requestImageMaxBytes: Number.isSafeInteger(configured.requestImageMaxBytes) && /** @type {number} */ (configured.requestImageMaxBytes) > 0 ? /** @type {number} */ (configured.requestImageMaxBytes) : fallbackConfig.requestImageMaxBytes,
243
- }
134
+ /** Resolve only the selected DSH profile; policy cannot supply route identity or credentials. */
135
+ export function resolveResponsesRouteConfig(ctx, options, policy) {
136
+ const provider = String(options?.provider ?? "");
137
+ const model = String(options?.model ?? "");
138
+ if (!provider.trim() || !/^gpt-/iu.test(model))
139
+ return undefined;
140
+ const section = settingsValue(ctx, "llm-pi-ai");
141
+ const profile = section?.providers?.[provider];
142
+ const lcxCapabilityRoute = isLcxCapabilityRoute(provider, model);
143
+ if (!isRecord(profile))
144
+ return undefined;
145
+ const configured = profile;
146
+ const builtin = builtinResponsesModel(provider, model);
147
+ const api = configured.api ?? builtin?.api;
148
+ if (api !== "openai-responses")
149
+ return undefined;
150
+ const baseURL = configured.baseURL ?? builtin?.baseUrl;
151
+ const apiKeyEnv = configured.apiKeyEnv;
152
+ if (typeof baseURL !== "string" || !normalizeBaseURL(baseURL) || typeof apiKeyEnv !== "string" || !apiKeyEnv.trim())
153
+ return undefined;
154
+ const responsesCompat = {};
155
+ copyResponsesCompat(responsesCompat, builtin?.compat);
156
+ copyResponsesCompat(responsesCompat, lcxCapabilityRoute ? policy.responsesCompat : undefined);
157
+ copyResponsesCompat(responsesCompat, configuredResponsesCompat(configured, model));
158
+ const promptCacheModel = lcxCapabilityRoute && policy.supportsExplicitPromptCacheMode === true ? builtinResponsesModel("openai", model) : undefined;
159
+ const promptCacheRaw = promptCacheModel;
160
+ const promptCacheCompat = isRecord(promptCacheRaw)
161
+ ? promptCacheRaw.compat
162
+ : undefined;
163
+ if (isRecord(promptCacheCompat) &&
164
+ promptCacheCompat.supportsExplicitPromptCacheMode === true &&
165
+ responsesCompat.supportsExplicitPromptCacheMode === undefined)
166
+ responsesCompat.supportsExplicitPromptCacheMode = true;
167
+ const resolvedCompat = Object.keys(responsesCompat).length ? responsesCompat : undefined;
168
+ const supportsLongCacheRetention = resolvedCompat?.supportsLongCacheRetention ?? (lcxCapabilityRoute && policy.supportsLongCacheRetention === true);
169
+ responsesCompat.supportsLongCacheRetention = supportsLongCacheRetention;
170
+ const configuredRetention = configured.cacheRetention;
171
+ const fallbackRetention = lcxCapabilityRoute ? policy.cacheRetention : undefined;
172
+ const requestedRetention = configuredRetention === "none" || configuredRetention === "short" || configuredRetention === "long" ? configuredRetention : fallbackRetention === "none" || fallbackRetention === "short" || fallbackRetention === "long" ? fallbackRetention : undefined;
173
+ const cacheRetention = requestedRetention === "long" && !supportsLongCacheRetention ? "short" : requestedRetention ?? (supportsLongCacheRetention ? "long" : "short");
174
+ return {
175
+ provider,
176
+ model,
177
+ api: "openai-responses",
178
+ baseURL: normalizeBaseURL(baseURL),
179
+ apiKeyEnv,
180
+ headers: { ...(configured.headers ?? {}) },
181
+ cacheRetention,
182
+ supportsLongCacheRetention,
183
+ responsesCompat,
184
+ // These are explicitly LCX-owned transport and request-capability policies.
185
+ timeoutMs: isPositiveInteger(configured.timeoutMs) ? configured.timeoutMs : policy.timeoutMs,
186
+ maxAttempts: retryAttempts(configured.retryPolicy, policy.maxAttempts),
187
+ maxRequestImageBytes: isPositiveInteger(configured.maxRequestImageBytes) ? configured.maxRequestImageBytes : policy.maxRequestImageBytes,
188
+ requestImagePixelBudget: isPositiveInteger(configured.requestImagePixelBudget) ? configured.requestImagePixelBudget : policy.requestImagePixelBudget,
189
+ requestImageMaxBytes: isPositiveInteger(configured.requestImageMaxBytes) ? configured.requestImageMaxBytes : policy.requestImageMaxBytes,
190
+ };
244
191
  }
245
-
246
- /**
247
- * @param {RouteContext | null | undefined} ctx
248
- * @param {Pick<ResolvedResponsesRoute, 'apiKeyEnv'>} config
249
- */
192
+ /** @param {RouteContext | null | undefined} ctx @param {Pick<ResolvedResponsesRoute, 'apiKeyEnv'>} config */
250
193
  export async function resolveApiKey(ctx, config) {
251
- const credentials = /** @type {CredentialsService | undefined} */ (ctx?.get?.('credentials') ?? ctx?.credentials)
252
- if (credentials?.resolve) {
253
- const resolved = await credentials.resolve(config.apiKeyEnv)
254
- if (typeof resolved?.value === 'string' && resolved.value.trim()) return resolved.value.trim()
255
- }
256
- const ambient = String(process.env[config.apiKeyEnv] ?? '').trim()
257
- if (ambient) return ambient
258
- /** @type {LcxError} */
259
- const error = new Error(`DSH provider credential is unavailable: ${config.apiKeyEnv}`)
260
- error.code = 'LCX_CREDENTIAL_UNAVAILABLE'
261
- throw error
194
+ const resolved = await ctx?.credentials.resolve(credentialRef(config.apiKeyEnv));
195
+ if (resolved?.value.trim())
196
+ return resolved.value.trim();
197
+ const ambient = String(process.env[config.apiKeyEnv] ?? "").trim();
198
+ if (ambient)
199
+ return ambient;
200
+ const error = new Error(`DSH provider credential is unavailable: ${config.apiKeyEnv}`);
201
+ error.code = "LCX_CREDENTIAL_UNAVAILABLE";
202
+ throw error;
262
203
  }
263
-
264
- /**
265
- * @param {HeaderMap | null | undefined} headers
266
- * @param {string} name
267
- */
268
- function hasHeader(headers, name) { return Object.keys(headers ?? {}).some((key) => key.toLowerCase() === name.toLowerCase()) }
204
+ /** @param {HeaderMap | null | undefined} headers @param {string} name */
205
+ function hasHeader(headers, name) { return Object.keys(headers ?? {}).some((key) => key.toLowerCase() === name.toLowerCase()); }
269
206
  /** @param {HeaderMap | null | undefined} headers */
270
- function hasExplicitSessionAffinity(headers) { return ['session-id', 'session_id', 'x-session-id'].some((name) => hasHeader(headers, name)) }
207
+ function hasExplicitSessionAffinity(headers) { return ["session-id", "session_id", "x-session-id"].some((name) => hasHeader(headers, name)); }
271
208
  /** @param {Partial<ResolvedResponsesRoute> | null | undefined} config */
272
209
  function sessionAffinityFormat(config) {
273
- if (config?.api && config.api !== 'openai-responses') return 'none'
274
- const explicit = config?.responsesCompat?.sessionAffinityFormat
275
- if (['openai', 'openai-nosession', 'openrouter'].includes(String(explicit))) return explicit
276
- const provider = String(config?.provider ?? '').toLowerCase()
277
- const baseURL = String(config?.baseURL ?? '').toLowerCase()
278
- return provider === 'openrouter' || baseURL.includes('openrouter.ai') ? 'openrouter' : 'openai'
210
+ if (config?.api && config.api !== "openai-responses")
211
+ return "none";
212
+ const explicit = config?.responsesCompat?.sessionAffinityFormat;
213
+ if (explicit === "openai" || explicit === "openai-nosession" || explicit === "openrouter")
214
+ return explicit;
215
+ return String(config?.provider ?? "").toLowerCase() === "openrouter" ||
216
+ String(config?.baseURL ?? "").toLowerCase().includes("openrouter.ai")
217
+ ? "openrouter"
218
+ : "openai";
279
219
  }
280
-
281
- /**
282
- * @param {RouteContext | null | undefined} ctx
283
- * @param {ResolvedResponsesRoute} config
284
- * @param {unknown} sessionId
285
- * @param {string | null | undefined} requestId
286
- * @returns {Promise<HeaderMap>}
287
- */
220
+ /** @param {RouteContext | null | undefined} ctx @param {ResolvedResponsesRoute} config @param {unknown} sessionId @param {string | null | undefined} requestId @returns {Promise<HeaderMap>} */
288
221
  export async function authenticatedHeaders(ctx, config, sessionId, requestId) {
289
- const explicit = { ...(config.headers ?? {}) }
290
- /** @type {HeaderMap} */
291
- const headers = {
292
- ...attributionHeaders(),
293
- authorization: `Bearer ${await resolveApiKey(ctx, config)}`,
294
- }
295
- const sid = sessionId ? String(sessionId) : undefined
296
- const format = sessionAffinityFormat(config)
297
- if (sid && !hasExplicitSessionAffinity(explicit)) {
298
- if (format === 'openai') headers.session_id = sid
299
- else if (format === 'openrouter') headers['x-session-id'] = sid
300
- }
301
- if (requestId !== null && !hasHeader(explicit, 'x-client-request-id')) {
302
- const correlation = requestId ?? (sid && (format === 'openai' || format === 'openai-nosession') ? sid : (!sid ? randomUUID() : undefined))
303
- if (correlation) headers['x-client-request-id'] = correlation
304
- }
305
- return { ...headers, ...explicit }
222
+ const explicit = { ...(config.headers ?? {}) };
223
+ const headers = {
224
+ ...attributionHeaders(),
225
+ authorization: `Bearer ${await resolveApiKey(ctx, config)}`,
226
+ };
227
+ const sid = sessionId ? String(sessionId) : undefined;
228
+ const format = sessionAffinityFormat(config);
229
+ if (sid && !hasExplicitSessionAffinity(explicit)) {
230
+ if (format === "openai")
231
+ headers.session_id = sid;
232
+ else if (format === "openrouter")
233
+ headers["x-session-id"] = sid;
234
+ }
235
+ if (requestId !== null && !hasHeader(explicit, "x-client-request-id")) {
236
+ const correlation = requestId ?? (sid && (format === "openai" || format === "openai-nosession") ? sid : !sid ? randomUUID() : undefined);
237
+ if (correlation)
238
+ headers["x-client-request-id"] = correlation;
239
+ }
240
+ return { ...headers, ...explicit };
306
241
  }
307
-
308
- /**
309
- * @param {RouteOptions | null | undefined} options
310
- * @param {Pick<UnresolvedRouteConfig, 'provider' | 'model' | 'baseURL'>} config
311
- * @returns {RouteIdentity}
312
- */
242
+ /** @param {RouteOptions | null | undefined} options @param {Pick<UnresolvedRouteConfig, 'provider' | 'model' | 'baseURL'>} config @returns {RouteIdentity} */
313
243
  export function currentRoute(options, config) {
314
- return {
315
- provider: String(options?.provider ?? config.provider ?? ''),
316
- model: String(options?.model ?? config.model ?? ''),
317
- baseURL: normalizeBaseURL(config.baseURL),
318
- sessionId: String(options?.sessionId ?? ''),
319
- }
244
+ return { provider: String(options?.provider ?? config.provider ?? ""), model: String(options?.model ?? config.model ?? ""), baseURL: normalizeBaseURL(config.baseURL), sessionId: String(options?.sessionId ?? "") };
320
245
  }
321
-
322
- /**
323
- * @param {RequestHeader | null | undefined} header
324
- * @param {Partial<RouteIdentity> | null | undefined} route
325
- * @returns {GenerationControls}
326
- */
246
+ /** @param {RequestHeader | null | undefined} header @param {Partial<RouteIdentity> | null | undefined} route @returns {GenerationControls} */
327
247
  export function generationControlsFromHeader(header, route) {
328
- const config = header?.config
329
- if (!config || String(config.provider ?? '') !== String(route?.provider ?? '') || String(config.model ?? '') !== String(route?.model ?? '')) return {}
330
- /** @type {GenerationControls} */
331
- const controls = {}
332
- if (config.reasoningEffort !== undefined) controls.reasoningEffort = config.reasoningEffort
333
- if (config.temperature !== undefined) controls.temperature = config.temperature
334
- if (config.maxTokens !== undefined) controls.maxTokens = config.maxTokens
335
- return controls
248
+ const config = header?.config;
249
+ if (!config || String(config.provider ?? "") !== String(route?.provider ?? "") || String(config.model ?? "") !== String(route?.model ?? ""))
250
+ return {};
251
+ const controls = {};
252
+ if (config.reasoningEffort !== undefined)
253
+ controls.reasoningEffort = config.reasoningEffort;
254
+ if (config.temperature !== undefined)
255
+ controls.temperature = config.temperature;
256
+ if (config.maxTokens !== undefined)
257
+ controls.maxTokens = config.maxTokens;
258
+ return controls;
336
259
  }
337
-
338
- /**
339
- * @param {RouteSession | null | undefined} session
340
- * @param {Partial<RouteIdentity> | null | undefined} route
341
- * @returns {GenerationControls}
342
- */
260
+ /** @param {RouteSession | null | undefined} session @param {Partial<RouteIdentity> | null | undefined} route @returns {GenerationControls} */
343
261
  export function generationControlsFromSession(session, route) {
344
- try { return generationControlsFromHeader(session?.requestHeader?.(), route) }
345
- catch { return {} }
346
- }
347
-
348
- /**
349
- * @param {{ set?: (key: string, value: RequestHeader) => unknown } | null | undefined} cache
350
- * @param {RouteSession | null | undefined} session
351
- * @param {{ type?: string, data?: { header?: RequestHeader } } | null | undefined} event
352
- */
353
- export function updateRequestHeaderCache(cache, session, event) {
354
- if (!cache?.set || !session?.id) return false
355
- let header
356
- if (event?.type === 'request/header') header = event?.data?.header
357
- else if (event?.type === 'compaction/start') {
358
- try { header = session.requestHeader?.() } catch { return false }
359
- }
360
- if (!header) return false
361
- cache.set(String(session.id), header)
362
- return true
363
- }
364
-
365
- /**
366
- * @param {RouteContext | null | undefined} ctx
367
- * @param {string | null | undefined} sessionId
368
- * @returns {string[]}
369
- */
370
- export function sessionAncestry(ctx, sessionId) {
371
- if (!sessionId) return []
372
- const sessions = /** @type {SessionsService | undefined} */ (ctx?.get?.('sessions') ?? ctx?.sessions)
373
- const result = []
374
- /** @type {Set<string>} */
375
- const seen = new Set()
376
- let current = sessions?.get?.(sessionId)
377
- while (current && !seen.has(current.id)) {
378
- seen.add(current.id)
379
- result.push(String(current.id))
380
- const parent = current.header?.parentSession
381
- if (!parent) break
382
- current = sessions?.get?.(parent)
383
- }
384
- return result
262
+ try {
263
+ return generationControlsFromHeader(session?.requestHeader?.(), route);
264
+ }
265
+ catch {
266
+ return {};
267
+ }
385
268
  }
386
-
387
- /**
388
- * @param {CheckpointRouteRecord | null | undefined} record
389
- * @param {RouteIdentity} route
390
- * @param {unknown} ctx
391
- */
269
+ /** @param {CheckpointRouteRecord | null | undefined} record @param {RouteIdentity} route @param {unknown} ctx */
392
270
  export function routeCompatible(record, route, ctx) {
393
- if (!record || ![4, 5].includes(record.version)) return false
394
- if (record.provider !== route.provider || record.model !== route.model) return false
395
- if (record.baseURLFingerprint !== baseURLFingerprint(route.baseURL)) return false
396
- // Ancestry authorizes portable migration only. Opaque native output is
397
- // replayable exclusively by the session that created the checkpoint.
398
- return record.sourceSessionId === route.sessionId
271
+ void ctx;
272
+ return !!record && record.version === 5 && record.provider === route.provider && record.model === route.model && record.baseURLFingerprint === baseURLFingerprint(route.baseURL) && record.sourceSessionId === route.sessionId;
399
273
  }