dsh-lcx-codex 0.4.2 → 0.4.3-pre.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/README.md +75 -224
- package/THIRD_PARTY_NOTICES.md +64 -0
- package/cordis.patch.yml +3 -20
- package/lib/auxiliary-usage.js +63 -0
- package/lib/client.js +1398 -167
- package/lib/compact-v2.js +218 -199
- package/lib/dsh-compat.js +294 -100
- package/lib/dsh-responses.js +512 -277
- package/lib/grok-native-search.js +391 -0
- package/lib/index.js +1066 -758
- package/lib/invocation-policy-scope.js +261 -0
- package/lib/json-store.js +57 -31
- package/lib/native-checkpoint.js +520 -194
- package/lib/pi-responses-runtime.js +1571 -0
- package/lib/responses-request.js +109 -121
- package/lib/responses-stream.js +1280 -447
- package/lib/route.js +425 -369
- package/lib/search-accounting.js +86 -0
- package/lib/search-usage.js +86 -0
- package/lib/service-mutex.js +73 -64
- package/lib/token-budget.js +176 -108
- package/lib/transport.js +308 -68
- package/lib/types/client/index.d.ts +18 -0
- package/lib/types/client/search-media.d.ts +16 -0
- package/lib/types/index.d.ts +83 -0
- package/lib/web-run-output.js +189 -18
- package/lib/web-search-alpha.js +1067 -163
- package/lib/web-search-capability.js +80 -65
- package/lib/web-search-hosted.js +321 -33
- package/lib/web-search-ref-store.js +145 -60
- package/package.json +112 -32
- package/ARCHITECTURE.md +0 -117
- package/CHANGELOG.md +0 -224
- package/README_EN.md +0 -277
- package/assets/dsh-lcx-codex-banner.jpg +0 -0
- package/lib/legacy-v3.js +0 -20
- package/lib/responses-replay.js +0 -68
- package/scripts/probe-alpha.mjs +0 -43
- package/scripts/validate-dsh-schema.mjs +0 -31
package/lib/route.js
CHANGED
|
@@ -1,399 +1,455 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import { getBuiltinModels } from
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/** @
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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 "./pi-responses-runtime.js";
|
|
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 isPositiveFinite(value) {
|
|
16
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
17
|
+
}
|
|
18
|
+
function asLlmSettingsSection(value) {
|
|
19
|
+
if (!isRecord(value))
|
|
20
|
+
return undefined;
|
|
21
|
+
const providers = value.providers;
|
|
22
|
+
if (providers === undefined)
|
|
23
|
+
return {};
|
|
24
|
+
if (!isRecord(providers))
|
|
25
|
+
return undefined;
|
|
26
|
+
return { providers: providers };
|
|
27
|
+
}
|
|
76
28
|
/** @param {unknown} value */
|
|
77
29
|
export function normalizeBaseURL(value) {
|
|
78
|
-
|
|
30
|
+
return String(value ?? "").trim().replace(/\/+$/u, "");
|
|
79
31
|
}
|
|
80
|
-
|
|
81
32
|
/** @param {unknown} baseURL */
|
|
82
33
|
export function baseURLFingerprint(baseURL) {
|
|
83
|
-
|
|
34
|
+
return createHash("sha256").update(normalizeBaseURL(baseURL), "utf8").digest("hex");
|
|
84
35
|
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* @param {Partial<RouteIdentity> | null | undefined} route
|
|
88
|
-
* @param {{ includeSession?: boolean }} [options]
|
|
89
|
-
*/
|
|
36
|
+
/** @param {Partial<RouteIdentity> | null | undefined} route @param {{ includeSession?: boolean }} [options] */
|
|
90
37
|
export function routeFingerprint(route, options = {}) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
38
|
+
const includeSession = options.includeSession !== false;
|
|
39
|
+
return createHash("sha256").update([
|
|
40
|
+
route?.provider ?? "", route?.model ?? "", normalizeBaseURL(route?.baseURL), includeSession ? (route?.sessionId ?? "") : "",
|
|
41
|
+
].join("\u001f"), "utf8").digest("hex");
|
|
94
42
|
}
|
|
95
|
-
|
|
96
43
|
/** @param {unknown} value */
|
|
97
44
|
function clampPromptCacheKey(value) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
45
|
+
if (value === undefined)
|
|
46
|
+
return undefined;
|
|
47
|
+
const chars = Array.from(String(value));
|
|
48
|
+
return chars.length <= 64 ? String(value) : chars.slice(0, 64).join("");
|
|
49
|
+
}
|
|
50
|
+
/** @param {Partial<RouteIdentity> | null | undefined} route @param {Partial<Pick<ResolvedResponsesRoute, 'cacheRetention'>>} [config] @param {RouteContext | null | undefined} [ctx] */
|
|
51
|
+
export function promptCacheSessionId(route, config = {}, ctx = undefined) {
|
|
52
|
+
if (config.cacheRetention === "none")
|
|
53
|
+
return undefined;
|
|
54
|
+
const sessionId = route?.sessionId ? String(route.sessionId) : undefined;
|
|
55
|
+
if (!sessionId)
|
|
56
|
+
return undefined;
|
|
57
|
+
const sessions = ctx?.sessions;
|
|
58
|
+
let current = sessions?.get(SessionId(sessionId));
|
|
59
|
+
if (current?.header?.origin !== "subagent")
|
|
60
|
+
return sessionId;
|
|
61
|
+
const seen = new Set([sessionId]);
|
|
62
|
+
while (current?.header?.origin === "subagent" && current.header.parentSession) {
|
|
63
|
+
const parentId = current.header.parentSession;
|
|
64
|
+
if (seen.has(parentId))
|
|
65
|
+
return sessionId;
|
|
66
|
+
const parent = sessions?.get(SessionId(parentId));
|
|
67
|
+
if (!parent)
|
|
68
|
+
return sessionId;
|
|
69
|
+
seen.add(parentId);
|
|
70
|
+
current = parent;
|
|
71
|
+
}
|
|
72
|
+
return current?.id ?? sessionId;
|
|
73
|
+
}
|
|
74
|
+
/** @param {Partial<RouteIdentity> | null | undefined} route @param {Partial<Pick<ResolvedResponsesRoute, 'cacheRetention'>>} [config] @param {RouteContext | null | undefined} [ctx] */
|
|
75
|
+
export function promptCacheKey(route, config = {}, ctx = undefined) {
|
|
76
|
+
return clampPromptCacheKey(promptCacheSessionId(route, config, ctx));
|
|
77
|
+
}
|
|
78
|
+
/** Grok follows Pi directly: cache/affinity belongs to the selected child session. */
|
|
79
|
+
export function grokPromptCacheSessionId(route, config = {}) {
|
|
80
|
+
if (config.cacheRetention === "none")
|
|
81
|
+
return undefined;
|
|
82
|
+
const sessionId = String(route?.sessionId ?? "");
|
|
83
|
+
return sessionId || undefined;
|
|
84
|
+
}
|
|
85
|
+
/** @param {Partial<Pick<ResolvedResponsesRoute, 'cacheRetention' | 'supportsLongCacheRetention' | 'responsesCompat'>>} [config] */
|
|
121
86
|
export function promptCacheRetention(config = {}) {
|
|
122
|
-
|
|
87
|
+
return config.cacheRetention === "long" && config.supportsLongCacheRetention === true && config.responsesCompat?.supportsExplicitPromptCacheMode !== true ? "24h" : undefined;
|
|
123
88
|
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* @param {RetryPolicyConfig | null | undefined} policy
|
|
127
|
-
* @param {number} [fallback]
|
|
128
|
-
*/
|
|
89
|
+
/** @param {RetryPolicyConfig | null | undefined} policy @param {number} [fallback] */
|
|
129
90
|
function retryAttempts(policy, fallback = 3) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
*/
|
|
91
|
+
if (!policy)
|
|
92
|
+
return fallback;
|
|
93
|
+
try {
|
|
94
|
+
const resolved = resolveRetryPolicy(policy, "llm-pi-ai provider retryPolicy");
|
|
95
|
+
if (resolved.mode === "normal" && isPositiveInteger(resolved.maxRetries))
|
|
96
|
+
return Math.min(resolved.maxRetries + 1, 6);
|
|
97
|
+
}
|
|
98
|
+
catch { }
|
|
99
|
+
return fallback;
|
|
100
|
+
}
|
|
101
|
+
/** @param {RouteContext | null | undefined} ctx @param {string} namespace */
|
|
142
102
|
export function settingsValue(ctx, namespace) {
|
|
143
|
-
|
|
144
|
-
|
|
103
|
+
return asLlmSettingsSection(ctx?.settings?.get(namespace));
|
|
104
|
+
}
|
|
105
|
+
/** The DSH 0.1.5 contract exposes deferred provider diagnostics separately from saved settings. */
|
|
106
|
+
function providerDirectoryUsable(ctx, provider) {
|
|
107
|
+
const llm = ctx?.llm;
|
|
108
|
+
const list = llm?.listConfigurableProviders;
|
|
109
|
+
if (typeof list !== "function")
|
|
110
|
+
return true;
|
|
111
|
+
try {
|
|
112
|
+
const entry = list.call(llm).find((candidate) => candidate.provider === provider && candidate.settingsNs === "llm-pi-ai");
|
|
113
|
+
return typeof entry?.error !== "string" || entry.error.trim() === "";
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
145
118
|
}
|
|
146
|
-
|
|
147
119
|
/** @type {Set<keyof ResponsesCompat>} */
|
|
148
|
-
const RESPONSES_COMPAT_FIELDS = new Set([
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* @param {ResponsesCompat} target
|
|
152
|
-
* @param {unknown} source
|
|
153
|
-
*/
|
|
120
|
+
const RESPONSES_COMPAT_FIELDS = new Set(["supportsDeveloperRole", "sessionAffinityFormat", "supportsStrictMode", "supportsLongCacheRetention", "supportsOpenAIGrammarTools", "supportsAdditionalTools", "supportsToolSearch", "supportsExplicitPromptCacheMode", "supportsMaxOutputTokens"]);
|
|
121
|
+
/** @param {ResponsesCompat} target @param {unknown} source */
|
|
154
122
|
function copyResponsesCompat(target, source) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
123
|
+
if (!isRecord(source))
|
|
124
|
+
return;
|
|
125
|
+
for (const field of RESPONSES_COMPAT_FIELDS) {
|
|
126
|
+
const value = source[field];
|
|
127
|
+
if (field === "sessionAffinityFormat") {
|
|
128
|
+
if (value === "openai" || value === "openai-nosession" || value === "openrouter")
|
|
129
|
+
target.sessionAffinityFormat = value;
|
|
130
|
+
}
|
|
131
|
+
else if (typeof value === "boolean")
|
|
132
|
+
Object.assign(target, { [field]: value });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
166
135
|
/** @param {unknown} provider @param {unknown} modelId */
|
|
167
136
|
function builtinResponsesModel(provider, modelId) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
137
|
+
const providerName = String(provider ?? "");
|
|
138
|
+
const builtinProvider = getBuiltinProviders().find((candidate) => candidate === providerName);
|
|
139
|
+
if (builtinProvider === undefined)
|
|
140
|
+
return undefined;
|
|
141
|
+
return getBuiltinModels(builtinProvider).find((model) => model.id === String(modelId ?? "") && model.api === "openai-responses");
|
|
142
|
+
}
|
|
143
|
+
function modelDescriptorDefaults(model) {
|
|
144
|
+
if (!model)
|
|
145
|
+
return undefined;
|
|
146
|
+
return {
|
|
147
|
+
reasoning: model.reasoning,
|
|
148
|
+
thinkingLevelMap: model.thinkingLevelMap,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function configuredModelProfile(profile, modelId) {
|
|
152
|
+
const configuredModels = Array.isArray(profile?.models) ? profile.models : [];
|
|
153
|
+
return configuredModels.length > 0
|
|
154
|
+
? configuredModels.find((entry) => String(entry?.id ?? "") === String(modelId))
|
|
155
|
+
: profile?.modelOverrides?.[String(modelId)];
|
|
156
|
+
}
|
|
157
|
+
/** @param {ProviderProfile | null | undefined} profile @param {unknown} modelId @returns {ResponsesCompat | undefined} */
|
|
179
158
|
function configuredResponsesCompat(profile, modelId) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
159
|
+
const compat = {};
|
|
160
|
+
copyResponsesCompat(compat, profile?.compat);
|
|
161
|
+
copyResponsesCompat(compat, configuredModelProfile(profile, modelId)?.compat);
|
|
162
|
+
return Object.keys(compat).length ? compat : undefined;
|
|
163
|
+
}
|
|
164
|
+
const MODEL_THINKING_LEVELS = [
|
|
165
|
+
"off",
|
|
166
|
+
"minimal",
|
|
167
|
+
"low",
|
|
168
|
+
"medium",
|
|
169
|
+
"high",
|
|
170
|
+
"xhigh",
|
|
171
|
+
"max",
|
|
172
|
+
];
|
|
173
|
+
function profileReasoning(value) {
|
|
174
|
+
return MODEL_THINKING_LEVELS.find((level) => level === value);
|
|
175
|
+
}
|
|
176
|
+
function configuredModelControls(profile, modelId, base) {
|
|
177
|
+
const configured = configuredModelProfile(profile, modelId);
|
|
178
|
+
const efforts = configured?.reasoningEfforts;
|
|
179
|
+
if (efforts === undefined)
|
|
180
|
+
return {
|
|
181
|
+
reasoning: base?.reasoning ?? false,
|
|
182
|
+
...(base?.thinkingLevelMap === undefined
|
|
183
|
+
? {}
|
|
184
|
+
: { thinkingLevelMap: base.thinkingLevelMap }),
|
|
185
|
+
};
|
|
186
|
+
if (efforts === false)
|
|
187
|
+
return { reasoning: false };
|
|
188
|
+
if (!isRecord(efforts))
|
|
189
|
+
return { reasoning: false };
|
|
190
|
+
const thinkingLevelMap = {};
|
|
191
|
+
for (const level of MODEL_THINKING_LEVELS) {
|
|
192
|
+
const value = efforts[level];
|
|
193
|
+
if (value === undefined)
|
|
194
|
+
thinkingLevelMap[level] = null;
|
|
195
|
+
else if (value === null) {
|
|
196
|
+
// DSH permits only off:null: it means supported-off by omitting the wire option.
|
|
197
|
+
if (level !== "off")
|
|
198
|
+
thinkingLevelMap[level] = null;
|
|
199
|
+
}
|
|
200
|
+
else if (typeof value === "string")
|
|
201
|
+
thinkingLevelMap[level] = value;
|
|
202
|
+
}
|
|
203
|
+
return { reasoning: true, thinkingLevelMap };
|
|
204
|
+
}
|
|
205
|
+
function isLcxCapabilityRoute(provider, model) {
|
|
206
|
+
return provider === "lcx" &&
|
|
207
|
+
/^gpt-5\.6-(?:sol|luna|terra)$/iu.test(model);
|
|
208
|
+
}
|
|
209
|
+
/** Resolve only the selected DSH profile; policy cannot supply route identity or credentials. */
|
|
210
|
+
export function resolveResponsesRouteConfig(ctx, options, policy) {
|
|
211
|
+
const provider = String(options?.provider ?? "");
|
|
212
|
+
const model = String(options?.model ?? "");
|
|
213
|
+
if (!provider.trim() || !/^gpt-/iu.test(model) || !providerDirectoryUsable(ctx, provider))
|
|
214
|
+
return undefined;
|
|
215
|
+
const section = settingsValue(ctx, "llm-pi-ai");
|
|
216
|
+
const profile = section?.providers?.[provider];
|
|
217
|
+
const lcxCapabilityRoute = isLcxCapabilityRoute(provider, model);
|
|
218
|
+
if (!isRecord(profile))
|
|
219
|
+
return undefined;
|
|
220
|
+
const configured = profile;
|
|
221
|
+
const builtin = builtinResponsesModel(provider, model);
|
|
222
|
+
const api = configured.api ?? builtin?.api;
|
|
223
|
+
if (api !== "openai-responses")
|
|
224
|
+
return undefined;
|
|
225
|
+
const baseURL = configured.baseURL ?? builtin?.baseUrl;
|
|
226
|
+
const apiKeyEnv = configured.apiKeyEnv;
|
|
227
|
+
if (typeof baseURL !== "string" || !normalizeBaseURL(baseURL) || typeof apiKeyEnv !== "string" || !apiKeyEnv.trim())
|
|
228
|
+
return undefined;
|
|
229
|
+
const responsesCompat = {};
|
|
230
|
+
copyResponsesCompat(responsesCompat, builtin?.compat);
|
|
231
|
+
copyResponsesCompat(responsesCompat, lcxCapabilityRoute ? policy.responsesCompat : undefined);
|
|
232
|
+
copyResponsesCompat(responsesCompat, configuredResponsesCompat(configured, model));
|
|
233
|
+
const promptCacheModel = lcxCapabilityRoute && policy.supportsExplicitPromptCacheMode === true ? builtinResponsesModel("openai", model) : undefined;
|
|
234
|
+
const promptCacheRaw = promptCacheModel;
|
|
235
|
+
const promptCacheCompat = isRecord(promptCacheRaw)
|
|
236
|
+
? promptCacheRaw.compat
|
|
237
|
+
: undefined;
|
|
238
|
+
if (isRecord(promptCacheCompat) &&
|
|
239
|
+
promptCacheCompat.supportsExplicitPromptCacheMode === true &&
|
|
240
|
+
responsesCompat.supportsExplicitPromptCacheMode === undefined)
|
|
241
|
+
responsesCompat.supportsExplicitPromptCacheMode = true;
|
|
242
|
+
const resolvedCompat = Object.keys(responsesCompat).length ? responsesCompat : undefined;
|
|
243
|
+
const supportsLongCacheRetention = resolvedCompat?.supportsLongCacheRetention ?? (lcxCapabilityRoute && policy.supportsLongCacheRetention === true);
|
|
244
|
+
responsesCompat.supportsLongCacheRetention = supportsLongCacheRetention;
|
|
245
|
+
const configuredRetention = configured.cacheRetention;
|
|
246
|
+
const fallbackRetention = lcxCapabilityRoute ? policy.cacheRetention : undefined;
|
|
247
|
+
const requestedRetention = configuredRetention === "none" || configuredRetention === "short" || configuredRetention === "long" ? configuredRetention : fallbackRetention === "none" || fallbackRetention === "short" || fallbackRetention === "long" ? fallbackRetention : undefined;
|
|
248
|
+
const cacheRetention = requestedRetention === "long" && !supportsLongCacheRetention ? "short" : requestedRetention ?? (supportsLongCacheRetention ? "long" : "short");
|
|
249
|
+
return {
|
|
250
|
+
provider,
|
|
251
|
+
model,
|
|
252
|
+
api: "openai-responses",
|
|
253
|
+
baseURL: normalizeBaseURL(baseURL),
|
|
254
|
+
apiKeyEnv,
|
|
255
|
+
headers: { ...(configured.headers ?? {}) },
|
|
256
|
+
cacheRetention,
|
|
257
|
+
supportsLongCacheRetention,
|
|
258
|
+
responsesCompat,
|
|
259
|
+
// These are explicitly LCX-owned transport and request-capability policies.
|
|
260
|
+
timeoutMs: isPositiveInteger(configured.timeoutMs) ? configured.timeoutMs : policy.timeoutMs,
|
|
261
|
+
maxAttempts: retryAttempts(configured.retryPolicy, policy.maxAttempts),
|
|
262
|
+
maxRequestImageBytes: isPositiveInteger(configured.maxRequestImageBytes) ? configured.maxRequestImageBytes : policy.maxRequestImageBytes,
|
|
263
|
+
requestImagePixelBudget: isPositiveInteger(configured.requestImagePixelBudget) ? configured.requestImagePixelBudget : policy.requestImagePixelBudget,
|
|
264
|
+
requestImageMaxBytes: isPositiveInteger(configured.requestImageMaxBytes) ? configured.requestImageMaxBytes : policy.requestImageMaxBytes,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/** Resolve a Grok-prefixed model through its selected DSH Responses profile. */
|
|
268
|
+
export function resolveGrokResponsesRouteConfig(ctx, options, policy) {
|
|
269
|
+
const provider = String(options?.provider ?? "");
|
|
270
|
+
const model = String(options?.model ?? "");
|
|
271
|
+
if (!provider.trim() || !/^grok/iu.test(model) || !providerDirectoryUsable(ctx, provider))
|
|
272
|
+
return undefined;
|
|
273
|
+
const section = settingsValue(ctx, "llm-pi-ai");
|
|
274
|
+
const configured = section?.providers?.[provider];
|
|
275
|
+
if (!isRecord(configured))
|
|
276
|
+
return undefined;
|
|
277
|
+
const selectedBuiltin = builtinResponsesModel(provider, model);
|
|
278
|
+
const api = configured.api ?? selectedBuiltin?.api;
|
|
279
|
+
if (api !== "openai-responses")
|
|
280
|
+
return undefined;
|
|
281
|
+
const baseURL = configured.baseURL ?? selectedBuiltin?.baseUrl;
|
|
282
|
+
const apiKeyEnv = configured.apiKeyEnv;
|
|
283
|
+
if (typeof baseURL !== "string" ||
|
|
284
|
+
!normalizeBaseURL(baseURL) ||
|
|
285
|
+
typeof apiKeyEnv !== "string" ||
|
|
286
|
+
!apiKeyEnv.trim())
|
|
287
|
+
return undefined;
|
|
288
|
+
const responsesCompat = {};
|
|
289
|
+
copyResponsesCompat(responsesCompat, selectedBuiltin?.compat);
|
|
290
|
+
copyResponsesCompat(responsesCompat, configuredResponsesCompat(configured, model));
|
|
291
|
+
const supportsLongCacheRetention = responsesCompat.supportsLongCacheRetention === true;
|
|
292
|
+
responsesCompat.supportsLongCacheRetention = supportsLongCacheRetention;
|
|
293
|
+
const requestedRetention = configured.cacheRetention;
|
|
294
|
+
const cacheRetention = requestedRetention === "none"
|
|
295
|
+
? "none"
|
|
296
|
+
: requestedRetention === "long" && supportsLongCacheRetention
|
|
297
|
+
? "long"
|
|
298
|
+
: "short";
|
|
299
|
+
return {
|
|
300
|
+
provider,
|
|
301
|
+
model,
|
|
302
|
+
api: "openai-responses",
|
|
303
|
+
baseURL: normalizeBaseURL(baseURL),
|
|
304
|
+
apiKeyEnv,
|
|
305
|
+
headers: effectiveGrokProfileHeaders(configured.headers),
|
|
306
|
+
cacheRetention,
|
|
307
|
+
supportsLongCacheRetention,
|
|
308
|
+
responsesCompat,
|
|
309
|
+
modelDefaults: modelDescriptorDefaults(selectedBuiltin),
|
|
310
|
+
modelControls: {
|
|
311
|
+
...configuredModelControls(configured, model, selectedBuiltin),
|
|
312
|
+
includeEncryptedReasoning: true,
|
|
313
|
+
},
|
|
314
|
+
profileReasoning: profileReasoning(configured.reasoning),
|
|
315
|
+
// Pi applies only an explicitly configured request deadline; stream liveness
|
|
316
|
+
// is owned separately by the provider idle watchdog.
|
|
317
|
+
timeoutMs: isPositiveInteger(configured.timeoutMs)
|
|
318
|
+
? configured.timeoutMs
|
|
319
|
+
: undefined,
|
|
320
|
+
streamIdleTimeoutMs: isPositiveFinite(configured.streamIdleTimeoutMs)
|
|
321
|
+
? configured.streamIdleTimeoutMs
|
|
322
|
+
: 300_000,
|
|
323
|
+
maxAttempts: retryAttempts(configured.retryPolicy, policy.maxAttempts),
|
|
324
|
+
maxRequestImageBytes: isPositiveInteger(configured.maxRequestImageBytes)
|
|
325
|
+
? configured.maxRequestImageBytes
|
|
326
|
+
: policy.maxRequestImageBytes,
|
|
327
|
+
requestImagePixelBudget: isPositiveInteger(configured.requestImagePixelBudget)
|
|
328
|
+
? configured.requestImagePixelBudget
|
|
329
|
+
: policy.requestImagePixelBudget,
|
|
330
|
+
requestImageMaxBytes: isPositiveInteger(configured.requestImageMaxBytes)
|
|
331
|
+
? configured.requestImageMaxBytes
|
|
332
|
+
: policy.requestImageMaxBytes,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/** @param {RouteContext | null | undefined} ctx @param {Pick<ResolvedResponsesRoute, 'apiKeyEnv'>} config */
|
|
250
336
|
export async function resolveApiKey(ctx, config) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
337
|
+
const resolved = await ctx?.credentials.resolve(credentialRef(config.apiKeyEnv));
|
|
338
|
+
if (resolved?.value.trim())
|
|
339
|
+
return resolved.value.trim();
|
|
340
|
+
const ambient = String(process.env[config.apiKeyEnv] ?? "").trim();
|
|
341
|
+
if (ambient)
|
|
342
|
+
return ambient;
|
|
343
|
+
const error = new Error(`DSH provider credential is unavailable: ${config.apiKeyEnv}`);
|
|
344
|
+
error.code = "LCX_CREDENTIAL_UNAVAILABLE";
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
function setHeaderCaseInsensitive(headers, name, value) {
|
|
348
|
+
const normalized = name.toLowerCase();
|
|
349
|
+
for (const existing of Object.keys(headers))
|
|
350
|
+
if (existing.toLowerCase() === normalized)
|
|
351
|
+
delete headers[existing];
|
|
352
|
+
headers[name] = value;
|
|
353
|
+
}
|
|
354
|
+
/** Apply DSH's case-insensitive attribution reservation at profile resolution. */
|
|
355
|
+
function effectiveGrokProfileHeaders(configured) {
|
|
356
|
+
const attribution = attributionHeaders();
|
|
357
|
+
const reserved = new Set(Object.keys(attribution).map((name) => name.toLowerCase()));
|
|
358
|
+
const effective = {};
|
|
359
|
+
for (const [name, value] of Object.entries(configured ?? {}))
|
|
360
|
+
if (!reserved.has(name.toLowerCase()))
|
|
361
|
+
setHeaderCaseInsensitive(effective, name, value);
|
|
362
|
+
return effective;
|
|
363
|
+
}
|
|
364
|
+
/** @param {HeaderMap | null | undefined} headers @param {string} name */
|
|
365
|
+
function hasHeader(headers, name) { return Object.keys(headers ?? {}).some((key) => key.toLowerCase() === name.toLowerCase()); }
|
|
269
366
|
/** @param {HeaderMap | null | undefined} headers */
|
|
270
|
-
function hasExplicitSessionAffinity(headers) { return [
|
|
367
|
+
function hasExplicitSessionAffinity(headers) { return ["session-id", "session_id", "x-session-id"].some((name) => hasHeader(headers, name)); }
|
|
271
368
|
/** @param {Partial<ResolvedResponsesRoute> | null | undefined} config */
|
|
272
369
|
function sessionAffinityFormat(config) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
* @param {unknown} sessionId
|
|
285
|
-
* @param {string | null | undefined} requestId
|
|
286
|
-
* @returns {Promise<HeaderMap>}
|
|
287
|
-
*/
|
|
370
|
+
if (config?.api && config.api !== "openai-responses")
|
|
371
|
+
return "none";
|
|
372
|
+
const explicit = config?.responsesCompat?.sessionAffinityFormat;
|
|
373
|
+
if (explicit === "openai" || explicit === "openai-nosession" || explicit === "openrouter")
|
|
374
|
+
return explicit;
|
|
375
|
+
return String(config?.provider ?? "").toLowerCase() === "openrouter" ||
|
|
376
|
+
String(config?.baseURL ?? "").toLowerCase().includes("openrouter.ai")
|
|
377
|
+
? "openrouter"
|
|
378
|
+
: "openai";
|
|
379
|
+
}
|
|
380
|
+
/** @param {RouteContext | null | undefined} ctx @param {ResolvedResponsesRoute} config @param {unknown} sessionId @param {string | null | undefined} requestId @returns {Promise<HeaderMap>} */
|
|
288
381
|
export async function authenticatedHeaders(ctx, config, sessionId, requestId) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
382
|
+
const explicit = { ...(config.headers ?? {}) };
|
|
383
|
+
const headers = {
|
|
384
|
+
...attributionHeaders(),
|
|
385
|
+
authorization: `Bearer ${await resolveApiKey(ctx, config)}`,
|
|
386
|
+
};
|
|
387
|
+
const sid = sessionId ? String(sessionId) : undefined;
|
|
388
|
+
const format = sessionAffinityFormat(config);
|
|
389
|
+
if (sid && !hasExplicitSessionAffinity(explicit)) {
|
|
390
|
+
if (format === "openai")
|
|
391
|
+
headers.session_id = sid;
|
|
392
|
+
else if (format === "openrouter")
|
|
393
|
+
headers["x-session-id"] = sid;
|
|
394
|
+
}
|
|
395
|
+
if (requestId !== null && !hasHeader(explicit, "x-client-request-id")) {
|
|
396
|
+
const correlation = requestId ?? (sid && (format === "openai" || format === "openai-nosession") ? sid : !sid ? randomUUID() : undefined);
|
|
397
|
+
if (correlation)
|
|
398
|
+
headers["x-client-request-id"] = correlation;
|
|
399
|
+
}
|
|
400
|
+
return { ...headers, ...explicit };
|
|
401
|
+
}
|
|
402
|
+
/** Grok mirrors Pi createClient ordering without changing accepted GPT headers. */
|
|
403
|
+
export async function authenticatedGrokHeaders(ctx, config, sessionId) {
|
|
404
|
+
const explicit = effectiveGrokProfileHeaders(config.headers);
|
|
405
|
+
const headers = {
|
|
406
|
+
authorization: `Bearer ${await resolveApiKey(ctx, config)}`,
|
|
407
|
+
};
|
|
408
|
+
const sid = sessionId ? String(sessionId) : undefined;
|
|
409
|
+
const format = sessionAffinityFormat(config);
|
|
410
|
+
if (sid) {
|
|
411
|
+
if (format === "openai")
|
|
412
|
+
headers.session_id = sid;
|
|
413
|
+
else if (format === "openrouter")
|
|
414
|
+
headers["x-session-id"] = sid;
|
|
415
|
+
if (format === "openai" || format === "openai-nosession")
|
|
416
|
+
headers["x-client-request-id"] = sid;
|
|
417
|
+
}
|
|
418
|
+
for (const [name, value] of Object.entries(explicit))
|
|
419
|
+
setHeaderCaseInsensitive(headers, name, value);
|
|
420
|
+
for (const [name, value] of Object.entries(attributionHeaders()))
|
|
421
|
+
setHeaderCaseInsensitive(headers, name, value);
|
|
422
|
+
return headers;
|
|
423
|
+
}
|
|
424
|
+
/** @param {RouteOptions | null | undefined} options @param {Pick<UnresolvedRouteConfig, 'provider' | 'model' | 'baseURL'>} config @returns {RouteIdentity} */
|
|
313
425
|
export function currentRoute(options, config) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
baseURL: normalizeBaseURL(config.baseURL),
|
|
318
|
-
sessionId: String(options?.sessionId ?? ''),
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* @param {RequestHeader | null | undefined} header
|
|
324
|
-
* @param {Partial<RouteIdentity> | null | undefined} route
|
|
325
|
-
* @returns {GenerationControls}
|
|
326
|
-
*/
|
|
426
|
+
return { provider: String(options?.provider ?? config.provider ?? ""), model: String(options?.model ?? config.model ?? ""), baseURL: normalizeBaseURL(config.baseURL), sessionId: String(options?.sessionId ?? "") };
|
|
427
|
+
}
|
|
428
|
+
/** @param {RequestHeader | null | undefined} header @param {Partial<RouteIdentity> | null | undefined} route @returns {GenerationControls} */
|
|
327
429
|
export function generationControlsFromHeader(header, route) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
* @returns {GenerationControls}
|
|
342
|
-
*/
|
|
430
|
+
const config = header?.config;
|
|
431
|
+
if (!config || String(config.provider ?? "") !== String(route?.provider ?? "") || String(config.model ?? "") !== String(route?.model ?? ""))
|
|
432
|
+
return {};
|
|
433
|
+
const controls = {};
|
|
434
|
+
if (config.reasoningEffort !== undefined)
|
|
435
|
+
controls.reasoningEffort = config.reasoningEffort;
|
|
436
|
+
if (config.temperature !== undefined)
|
|
437
|
+
controls.temperature = config.temperature;
|
|
438
|
+
if (config.maxTokens !== undefined)
|
|
439
|
+
controls.maxTokens = config.maxTokens;
|
|
440
|
+
return controls;
|
|
441
|
+
}
|
|
442
|
+
/** @param {RouteSession | null | undefined} session @param {Partial<RouteIdentity> | null | undefined} route @returns {GenerationControls} */
|
|
343
443
|
export function generationControlsFromSession(session, route) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* @param {CheckpointRouteRecord | null | undefined} record
|
|
389
|
-
* @param {RouteIdentity} route
|
|
390
|
-
* @param {unknown} ctx
|
|
391
|
-
*/
|
|
444
|
+
try {
|
|
445
|
+
return generationControlsFromHeader(session?.requestHeader?.(), route);
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
return {};
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/** @param {CheckpointRouteRecord | null | undefined} record @param {RouteIdentity} route @param {unknown} ctx */
|
|
392
452
|
export function routeCompatible(record, route, ctx) {
|
|
393
|
-
|
|
394
|
-
|
|
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
|
|
453
|
+
void ctx;
|
|
454
|
+
return !!record && record.version === 5 && record.provider === route.provider && record.model === route.model && record.baseURLFingerprint === baseURLFingerprint(route.baseURL) && record.sourceSessionId === route.sessionId;
|
|
399
455
|
}
|