dsh-lcx-codex 0.3.3 → 0.4.0-rc.8
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/ARCHITECTURE.md +89 -0
- package/CHANGELOG.md +84 -43
- package/README.md +208 -100
- package/README_EN.md +133 -101
- package/assets/dsh-lcx-codex-banner.jpg +0 -0
- package/cordis.patch.yml +10 -4
- package/lib/client.js +117 -194
- package/lib/compact-v2.js +90 -278
- package/lib/dsh-responses.js +220 -0
- package/lib/index.js +491 -1632
- package/lib/json-store.js +34 -0
- package/lib/legacy-v3.js +20 -0
- package/lib/native-checkpoint.js +93 -0
- package/lib/responses-replay.js +191 -0
- package/lib/route.js +137 -0
- package/lib/transport.js +58 -380
- package/lib/web-run-output.js +13 -152
- package/lib/web-search-alpha.js +26 -448
- package/lib/web-search-capability.js +12 -76
- package/lib/web-search-hosted.js +22 -372
- package/lib/web-search-ref-store.js +9 -69
- package/package.json +20 -17
- package/scripts/probe-alpha.mjs +28 -76
- package/scripts/validate-dsh-schema.mjs +31 -0
- package/lib/checkpoint-store-v3.js +0 -459
- package/lib/compact.js +0 -752
- package/lib/dsh-pi-responses.js +0 -339
- package/lib/private-file.js +0 -18
- package/lib/session-lease.js +0 -70
- package/lib/web-search-store.js +0 -181
package/lib/index.js
CHANGED
|
@@ -1,31 +1,40 @@
|
|
|
1
1
|
import z from '@deepseek-ai/schemastery'
|
|
2
|
-
import {
|
|
3
|
-
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
4
|
-
import { clampOpenAIPromptCacheKey } from '@earendil-works/pi-ai/api/openai-prompt-cache'
|
|
2
|
+
import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
5
3
|
import { randomUUID } from 'node:crypto'
|
|
6
4
|
import { homedir } from 'node:os'
|
|
7
5
|
import { join } from 'node:path'
|
|
8
|
-
import {
|
|
6
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
7
|
+
import { fetchJsonWithRetry } from './transport.js'
|
|
9
8
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
buildPortableResponsesInput,
|
|
15
|
-
buildPortableResponsesInputWithImages,
|
|
16
|
-
hasPortableCheckpoint,
|
|
17
|
-
hydrateNativeImageReferences,
|
|
18
|
-
inputImageCount,
|
|
19
|
-
checkpointNativeReplacementHistory,
|
|
20
|
-
latestPortableMarker,
|
|
21
|
-
normalizeCompactionResponse,
|
|
22
|
-
persistNativeImageReferences,
|
|
9
|
+
authenticatedHeaders,
|
|
10
|
+
currentRoute,
|
|
11
|
+
promptCacheKey,
|
|
12
|
+
resolveResponsesRouteConfig,
|
|
23
13
|
routeFingerprint,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
} from './route.js'
|
|
15
|
+
import {
|
|
16
|
+
DEFAULT_MAX_REQUEST_IMAGE_BYTES,
|
|
17
|
+
DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET,
|
|
18
|
+
DEFAULT_REQUEST_IMAGE_MAX_BYTES,
|
|
19
|
+
hydrateNativeImageReferences,
|
|
20
|
+
resolveModelImageSupport,
|
|
21
|
+
serializeDshMessages,
|
|
22
|
+
} from './dsh-responses.js'
|
|
23
|
+
import { requestNativeCompaction } from './compact-v2.js'
|
|
24
|
+
import { requestNativeReplay } from './responses-replay.js'
|
|
25
|
+
import {
|
|
26
|
+
checkpointStateForMessage,
|
|
27
|
+
compactCheckpointId,
|
|
28
|
+
createNativeCheckpointBlock,
|
|
29
|
+
legacyCheckpointId,
|
|
30
|
+
nativeCheckpointChunks,
|
|
31
|
+
portableMessagesForCheckpoint,
|
|
32
|
+
retainedConversationInput,
|
|
33
|
+
shadowedMessagesForCheckpoint,
|
|
34
|
+
rewriteCheckpointsPortable,
|
|
35
|
+
stateRouteCompatible,
|
|
36
|
+
} from './native-checkpoint.js'
|
|
37
|
+
import { legacyRouteCompatible, legacyV3Id, loadLegacyRecord } from './legacy-v3.js'
|
|
29
38
|
import {
|
|
30
39
|
HOSTED_SEARCH_OUTPUT,
|
|
31
40
|
HOSTED_SEARCH_PARAMETERS,
|
|
@@ -45,28 +54,27 @@ import {
|
|
|
45
54
|
} from './web-search-alpha.js'
|
|
46
55
|
import { AlphaCapabilityStore, alphaCapabilityFingerprint, alphaCapabilityUsable } from './web-search-capability.js'
|
|
47
56
|
import { AlphaRefStore } from './web-search-ref-store.js'
|
|
48
|
-
import { createSessionGenerationTracker } from './session-lease.js'
|
|
49
57
|
|
|
50
|
-
const name = 'lcx-codex'
|
|
51
|
-
const inject = ['llm', 'web'
|
|
52
|
-
const
|
|
53
|
-
const
|
|
58
|
+
export const name = 'lcx-codex'
|
|
59
|
+
export const inject = ['llm', 'web']
|
|
60
|
+
const SETTINGS_NS = settingsNamespace('lcx-codex')
|
|
61
|
+
const ADVANCED_HOSTED_TOOL = 'websearch_gpt_advanced'
|
|
62
|
+
const ALPHA_TOOL = 'websearch_alpha'
|
|
54
63
|
const COMPACTION_DIRECTIVE = 'You are now acting as a compaction engine'
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const UNSUPPORTED_IMAGE_PLACEHOLDER = '[image omitted because the target model does not support image input]'
|
|
64
|
+
const bypassReplayOptions = new WeakSet()
|
|
65
|
+
const hostedSearchRouteContext = new AsyncLocalStorage()
|
|
66
|
+
|
|
67
|
+
function dshHome() { return process.env.DSH_HOME ?? join(homedir(), '.dsh') }
|
|
68
|
+
function defaultLegacyCheckpointPath() { return join(dshHome(), 'storages', 'lcx-codex', 'checkpoints-v3.json') }
|
|
69
|
+
function defaultAlphaCapabilityPath() { return join(dshHome(), 'storages', 'lcx-codex', 'web-alpha-capabilities.json') }
|
|
70
|
+
function defaultAlphaRefPath() { return join(dshHome(), 'storages', 'lcx-codex', 'web-alpha-refs.json') }
|
|
63
71
|
|
|
64
|
-
const Config = z.object({
|
|
72
|
+
export const Config = z.object({
|
|
65
73
|
provider: z.string().default('lcx'),
|
|
66
74
|
baseURL: z.string().default('https://api.lcxbot.com/v1'),
|
|
67
75
|
apiKeyEnv: z.string().default('LCX_API_KEY'),
|
|
68
76
|
model: z.string().default('gpt-5.6-sol'),
|
|
69
|
-
|
|
77
|
+
legacyCheckpointPath: z.string().default(''),
|
|
70
78
|
checkpointPath: z.string().default(''),
|
|
71
79
|
alphaCapabilityPath: z.string().default(''),
|
|
72
80
|
alphaRefPath: z.string().default(''),
|
|
@@ -76,80 +84,46 @@ const Config = z.object({
|
|
|
76
84
|
webSearchProvider: z.string().default('lcx-responses'),
|
|
77
85
|
webMaxResults: z.number().default(8),
|
|
78
86
|
timeoutMs: z.number().default(300000),
|
|
79
|
-
maxResponseBytes: z.number().default(
|
|
87
|
+
maxResponseBytes: z.number().default(8 * 1024 * 1024),
|
|
80
88
|
maxAttempts: z.number().default(3),
|
|
81
89
|
maxRequestImageBytes: z.number().default(DEFAULT_MAX_REQUEST_IMAGE_BYTES),
|
|
90
|
+
requestImagePixelBudget: z.number().default(DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET),
|
|
91
|
+
requestImageMaxBytes: z.number().default(DEFAULT_REQUEST_IMAGE_MAX_BYTES),
|
|
92
|
+
portableReplayMaxChars: z.number().default(80_000),
|
|
93
|
+
nativeRetentionTokenBudget: z.number().default(64_000),
|
|
94
|
+
assistantRetentionTokenReserve: z.number().default(24_000),
|
|
95
|
+
assistantRetentionPerMessageTokenCap: z.number().default(3_000),
|
|
96
|
+
webSearchTimeoutMs: z.number().default(240_000),
|
|
97
|
+
autoCompactionThresholdPercent: z.number().default(90),
|
|
98
|
+
emergencyPruneThresholdPercent: z.number().default(95),
|
|
82
99
|
})
|
|
83
100
|
|
|
84
101
|
const SettingsSchema = z.object({
|
|
85
102
|
enabled: z.boolean().default(false),
|
|
86
103
|
webSearch: z.boolean().default(false),
|
|
104
|
+
advancedHostedSearch: z.boolean().default(false),
|
|
87
105
|
alphaSearch: z.boolean().default(false),
|
|
88
106
|
remoteCompaction: z.boolean().default(false),
|
|
89
107
|
fallbackToBasicCompaction: z.boolean().default(true),
|
|
108
|
+
autoCompaction: z.boolean().default(true),
|
|
109
|
+
webSearchTimeoutSeconds: z.number().default(240),
|
|
110
|
+
autoCompactionThresholdPercent: z.number().default(90),
|
|
111
|
+
emergencyPruneThresholdPercent: z.number().default(95),
|
|
90
112
|
provider: z.string().default('lcx'),
|
|
91
113
|
baseURL: z.string().default('https://api.lcxbot.com/v1'),
|
|
92
114
|
apiKeyEnv: z.string().default('LCX_API_KEY'),
|
|
93
115
|
model: z.string().default('gpt-5.6-sol'),
|
|
94
|
-
compactTransport: z.string().default('native-v2'),
|
|
95
116
|
})
|
|
96
117
|
|
|
97
|
-
function webError(message, code, cause) {
|
|
98
|
-
const error = new Error(message, cause === undefined ? undefined : { cause })
|
|
99
|
-
error.name = 'WebError'
|
|
100
|
-
error.code = code
|
|
101
|
-
return error
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function defaultCheckpointPath() {
|
|
105
|
-
return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'storages', 'lcx-codex', 'checkpoints-v3.json')
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function defaultAlphaCapabilityPath() {
|
|
109
|
-
return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'storages', 'lcx-codex', 'web-alpha-capabilities.json')
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function defaultAlphaRefPath() {
|
|
113
|
-
return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'storages', 'lcx-codex', 'web-alpha-refs.json')
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function portableCheckpointPath(checkpointPath) {
|
|
117
|
-
const value = String(checkpointPath)
|
|
118
|
-
if (/-v3\.json$/iu.test(value)) return value
|
|
119
|
-
return /\.json$/iu.test(value) ? value.replace(/\.json$/iu, '-v3.json') : `${value}-v3.json`
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const COMPACT_TRANSPORT_UNSUPPORTED_CODE = 'LCX_COMPACT_TRANSPORT_UNSUPPORTED'
|
|
123
|
-
const COMPACT_TRANSPORT_INVALID_CODE = 'LCX_COMPACT_TRANSPORT_INVALID'
|
|
124
|
-
|
|
125
|
-
function normalizeCompactTransport(value) {
|
|
126
|
-
const normalized = String(value ?? 'native-v2').trim().toLowerCase()
|
|
127
|
-
if (normalized === 'legacy') {
|
|
128
|
-
const error = new Error('Legacy compact transport is unsupported; only native-v2 is available')
|
|
129
|
-
error.code = COMPACT_TRANSPORT_UNSUPPORTED_CODE
|
|
130
|
-
throw error
|
|
131
|
-
}
|
|
132
|
-
if (normalized !== 'native-v2') {
|
|
133
|
-
const error = new Error(`Unsupported compact transport: ${String(value)}`)
|
|
134
|
-
error.code = COMPACT_TRANSPORT_INVALID_CODE
|
|
135
|
-
throw error
|
|
136
|
-
}
|
|
137
|
-
return normalized
|
|
138
|
-
}
|
|
139
|
-
|
|
140
118
|
function normalizeConfig(input = {}) {
|
|
141
|
-
const
|
|
119
|
+
const legacy = input.legacyCheckpointPath || input.checkpointPath || defaultLegacyCheckpointPath()
|
|
142
120
|
return {
|
|
143
121
|
provider: input.provider || 'lcx',
|
|
144
122
|
baseURL: String(input.baseURL || 'https://api.lcxbot.com/v1').replace(/\/+$/u, ''),
|
|
145
123
|
apiKeyEnv: input.apiKeyEnv || 'LCX_API_KEY',
|
|
146
124
|
model: input.model || 'gpt-5.6-sol',
|
|
147
|
-
compactTransport: normalizeCompactTransport(input.compactTransport),
|
|
148
125
|
headers: input.headers && typeof input.headers === 'object' ? { ...input.headers } : {},
|
|
149
|
-
|
|
150
|
-
supportsLongCacheRetention: input.supportsLongCacheRetention !== false,
|
|
151
|
-
checkpointPath: input.checkpointPath || defaultCheckpointPath(),
|
|
152
|
-
portableCheckpointPath: input.portableCheckpointPath || portableCheckpointPath(input.checkpointPath || defaultCheckpointPath()),
|
|
126
|
+
legacyCheckpointPath: legacy,
|
|
153
127
|
alphaCapabilityPath: input.alphaCapabilityPath || defaultAlphaCapabilityPath(),
|
|
154
128
|
alphaRefPath: input.alphaRefPath || defaultAlphaRefPath(),
|
|
155
129
|
alphaProfile: String(input.alphaProfile ?? ''),
|
|
@@ -158,1626 +132,511 @@ function normalizeConfig(input = {}) {
|
|
|
158
132
|
webSearchProvider: input.webSearchProvider || 'lcx-responses',
|
|
159
133
|
webMaxResults: Number.isInteger(input.webMaxResults) && input.webMaxResults > 0 ? input.webMaxResults : 8,
|
|
160
134
|
timeoutMs: Number.isInteger(input.timeoutMs) && input.timeoutMs > 0 ? input.timeoutMs : 300000,
|
|
161
|
-
maxResponseBytes: Number.isInteger(input.maxResponseBytes) && input.maxResponseBytes > 0 ? input.maxResponseBytes :
|
|
135
|
+
maxResponseBytes: Number.isInteger(input.maxResponseBytes) && input.maxResponseBytes > 0 ? input.maxResponseBytes : 8 * 1024 * 1024,
|
|
162
136
|
maxAttempts: Number.isInteger(input.maxAttempts) && input.maxAttempts > 0 ? Math.min(input.maxAttempts, 6) : 3,
|
|
163
137
|
maxRequestImageBytes: Number.isSafeInteger(input.maxRequestImageBytes) && input.maxRequestImageBytes > 0 ? input.maxRequestImageBytes : DEFAULT_MAX_REQUEST_IMAGE_BYTES,
|
|
138
|
+
requestImagePixelBudget: Number.isSafeInteger(input.requestImagePixelBudget) && input.requestImagePixelBudget > 0 ? input.requestImagePixelBudget : DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET,
|
|
139
|
+
requestImageMaxBytes: Number.isSafeInteger(input.requestImageMaxBytes) && input.requestImageMaxBytes > 0 ? input.requestImageMaxBytes : DEFAULT_REQUEST_IMAGE_MAX_BYTES,
|
|
140
|
+
portableReplayMaxChars: Number.isSafeInteger(input.portableReplayMaxChars) && input.portableReplayMaxChars > 0 ? input.portableReplayMaxChars : 80_000,
|
|
141
|
+
nativeRetentionTokenBudget: Number.isSafeInteger(input.nativeRetentionTokenBudget) && input.nativeRetentionTokenBudget > 0 ? input.nativeRetentionTokenBudget : 64_000,
|
|
142
|
+
assistantRetentionTokenReserve: Number.isSafeInteger(input.assistantRetentionTokenReserve) && input.assistantRetentionTokenReserve >= 0 ? input.assistantRetentionTokenReserve : 24_000,
|
|
143
|
+
assistantRetentionPerMessageTokenCap: Number.isSafeInteger(input.assistantRetentionPerMessageTokenCap) && input.assistantRetentionPerMessageTokenCap > 0 ? input.assistantRetentionPerMessageTokenCap : 3_000,
|
|
144
|
+
webSearchTimeoutMs: Number.isSafeInteger(input.webSearchTimeoutMs) && input.webSearchTimeoutMs >= 30_000 ? Math.min(input.webSearchTimeoutMs, 600_000) : 240_000,
|
|
145
|
+
autoCompactionThresholdPercent: Number.isFinite(input.autoCompactionThresholdPercent) ? Math.min(95, Math.max(85, Number(input.autoCompactionThresholdPercent))) : 90,
|
|
146
|
+
emergencyPruneThresholdPercent: Number.isFinite(input.emergencyPruneThresholdPercent) ? Math.min(99, Math.max(90, Number(input.emergencyPruneThresholdPercent))) : 95,
|
|
164
147
|
}
|
|
165
148
|
}
|
|
166
149
|
|
|
167
|
-
function
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
function settingsValue(ctx, namespace) {
|
|
172
|
-
const settings = ctx?.get?.('settings') ?? ctx?.settings
|
|
173
|
-
return settings?.get?.(settingsNamespace(namespace))
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function resolveResponsesRouteConfig(ctx, options, fallbackConfig) {
|
|
177
|
-
if (!isGptModel(options.model)) return undefined
|
|
178
|
-
const provider = String(options.provider ?? '')
|
|
179
|
-
const section = settingsValue(ctx, 'llm-pi-ai')
|
|
180
|
-
const profile = section?.providers?.[provider]
|
|
181
|
-
if (profile === undefined) {
|
|
182
|
-
if (provider !== fallbackConfig.provider) return undefined
|
|
183
|
-
return normalizeConfig({ ...fallbackConfig, provider, model: options.model })
|
|
184
|
-
}
|
|
185
|
-
if (profile.api !== 'openai-responses') return undefined
|
|
186
|
-
const baseURL = profile.baseURL ?? (provider === fallbackConfig.provider ? fallbackConfig.baseURL : undefined)
|
|
187
|
-
const apiKeyEnv = profile.apiKeyEnv ?? (provider === fallbackConfig.provider ? fallbackConfig.apiKeyEnv : undefined)
|
|
188
|
-
if (!baseURL || !apiKeyEnv) return undefined
|
|
189
|
-
return normalizeConfig({
|
|
190
|
-
...fallbackConfig,
|
|
191
|
-
provider,
|
|
192
|
-
baseURL,
|
|
193
|
-
apiKeyEnv,
|
|
194
|
-
headers: profile.headers ?? fallbackConfig.headers,
|
|
195
|
-
cacheRetention: profile.cacheRetention ?? fallbackConfig.cacheRetention,
|
|
196
|
-
supportsLongCacheRetention: profile.compat?.supportsLongCacheRetention ?? fallbackConfig.supportsLongCacheRetention,
|
|
197
|
-
model: options.model,
|
|
198
|
-
compactTransport: profile.compactTransport ?? fallbackConfig.compactTransport,
|
|
199
|
-
timeoutMs: profile.timeoutMs ?? fallbackConfig.timeoutMs,
|
|
200
|
-
maxRequestImageBytes: profile.maxRequestImageBytes ?? fallbackConfig.maxRequestImageBytes,
|
|
201
|
-
maxAttempts: retryAttempts(profile.retryPolicy, fallbackConfig.maxAttempts),
|
|
202
|
-
})
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function retryAttempts(policy, fallback) {
|
|
206
|
-
if (!policy || typeof policy !== 'object') return fallback
|
|
207
|
-
try {
|
|
208
|
-
const resolved = resolveRetryPolicy(policy, 'llm-pi-ai provider retryPolicy')
|
|
209
|
-
if (resolved.mode === 'normal' && Number.isSafeInteger(resolved.maxRetries)) return Math.min(resolved.maxRetries + 1, 6)
|
|
210
|
-
} catch {
|
|
211
|
-
return fallback
|
|
212
|
-
}
|
|
213
|
-
return fallback
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function nativeRouteError(options) {
|
|
217
|
-
const error = new Error(`【GPT 专属原生远程压缩】当前路由不满足 GPT + openai-responses 条件:${String(options.provider)}/${String(options.model)}`)
|
|
218
|
-
error.code = 'LCX_CHECKPOINT_ROUTE_MISMATCH'
|
|
219
|
-
return error
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function ambientApiKey(config) {
|
|
223
|
-
const key = String(process.env[config.apiKeyEnv] ?? '').trim()
|
|
224
|
-
if (!key) throw new Error(`DSH provider credential is unavailable: ${config.apiKeyEnv}`)
|
|
225
|
-
return key
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async function resolveApiKey(ctx, config) {
|
|
229
|
-
const credentials = ctx?.get?.('credentials') ?? ctx?.credentials
|
|
230
|
-
if (credentials?.resolve) {
|
|
231
|
-
const resolved = await credentials.resolve(config.apiKeyEnv)
|
|
232
|
-
if (typeof resolved?.value === 'string' && resolved.value.length > 0) return resolved.value
|
|
233
|
-
}
|
|
234
|
-
return ambientApiKey(config)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function attachmentImageResolver(ctx) {
|
|
238
|
-
const attachments = ctx?.get?.('attachments') ?? ctx?.attachments
|
|
239
|
-
return async (block, signal) => {
|
|
240
|
-
if (!attachments?.readImage) {
|
|
241
|
-
const error = new Error('LCX Compact image input requires the DSH attachment service')
|
|
242
|
-
error.code = 'LCX_COMPACT_IMAGE_UNAVAILABLE'
|
|
243
|
-
throw error
|
|
244
|
-
}
|
|
245
|
-
try {
|
|
246
|
-
const stored = await attachments.readImage(block?.attachment, signal)
|
|
247
|
-
return { data: stored?.data, mediaType: stored?.ref?.mediaType ?? block?.attachment?.mediaType }
|
|
248
|
-
} catch (error) {
|
|
249
|
-
const wrapped = new Error('LCX Compact failed to read image attachment', { cause: error })
|
|
250
|
-
wrapped.code = 'LCX_COMPACT_IMAGE_UNAVAILABLE'
|
|
251
|
-
throw wrapped
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function usageFrom(value, seen = new Set()) {
|
|
257
|
-
if (!value || typeof value !== 'object' || seen.has(value)) return undefined
|
|
258
|
-
seen.add(value)
|
|
259
|
-
if (Number.isFinite(value.input_tokens) || Number.isFinite(value.output_tokens)) {
|
|
260
|
-
const totalInputTokens = Number(value.input_tokens ?? 0)
|
|
261
|
-
const outputTokens = Number(value.output_tokens ?? 0)
|
|
262
|
-
const cachedValue = Number(value.input_tokens_details?.cached_tokens ?? value.input_token_details?.cached_tokens ?? value.prompt_tokens_details?.cached_tokens ?? 0)
|
|
263
|
-
const cacheWriteValue = Number(value.input_tokens_details?.cache_write_tokens ?? value.input_token_details?.cache_write_tokens ?? value.prompt_tokens_details?.cache_write_tokens ?? 0)
|
|
264
|
-
const cacheReadTokens = Number.isFinite(cachedValue) && cachedValue > 0 ? cachedValue : 0
|
|
265
|
-
const cacheWriteTokens = Number.isFinite(cacheWriteValue) && cacheWriteValue > 0 ? cacheWriteValue : 0
|
|
266
|
-
const inputTokens = Number.isFinite(totalInputTokens)
|
|
267
|
-
? Math.max(0, totalInputTokens - cacheReadTokens - cacheWriteTokens)
|
|
268
|
-
: 0
|
|
269
|
-
const normalizedOutputTokens = Number.isFinite(outputTokens) && outputTokens > 0 ? outputTokens : 0
|
|
270
|
-
const reasoningValue = Number(value.output_token_details?.reasoning_tokens ?? 0)
|
|
271
|
-
if (inputTokens <= 0 && cacheReadTokens <= 0 && cacheWriteTokens <= 0 && normalizedOutputTokens <= 0) return undefined
|
|
272
|
-
const usage = { inputTokens, outputTokens: normalizedOutputTokens }
|
|
273
|
-
if (cacheReadTokens > 0) usage.cacheReadTokens = cacheReadTokens
|
|
274
|
-
if (cacheWriteTokens > 0) usage.cacheWriteTokens = cacheWriteTokens
|
|
275
|
-
if (Number.isFinite(reasoningValue) && reasoningValue > 0) usage.reasoningTokens = reasoningValue
|
|
276
|
-
return usage
|
|
277
|
-
}
|
|
278
|
-
if (Array.isArray(value)) {
|
|
279
|
-
for (const item of value) {
|
|
280
|
-
const found = usageFrom(item, seen)
|
|
281
|
-
if (found) return found
|
|
282
|
-
}
|
|
283
|
-
} else {
|
|
284
|
-
for (const item of Object.values(value)) {
|
|
285
|
-
const found = usageFrom(item, seen)
|
|
286
|
-
if (found) return found
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return undefined
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function responseTextParts(item) {
|
|
293
|
-
return (Array.isArray(item?.content) ? item.content : [])
|
|
294
|
-
.filter((part) => part?.type === 'output_text' && typeof part.text === 'string')
|
|
295
|
-
.map((part) => part.text)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function responseReasoningText(item) {
|
|
299
|
-
const textOf = (parts) => (Array.isArray(parts) ? parts : [])
|
|
300
|
-
.filter((part) => typeof part?.text === 'string')
|
|
301
|
-
.map((part) => part.text)
|
|
302
|
-
.filter((text) => text.length > 0)
|
|
303
|
-
.join('\n\n')
|
|
304
|
-
return textOf(item?.summary) || textOf(item?.content)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
async function authenticatedHeaders(ctx, config, sessionId, clientRequestId) {
|
|
308
|
-
return {
|
|
309
|
-
...config.headers,
|
|
310
|
-
...attributionHeaders(),
|
|
311
|
-
authorization: `Bearer ${await resolveApiKey(ctx, config)}`,
|
|
312
|
-
...(clientRequestId === null ? {} : { 'x-client-request-id': clientRequestId ?? (sessionId ? String(sessionId) : randomUUID()) }),
|
|
313
|
-
...(sessionId ? { 'session-id': String(sessionId) } : {}),
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const REMOTE_COMPACTION_ERROR_CODES = new Set([
|
|
318
|
-
'LCX_HTTP_RETRYABLE',
|
|
319
|
-
'LCX_TIMEOUT',
|
|
320
|
-
'LCX_RETRY_EXHAUSTED',
|
|
321
|
-
])
|
|
322
|
-
const TRANSIENT_NETWORK_ERROR_CODES = new Set(['ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EAI_AGAIN', 'UND_ERR_CONNECT_TIMEOUT'])
|
|
323
|
-
|
|
324
|
-
function hasRemoteCompactionErrorCode(error) {
|
|
325
|
-
const seen = new Set()
|
|
326
|
-
let current = error
|
|
327
|
-
while (current && !seen.has(current)) {
|
|
328
|
-
seen.add(current)
|
|
329
|
-
const statusRetryable = Number.isInteger(current.status) && (current.status === 429 || (current.status >= 500 && current.status <= 599))
|
|
330
|
-
if (current instanceof TypeError || current.name === 'TypeError' || statusRetryable || current.retryable === true || REMOTE_COMPACTION_ERROR_CODES.has(current.code) || TRANSIENT_NETWORK_ERROR_CODES.has(current.code)) return true
|
|
331
|
-
current = current.cause
|
|
332
|
-
}
|
|
333
|
-
return false
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function markRemoteCompactionRequestError(error) {
|
|
337
|
-
const failure = error instanceof Error ? error : new Error(String(error))
|
|
338
|
-
failure.remoteCompactionRequest = hasRemoteCompactionErrorCode(failure)
|
|
339
|
-
return failure
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function currentRoute(options, config) {
|
|
343
|
-
return {
|
|
344
|
-
provider: String(options.provider ?? config.provider ?? ''),
|
|
345
|
-
model: String(options.model ?? config.model ?? ''),
|
|
346
|
-
baseURL: config.baseURL,
|
|
347
|
-
sessionId: options.sessionId ? String(options.sessionId) : '',
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function sessionAncestry(ctx, sessionId) {
|
|
352
|
-
const id = typeof sessionId === 'string' ? sessionId : ''
|
|
353
|
-
const sessions = ctx?.get?.('sessions') ?? ctx?.sessions
|
|
354
|
-
if (!id || !sessions?.get) return []
|
|
355
|
-
const ancestors = []
|
|
356
|
-
const seen = new Set([id])
|
|
357
|
-
let current = sessions.get(id)
|
|
358
|
-
while (current && ancestors.length < 32) {
|
|
359
|
-
const parent = current.header?.parentSession
|
|
360
|
-
if (typeof parent !== 'string' || parent.length === 0 || seen.has(parent)) break
|
|
361
|
-
ancestors.push(parent)
|
|
362
|
-
seen.add(parent)
|
|
363
|
-
current = sessions.get(parent)
|
|
364
|
-
}
|
|
365
|
-
return ancestors
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function routeWithSessionAncestry(ctx, route) {
|
|
369
|
-
return { ...route, ancestorSessionIds: sessionAncestry(ctx, route.sessionId) }
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function promptCacheSessionId(route, config) {
|
|
373
|
-
if (config?.cacheRetention === 'none') return undefined
|
|
374
|
-
return route?.sessionId ? String(route.sessionId) : undefined
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function promptCacheKey(route, config) {
|
|
378
|
-
const sessionId = promptCacheSessionId(route, config)
|
|
379
|
-
return sessionId ? clampOpenAIPromptCacheKey(sessionId) : undefined
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function promptCacheRetention(config) {
|
|
383
|
-
return config?.cacheRetention === 'long' && config.supportsLongCacheRetention !== false ? '24h' : undefined
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function promptCacheHeaders(ctx, config, route) {
|
|
387
|
-
const sessionId = promptCacheSessionId(route, config)
|
|
388
|
-
return authenticatedHeaders(ctx, config, sessionId, sessionId === undefined ? null : undefined)
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
async function requestRemoteCompaction(options, input, config, signal, ctx, route, preflightHeaders) {
|
|
392
|
-
try {
|
|
393
|
-
const headers = preflightHeaders ?? await promptCacheHeaders(ctx, config, route)
|
|
394
|
-
return await requestNativeCompaction({
|
|
395
|
-
baseURL: config.baseURL,
|
|
396
|
-
model: options.model,
|
|
397
|
-
input,
|
|
398
|
-
instructions: options.system,
|
|
399
|
-
promptCacheKey: promptCacheKey(route, config),
|
|
400
|
-
promptCacheRetention: promptCacheRetention(config),
|
|
401
|
-
idempotencyKey: randomUUID(),
|
|
402
|
-
tools: options.tools,
|
|
403
|
-
headers,
|
|
404
|
-
signal,
|
|
405
|
-
timeoutMs: config.timeoutMs,
|
|
406
|
-
maxAttempts: config.maxAttempts,
|
|
407
|
-
maxResponseBytes: config.maxResponseBytes,
|
|
408
|
-
})
|
|
409
|
-
} catch (error) {
|
|
410
|
-
throw markRemoteCompactionRequestError(error)
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function portableSummaryText(model, usage) {
|
|
415
|
-
const input = Number.isFinite(usage?.inputTokens) ? usage.inputTokens : undefined
|
|
416
|
-
const output = Number.isFinite(usage?.outputTokens) ? usage.outputTokens : undefined
|
|
417
|
-
const usageText = input === undefined || output === undefined ? 'usage:未知' : `usage:${input}/${output} tokens`
|
|
418
|
-
return `LCX 压缩完成 · Native V2 · v3 已保存 · 模型:${String(model)} · ${usageText}`
|
|
150
|
+
function webError(message, code, cause) {
|
|
151
|
+
const error = new Error(message, cause === undefined ? undefined : { cause })
|
|
152
|
+
error.name = 'WebError'; error.code = code; return error
|
|
419
153
|
}
|
|
420
154
|
|
|
421
|
-
function
|
|
155
|
+
function activeAgentRoute(exec, fallback) {
|
|
156
|
+
const agent = exec?.agent
|
|
157
|
+
const latest = agent?.session?.requestHeader?.()?.config
|
|
422
158
|
return {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
...(checkpointId ? { checkpointId } : {}),
|
|
159
|
+
provider: latest?.provider ?? agent?.options?.provider ?? fallback.provider,
|
|
160
|
+
model: latest?.model ?? agent?.options?.model ?? fallback.model,
|
|
161
|
+
sessionId: String(agent?.session?.id ?? ''),
|
|
427
162
|
}
|
|
428
163
|
}
|
|
429
164
|
|
|
430
|
-
function
|
|
431
|
-
|
|
432
|
-
}
|
|
165
|
+
function sessionsService(ctx) { return ctx?.get?.('sessions') ?? ctx?.sessions }
|
|
166
|
+
function sessionFor(ctx, sessionId) { return sessionId ? sessionsService(ctx)?.get?.(sessionId) : undefined }
|
|
433
167
|
|
|
434
|
-
function
|
|
168
|
+
function requestImageOptions(routeConfig, imageSupport, signal, imageMap) {
|
|
435
169
|
return {
|
|
436
|
-
|
|
437
|
-
|
|
170
|
+
imageSupport, signal, imageMap,
|
|
171
|
+
maxRequestImageBytes: routeConfig.maxRequestImageBytes,
|
|
172
|
+
requestImagePixelBudget: routeConfig.requestImagePixelBudget,
|
|
173
|
+
requestImageMaxBytes: routeConfig.requestImageMaxBytes,
|
|
438
174
|
}
|
|
439
175
|
}
|
|
440
176
|
|
|
441
|
-
function
|
|
442
|
-
|
|
443
|
-
const normalized = stripCheckpointMarker(value)
|
|
444
|
-
if (!normalized || budget.chars <= 0 || budget.bytes <= 0) return undefined
|
|
445
|
-
let text = normalized.slice(0, Math.min(PORTABLE_REPLAY_TEXT_MAX_CHARS, budget.chars))
|
|
446
|
-
while (text.length > 0 && Buffer.byteLength(text, 'utf8') > budget.bytes) text = text.slice(0, -1)
|
|
447
|
-
if (!text) return undefined
|
|
448
|
-
budget.chars -= text.length
|
|
449
|
-
budget.bytes -= Buffer.byteLength(text, 'utf8')
|
|
450
|
-
return text
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
function exactReplayText(value, budget) {
|
|
454
|
-
if (typeof value !== 'string' || value.length === 0 || value.length > PORTABLE_REPLAY_TEXT_MAX_CHARS) return undefined
|
|
455
|
-
const byteLength = Buffer.byteLength(value, 'utf8')
|
|
456
|
-
if (value.length > budget.chars || byteLength > budget.bytes) return undefined
|
|
457
|
-
budget.chars -= value.length
|
|
458
|
-
budget.bytes -= byteLength
|
|
459
|
-
return value
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function isPortableReplayItem(item) {
|
|
463
|
-
return item !== null && typeof item === 'object' && !Array.isArray(item) &&
|
|
464
|
-
typeof item.type === 'string' && !item.type.startsWith('response.') &&
|
|
465
|
-
item.type !== 'compaction' && item.type !== 'context_compaction' && item.type !== 'compaction_trigger' &&
|
|
466
|
-
!Object.prototype.hasOwnProperty.call(item, 'encrypted_content')
|
|
177
|
+
function readWebSearchProvider(ctx) {
|
|
178
|
+
try { return Reflect.get(ctx?.web, 'searchProviderId') } catch { return undefined }
|
|
467
179
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const error = new Error(`LCX checkpoint portable replay cannot represent Responses item type: ${String(type)}`)
|
|
471
|
-
error.code = 'LCX_CHECKPOINT_PORTABLE_UNSUPPORTED_CONTENT'
|
|
472
|
-
return error
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function safePortableCall(item) {
|
|
476
|
-
if (!isPortableReplayItem(item) || item.type !== 'function_call') return undefined
|
|
477
|
-
if (typeof item.call_id !== 'string' || item.call_id.length === 0 || item.call_id.length > 256) return undefined
|
|
478
|
-
if (typeof item.name !== 'string' || item.name.trim().length === 0 || item.name.length > 256) return undefined
|
|
479
|
-
if (typeof item.arguments !== 'string' || item.arguments.length === 0 || item.arguments.length > PORTABLE_REPLAY_TEXT_MAX_CHARS) return undefined
|
|
180
|
+
function writeWebSearchProvider(ctx, providerId) {
|
|
181
|
+
if (!ctx?.web) return false
|
|
480
182
|
try {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}
|
|
485
|
-
return { callId: item.call_id, name: item.name, arguments: item.arguments }
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
function safePortableOutput(item) {
|
|
489
|
-
if (!isPortableReplayItem(item) || item.type !== 'function_call_output') return undefined
|
|
490
|
-
if (typeof item.call_id !== 'string' || item.call_id.length === 0 || item.call_id.length > 256) return undefined
|
|
491
|
-
let outputParts
|
|
492
|
-
if (typeof item.output === 'string') {
|
|
493
|
-
outputParts = [{ type: 'input_text', text: item.output }]
|
|
494
|
-
} else if (Array.isArray(item.output)) {
|
|
495
|
-
for (const part of item.output) {
|
|
496
|
-
if (part === null || typeof part !== 'object' || Array.isArray(part) ||
|
|
497
|
-
(part.type !== 'dsh_image_attachment' &&
|
|
498
|
-
((part.type !== 'input_text' && part.type !== 'output_text') || typeof part.text !== 'string'))) {
|
|
499
|
-
throw portableReplayUnsupportedContentError(part?.type)
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
outputParts = item.output
|
|
503
|
-
} else {
|
|
504
|
-
return undefined
|
|
505
|
-
}
|
|
506
|
-
const textLength = outputParts.reduce((sum, part) => sum + (typeof part.text === 'string' ? part.text.length : 0), 0)
|
|
507
|
-
if (textLength > PORTABLE_REPLAY_TEXT_MAX_CHARS) return undefined
|
|
508
|
-
return { callId: item.call_id, outputParts }
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function portableImageCapabilityError() {
|
|
512
|
-
const error = new Error('LCX Compact cannot determine whether the target model accepts checkpoint images')
|
|
513
|
-
error.code = 'LCX_COMPACT_IMAGE_CAPABILITY_UNKNOWN'
|
|
514
|
-
return error
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
function portableImageBlock(part, imageSupport) {
|
|
518
|
-
if (!part?.attachment || typeof part.attachment !== 'object' || Array.isArray(part.attachment)) {
|
|
519
|
-
throw portableReplayUnsupportedContentError('image')
|
|
520
|
-
}
|
|
521
|
-
if (imageSupport === 'unknown') throw portableImageCapabilityError()
|
|
522
|
-
return imageSupport === 'supported'
|
|
523
|
-
? { type: 'image', attachment: structuredClone(part.attachment) }
|
|
524
|
-
: { type: 'text', text: UNSUPPORTED_IMAGE_PLACEHOLDER }
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function dshBlocksContainImage(blocks) {
|
|
528
|
-
return (blocks ?? []).some((block) => block?.type === 'image' ||
|
|
529
|
-
(block?.type === 'tool-result' && dshBlocksContainImage(block.content)))
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function dshMessagesContainImage(messages) {
|
|
533
|
-
return (messages ?? []).some((message) => dshBlocksContainImage(message?.content))
|
|
183
|
+
if (!Reflect.set(ctx.web, 'searchProviderId', providerId)) return false
|
|
184
|
+
return Reflect.get(ctx.web, 'searchProviderId') === providerId
|
|
185
|
+
} catch { return false }
|
|
534
186
|
}
|
|
535
187
|
|
|
536
|
-
function
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
for (const part of parts ?? []) {
|
|
546
|
-
if (expectedTypes.has(part?.type) && typeof part.text === 'string') {
|
|
547
|
-
pendingText += part.text
|
|
548
|
-
continue
|
|
549
|
-
}
|
|
550
|
-
if (part?.type === 'dsh_image_attachment' && part.attachment) {
|
|
551
|
-
flushText()
|
|
552
|
-
content.push(portableImageBlock(part, imageSupport))
|
|
553
|
-
continue
|
|
554
|
-
}
|
|
555
|
-
throw portableReplayUnsupportedContentError(part?.type)
|
|
556
|
-
}
|
|
557
|
-
flushText()
|
|
558
|
-
return content
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
function portableTextMessage(item, budget, checkpointId, imageSupport) {
|
|
562
|
-
if (!isPortableReplayItem(item) || item.type !== 'message' || (item.role !== 'user' && item.role !== 'assistant')) return undefined
|
|
563
|
-
const expectedType = item.role === 'assistant' ? 'output_text' : 'input_text'
|
|
564
|
-
const parts = Array.isArray(item.content) ? item.content : []
|
|
565
|
-
const content = portableContentBlocks(parts, new Set([expectedType]), budget, imageSupport)
|
|
566
|
-
if (content.length === 0) return undefined
|
|
567
|
-
return {
|
|
568
|
-
id: randomUUID(),
|
|
569
|
-
role: item.role,
|
|
570
|
-
content,
|
|
571
|
-
source: checkpointReplaySource(checkpointId),
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function cloneReplayBlock(block, imageSupport) {
|
|
576
|
-
const supported = new Set(['text', 'tool-call', 'tool-result', 'reasoning', 'image'])
|
|
577
|
-
if (!supported.has(block?.type)) throw portableReplayUnsupportedContentError(block?.type)
|
|
578
|
-
if (block.type === 'reasoning') return undefined
|
|
579
|
-
if (block.type === 'image') return portableImageBlock({ attachment: block.attachment }, imageSupport)
|
|
580
|
-
if (block.type === 'tool-result') {
|
|
581
|
-
return {
|
|
582
|
-
...structuredClone(block),
|
|
583
|
-
content: (block.content ?? []).map((part) => cloneReplayBlock(part, imageSupport)).filter(Boolean),
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
return structuredClone(block)
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
function cloneReplayTail(messages, imageSupport = 'unknown') {
|
|
590
|
-
const projected = []
|
|
591
|
-
for (const message of messages ?? []) {
|
|
592
|
-
if (message === null || typeof message !== 'object' || Array.isArray(message) ||
|
|
593
|
-
typeof message.role !== 'string' || !Array.isArray(message.content)) continue
|
|
594
|
-
const content = []
|
|
595
|
-
for (const block of message.content) {
|
|
596
|
-
const cloned = cloneReplayBlock(block, imageSupport)
|
|
597
|
-
if (cloned) content.push(cloned)
|
|
598
|
-
}
|
|
599
|
-
if (content.length === 0) continue
|
|
600
|
-
projected.push({ ...structuredClone(message), content })
|
|
601
|
-
}
|
|
602
|
-
return projected
|
|
188
|
+
async function executeHostedSearch(ctx, routeConfig, args, signal, sessionId = '') {
|
|
189
|
+
const normalized = normalizeHostedSearchArgs(args)
|
|
190
|
+
const requestId = randomUUID()
|
|
191
|
+
const route = currentRoute({ provider: routeConfig.provider, model: routeConfig.model, sessionId }, routeConfig)
|
|
192
|
+
const headers = await authenticatedHeaders(ctx, routeConfig, sessionId, requestId)
|
|
193
|
+
const searchCacheKey = sessionId ? `dsh-lcx-search:${routeFingerprint(route)}` : undefined
|
|
194
|
+
const body = buildHostedSearchBody(normalized, routeConfig.model, { promptCacheKey: searchCacheKey })
|
|
195
|
+
const response = await fetchJsonWithRetry(`${routeConfig.baseURL}/responses`, body, headers, signal, routeConfig.timeoutMs, { maxAttempts: routeConfig.maxAttempts, maxResponseBytes: routeConfig.maxResponseBytes })
|
|
196
|
+
return parseHostedSearchResponse(response, requestId, routeConfig.webMaxResults)
|
|
603
197
|
}
|
|
604
198
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
if (!isPortableReplayItem(item)) continue
|
|
612
|
-
if (item.type === 'message') {
|
|
613
|
-
const expectedType = item.role === 'assistant' ? 'output_text' : item.role === 'user' ? 'input_text' : undefined
|
|
614
|
-
if (!expectedType || !Array.isArray(item.content) || item.content.some((part) =>
|
|
615
|
-
part?.type !== expectedType && part?.type !== 'dsh_image_attachment')) {
|
|
616
|
-
throw portableReplayUnsupportedContentError(item.type)
|
|
617
|
-
}
|
|
618
|
-
} else if (item.type !== 'function_call' && item.type !== 'function_call_output') {
|
|
619
|
-
throw portableReplayUnsupportedContentError(item.type)
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
const calls = new Map()
|
|
623
|
-
const outputs = new Map()
|
|
624
|
-
for (let index = 0; index < items.length; index += 1) {
|
|
625
|
-
const call = safePortableCall(items[index])
|
|
626
|
-
const output = safePortableOutput(items[index])
|
|
627
|
-
if (call) calls.set(call.callId, [...(calls.get(call.callId) ?? []), { ...call, index }])
|
|
628
|
-
if (output) outputs.set(output.callId, [...(outputs.get(output.callId) ?? []), { ...output, index }])
|
|
629
|
-
}
|
|
630
|
-
const pairs = new Map()
|
|
631
|
-
for (const [callId, callItems] of calls) {
|
|
632
|
-
const outputItems = outputs.get(callId)
|
|
633
|
-
if (callItems.length === 1 && outputItems?.length === 1 && callItems[0].index < outputItems[0].index) {
|
|
634
|
-
pairs.set(callId, { call: callItems[0], output: outputItems[0] })
|
|
635
|
-
}
|
|
199
|
+
class LcxResponsesSearchProvider {
|
|
200
|
+
constructor(ctx, getConfig, enabled) { this.ctx = ctx; this.getConfig = getConfig; this.enabled = enabled; this.id = getConfig().webSearchProvider }
|
|
201
|
+
available() {
|
|
202
|
+
const config = this.getConfig()
|
|
203
|
+
const route = resolveResponsesRouteConfig(this.ctx, { provider: config.provider, model: config.model }, config)
|
|
204
|
+
return this.enabled() && Boolean(route)
|
|
636
205
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
const call = safePortableCall(item)
|
|
647
|
-
const pair = call ? pairs.get(call.callId) : undefined
|
|
648
|
-
if (pair) {
|
|
649
|
-
const before = { ...budget }
|
|
650
|
-
const argumentsText = exactReplayText(pair.call.arguments, budget)
|
|
651
|
-
let outputContent = portableContentBlocks(
|
|
652
|
-
pair.output.outputParts,
|
|
653
|
-
new Set(['input_text', 'output_text']),
|
|
654
|
-
budget,
|
|
655
|
-
imageSupport,
|
|
656
|
-
)
|
|
657
|
-
if (outputContent.length === 0) {
|
|
658
|
-
const emptyOutput = exactReplayText('(no output)', budget)
|
|
659
|
-
outputContent = emptyOutput ? [{ type: 'text', text: emptyOutput }] : []
|
|
660
|
-
}
|
|
661
|
-
if (!argumentsText || outputContent.length === 0) {
|
|
662
|
-
budget.chars = before.chars
|
|
663
|
-
budget.bytes = before.bytes
|
|
664
|
-
continue
|
|
665
|
-
}
|
|
666
|
-
reservedPairs.set(call.callId, outputContent)
|
|
667
|
-
messages.push({
|
|
668
|
-
id: randomUUID(),
|
|
669
|
-
role: 'assistant',
|
|
670
|
-
content: [{ type: 'tool-call', id: call.callId, name: call.name, arguments: argumentsText }],
|
|
671
|
-
source: checkpointReplaySource(checkpointId),
|
|
672
|
-
})
|
|
673
|
-
continue
|
|
674
|
-
}
|
|
675
|
-
const output = safePortableOutput(item)
|
|
676
|
-
const outputContent = output ? reservedPairs.get(output.callId) : undefined
|
|
677
|
-
if (output && outputContent !== undefined) {
|
|
678
|
-
messages.push({
|
|
679
|
-
id: randomUUID(),
|
|
680
|
-
role: 'user',
|
|
681
|
-
content: [{ type: 'tool-result', toolCallId: output.callId, content: outputContent }],
|
|
682
|
-
source: checkpointReplaySource(checkpointId),
|
|
683
|
-
})
|
|
684
|
-
reservedPairs.delete(output.callId)
|
|
206
|
+
async search(request, signal) {
|
|
207
|
+
const config = this.getConfig()
|
|
208
|
+
const active = hostedSearchRouteContext.getStore()
|
|
209
|
+
const requested = active ?? { provider: config.provider, model: config.model, sessionId: '' }
|
|
210
|
+
let route = resolveResponsesRouteConfig(this.ctx, requested, config)
|
|
211
|
+
let source = active ? 'active-agent' : 'fallback'
|
|
212
|
+
if (!route && active) {
|
|
213
|
+
route = resolveResponsesRouteConfig(this.ctx, { provider: config.provider, model: config.model }, config)
|
|
214
|
+
source = 'fallback'
|
|
685
215
|
}
|
|
216
|
+
if (!route) throw webError('LCX Hosted Search requires a configured GPT openai-responses route', 'LCX_WEB_ROUTE_UNAVAILABLE')
|
|
217
|
+
const sessionId = String((source === 'active-agent' ? active?.sessionId : '') ?? '')
|
|
218
|
+
this.ctx.logger?.info?.(`[lcx-codex] web_search route: ${route.provider}/${route.model} (${source})`)
|
|
219
|
+
const result = await executeHostedSearch(this.ctx, route, { query: request.query }, signal, sessionId)
|
|
220
|
+
const max = Number.isInteger(request.maxResults) && request.maxResults > 0 ? request.maxResults : route.webMaxResults
|
|
221
|
+
return { content: result.content || undefined, sources: result.sources.slice(0, max), truncated: result.truncated || result.sources.length > max }
|
|
686
222
|
}
|
|
687
|
-
return messages
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function portableReplayRouteError(record, route) {
|
|
691
|
-
const dimensions = []
|
|
692
|
-
if (record.provider !== String(route.provider ?? '')) dimensions.push('provider')
|
|
693
|
-
if (record.baseURLFingerprint !== baseURLFingerprint(route.baseURL)) dimensions.push('endpoint')
|
|
694
|
-
const sessionId = String(route.sessionId ?? '')
|
|
695
|
-
const sameSessionLineage = !sessionId || record.lineageId === sessionId ||
|
|
696
|
-
(Array.isArray(route.ancestorSessionIds) && route.ancestorSessionIds.includes(record.lineageId))
|
|
697
|
-
if (!sameSessionLineage) dimensions.push('session')
|
|
698
|
-
const error = new Error(`LCX v3 checkpoint cannot migrate; route mismatch dimensions: ${(dimensions.length > 0 ? dimensions : ['route']).join(', ')}`)
|
|
699
|
-
error.code = 'LCX_CHECKPOINT_ROUTE_MISMATCH'
|
|
700
|
-
return error
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
function buildPortableReplayMessages(messages, store, route, options = {}) {
|
|
704
|
-
const marker = latestPortableMarker(messages)
|
|
705
|
-
if (!marker) return cloneReplayTail(messages)
|
|
706
|
-
const record = store?.get?.(marker.id)
|
|
707
|
-
if (!record) {
|
|
708
|
-
const error = new Error(`LCX v3 checkpoint ${marker.id} is missing from ${store?.file ?? 'checkpoint store'}`)
|
|
709
|
-
error.code = 'LCX_CHECKPOINT_V3_CORRUPT'
|
|
710
|
-
throw error
|
|
711
|
-
}
|
|
712
|
-
const exactRoute = Boolean(route.sessionId) && record.routeFingerprint === routeFingerprint(route)
|
|
713
|
-
const sessionId = String(route.sessionId ?? '')
|
|
714
|
-
const sameSessionLineage = !sessionId || record.lineageId === sessionId ||
|
|
715
|
-
(Array.isArray(route.ancestorSessionIds) && route.ancestorSessionIds.includes(record.lineageId))
|
|
716
|
-
const portableRoute = record.provider === String(route.provider ?? '') &&
|
|
717
|
-
record.baseURLFingerprint === baseURLFingerprint(route.baseURL) &&
|
|
718
|
-
sameSessionLineage
|
|
719
|
-
if (!exactRoute && !portableRoute) throw portableReplayRouteError(record, route)
|
|
720
|
-
const portableImages = Number(record.portableImageCount ?? 0)
|
|
721
|
-
const durableImages = inputImageCount(record.portableHistory)
|
|
722
|
-
if (portableImages > durableImages) {
|
|
723
|
-
throw portableReplayUnsupportedContentError('image')
|
|
724
|
-
}
|
|
725
|
-
const imageSupport = options.imageSupport ?? 'unknown'
|
|
726
|
-
const budget = replayBudget()
|
|
727
|
-
const summary = boundedReplayText(record.portableSummary, budget)
|
|
728
|
-
const recalled = []
|
|
729
|
-
if (summary) recalled.push({
|
|
730
|
-
id: randomUUID(),
|
|
731
|
-
role: 'assistant',
|
|
732
|
-
content: [{ type: 'text', text: summary }],
|
|
733
|
-
source: checkpointReplaySource(marker.id),
|
|
734
|
-
})
|
|
735
|
-
recalled.push(...portableResponsesToMessages(record.portableHistory, { budget, checkpointId: marker.id, imageSupport }))
|
|
736
|
-
recalled.push(...cloneReplayTail((messages ?? []).slice(marker.index + 1), imageSupport))
|
|
737
|
-
return recalled
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
function checkpointReplayUnavailableError() {
|
|
741
|
-
const error = new Error('LCX checkpoint replay requires the public DSH ctx.llm.stream API')
|
|
742
|
-
error.code = CHECKPOINT_REPLAY_UNAVAILABLE_CODE
|
|
743
|
-
return error
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
function checkpointReplayRecord(messages, store) {
|
|
747
|
-
const marker = latestPortableMarker(messages)
|
|
748
|
-
if (!marker) return undefined
|
|
749
|
-
const record = store?.get?.(marker.id)
|
|
750
|
-
if (!record) {
|
|
751
|
-
const error = new Error(`LCX v3 checkpoint ${marker.id} is missing from ${store?.file ?? 'checkpoint store'}`)
|
|
752
|
-
error.code = 'LCX_CHECKPOINT_V3_CORRUPT'
|
|
753
|
-
throw error
|
|
754
|
-
}
|
|
755
|
-
return { marker, record }
|
|
756
223
|
}
|
|
757
224
|
|
|
758
|
-
function
|
|
225
|
+
function createAdvancedHostedTool(ctx, state, getConfig) {
|
|
759
226
|
return {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
async function* nativeCheckpointReplayStream(options, config, portableStore, ctx, record, marker) {
|
|
772
|
-
const route = routeWithSessionAncestry(ctx, currentRoute(options, config))
|
|
773
|
-
assertCheckpointRoute(record, route)
|
|
774
|
-
const llm = ctx?.llm ?? ctx?.get?.('llm')
|
|
775
|
-
const imageSupport = typeof llm?.resolveModelInfo === 'function'
|
|
776
|
-
? await resolveModelImageSupport(llm, route, options.signal)
|
|
777
|
-
: 'supported'
|
|
778
|
-
const imageOptions = { resolveImage: attachmentImageResolver(ctx), imageSupport, signal: options.signal, maxRequestImageBytes: config.maxRequestImageBytes }
|
|
779
|
-
const nativeOutput = await hydrateNativeImageReferences(checkpointNativeReplacementHistory(record), imageOptions)
|
|
780
|
-
const tailInput = await buildPortableResponsesInputWithImages(
|
|
781
|
-
(options.messages ?? []).slice(marker.index + 1),
|
|
782
|
-
portableStore,
|
|
783
|
-
route,
|
|
784
|
-
imageOptions,
|
|
785
|
-
)
|
|
786
|
-
const replaySignals = replayEffectiveSignal(options.signal, config.timeoutMs)
|
|
787
|
-
try {
|
|
788
|
-
const response = await fetchSse(
|
|
789
|
-
`${config.baseURL}/responses`,
|
|
790
|
-
nativeReplayBody(options, route, config, nativeOutput, tailInput),
|
|
791
|
-
mergeFeatureHeader(await promptCacheHeaders(ctx, config, route)),
|
|
792
|
-
replaySignals.signal,
|
|
793
|
-
config.timeoutMs + 1000,
|
|
794
|
-
{ maxResponseBytes: config.maxResponseBytes },
|
|
795
|
-
)
|
|
796
|
-
yield* responsesSseChunks(response, {
|
|
797
|
-
signal: replaySignals.signal,
|
|
798
|
-
requestSignal: options.signal,
|
|
799
|
-
timeoutSignal: replaySignals.timeoutSignal,
|
|
800
|
-
timeoutMs: config.timeoutMs,
|
|
801
|
-
maxResponseBytes: config.maxResponseBytes,
|
|
802
|
-
})
|
|
803
|
-
} catch (error) {
|
|
804
|
-
if (replaySignals.timeoutSignal.aborted && !options.signal?.aborted) {
|
|
805
|
-
throw replayTimeoutError(config.timeoutMs, error)
|
|
806
|
-
}
|
|
807
|
-
throw error
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
async function* textChunks(text, usage) {
|
|
812
|
-
yield { type: 'block-start', index: 0, blockType: 'text' }
|
|
813
|
-
yield { type: 'text-delta', index: 0, text }
|
|
814
|
-
yield { type: 'block-end', index: 0, block: { type: 'text', text } }
|
|
815
|
-
if (usage) yield { type: 'usage', usage }
|
|
816
|
-
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
function isCompactionDirective(message) {
|
|
820
|
-
return textOfMessage(message).includes(COMPACTION_DIRECTIVE)
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
function diagnosticEndpoint(baseURL) {
|
|
824
|
-
try {
|
|
825
|
-
const url = new URL(`${String(baseURL).replace(/\/+$/u, '')}/responses`)
|
|
826
|
-
return `${url.origin}${url.pathname}`
|
|
827
|
-
} catch {
|
|
828
|
-
return 'invalid'
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
function logCompactionDiagnostic(ctx, options, config, error, phase) {
|
|
833
|
-
const failure = failureOf(error)
|
|
834
|
-
const status = failure.status === undefined ? '' : ` status=${failure.status}`
|
|
835
|
-
const detail = `dsh-lcx-codex compaction ${phase}: code=${failure.code}${status} provider=${String(options.provider ?? config.provider)} model=${String(options.model ?? config.model)} transport=${config.compactTransport} endpoint=${diagnosticEndpoint(config.baseURL)} inputItems=${Array.isArray(options.messages) ? options.messages.length : 0}`
|
|
836
|
-
try {
|
|
837
|
-
if (typeof ctx?.logger?.error === 'function') ctx.logger.error(detail)
|
|
838
|
-
else if (typeof console?.error === 'function') console.error(detail)
|
|
839
|
-
} catch {
|
|
840
|
-
// Diagnostics must never change compaction behavior.
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
async function prepareRemoteCompaction(options, config, portableStore, ctx, sessionTracker) {
|
|
845
|
-
const last = options.messages?.[options.messages.length - 1]
|
|
846
|
-
const history = last && isCompactionDirective(last) ? options.messages.slice(0, -1) : options.messages
|
|
847
|
-
const route = currentRoute(options, config)
|
|
848
|
-
const lease = sessionTracker?.capture(route.sessionId)
|
|
849
|
-
lease?.assert('request-start')
|
|
850
|
-
const headers = await promptCacheHeaders(ctx, config, route)
|
|
851
|
-
responsesTools(options.tools)
|
|
852
|
-
const llm = ctx?.llm ?? ctx?.get?.('llm')
|
|
853
|
-
const imageSupport = typeof llm?.resolveModelInfo === 'function'
|
|
854
|
-
? await resolveModelImageSupport(llm, route, options.signal)
|
|
855
|
-
: 'supported'
|
|
856
|
-
const imageReferences = new Map()
|
|
857
|
-
const input = await buildPortableResponsesInputWithImages(history, portableStore, route, {
|
|
858
|
-
resolveImage: attachmentImageResolver(ctx),
|
|
859
|
-
imageSupport,
|
|
860
|
-
signal: options.signal,
|
|
861
|
-
maxRequestImageBytes: config.maxRequestImageBytes,
|
|
862
|
-
onImageResolved: ({ imageUrl, attachment }) => imageReferences.set(imageUrl, attachment),
|
|
863
|
-
})
|
|
864
|
-
lease?.assert('preflight')
|
|
865
|
-
return { history, route, lease, input, imageReferences, headers }
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
function mergeUsage(...values) {
|
|
869
|
-
const keys = ['inputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens', 'reasoningTokens']
|
|
870
|
-
const merged = {}
|
|
871
|
-
for (const key of keys) {
|
|
872
|
-
const total = values.reduce((sum, value) => sum + (Number.isFinite(value?.[key]) ? value[key] : 0), 0)
|
|
873
|
-
if (total > 0) merged[key] = total
|
|
874
|
-
}
|
|
875
|
-
return Object.keys(merged).length > 0 ? merged : undefined
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
function readableSummaryText(text, model, usage) {
|
|
879
|
-
const summary = stripCheckpointMarker(String(text ?? '')).trim().slice(0, 16 * 1024)
|
|
880
|
-
return summary || portableSummaryText(model, usage)
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
function portableMarkerTextWithSummary(id, model, usage, summary) {
|
|
884
|
-
const summaryText = stripCheckpointMarker(String(summary ?? '')).trim().slice(0, 16 * 1024)
|
|
885
|
-
const status = portableSummaryText(model, usage)
|
|
886
|
-
return `${summaryText ? `${summaryText}\n` : ''}${status}\n[dsh-lcx-codex-v3-checkpoint:${id}]`
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
async function commitRemoteCompaction(prepared, result, portableStore, config, summary, usage) {
|
|
890
|
-
const { history, route, lease, input, imageReferences } = prepared
|
|
891
|
-
lease?.assert('commit')
|
|
892
|
-
const replacementHistory = buildNativeReplacementHistory(input, result.compaction)
|
|
893
|
-
const persistedNativeOutput = persistNativeImageReferences(replacementHistory, imageReferences)
|
|
894
|
-
const persistedPortableInput = persistNativeImageReferences(input, imageReferences)
|
|
895
|
-
const id = randomUUID()
|
|
896
|
-
const parent = latestPortableMarker(history)
|
|
897
|
-
const portableImageCount = inputImageCount(input)
|
|
898
|
-
const portableSummary = readableSummaryText(summary, route.model, usage)
|
|
899
|
-
portableStore.put(id, {
|
|
900
|
-
version: CHECKPOINT_V3_VERSION,
|
|
901
|
-
checkpointId: id,
|
|
902
|
-
...(parent ? { parentCheckpointId: parent.id } : {}),
|
|
903
|
-
lineageId: route.sessionId || `${route.provider}:${route.baseURL}`,
|
|
904
|
-
sourceSessionId: route.sessionId || `${route.provider}:${route.baseURL}`,
|
|
905
|
-
provider: route.provider,
|
|
906
|
-
model: route.model,
|
|
907
|
-
modelKey: `${route.provider}:${route.model}`,
|
|
908
|
-
transport: config.compactTransport,
|
|
909
|
-
baseURLFingerprint: baseURLFingerprint(route.baseURL),
|
|
910
|
-
routeFingerprint: routeFingerprint(route),
|
|
911
|
-
nativeOutput: persistedNativeOutput,
|
|
912
|
-
nativeCompaction: result.compaction,
|
|
913
|
-
portableHistory: buildPortableHistory(persistedPortableInput),
|
|
914
|
-
...(portableImageCount > 0 ? { portableImageCount } : {}),
|
|
915
|
-
portableSummary,
|
|
916
|
-
createdAt: Date.now(),
|
|
917
|
-
usage,
|
|
918
|
-
})
|
|
919
|
-
return { id, route, usage, text: portableMarkerTextWithSummary(id, route.model, usage, summary) }
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
async function* remoteCompactionStream(options, config, portableStore, ctx, sessionTracker) {
|
|
923
|
-
try {
|
|
924
|
-
const prepared = await prepareRemoteCompaction(options, config, portableStore, ctx, sessionTracker)
|
|
925
|
-
const result = await requestRemoteCompaction(options, prepared.input, config, options.signal, ctx, prepared.route, prepared.headers)
|
|
926
|
-
const committed = await commitRemoteCompaction(prepared, result, portableStore, config, undefined, result.usage)
|
|
927
|
-
yield* textChunks(committed.text, committed.usage)
|
|
928
|
-
} catch (error) {
|
|
929
|
-
logCompactionDiagnostic(ctx, options, config, error, 'failed')
|
|
930
|
-
throw error
|
|
227
|
+
name: ADVANCED_HOSTED_TOOL,
|
|
228
|
+
description: 'Advanced GPT Responses Hosted Search. Use DSH web_search for ordinary lookup. Call this only when you need domain allow/block filters, approximate user location, search-context size, image search, external-web-access or return-token-budget controls. It is one-shot and has no open/find/click state.',
|
|
229
|
+
parameters: HOSTED_SEARCH_PARAMETERS,
|
|
230
|
+
output: { schema: HOSTED_SEARCH_OUTPUT, render: (_args, value) => renderHostedSearchResult(value) },
|
|
231
|
+
async execute(args, exec) {
|
|
232
|
+
if (!state.enabled || !state.webSearch || !state.advancedHostedSearch) throw webError(`${ADVANCED_HOSTED_TOOL} is disabled`, 'LCX_WEB_DISABLED')
|
|
233
|
+
const config = getConfig(); const active = activeAgentRoute(exec, config)
|
|
234
|
+
const route = resolveResponsesRouteConfig(ctx, active, config)
|
|
235
|
+
if (!route) throw webError('Advanced Hosted Search requires the active GPT openai-responses route', 'LCX_WEB_ROUTE_UNAVAILABLE')
|
|
236
|
+
return executeHostedSearch(ctx, route, args, exec?.signal, active.sessionId)
|
|
237
|
+
},
|
|
931
238
|
}
|
|
932
239
|
}
|
|
933
240
|
|
|
934
|
-
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
for await (const chunk of stream) chunks.push(chunk)
|
|
938
|
-
const text = chunks.filter((chunk) => chunk?.type === 'text-delta' && typeof chunk.text === 'string').map((chunk) => chunk.text).join('')
|
|
939
|
-
const usage = chunks.find((chunk) => chunk?.type === 'usage')?.usage
|
|
940
|
-
return { chunks, text, usage }
|
|
241
|
+
function alphaCapabilityFor(config, store) {
|
|
242
|
+
const fingerprint = alphaCapabilityFingerprint({ baseURL: config.baseURL, provider: config.provider, model: config.model, profile: config.alphaProfile, group: config.alphaGroup, schemaFingerprint: ALPHA_SCHEMA_FINGERPRINT })
|
|
243
|
+
return { fingerprint, record: store.get(fingerprint) }
|
|
941
244
|
}
|
|
942
245
|
|
|
943
|
-
async function
|
|
944
|
-
|
|
246
|
+
async function executeAlpha(ctx, routeConfig, capability, refStore, args, exec) {
|
|
247
|
+
const normalized = normalizeAlphaSearchArgs(args)
|
|
248
|
+
const sessionId = String(exec?.agent?.session?.id ?? '')
|
|
249
|
+
if (!sessionId) throw webError('Alpha Search requires a DSH session', 'LCX_ALPHA_SESSION_REQUIRED')
|
|
250
|
+
const routeFp = routeFingerprint({ provider: routeConfig.provider, model: routeConfig.model, baseURL: routeConfig.baseURL, sessionId })
|
|
251
|
+
if (['open','find','click','screenshot'].includes(normalized.action)) refStore.assertUsable(sessionId, routeFp, normalized.refId)
|
|
252
|
+
const requestId = randomUUID(); const headers = await authenticatedHeaders(ctx, routeConfig, sessionId, requestId)
|
|
253
|
+
let response
|
|
945
254
|
try {
|
|
946
|
-
|
|
255
|
+
response = await fetchJsonWithRetry(`${routeConfig.baseURL}/alpha/search`, buildAlphaSearchBody(normalized, routeConfig.model, sessionId, true, routeConfig.alphaMaxOutputTokens), headers, exec?.signal, routeConfig.timeoutMs, { maxAttempts: routeConfig.maxAttempts, maxResponseBytes: routeConfig.maxResponseBytes })
|
|
947
256
|
} catch (error) {
|
|
948
|
-
|
|
949
|
-
throw error
|
|
257
|
+
if ([404,405].includes(error?.status) || /channel does not support/iu.test(String(error?.message ?? ''))) throw webError('Alpha Search is not supported by this route', 'LCX_ALPHA_UNAVAILABLE', error)
|
|
258
|
+
throw webError('Alpha Search provider request failed', 'LCX_ALPHA_PROVIDER_ERROR', error)
|
|
950
259
|
}
|
|
951
|
-
const
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
if (remoteOutcome.status === 'fulfilled' && remoteOutcome.value.result) {
|
|
956
|
-
const local = localOutcome.status === 'fulfilled' ? localOutcome.value : { text: '', usage: undefined }
|
|
957
|
-
const usage = mergeUsage(remoteOutcome.value.result.usage, local.usage)
|
|
958
|
-
try {
|
|
959
|
-
const committed = await commitRemoteCompaction(prepared, remoteOutcome.value.result, portableStore, config, local.text, usage)
|
|
960
|
-
yield* textChunks(committed.text, committed.usage)
|
|
961
|
-
return
|
|
962
|
-
} catch (error) {
|
|
963
|
-
logCompactionDiagnostic(ctx, options, config, error, 'failed')
|
|
964
|
-
throw error
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
const remoteError = remoteOutcome.status === 'fulfilled' ? remoteOutcome.value.error : remoteOutcome.reason
|
|
968
|
-
if (options.signal?.aborted || remoteError?.remoteCompactionRequest !== true) throw remoteError
|
|
969
|
-
diagnostic?.(remoteError, 'failed')
|
|
970
|
-
diagnostic?.(remoteError, 'fallback')
|
|
971
|
-
if (localOutcome.status !== 'fulfilled') throw localOutcome.reason
|
|
972
|
-
for (const chunk of localOutcome.value.chunks) yield chunk
|
|
260
|
+
const result = parseAlphaSearchResponse(response, { action: normalized.action, capability: capability.classification, requestId })
|
|
261
|
+
refStore.record(sessionId, routeFp, result.refRecords ?? result.refs.map((refId) => ({ refId })))
|
|
262
|
+
delete result.refRecords
|
|
263
|
+
return result
|
|
973
264
|
}
|
|
974
265
|
|
|
975
|
-
function
|
|
976
|
-
const error = new Error(message)
|
|
977
|
-
error.code = code
|
|
978
|
-
return error
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
function replayTimeoutError(timeoutMs, cause) {
|
|
982
|
-
const error = replaySseError(`LCX Responses replay timed out after ${timeoutMs} ms`, 'LCX_TIMEOUT')
|
|
983
|
-
if (cause !== undefined) error.cause = cause
|
|
984
|
-
return error
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
function replayAbortError(options, cause) {
|
|
988
|
-
if (options.timeoutSignal?.aborted && !options.requestSignal?.aborted) {
|
|
989
|
-
return replayTimeoutError(options.timeoutMs, cause)
|
|
990
|
-
}
|
|
991
|
-
return cause ?? options.signal?.reason ?? new Error('request aborted')
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
function replayEffectiveSignal(signal, timeoutMs) {
|
|
995
|
-
const timeoutSignal = AbortSignal.timeout(timeoutMs)
|
|
266
|
+
function createAlphaTool(ctx, state, getConfig, capabilityStore, refStore) {
|
|
996
267
|
return {
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
} catch (error) {
|
|
1011
|
-
throw replaySseError(`LCX Responses replay returned malformed SSE JSON: ${String(error)}`, 'LCX_INVALID_SSE')
|
|
268
|
+
name: ALPHA_TOOL,
|
|
269
|
+
description: 'Stateful Codex/Alpha web command tool. Use it for search/open/find/click/PDF screenshot and the structured image/finance/weather/sports/time actions. Use DSH web_search for ordinary search, and websearch_gpt_advanced only for Hosted Search controls.',
|
|
270
|
+
parameters: ALPHA_SEARCH_PARAMETERS,
|
|
271
|
+
output: { schema: ALPHA_SEARCH_OUTPUT, render: (_args, value) => renderAlphaSearchResult(value) },
|
|
272
|
+
async execute(args, exec) {
|
|
273
|
+
if (!state.enabled || !state.alphaSearch) throw webError(`${ALPHA_TOOL} is disabled`, 'LCX_ALPHA_DISABLED')
|
|
274
|
+
const config = getConfig(); const active = activeAgentRoute(exec, config); const route = resolveResponsesRouteConfig(ctx, active, config)
|
|
275
|
+
if (!route) throw webError('Alpha Search requires the active GPT openai-responses route', 'LCX_ALPHA_ROUTE_UNAVAILABLE')
|
|
276
|
+
const fingerprint = alphaCapabilityFingerprint({ baseURL: route.baseURL, provider: route.provider, model: route.model, profile: route.alphaProfile, group: route.alphaGroup, schemaFingerprint: ALPHA_SCHEMA_FINGERPRINT })
|
|
277
|
+
const capability = capabilityStore.get(fingerprint)
|
|
278
|
+
if (!alphaCapabilityUsable(capability) || capability?.schemaFingerprint !== ALPHA_SCHEMA_FINGERPRINT) throw webError('Alpha Search capability has not been verified for this exact route/schema', 'LCX_ALPHA_CAPABILITY_UNVERIFIED')
|
|
279
|
+
return executeAlpha(ctx, route, capability, refStore, args, exec)
|
|
280
|
+
},
|
|
1012
281
|
}
|
|
1013
282
|
}
|
|
1014
283
|
|
|
1015
|
-
function
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
function replayItemId(event, item) {
|
|
1021
|
-
const value = event?.item_id ?? item?.id
|
|
1022
|
-
return typeof value === 'string' && value.length > 0 ? value : undefined
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
function replayItemKey(event, item) {
|
|
1026
|
-
const outputIndex = replayOutputIndex(event, item)
|
|
1027
|
-
if (outputIndex !== undefined) return `output:${outputIndex}`
|
|
1028
|
-
const itemId = replayItemId(event, item)
|
|
1029
|
-
return itemId ? `item:${itemId}` : undefined
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
function replayKind(type) {
|
|
1033
|
-
if (type === 'function_call' || type === 'response.function_call' || type === 'tool-call') return 'tool-call'
|
|
1034
|
-
if (type === 'reasoning') return 'reasoning'
|
|
1035
|
-
return 'text'
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
function replayItemText(item, kind) {
|
|
1039
|
-
if (kind === 'reasoning') return responseReasoningText(item)
|
|
1040
|
-
return responseTextParts(item).join('')
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
function replayEnsureOpen(state) {
|
|
1044
|
-
if (state.open) return []
|
|
1045
|
-
state.open = true
|
|
1046
|
-
state.started = true
|
|
1047
|
-
return [{ type: 'block-start', index: state.index, blockType: state.kind }]
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
function replayClose(state, finalText) {
|
|
1051
|
-
const chunks = []
|
|
1052
|
-
if (!state.open) {
|
|
1053
|
-
if (!state.started && typeof finalText === 'string' && finalText.length > 0) chunks.push(...replayEnsureOpen(state))
|
|
1054
|
-
else return []
|
|
1055
|
-
}
|
|
1056
|
-
if (typeof finalText === 'string' && (state.text.length === 0 || finalText.length >= state.text.length)) state.text = finalText
|
|
1057
|
-
state.open = false
|
|
1058
|
-
state.closed = true
|
|
1059
|
-
return [...chunks, {
|
|
1060
|
-
type: 'block-end',
|
|
1061
|
-
index: state.index,
|
|
1062
|
-
block: state.kind === 'tool-call'
|
|
1063
|
-
? { type: 'tool-call', id: state.id, name: state.name, arguments: state.arguments }
|
|
1064
|
-
: { type: state.kind, text: state.text },
|
|
1065
|
-
}]
|
|
284
|
+
function isDshCompactionDirective(message) {
|
|
285
|
+
if (message?.role !== 'user') return false
|
|
286
|
+
if (message?.source?.kind === 'plugin' && message?.source?.plugin === 'dsh-compaction-basic') return true
|
|
287
|
+
const text = (message?.content ?? []).filter((b) => b?.type === 'text').map((b) => b.text).join('')
|
|
288
|
+
return text.includes(COMPACTION_DIRECTIVE)
|
|
1066
289
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
state.kind = kind
|
|
1071
|
-
if (kind === 'tool-call') {
|
|
1072
|
-
if (typeof item?.call_id === 'string' && item.call_id.length > 0) state.id = item.call_id
|
|
1073
|
-
if (!state.id) state.id = randomUUID()
|
|
1074
|
-
state.name = String(item?.name ?? state.name ?? '')
|
|
1075
|
-
const finalArguments = typeof item?.arguments === 'string' ? item.arguments : undefined
|
|
1076
|
-
const chunks = []
|
|
1077
|
-
if (!state.started && (state.name || finalArguments)) {
|
|
1078
|
-
chunks.push(...replayEnsureOpen(state))
|
|
1079
|
-
if (finalArguments) {
|
|
1080
|
-
state.arguments = finalArguments
|
|
1081
|
-
chunks.push({ type: 'tool-call-delta', index: state.index, id: state.id, name: state.name, argumentsDelta: finalArguments })
|
|
1082
|
-
}
|
|
1083
|
-
} else if (finalArguments && state.arguments.length === 0) {
|
|
1084
|
-
state.arguments = finalArguments
|
|
1085
|
-
}
|
|
1086
|
-
chunks.push(...replayClose(state))
|
|
1087
|
-
return chunks
|
|
1088
|
-
}
|
|
1089
|
-
const finalText = replayItemText(item, kind)
|
|
1090
|
-
const chunks = []
|
|
1091
|
-
if (!state.started && finalText) {
|
|
1092
|
-
chunks.push(...replayEnsureOpen(state))
|
|
1093
|
-
state.text = finalText
|
|
1094
|
-
chunks.push({ type: kind === 'reasoning' ? 'reasoning-delta' : 'text-delta', index: state.index, text: finalText })
|
|
1095
|
-
}
|
|
1096
|
-
chunks.push(...replayClose(state, finalText))
|
|
1097
|
-
return chunks
|
|
290
|
+
function stripCompactionDirective(messages) {
|
|
291
|
+
if (!Array.isArray(messages) || messages.length === 0) return []
|
|
292
|
+
return isDshCompactionDirective(messages.at(-1)) ? messages.slice(0, -1) : messages
|
|
1098
293
|
}
|
|
1099
294
|
|
|
1100
|
-
|
|
1101
|
-
if (!response?.body) throw replaySseError('LCX Responses replay response did not include an SSE body', 'LCX_INVALID_SSE')
|
|
1102
|
-
const maxBytes = options.maxResponseBytes ?? 8 * 1024 * 1024
|
|
1103
|
-
const reader = response.body.getReader()
|
|
1104
|
-
const decoder = new TextDecoder()
|
|
1105
|
-
const states = new Map()
|
|
1106
|
-
let nextIndex = 0
|
|
1107
|
-
let pending = ''
|
|
1108
|
-
let dataLines = []
|
|
1109
|
-
let bytes = 0
|
|
1110
|
-
let sawCompleted = false
|
|
1111
|
-
let hasToolCall = false
|
|
1112
|
-
let completedResponse
|
|
1113
|
-
const cancel = () => { reader.cancel(options.signal?.reason).catch(() => undefined) }
|
|
1114
|
-
options.signal?.addEventListener('abort', cancel, { once: true })
|
|
1115
|
-
|
|
1116
|
-
const stateFor = (event, item, kindHint) => {
|
|
1117
|
-
const outputIndex = replayOutputIndex(event, item)
|
|
1118
|
-
const itemId = replayItemId(event, item)
|
|
1119
|
-
const kind = kindHint ?? replayKind(item?.type)
|
|
1120
|
-
const key = replayItemKey(event, item)
|
|
1121
|
-
let state = key ? states.get(key) : undefined
|
|
1122
|
-
if (!state) {
|
|
1123
|
-
state = [...states.values()].find((candidate) =>
|
|
1124
|
-
!candidate.closed &&
|
|
1125
|
-
((outputIndex !== undefined && candidate.outputIndex === outputIndex) ||
|
|
1126
|
-
(itemId !== undefined && candidate.itemId === itemId) ||
|
|
1127
|
-
(outputIndex === undefined && itemId === undefined && candidate.kind === kind)))
|
|
1128
|
-
}
|
|
1129
|
-
if (!state) {
|
|
1130
|
-
state = {
|
|
1131
|
-
index: nextIndex++,
|
|
1132
|
-
kind,
|
|
1133
|
-
open: false,
|
|
1134
|
-
closed: false,
|
|
1135
|
-
started: false,
|
|
1136
|
-
text: '',
|
|
1137
|
-
arguments: '',
|
|
1138
|
-
id: undefined,
|
|
1139
|
-
itemId: undefined,
|
|
1140
|
-
outputIndex: undefined,
|
|
1141
|
-
name: '',
|
|
1142
|
-
}
|
|
1143
|
-
states.set(key ?? `anonymous:${state.index}`, state)
|
|
1144
|
-
}
|
|
1145
|
-
if (outputIndex !== undefined) state.outputIndex = outputIndex
|
|
1146
|
-
if (itemId !== undefined) state.itemId = itemId
|
|
1147
|
-
if (item?.type) state.kind = replayKind(item.type)
|
|
1148
|
-
if (typeof event?.call_id === 'string' && event.call_id.length > 0) state.id = event.call_id
|
|
1149
|
-
if (typeof item?.call_id === 'string' && item.call_id.length > 0) state.id = item.call_id
|
|
1150
|
-
if (item?.name) state.name = String(item.name)
|
|
1151
|
-
return state
|
|
1152
|
-
}
|
|
295
|
+
function mergeMap(target, source) { for (const [key, value] of source ?? []) target.set(key, value) }
|
|
1153
296
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
state = [...states.values()].find((candidate) => !consumed.has(candidate) && candidate.kind === kind)
|
|
1163
|
-
}
|
|
1164
|
-
if (!state) state = stateFor({ output_index: outputIndex }, item, kind)
|
|
1165
|
-
consumed.add(state)
|
|
1166
|
-
return state
|
|
297
|
+
async function serializeNativeAware(messages, route, routeConfig, ctx, options = {}) {
|
|
298
|
+
const session = sessionFor(ctx, route.sessionId)
|
|
299
|
+
const imageSupport = await resolveModelImageSupport(ctx, route, options.signal)
|
|
300
|
+
const input = []; const imageMap = new Map(); let normal = []
|
|
301
|
+
const flush = async () => {
|
|
302
|
+
if (!normal.length) return
|
|
303
|
+
const serialized = await serializeDshMessages(normal, ctx, requestImageOptions(routeConfig, imageSupport, options.signal))
|
|
304
|
+
input.push(...serialized.input); mergeMap(imageMap, serialized.imageMap); normal = []
|
|
1167
305
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
if (
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
if (typeof event.name === 'string') state.name = event.name
|
|
1199
|
-
const delta = typeof event.delta === 'string' ? event.delta : ''
|
|
1200
|
-
if (!delta) return []
|
|
1201
|
-
hasToolCall = true
|
|
1202
|
-
const chunks = replayEnsureOpen(state)
|
|
1203
|
-
state.arguments += delta
|
|
1204
|
-
chunks.push({ type: 'tool-call-delta', index: state.index, id: state.id, name: state.name, argumentsDelta: delta })
|
|
1205
|
-
return chunks
|
|
1206
|
-
}
|
|
1207
|
-
if (event?.type === 'response.function_call_arguments.done') {
|
|
1208
|
-
const state = stateFor(event, undefined, 'tool-call')
|
|
1209
|
-
state.kind = 'tool-call'
|
|
1210
|
-
hasToolCall = true
|
|
1211
|
-
if (typeof event.call_id === 'string' && event.call_id.length > 0) state.id = event.call_id
|
|
1212
|
-
if (!state.id) state.id = randomUUID()
|
|
1213
|
-
if (typeof event.name === 'string') state.name = event.name
|
|
1214
|
-
const chunks = []
|
|
1215
|
-
const finalArguments = typeof event.arguments === 'string' ? event.arguments : ''
|
|
1216
|
-
if (!state.started && (state.name || finalArguments)) {
|
|
1217
|
-
chunks.push(...replayEnsureOpen(state))
|
|
1218
|
-
if (finalArguments) {
|
|
1219
|
-
state.arguments = finalArguments
|
|
1220
|
-
chunks.push({ type: 'tool-call-delta', index: state.index, id: state.id, name: state.name, argumentsDelta: finalArguments })
|
|
1221
|
-
}
|
|
1222
|
-
} else if (finalArguments && state.arguments.length === 0) {
|
|
1223
|
-
state.arguments = finalArguments
|
|
1224
|
-
}
|
|
1225
|
-
chunks.push(...replayClose(state))
|
|
1226
|
-
return chunks
|
|
1227
|
-
}
|
|
1228
|
-
if (event?.type === 'response.output_item.done') {
|
|
1229
|
-
const state = stateFor(event, event.item)
|
|
1230
|
-
if (event.item?.type === 'function_call' || state.kind === 'tool-call') hasToolCall = true
|
|
1231
|
-
if (state.kind === 'text') {
|
|
1232
|
-
const finalText = replayItemText(event.item, 'text')
|
|
1233
|
-
if (finalText && [...states.values()].some((candidate) =>
|
|
1234
|
-
candidate !== state && candidate.closed && candidate.kind === 'text' && candidate.text === finalText)) return []
|
|
1235
|
-
}
|
|
1236
|
-
return replayCloseItem(state, event.item)
|
|
1237
|
-
}
|
|
1238
|
-
if (event?.type === 'response.completed') {
|
|
1239
|
-
sawCompleted = true
|
|
1240
|
-
completedResponse = event.response && typeof event.response === 'object' ? event.response : {}
|
|
1241
|
-
const chunks = []
|
|
1242
|
-
const completedStates = new Set()
|
|
1243
|
-
if (Array.isArray(completedResponse.output)) {
|
|
1244
|
-
for (let outputIndex = 0; outputIndex < completedResponse.output.length; outputIndex += 1) {
|
|
1245
|
-
const item = completedResponse.output[outputIndex]
|
|
1246
|
-
if (item?.type === 'function_call') hasToolCall = true
|
|
1247
|
-
const completedText = replayItemText(item, 'text')
|
|
1248
|
-
if (completedText && [...states.values()].some((candidate) =>
|
|
1249
|
-
candidate.closed && candidate.kind === 'text' && candidate.text === completedText)) {
|
|
306
|
+
for (const message of messages ?? []) {
|
|
307
|
+
const state = session ? checkpointStateForMessage(session, message) : undefined
|
|
308
|
+
if (state) {
|
|
309
|
+
await flush()
|
|
310
|
+
if (stateRouteCompatible(state, route, ctx)) {
|
|
311
|
+
let nativeOutput = state.nativeOutput
|
|
312
|
+
if (state.version === 4) {
|
|
313
|
+
const checkpointId = compactCheckpointId(message)
|
|
314
|
+
const shadowed = shadowedMessagesForCheckpoint(session, checkpointId)
|
|
315
|
+
if (shadowed.length > 0) {
|
|
316
|
+
const serializedShadowed = await serializeDshMessages(shadowed, ctx, requestImageOptions(routeConfig, imageSupport, options.signal))
|
|
317
|
+
const repairedRetained = retainedConversationInput(serializedShadowed.input, {
|
|
318
|
+
tokenBudget: routeConfig.nativeRetentionTokenBudget,
|
|
319
|
+
assistantTokenReserve: routeConfig.assistantRetentionTokenReserve,
|
|
320
|
+
assistantPerMessageTokenCap: routeConfig.assistantRetentionPerMessageTokenCap,
|
|
321
|
+
})
|
|
322
|
+
const opaque = state.nativeCompaction ?? nativeOutput.find((item) => item?.type === 'compaction')
|
|
323
|
+
if (repairedRetained.length > 0 && opaque) {
|
|
324
|
+
nativeOutput = [...repairedRetained, structuredClone(opaque)]
|
|
325
|
+
mergeMap(imageMap, serializedShadowed.imageMap)
|
|
326
|
+
} else if (state.retainedInputCount === undefined) {
|
|
327
|
+
const portable = portableMessagesForCheckpoint(session, checkpointId, { maxChars: routeConfig.portableReplayMaxChars })
|
|
328
|
+
const serializedPortable = await serializeDshMessages(portable, ctx, requestImageOptions(routeConfig, imageSupport, options.signal))
|
|
329
|
+
input.push(...serializedPortable.input); mergeMap(imageMap, serializedPortable.imageMap)
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
332
|
+
} else if (state.retainedInputCount === undefined) {
|
|
333
|
+
const portable = portableMessagesForCheckpoint(session, checkpointId, { maxChars: routeConfig.portableReplayMaxChars })
|
|
334
|
+
const serializedPortable = await serializeDshMessages(portable, ctx, requestImageOptions(routeConfig, imageSupport, options.signal))
|
|
335
|
+
input.push(...serializedPortable.input); mergeMap(imageMap, serializedPortable.imageMap)
|
|
1250
336
|
continue
|
|
1251
337
|
}
|
|
1252
|
-
const state = stateForCompleted(item, outputIndex, completedStates)
|
|
1253
|
-
// A provider may emit output_text.done/output_item.done before the
|
|
1254
|
-
// authoritative response.completed event. The completed event closes
|
|
1255
|
-
// the response, but must not create a second visible DSH block for an
|
|
1256
|
-
// item that has already been closed on the stream.
|
|
1257
|
-
if (state.closed) continue
|
|
1258
|
-
// Some gateways change output indexes or omit item ids between
|
|
1259
|
-
// streaming events and response.completed. If the completed text
|
|
1260
|
-
// exactly matches an already closed text state, it is the same
|
|
1261
|
-
// visible item and must not be projected a second time.
|
|
1262
|
-
chunks.push(...replayCloseItem(state, item))
|
|
1263
338
|
}
|
|
339
|
+
const hydratedMap = new Map()
|
|
340
|
+
const hydrated = await hydrateNativeImageReferences(nativeOutput, ctx, requestImageOptions(routeConfig, imageSupport, options.signal, hydratedMap))
|
|
341
|
+
input.push(...hydrated); mergeMap(imageMap, hydratedMap)
|
|
342
|
+
} else {
|
|
343
|
+
const portable = portableMessagesForCheckpoint(session, compactCheckpointId(message), { maxChars: routeConfig.portableReplayMaxChars })
|
|
344
|
+
const serialized = await serializeDshMessages(portable, ctx, requestImageOptions(routeConfig, imageSupport, options.signal))
|
|
345
|
+
input.push(...serialized.input); mergeMap(imageMap, serialized.imageMap)
|
|
1264
346
|
}
|
|
1265
|
-
|
|
1266
|
-
if (!completedStates.has(state)) chunks.push(...replayClose(state))
|
|
1267
|
-
}
|
|
1268
|
-
const usage = usageFrom(completedResponse)
|
|
1269
|
-
if (usage) chunks.push({ type: 'usage', usage })
|
|
1270
|
-
chunks.push({ type: 'finish', reason: { kind: hasToolCall ? 'tool-calls' : 'stop' } })
|
|
1271
|
-
return chunks
|
|
1272
|
-
}
|
|
1273
|
-
if (event?.type === 'response.reasoning_summary_text.delta' || event?.type === 'response.reasoning_text.delta' || event?.type === 'response.reasoning.delta') {
|
|
1274
|
-
const state = stateFor(event, undefined, 'reasoning')
|
|
1275
|
-
state.kind = 'reasoning'
|
|
1276
|
-
const delta = typeof event.delta === 'string' ? event.delta : ''
|
|
1277
|
-
if (!delta) return []
|
|
1278
|
-
const chunks = replayEnsureOpen(state)
|
|
1279
|
-
state.text += delta
|
|
1280
|
-
chunks.push({ type: 'reasoning-delta', index: state.index, text: delta })
|
|
1281
|
-
return chunks
|
|
1282
|
-
}
|
|
1283
|
-
if (event?.type === 'response.reasoning_summary_text.done' || event?.type === 'response.reasoning_text.done' || event?.type === 'response.reasoning.done') {
|
|
1284
|
-
const state = stateFor(event, undefined, 'reasoning')
|
|
1285
|
-
state.kind = 'reasoning'
|
|
1286
|
-
return replayClose(state, typeof event.text === 'string' ? event.text : undefined)
|
|
1287
|
-
}
|
|
1288
|
-
return []
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
const dispatch = () => {
|
|
1292
|
-
const event = replaySseEvent(dataLines)
|
|
1293
|
-
dataLines = []
|
|
1294
|
-
return event ? eventChunks(event) : []
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
try {
|
|
1298
|
-
while (true) {
|
|
1299
|
-
if (options.signal?.aborted) throw replayAbortError(options)
|
|
1300
|
-
const result = await reader.read()
|
|
1301
|
-
if (result.done) break
|
|
1302
|
-
bytes += result.value.byteLength
|
|
1303
|
-
if (bytes > maxBytes) throw replaySseError(`LCX Responses replay SSE response exceeds ${maxBytes} bytes`, 'LCX_RESPONSE_TOO_LARGE')
|
|
1304
|
-
pending += decoder.decode(result.value, { stream: true })
|
|
1305
|
-
let newline
|
|
1306
|
-
while ((newline = pending.indexOf('\n')) >= 0) {
|
|
1307
|
-
const line = pending.slice(0, newline).replace(/\r$/u, '')
|
|
1308
|
-
pending = pending.slice(newline + 1)
|
|
1309
|
-
if (line === '') {
|
|
1310
|
-
for (const chunk of dispatch()) yield chunk
|
|
1311
|
-
} else if (!line.startsWith(':')) {
|
|
1312
|
-
if (line.startsWith('data:')) dataLines.push(line.slice(5).replace(/^ /u, ''))
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
pending += decoder.decode()
|
|
1317
|
-
if (pending.length > 0) {
|
|
1318
|
-
const line = pending.replace(/\r$/u, '')
|
|
1319
|
-
if (line.startsWith('data:')) dataLines.push(line.slice(5).replace(/^ /u, ''))
|
|
347
|
+
continue
|
|
1320
348
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
if (!sawCompleted) throw replaySseError('LCX Responses replay SSE ended without response.completed', 'LCX_INCOMPLETE_SSE')
|
|
1332
|
-
if (!completedResponse) throw replaySseError('LCX Responses replay did not return a completed response', 'LCX_INCOMPLETE_SSE')
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
function failureOf(error) {
|
|
1336
|
-
return {
|
|
1337
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1338
|
-
code: typeof error?.code === 'string' ? error.code : 'LCX_CODEX_ERROR',
|
|
1339
|
-
...(Number.isInteger(error?.status) ? { status: error.status } : {}),
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
function guardedStream(factory, signal) {
|
|
1344
|
-
return (async function* () {
|
|
1345
|
-
try {
|
|
1346
|
-
yield* factory()
|
|
1347
|
-
} catch (error) {
|
|
1348
|
-
const failure = failureOf(error)
|
|
1349
|
-
yield {
|
|
1350
|
-
type: 'finish',
|
|
1351
|
-
reason: signal?.aborted ? { kind: 'aborted', failure } : { kind: 'error', failure },
|
|
349
|
+
const legacyId = legacyV3Id(message)
|
|
350
|
+
if (legacyId) {
|
|
351
|
+
const legacy = loadLegacyRecord(routeConfig.legacyCheckpointPath, legacyId)
|
|
352
|
+
if (legacy) {
|
|
353
|
+
await flush()
|
|
354
|
+
const legacyItems = legacyRouteCompatible(legacy, route, ctx) ? legacy.nativeOutput : (legacy.portableHistory ?? [])
|
|
355
|
+
const hydratedMap = new Map()
|
|
356
|
+
const hydrated = await hydrateNativeImageReferences(legacyItems, ctx, requestImageOptions(routeConfig, imageSupport, options.signal, hydratedMap))
|
|
357
|
+
input.push(...hydrated); mergeMap(imageMap, hydratedMap)
|
|
358
|
+
continue
|
|
1352
359
|
}
|
|
1353
360
|
}
|
|
1354
|
-
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
async function executeHostedSearch(ctx, config, args, signal) {
|
|
1358
|
-
abortIfNeeded(signal)
|
|
1359
|
-
const normalized = normalizeHostedSearchArgs(args)
|
|
1360
|
-
const requestId = randomUUID()
|
|
1361
|
-
try {
|
|
1362
|
-
const response = await fetchJsonWithRetry(
|
|
1363
|
-
`${config.baseURL}/responses`,
|
|
1364
|
-
buildHostedSearchBody(normalized, config.model),
|
|
1365
|
-
await authenticatedHeaders(ctx, config, undefined, requestId),
|
|
1366
|
-
signal,
|
|
1367
|
-
config.timeoutMs,
|
|
1368
|
-
{ maxAttempts: config.maxAttempts, maxResponseBytes: config.maxResponseBytes },
|
|
1369
|
-
)
|
|
1370
|
-
return parseHostedSearchResponse(response, requestId, config.webMaxResults)
|
|
1371
|
-
} catch (error) {
|
|
1372
|
-
if (signal?.aborted) throw webError('LCX hosted Web Search aborted', 'LCX_WEB_ABORTED', error)
|
|
1373
|
-
if (error?.code === 'LCX_TIMEOUT') throw webError(String(error), 'LCX_WEB_TIMEOUT', error)
|
|
1374
|
-
if (error?.code?.startsWith?.('WEB_')) throw error
|
|
1375
|
-
throw webError(String(error), 'LCX_WEB_PROVIDER_ERROR', error)
|
|
361
|
+
normal.push(message)
|
|
1376
362
|
}
|
|
363
|
+
await flush()
|
|
364
|
+
return { input, imageMap, imageSupport }
|
|
1377
365
|
}
|
|
1378
366
|
|
|
1379
|
-
function
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
function currentAlphaCapabilityFingerprint(config, model = config.model) {
|
|
1385
|
-
return alphaCapabilityFingerprint({
|
|
1386
|
-
baseURL: config.baseURL,
|
|
1387
|
-
provider: config.provider,
|
|
1388
|
-
model,
|
|
1389
|
-
profile: config.alphaProfile,
|
|
1390
|
-
group: config.alphaGroup,
|
|
1391
|
-
schemaFingerprint: ALPHA_SCHEMA_FINGERPRINT,
|
|
1392
|
-
})
|
|
367
|
+
function fallbackEligible(error, signal) {
|
|
368
|
+
if (signal?.aborted) return false
|
|
369
|
+
if (error?.name === 'AbortError' || error?.code === 'LCX_ABORTED') return false
|
|
370
|
+
return true
|
|
1393
371
|
}
|
|
1394
372
|
|
|
1395
|
-
function
|
|
373
|
+
async function* remoteCompactionStream(options, routeConfig, state, ctx, next) {
|
|
374
|
+
const history = stripCompactionDirective(options.messages)
|
|
375
|
+
const route = currentRoute(options, routeConfig)
|
|
1396
376
|
try {
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
model: requestContext?.model ?? requestConfig?.model ?? agent?.options?.model,
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
async function executeAlphaSearch(ctx, config, capabilityStore, refStore, args, exec) {
|
|
1417
|
-
abortIfNeeded(exec?.signal)
|
|
1418
|
-
const normalized = normalizeAlphaSearchArgs(args)
|
|
1419
|
-
const sessionId = typeof exec?.agent?.id === 'string' ? exec.agent.id : undefined
|
|
1420
|
-
if (!sessionId) throw webError('Alpha Web Search requires a live DSH agent session', 'LCX_ALPHA_SESSION_REQUIRED')
|
|
1421
|
-
const { provider: agentProvider, model: agentModel } = activeAgentRoute(exec?.agent)
|
|
1422
|
-
const routeConfig = resolveResponsesRouteConfig(ctx, {
|
|
1423
|
-
provider: agentProvider ?? config.provider,
|
|
1424
|
-
model: agentModel ?? config.model,
|
|
1425
|
-
}, config)
|
|
1426
|
-
if (!routeConfig) throw alphaUnavailable('Alpha Web Search requires an active DSH GPT openai-responses model route')
|
|
1427
|
-
const fingerprint = currentAlphaCapabilityFingerprint(routeConfig)
|
|
1428
|
-
const capability = capabilityStore.get(fingerprint)
|
|
1429
|
-
if (!alphaCapabilityUsable(capability) || capability.schemaFingerprint !== ALPHA_SCHEMA_FINGERPRINT || capability.actions?.[normalized.action] !== 'supported') {
|
|
1430
|
-
throw alphaUnavailable()
|
|
1431
|
-
}
|
|
1432
|
-
if (normalized.refId && !isHttpReference(normalized.refId)) refStore.assertUsable(sessionId, fingerprint, normalized.refId)
|
|
1433
|
-
const requestId = randomUUID()
|
|
1434
|
-
try {
|
|
1435
|
-
const response = await fetchJsonWithRetry(
|
|
1436
|
-
`${routeConfig.baseURL}/alpha/search`,
|
|
1437
|
-
buildAlphaSearchBody(normalized, routeConfig.model, sessionId, true, routeConfig.alphaMaxOutputTokens),
|
|
1438
|
-
await authenticatedHeaders(ctx, routeConfig, sessionId, requestId),
|
|
1439
|
-
exec?.signal,
|
|
1440
|
-
routeConfig.timeoutMs,
|
|
1441
|
-
{ maxAttempts: routeConfig.maxAttempts, maxResponseBytes: routeConfig.maxResponseBytes },
|
|
1442
|
-
)
|
|
1443
|
-
const result = parseAlphaSearchResponse(response, {
|
|
1444
|
-
action: normalized.action,
|
|
1445
|
-
capability: capability.classification,
|
|
1446
|
-
requestId,
|
|
377
|
+
const prepared = await serializeNativeAware(history, route, routeConfig, ctx, { signal: options.signal })
|
|
378
|
+
const headers = await authenticatedHeaders(ctx, routeConfig, route.sessionId)
|
|
379
|
+
const result = await requestNativeCompaction({
|
|
380
|
+
baseURL: routeConfig.baseURL,
|
|
381
|
+
model: route.model,
|
|
382
|
+
input: prepared.input,
|
|
383
|
+
instructions: options.system,
|
|
384
|
+
tools: options.tools,
|
|
385
|
+
promptCacheKey: promptCacheKey(route),
|
|
386
|
+
idempotencyKey: randomUUID(),
|
|
387
|
+
headers,
|
|
388
|
+
signal: options.signal,
|
|
389
|
+
timeoutMs: routeConfig.timeoutMs,
|
|
390
|
+
maxAttempts: routeConfig.maxAttempts,
|
|
391
|
+
maxResponseBytes: routeConfig.maxResponseBytes,
|
|
1447
392
|
})
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
393
|
+
const session = sessionFor(ctx, route.sessionId)
|
|
394
|
+
const block = createNativeCheckpointBlock({
|
|
395
|
+
session, route, result, input: prepared.input, imageMap: prepared.imageMap,
|
|
396
|
+
retentionOptions: {
|
|
397
|
+
tokenBudget: routeConfig.nativeRetentionTokenBudget,
|
|
398
|
+
assistantTokenReserve: routeConfig.assistantRetentionTokenReserve,
|
|
399
|
+
assistantPerMessageTokenCap: routeConfig.assistantRetentionPerMessageTokenCap,
|
|
400
|
+
},
|
|
401
|
+
})
|
|
402
|
+
ctx.logger?.info?.(`[lcx-codex] native V2 compaction succeeded; retained ${block.retainedClientCount ?? 0} client + ${block.retainedAssistantCount ?? 0} assistant-visible item(s) (~${block.retainedEstimatedTokens ?? 0} tokens) with the opaque checkpoint`)
|
|
403
|
+
for (const chunk of nativeCheckpointChunks(block, result.usage)) yield chunk
|
|
1455
404
|
} catch (error) {
|
|
1456
|
-
|
|
1457
|
-
if (error
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
405
|
+
ctx.logger?.warn?.(`[lcx-codex] native V2 compaction failed: ${error?.code ?? error?.name ?? 'ERROR'} ${error?.message ?? String(error)}`)
|
|
406
|
+
if (!state.fallbackToBasicCompaction || !fallbackEligible(error, options.signal)) throw error
|
|
407
|
+
ctx.logger?.info?.('[lcx-codex] falling back to DSH basic compaction after native failure')
|
|
408
|
+
const stream = await next()
|
|
409
|
+
for await (const chunk of stream) yield chunk
|
|
1461
410
|
}
|
|
1462
411
|
}
|
|
1463
412
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
function
|
|
1492
|
-
return {
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
if (
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
const
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
413
|
+
function routedTargetForAgent(agent, fallback) {
|
|
414
|
+
const latest = agent?.session?.requestHeader?.()?.config
|
|
415
|
+
const provider = latest?.provider ?? agent?.options?.provider ?? fallback.provider
|
|
416
|
+
const model = latest?.model ?? agent?.options?.model ?? fallback.model
|
|
417
|
+
return provider && model ? { provider, model } : undefined
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function clampPercent(value, fallback, min, max) {
|
|
421
|
+
const number = Number(value)
|
|
422
|
+
return Number.isFinite(number) ? Math.min(max, Math.max(min, number)) : fallback
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function pressurePolicy(state, config) {
|
|
426
|
+
const auto = clampPercent(state.autoCompactionThresholdPercent, config.autoCompactionThresholdPercent, 85, 95)
|
|
427
|
+
const emergencyRaw = clampPercent(state.emergencyPruneThresholdPercent, config.emergencyPruneThresholdPercent, 90, 99)
|
|
428
|
+
const emergency = Math.max(auto + 1, emergencyRaw)
|
|
429
|
+
return { auto, emergency: Math.min(99, emergency) }
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function adjustedCompactionConfig(config, target, thresholdRatio) {
|
|
433
|
+
if (!config || typeof config !== 'object') return config
|
|
434
|
+
const modelPolicies = Array.isArray(config.modelPolicies)
|
|
435
|
+
? config.modelPolicies.map((policy) => policy?.provider === target.provider && policy?.model === target.model ? { ...policy, thresholdRatio } : policy)
|
|
436
|
+
: config.modelPolicies
|
|
437
|
+
return { ...config, thresholdRatio, ...(modelPolicies === undefined ? {} : { modelPolicies }) }
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function resolveScopedService(agent, name) {
|
|
441
|
+
try { return agent?.ctx?.get?.(name) ?? agent?.ctx?.[name] } catch { return undefined }
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function patchCompactionPressureForAgent(agent, state, getConfig, ctx, records) {
|
|
445
|
+
const compaction = resolveScopedService(agent, 'compaction')
|
|
446
|
+
if (!compaction || typeof compaction.compactIfNeeded !== 'function' || records.has(compaction)) return
|
|
447
|
+
const original = compaction.compactIfNeeded
|
|
448
|
+
const record = { compaction, original, wrapper: undefined }
|
|
449
|
+
const wrapper = async function(agentArg, trigger, signal) {
|
|
450
|
+
if (trigger !== 'pressure' || !state.enabled || !state.remoteCompaction || !state.autoCompaction) return original.call(this, agentArg, trigger, signal)
|
|
451
|
+
const config = getConfig()
|
|
452
|
+
const target = routedTargetForAgent(agentArg, config)
|
|
453
|
+
if (!target || !resolveResponsesRouteConfig(ctx, target, config)) return original.call(this, agentArg, trigger, signal)
|
|
454
|
+
const tokenMeter = resolveScopedService(agentArg, 'tokenMeter') ?? this?.ctx?.get?.('tokenMeter')
|
|
455
|
+
const llm = resolveScopedService(agentArg, 'llm') ?? ctx?.get?.('llm') ?? ctx?.llm
|
|
456
|
+
if (!tokenMeter?.measure || !llm?.resolveModelInfo) return original.call(this, agentArg, trigger, signal)
|
|
457
|
+
let contextWindow
|
|
458
|
+
try { contextWindow = (await llm.resolveModelInfo(target.provider, target.model, signal))?.context?.contextWindow } catch { return original.call(this, agentArg, trigger, signal) }
|
|
459
|
+
if (!Number.isFinite(contextWindow) || contextWindow <= 0) return original.call(this, agentArg, trigger, signal)
|
|
460
|
+
const totalTokens = Number(tokenMeter.measure(agentArg.session)?.totalTokens ?? 0)
|
|
461
|
+
const { auto, emergency } = pressurePolicy(state, config)
|
|
462
|
+
const ratioPercent = totalTokens / contextWindow * 100
|
|
463
|
+
if (ratioPercent < auto) return null
|
|
464
|
+
const pruner = resolveScopedService(agentArg, 'toolResultPruner') ?? this?.ctx?.get?.('toolResultPruner')
|
|
465
|
+
const originalPrune = pruner?.pruneSession
|
|
466
|
+
const originalConfig = this?.config
|
|
467
|
+
const nativeFirst = ratioPercent < emergency
|
|
468
|
+
if (nativeFirst && pruner && typeof originalPrune === 'function') pruner.pruneSession = () => ({ pruned: [], charsRemoved: 0 })
|
|
469
|
+
if (originalConfig && this && Object.prototype.hasOwnProperty.call(this, 'config')) { try { this.config = adjustedCompactionConfig(originalConfig, target, auto / 100) } catch {} }
|
|
470
|
+
ctx.logger?.info?.(`[lcx-codex] auto pressure ${ratioPercent.toFixed(1)}%: ${nativeFirst ? 'Native V2 first' : 'emergency DSH prune allowed'} (native ${auto}%, emergency ${emergency}%)`)
|
|
471
|
+
try { return await original.call(this, agentArg, trigger, signal) }
|
|
472
|
+
finally {
|
|
473
|
+
if (originalConfig && this && this.config !== originalConfig) { try { this.config = originalConfig } catch {} }
|
|
474
|
+
if (nativeFirst && pruner && typeof originalPrune === 'function' && pruner.pruneSession !== originalPrune) { try { pruner.pruneSession = originalPrune } catch {} }
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
record.wrapper = wrapper
|
|
478
|
+
try { compaction.compactIfNeeded = wrapper } catch { return }
|
|
479
|
+
records.set(compaction, record)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function restoreCompactionPressure(records) {
|
|
483
|
+
for (const record of records.values()) if (record.compaction?.compactIfNeeded === record.wrapper) { try { record.compaction.compactIfNeeded = record.original } catch {} }
|
|
484
|
+
records.clear()
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function patchVisibleWebSearchTimeout(agent, state, getConfig, patchedDefinitions) {
|
|
488
|
+
const tools = resolveScopedService(agent, 'tools')
|
|
489
|
+
const definition = tools?.get?.('web_search', agent)
|
|
490
|
+
if (!definition || typeof definition !== 'object') return
|
|
491
|
+
if (!patchedDefinitions.has(definition)) patchedDefinitions.set(definition, definition.timeoutMs)
|
|
492
|
+
const config = getConfig()
|
|
493
|
+
const timeoutMs = Number.isFinite(state.webSearchTimeoutSeconds) ? Math.min(600_000, Math.max(30_000, Math.round(state.webSearchTimeoutSeconds * 1000))) : config.webSearchTimeoutMs
|
|
494
|
+
try { definition.timeoutMs = state.enabled && state.webSearch ? timeoutMs : patchedDefinitions.get(definition) } catch {}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function refreshPatchedWebSearchTimeouts(state, getConfig, patchedDefinitions) {
|
|
498
|
+
const config = getConfig()
|
|
499
|
+
const timeoutMs = Number.isFinite(state.webSearchTimeoutSeconds) ? Math.min(600_000, Math.max(30_000, Math.round(state.webSearchTimeoutSeconds * 1000))) : config.webSearchTimeoutMs
|
|
500
|
+
for (const [definition, original] of patchedDefinitions.entries()) { try { definition.timeoutMs = state.enabled && state.webSearch ? timeoutMs : original } catch {} }
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function restoreWebSearchTimeouts(patchedDefinitions) {
|
|
504
|
+
for (const [definition, original] of patchedDefinitions.entries()) { try { if (original === undefined) delete definition.timeoutMs; else definition.timeoutMs = original } catch {} }
|
|
505
|
+
patchedDefinitions.clear()
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function messagesContainNativeCheckpoint(messages, session) { return Boolean(session && (messages ?? []).some((message) => checkpointStateForMessage(session, message))) }
|
|
509
|
+
function messagesContainLegacyCheckpoint(messages) { return (messages ?? []).some((message) => Boolean(legacyCheckpointId(message))) }
|
|
510
|
+
|
|
511
|
+
async function* recursiveLlmStream(ctx, options, messages) {
|
|
512
|
+
const llm = ctx?.get?.('llm') ?? ctx?.llm
|
|
513
|
+
if (!llm?.stream) throw Object.assign(new Error('LCX portable replay requires ctx.llm.stream'), { code: 'LCX_CHECKPOINT_REPLAY_UNAVAILABLE' })
|
|
514
|
+
const rewritten = { ...options, messages }
|
|
515
|
+
bypassReplayOptions.add(rewritten)
|
|
516
|
+
const stream = await llm.stream(rewritten)
|
|
517
|
+
for await (const chunk of stream) yield chunk
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async function* replayCheckpointStream(options, routeConfig, ctx) {
|
|
521
|
+
const route = currentRoute(options, routeConfig)
|
|
522
|
+
const session = sessionFor(ctx, route.sessionId)
|
|
523
|
+
if (!session) throw Object.assign(new Error('Native checkpoint replay requires the live DSH session'), { code: 'LCX_SESSION_UNAVAILABLE' })
|
|
524
|
+
const incompatibleV4 = (options.messages ?? []).some((message) => { const state = checkpointStateForMessage(session, message); return state && !stateRouteCompatible(state, route, ctx) })
|
|
525
|
+
if (incompatibleV4) {
|
|
526
|
+
const portable = rewriteCheckpointsPortable(options.messages, session, { maxChars: routeConfig.portableReplayMaxChars })
|
|
527
|
+
yield* recursiveLlmStream(ctx, options, portable)
|
|
528
|
+
return
|
|
529
|
+
}
|
|
530
|
+
const prepared = await serializeNativeAware(options.messages, route, routeConfig, ctx, { signal: options.signal })
|
|
531
|
+
const headers = await authenticatedHeaders(ctx, routeConfig, route.sessionId)
|
|
532
|
+
yield* requestNativeReplay({ baseURL: routeConfig.baseURL, model: route.model, input: prepared.input, system: options.system, tools: options.tools, promptCacheKey: promptCacheKey(route), headers, signal: options.signal, timeoutMs: routeConfig.timeoutMs, maxAttempts: routeConfig.maxAttempts, maxResponseBytes: routeConfig.maxResponseBytes })
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function installInjected(ctx, configInput = {}) {
|
|
536
|
+
const baseConfig = normalizeConfig(configInput)
|
|
537
|
+
let runtimeConfig = baseConfig
|
|
538
|
+
const state = { enabled: false, webSearch: false, advancedHostedSearch: false, alphaSearch: false, remoteCompaction: false, fallbackToBasicCompaction: true, autoCompaction: true, webSearchTimeoutSeconds: baseConfig.webSearchTimeoutMs / 1000, autoCompactionThresholdPercent: baseConfig.autoCompactionThresholdPercent, emergencyPruneThresholdPercent: baseConfig.emergencyPruneThresholdPercent }
|
|
539
|
+
const originalSearchProvider = readWebSearchProvider(ctx)
|
|
540
|
+
let warnedWebSelection = false
|
|
541
|
+
const provider = new LcxResponsesSearchProvider(ctx, () => runtimeConfig, () => state.enabled && state.webSearch)
|
|
1587
542
|
ctx.web.registerSearchProvider(provider)
|
|
1588
|
-
const tools = ctx
|
|
1589
|
-
|
|
1590
|
-
const
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
543
|
+
const tools = ctx?.get?.('tools') ?? ctx?.tools
|
|
544
|
+
let disposeAdvanced, disposeAlpha
|
|
545
|
+
const capabilityStore = new AlphaCapabilityStore(baseConfig.alphaCapabilityPath)
|
|
546
|
+
const refStore = new AlphaRefStore(baseConfig.alphaRefPath)
|
|
547
|
+
const compactionPatchRecords = new Map()
|
|
548
|
+
const patchedWebSearchDefinitions = new Map()
|
|
549
|
+
|
|
550
|
+
const refreshTools = () => {
|
|
551
|
+
if (state.enabled && state.webSearch && state.advancedHostedSearch && !disposeAdvanced && tools?.register) disposeAdvanced = tools.register(createAdvancedHostedTool(ctx, state, () => runtimeConfig))
|
|
552
|
+
if ((!state.enabled || !state.webSearch || !state.advancedHostedSearch) && disposeAdvanced) { disposeAdvanced(); disposeAdvanced = undefined }
|
|
553
|
+
let alphaUsable = false
|
|
554
|
+
if (state.enabled && state.alphaSearch) {
|
|
555
|
+
try { const { record } = alphaCapabilityFor(runtimeConfig, capabilityStore); alphaUsable = alphaCapabilityUsable(record) && record?.schemaFingerprint === ALPHA_SCHEMA_FINGERPRINT } catch (error) { ctx.logger?.warn?.(`[lcx-codex] Alpha capability store unavailable: ${error?.message ?? error}`) }
|
|
556
|
+
}
|
|
557
|
+
if (alphaUsable && !disposeAlpha && tools?.register) disposeAlpha = tools.register(createAlphaTool(ctx, state, () => runtimeConfig, capabilityStore, refStore))
|
|
558
|
+
if (!alphaUsable && disposeAlpha) { disposeAlpha(); disposeAlpha = undefined }
|
|
559
|
+
if (ctx.web) {
|
|
560
|
+
const target = state.enabled && state.webSearch ? runtimeConfig.webSearchProvider : originalSearchProvider
|
|
561
|
+
const selected = writeWebSearchProvider(ctx, target)
|
|
562
|
+
if (!selected && state.enabled && state.webSearch && !warnedWebSelection) {
|
|
563
|
+
warnedWebSelection = true
|
|
564
|
+
ctx.logger?.warn?.('[lcx-codex] DSH web provider selection could not be changed at runtime; websearch_gpt_advanced still works, but DSH web_search may remain on its configured provider. Pin web.searchProvider=lcx-responses in a profile overlay if this DSH version removes the runtime compatibility field.')
|
|
565
|
+
}
|
|
1611
566
|
}
|
|
1612
567
|
}
|
|
1613
568
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
if (!hostedEnabled && disposeHostedPrompt) {
|
|
1647
|
-
disposeHostedPrompt()
|
|
1648
|
-
disposeHostedPrompt = undefined
|
|
1649
|
-
}
|
|
1650
|
-
if (alphaEnabled && !disposeAlphaPrompt && systemPrompt?.section) {
|
|
1651
|
-
disposeAlphaPrompt = systemPrompt.section({
|
|
1652
|
-
name: 'tool:websearch_alpha',
|
|
1653
|
-
order: 112,
|
|
1654
|
-
text: 'websearch_alpha 是独立 Alpha command API。一次只调用一个 action;只能对当前 session 已返回的 ref_id 执行 open/find/click/screenshot,最终回答引用直接 URL,不暴露内部 ref_id。',
|
|
1655
|
-
}) ?? (() => {})
|
|
1656
|
-
}
|
|
1657
|
-
if (!alphaEnabled && disposeAlphaPrompt) {
|
|
1658
|
-
disposeAlphaPrompt()
|
|
1659
|
-
disposeAlphaPrompt = undefined
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
569
|
+
let source = () => ({ ...baseConfig, ...state })
|
|
570
|
+
installSettingsSection(ctx, SETTINGS_NS, SettingsSchema, {
|
|
571
|
+
enabled: false, webSearch: false, advancedHostedSearch: false, alphaSearch: false, remoteCompaction: false, fallbackToBasicCompaction: true, autoCompaction: true,
|
|
572
|
+
webSearchTimeoutSeconds: baseConfig.webSearchTimeoutMs / 1000,
|
|
573
|
+
autoCompactionThresholdPercent: baseConfig.autoCompactionThresholdPercent,
|
|
574
|
+
emergencyPruneThresholdPercent: baseConfig.emergencyPruneThresholdPercent,
|
|
575
|
+
provider: baseConfig.provider, baseURL: baseConfig.baseURL, apiKeyEnv: baseConfig.apiKeyEnv, model: baseConfig.model,
|
|
576
|
+
}, {
|
|
577
|
+
setSource: (current) => { source = current },
|
|
578
|
+
onChange: () => {
|
|
579
|
+
const value = source()
|
|
580
|
+
state.enabled = Boolean(value.enabled); state.webSearch = Boolean(value.webSearch); state.advancedHostedSearch = Boolean(value.advancedHostedSearch); state.alphaSearch = Boolean(value.alphaSearch); state.remoteCompaction = Boolean(value.remoteCompaction)
|
|
581
|
+
state.fallbackToBasicCompaction = value.fallbackToBasicCompaction !== false; state.autoCompaction = value.autoCompaction !== false
|
|
582
|
+
state.webSearchTimeoutSeconds = Number.isFinite(value.webSearchTimeoutSeconds) ? value.webSearchTimeoutSeconds : baseConfig.webSearchTimeoutMs / 1000
|
|
583
|
+
state.autoCompactionThresholdPercent = clampPercent(value.autoCompactionThresholdPercent, baseConfig.autoCompactionThresholdPercent, 85, 95)
|
|
584
|
+
state.emergencyPruneThresholdPercent = clampPercent(value.emergencyPruneThresholdPercent, baseConfig.emergencyPruneThresholdPercent, 90, 99)
|
|
585
|
+
runtimeConfig = normalizeConfig({ ...baseConfig, provider: value.provider ?? baseConfig.provider, baseURL: value.baseURL ?? baseConfig.baseURL, apiKeyEnv: value.apiKeyEnv ?? baseConfig.apiKeyEnv, model: value.model ?? baseConfig.model })
|
|
586
|
+
refreshTools(); refreshPatchedWebSearchTimeouts(state, () => runtimeConfig, patchedWebSearchDefinitions)
|
|
587
|
+
},
|
|
588
|
+
})
|
|
589
|
+
try {
|
|
590
|
+
const value = source()
|
|
591
|
+
Object.assign(state, { enabled: Boolean(value.enabled), webSearch: Boolean(value.webSearch), advancedHostedSearch: Boolean(value.advancedHostedSearch), alphaSearch: Boolean(value.alphaSearch), remoteCompaction: Boolean(value.remoteCompaction), fallbackToBasicCompaction: value.fallbackToBasicCompaction !== false, autoCompaction: value.autoCompaction !== false, webSearchTimeoutSeconds: Number.isFinite(value.webSearchTimeoutSeconds) ? value.webSearchTimeoutSeconds : baseConfig.webSearchTimeoutMs / 1000, autoCompactionThresholdPercent: clampPercent(value.autoCompactionThresholdPercent, baseConfig.autoCompactionThresholdPercent, 85, 95), emergencyPruneThresholdPercent: clampPercent(value.emergencyPruneThresholdPercent, baseConfig.emergencyPruneThresholdPercent, 90, 99) })
|
|
592
|
+
runtimeConfig = normalizeConfig({ ...baseConfig, provider: value.provider ?? baseConfig.provider, baseURL: value.baseURL ?? baseConfig.baseURL, apiKeyEnv: value.apiKeyEnv ?? baseConfig.apiKeyEnv, model: value.model ?? baseConfig.model })
|
|
593
|
+
} catch {}
|
|
594
|
+
refreshTools()
|
|
595
|
+
|
|
596
|
+
ctx.on('tools/execute', async (exec, next) => {
|
|
597
|
+
if (!state.enabled || !state.webSearch || exec?.name !== 'web_search') return next()
|
|
598
|
+
const active = activeAgentRoute(exec, runtimeConfig)
|
|
599
|
+
return hostedSearchRouteContext.run(active, () => next())
|
|
600
|
+
})
|
|
1662
601
|
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
syncToolRegistration()
|
|
1669
|
-
}
|
|
1670
|
-
const settingsBase = {
|
|
1671
|
-
enabled: false,
|
|
1672
|
-
webSearch: false,
|
|
1673
|
-
alphaSearch: false,
|
|
1674
|
-
remoteCompaction: false,
|
|
1675
|
-
fallbackToBasicCompaction: true,
|
|
1676
|
-
provider: config.provider,
|
|
1677
|
-
baseURL: config.baseURL,
|
|
1678
|
-
apiKeyEnv: config.apiKeyEnv,
|
|
1679
|
-
model: config.model,
|
|
1680
|
-
compactTransport: config.compactTransport,
|
|
1681
|
-
}
|
|
1682
|
-
const attachSettings = (settingsContext) => {
|
|
1683
|
-
const settings = settingsContext.settings ?? settingsContext
|
|
1684
|
-
if (!settings?.register) return
|
|
1685
|
-
const scope = settings.register(settingsNamespace(SETTINGS_NAMESPACE), SettingsSchema, { base: settingsBase })
|
|
1686
|
-
sync(scope.get())
|
|
1687
|
-
scope.watch(() => sync(scope.get()))
|
|
1688
|
-
}
|
|
1689
|
-
if (typeof ctx.inject === 'function') ctx.inject(['settings'], attachSettings)
|
|
1690
|
-
else attachSettings(ctx.get?.('settings') ?? {})
|
|
602
|
+
ctx.on('agent/status', ({ agent, status }) => {
|
|
603
|
+
if (status !== 'running' || !agent) return
|
|
604
|
+
patchCompactionPressureForAgent(agent, state, () => runtimeConfig, ctx, compactionPatchRecords)
|
|
605
|
+
patchVisibleWebSearchTimeout(agent, state, () => runtimeConfig, patchedWebSearchDefinitions)
|
|
606
|
+
})
|
|
1691
607
|
|
|
1692
|
-
ctx.effect?.(() => () => {
|
|
1693
|
-
disposeHostedTool?.()
|
|
1694
|
-
disposeAlphaTool?.()
|
|
1695
|
-
disposeHostedPrompt?.()
|
|
1696
|
-
disposeAlphaPrompt?.()
|
|
1697
|
-
sessionTracker.dispose()
|
|
1698
|
-
ctx.web.searchProviderId = originalSearchProvider
|
|
1699
|
-
}, 'lcx-codex: restore web provider selection')
|
|
1700
608
|
ctx.on('llm/stream', (options, next) => {
|
|
1701
|
-
if (
|
|
1702
|
-
if (!state.enabled || !state.remoteCompaction) return next()
|
|
1703
|
-
if (options.purpose === 'session-title') return next()
|
|
609
|
+
if (bypassReplayOptions.delete(options)) return next()
|
|
610
|
+
if (!state.enabled || !state.remoteCompaction || options.purpose === 'session-title') return next()
|
|
1704
611
|
const routeConfig = resolveResponsesRouteConfig(ctx, options, runtimeConfig)
|
|
1705
|
-
let portableCheckpoint
|
|
1706
|
-
try {
|
|
1707
|
-
portableCheckpoint = hasPortableCheckpoint(options.messages)
|
|
1708
|
-
} catch (error) {
|
|
1709
|
-
return guardedStream(() => { throw error }, options.signal)
|
|
1710
|
-
}
|
|
1711
|
-
if (portableCheckpoint) {
|
|
1712
|
-
if (!routeConfig) return guardedStream(() => { throw nativeRouteError(options) }, options.signal)
|
|
1713
|
-
if (options.purpose === 'compaction') {
|
|
1714
|
-
const factory = () => remoteCompactionStream(options, routeConfig, portableStore, ctx, sessionTracker)
|
|
1715
|
-
return guardedStream(factory, options.signal)
|
|
1716
|
-
}
|
|
1717
|
-
let replay
|
|
1718
|
-
try {
|
|
1719
|
-
replay = checkpointReplayRecord(options.messages, portableStore)
|
|
1720
|
-
} catch (error) {
|
|
1721
|
-
return guardedStream(() => { throw error }, options.signal)
|
|
1722
|
-
}
|
|
1723
|
-
const route = routeWithSessionAncestry(ctx, currentRoute(options, routeConfig))
|
|
1724
|
-
if (options.sessionId && replay?.record.routeFingerprint === routeFingerprint(route)) {
|
|
1725
|
-
return guardedStream(
|
|
1726
|
-
() => nativeCheckpointReplayStream(options, routeConfig, portableStore, ctx, replay.record, replay.marker),
|
|
1727
|
-
options.signal,
|
|
1728
|
-
)
|
|
1729
|
-
}
|
|
1730
|
-
const llm = ctx?.llm ?? ctx?.get?.('llm')
|
|
1731
|
-
if (!llm || typeof llm.stream !== 'function') {
|
|
1732
|
-
return guardedStream(() => { throw checkpointReplayUnavailableError() }, options.signal)
|
|
1733
|
-
}
|
|
1734
|
-
return guardedStream(async function* () {
|
|
1735
|
-
const markerTail = replay?.marker ? options.messages.slice(replay.marker.index + 1) : []
|
|
1736
|
-
const needsImageCapability = Number(replay?.record.portableImageCount ?? 0) > 0 || dshMessagesContainImage(markerTail)
|
|
1737
|
-
const imageSupport = needsImageCapability
|
|
1738
|
-
? await resolveModelImageSupport(llm, route, options.signal)
|
|
1739
|
-
: 'unknown'
|
|
1740
|
-
const rewrittenOptions = {
|
|
1741
|
-
...options,
|
|
1742
|
-
messages: buildPortableReplayMessages(options.messages, portableStore, route, { imageSupport }),
|
|
1743
|
-
}
|
|
1744
|
-
checkpointReplayOptions.add(rewrittenOptions)
|
|
1745
|
-
const stream = await llm.stream(rewrittenOptions)
|
|
1746
|
-
for await (const chunk of stream) yield chunk
|
|
1747
|
-
}, options.signal)
|
|
1748
|
-
}
|
|
1749
612
|
if (options.purpose === 'compaction') {
|
|
1750
613
|
if (!routeConfig) return next()
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
614
|
+
return remoteCompactionStream(options, routeConfig, state, ctx, next)
|
|
615
|
+
}
|
|
616
|
+
const session = sessionFor(ctx, String(options.sessionId ?? ''))
|
|
617
|
+
const hasNative = messagesContainNativeCheckpoint(options.messages, session)
|
|
618
|
+
const hasLegacy = messagesContainLegacyCheckpoint(options.messages)
|
|
619
|
+
if (!hasNative && !hasLegacy) return next()
|
|
620
|
+
if (!routeConfig) {
|
|
621
|
+
if (hasNative && session) return recursiveLlmStream(ctx, options, rewriteCheckpointsPortable(options.messages, session, { maxChars: runtimeConfig.portableReplayMaxChars }))
|
|
622
|
+
return next()
|
|
1759
623
|
}
|
|
1760
|
-
return
|
|
1761
|
-
}
|
|
624
|
+
return replayCheckpointStream(options, routeConfig, ctx)
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
ctx.effect?.(() => () => {
|
|
628
|
+
disposeAdvanced?.(); disposeAlpha?.()
|
|
629
|
+
restoreCompactionPressure(compactionPatchRecords)
|
|
630
|
+
restoreWebSearchTimeouts(patchedWebSearchDefinitions)
|
|
631
|
+
writeWebSearchProvider(ctx, originalSearchProvider)
|
|
632
|
+
}, 'lcx-codex cleanup')
|
|
1762
633
|
}
|
|
1763
634
|
|
|
1764
|
-
export {
|
|
1765
|
-
|
|
1766
|
-
SettingsSchema,
|
|
1767
|
-
CheckpointV3Store,
|
|
1768
|
-
LcxResponsesSearchProvider,
|
|
1769
|
-
apply,
|
|
1770
|
-
baseURLFingerprint,
|
|
1771
|
-
buildPortableHistory,
|
|
1772
|
-
buildPortableReplayMessages,
|
|
1773
|
-
buildPortableResponsesInput,
|
|
1774
|
-
hasPortableCheckpoint,
|
|
1775
|
-
inject,
|
|
1776
|
-
name,
|
|
1777
|
-
normalizeConfig,
|
|
1778
|
-
normalizeCompactionResponse,
|
|
1779
|
-
normalizeHostedSearchArgs,
|
|
1780
|
-
normalizeAlphaSearchArgs,
|
|
1781
|
-
portableResponsesToMessages,
|
|
1782
|
-
routeFingerprint,
|
|
635
|
+
export function apply(ctx, configInput = {}) {
|
|
636
|
+
return ctx.inject(['llm', 'web'], injectedCtx => installInjected(injectedCtx, configInput))
|
|
1783
637
|
}
|
|
638
|
+
|
|
639
|
+
apply.inject = inject
|
|
640
|
+
apply.Config = Config
|
|
641
|
+
|
|
642
|
+
export default apply
|