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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +75 -224
  2. package/THIRD_PARTY_NOTICES.md +64 -0
  3. package/cordis.patch.yml +3 -20
  4. package/lib/auxiliary-usage.js +63 -0
  5. package/lib/client.js +1398 -167
  6. package/lib/compact-v2.js +218 -199
  7. package/lib/dsh-compat.js +294 -100
  8. package/lib/dsh-responses.js +512 -277
  9. package/lib/grok-native-search.js +391 -0
  10. package/lib/index.js +1066 -758
  11. package/lib/invocation-policy-scope.js +261 -0
  12. package/lib/json-store.js +57 -31
  13. package/lib/native-checkpoint.js +520 -194
  14. package/lib/pi-responses-runtime.js +1571 -0
  15. package/lib/responses-request.js +109 -121
  16. package/lib/responses-stream.js +1280 -447
  17. package/lib/route.js +425 -369
  18. package/lib/search-accounting.js +86 -0
  19. package/lib/search-usage.js +86 -0
  20. package/lib/service-mutex.js +73 -64
  21. package/lib/token-budget.js +176 -108
  22. package/lib/transport.js +308 -68
  23. package/lib/types/client/index.d.ts +18 -0
  24. package/lib/types/client/search-media.d.ts +16 -0
  25. package/lib/types/index.d.ts +83 -0
  26. package/lib/web-run-output.js +189 -18
  27. package/lib/web-search-alpha.js +1067 -163
  28. package/lib/web-search-capability.js +80 -65
  29. package/lib/web-search-hosted.js +321 -33
  30. package/lib/web-search-ref-store.js +145 -60
  31. package/package.json +112 -32
  32. package/ARCHITECTURE.md +0 -117
  33. package/CHANGELOG.md +0 -224
  34. package/README_EN.md +0 -277
  35. package/assets/dsh-lcx-codex-banner.jpg +0 -0
  36. package/lib/legacy-v3.js +0 -20
  37. package/lib/responses-replay.js +0 -68
  38. package/scripts/probe-alpha.mjs +0 -43
  39. package/scripts/validate-dsh-schema.mjs +0 -31
package/lib/index.js CHANGED
@@ -1,798 +1,1106 @@
1
- import z from '@deepseek-ai/schemastery'
2
- import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
3
- import { randomUUID } from 'node:crypto'
4
- import { homedir } from 'node:os'
5
- import { join } from 'node:path'
6
- import { AsyncLocalStorage } from 'node:async_hooks'
7
- import { fetchJsonWithRetry } from './transport.js'
8
- import {
9
- agentSessionId,
10
- compactionConfigState,
11
- compactionPatchCandidate,
12
- contextService,
13
- installCompactionPatch,
14
- patchCompactionConfig,
15
- patchToolResultPruner,
16
- patchVisibleWebSearchTimeout,
17
- readAgentRouteState,
18
- readWebSearchProvider,
19
- refreshVisibleWebSearchTimeouts,
20
- resolveAgentService,
21
- resolveContextService,
22
- resolveScopedService,
23
- restoreCompactionConfig,
24
- restoreCompactionPatches,
25
- restoreToolResultPruner,
26
- restoreVisibleWebSearchTimeouts,
27
- sessionFor,
28
- sessionsService,
29
- toolResultPrunerState,
30
- writeWebSearchProvider,
31
- } from './dsh-compat.js'
32
- import {
33
- authenticatedHeaders,
34
- currentRoute,
35
- generationControlsFromHeader,
36
- generationControlsFromSession,
37
- promptCacheKey,
38
- promptCacheRetention,
39
- promptCacheSessionId,
40
- resolveResponsesRouteConfig,
41
- routeFingerprint,
42
- updateRequestHeaderCache,
43
- } from './route.js'
44
- import {
45
- DEFAULT_MAX_REQUEST_IMAGE_BYTES,
46
- DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET,
47
- DEFAULT_REQUEST_IMAGE_MAX_BYTES,
48
- hydrateNativeImageReferences,
49
- resolveModelImageSupport,
50
- serializeDshMessages,
51
- } from './dsh-responses.js'
52
- import { mergeFeatureHeader, requestNativeCompaction } from './compact-v2.js'
53
- import { buildResponsesBody } from './responses-request.js'
54
- import { managedFailureChunk, streamResponsesRequest } from './responses-stream.js'
55
- import {
56
- checkpointStateForMessage,
57
- compactCheckpointId,
58
- createNativeCheckpointBlock,
59
- legacyCheckpointId,
60
- nativeCheckpointChunks,
61
- portableMessagesForCheckpoint,
62
- retainedConversationInput,
63
- shadowedMessagesForCheckpoint,
64
- rewriteCheckpointsPortable,
65
- stateRouteCompatible,
66
- } from './native-checkpoint.js'
67
- import { legacyRouteCompatible, legacyV3Id, loadLegacyRecord } from './legacy-v3.js'
68
- import {
69
- HOSTED_SEARCH_OUTPUT,
70
- HOSTED_SEARCH_PARAMETERS,
71
- buildHostedSearchBody,
72
- normalizeHostedSearchArgs,
73
- parseHostedSearchResponse,
74
- renderHostedSearchResult,
75
- } from './web-search-hosted.js'
76
- import {
77
- ALPHA_SCHEMA_FINGERPRINT,
78
- alphaRefRequiresStore,
79
- isAlphaContinuationUrl,
80
- isAlphaHttpUrl,
81
- ALPHA_SEARCH_OUTPUT,
82
- ALPHA_SEARCH_PARAMETERS,
83
- buildAlphaSearchBody,
84
- normalizeAlphaSearchArgs,
85
- parseAlphaSearchResponse,
86
- renderAlphaSearchResult,
87
- } from './web-search-alpha.js'
88
- import { AlphaCapabilityStore, alphaCapabilityFingerprint, alphaCapabilityUsable } from './web-search-capability.js'
89
- import { AlphaRefStore } from './web-search-ref-store.js'
90
- import { ServiceMutex } from './service-mutex.js'
91
-
92
- export const name = 'lcx-codex'
93
- export const inject = ['llm', 'web', 'sessions']
94
- const SETTINGS_NS = settingsNamespace('lcx-codex')
95
- const ADVANCED_HOSTED_TOOL = 'websearch_gpt_advanced'
96
- const ALPHA_TOOL = 'websearch_alpha'
97
- const COMPACTION_DIRECTIVE = 'You are now acting as a compaction engine'
98
- const hostedSearchRouteContext = new AsyncLocalStorage()
99
-
100
- function dshHome() { return process.env.DSH_HOME ?? join(homedir(), '.dsh') }
101
- function defaultLegacyCheckpointPath() { return join(dshHome(), 'storages', 'lcx-codex', 'checkpoints-v3.json') }
102
- function defaultAlphaCapabilityPath() { return join(dshHome(), 'storages', 'lcx-codex', 'web-alpha-capabilities.json') }
103
- function defaultAlphaRefPath() { return join(dshHome(), 'storages', 'lcx-codex', 'web-alpha-refs.json') }
104
-
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { recordHostedUsage, withAuxiliaryUsage } from "./auxiliary-usage.js";
3
+ import { installSearchUsage } from "./search-usage.js";
4
+ import { WEB_SEARCH_MAX_QUERIES, applyWebSearchTool, } from "@deepseek-ai/dsh-tool-web";
5
+ import "@deepseek-ai/dsh-agent";
6
+ import "@deepseek-ai/dsh-tools";
7
+ import "@deepseek-ai/dsh-web";
8
+ import { randomUUID } from "node:crypto";
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { AsyncLocalStorage } from "node:async_hooks";
12
+ import { getSupportedThinkingLevels } from "./pi-responses-runtime.js";
13
+ import { fetchJsonWithRetry } from "./transport.js";
14
+ import { agentSessionId, agentUsesSession, compactionConfigState, compactionPatchCandidate, installCompactionPatch, patchCompactionConfig, patchToolResultPruner, readAgentRouteState, resolveAgentService, resolveContextService, resolveScopedService, restoreCompactionConfig, restoreCompactionPatches, restoreToolResultPruner, sessionFor, sessionFromAgent, scopedToolRuntime, tokenMeterTotal, toolResultPrunerState, } from "./dsh-compat.js";
15
+ import { InvocationPolicyScope } from "./invocation-policy-scope.js";
16
+ import { authenticatedGrokHeaders, authenticatedHeaders, currentRoute, generationControlsFromSession, grokPromptCacheSessionId, promptCacheKey, promptCacheRetention, promptCacheSessionId, resolveGrokResponsesRouteConfig, resolveResponsesRouteConfig, routeFingerprint, } from "./route.js";
17
+ import { DEFAULT_MAX_REQUEST_IMAGE_BYTES, DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET, DEFAULT_REQUEST_IMAGE_MAX_BYTES, hydrateNativeImageReferences, resolveModelImageSupport, responseInputItems, serializeDshMessages, } from "./dsh-responses.js";
18
+ import { mergeFeatureHeader, requestNativeCompaction } from "./compact-v2.js";
19
+ import { buildResponsesBody } from "./responses-request.js";
20
+ import { managedFailureChunk, streamResponsesRequest, } from "./responses-stream.js";
21
+ import { assertSupportedCheckpointMessage, checkpointStateForMessage, compactCheckpointId, createNativeCheckpointBlock, nativeCheckpointChunks, portableMessagesForCheckpoint, stateRouteCompatible, } from "./native-checkpoint.js";
22
+ import { HOSTED_SEARCH_OUTPUT, HOSTED_SEARCH_PARAMETERS, buildHostedSearchBody, hostedMediaPresentationMeta, normalizeHostedSearchArgs, parseHostedSearchResponse, renderHostedSearchResult, } from "./web-search-hosted.js";
23
+ import { ALPHA_SCHEMA_FINGERPRINT, alphaRefRequiresStore, isAlphaContinuationUrl, isAlphaHttpUrl, ALPHA_SEARCH_OUTPUT, ALPHA_SEARCH_PARAMETERS, alphaSearchRetryOptions, buildAlphaSearchBody, normalizeAlphaSearchArgs, parseAlphaSearchResponse, renderAlphaSearchResult, runWithAlphaSessionLock, } from "./web-search-alpha.js";
24
+ import { AlphaCapabilityStore, alphaCapabilityFingerprint, alphaCapabilityUsable, alphaSearchParametersFor, assertAlphaActionAllowed, } from "./web-search-capability.js";
25
+ import { AlphaRefStore } from "./web-search-ref-store.js";
26
+ import { ServiceMutex } from "./service-mutex.js";
27
+ import { GROK_NATIVE_SERVER_TOOL_TYPES, grokNativeSearchEnabled, grokVisibleFunctionTools, isGrokNativeServerToolItem, grokWireTools, restoreGrokNativeReplay, } from "./grok-native-search.js";
28
+ export const name = "lcx-codex";
29
+ export const inject = ["llm", "web", "sessions", "tools", "settings", "credentials", "attachments", "fs"];
30
+ const SETTINGS_NS = "lcx-codex";
31
+ const ADVANCED_HOSTED_TOOL = "websearch_gpt_advanced";
32
+ const ALPHA_TOOL = "websearch_alpha";
33
+ const hostedSearchRouteContext = new AsyncLocalStorage();
34
+ const WEB_SEARCH_TIMEOUT_MS = 240_000;
35
+ const AUTO_COMPACTION_THRESHOLD_PERCENT = 90;
36
+ const EMERGENCY_PRUNE_THRESHOLD_PERCENT = 95;
37
+ function positiveInteger(value, fallback, maximum) {
38
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0)
39
+ return fallback;
40
+ return maximum === undefined ? value : Math.min(value, maximum);
41
+ }
42
+ function stringValue(value, fallback) {
43
+ return typeof value === "string" && value.trim() ? value : fallback;
44
+ }
45
+ function routeWithPolicies(route, config) {
46
+ return {
47
+ ...route,
48
+ alphaProfile: config.alphaProfile,
49
+ alphaGroup: config.alphaGroup,
50
+ alphaMaxOutputTokens: config.alphaMaxOutputTokens,
51
+ assistantRetentionPerMessageTokenCap: config.assistantRetentionPerMessageTokenCap,
52
+ assistantRetentionTokenReserve: config.assistantRetentionTokenReserve,
53
+ maxRequestImageBytes: route.maxRequestImageBytes ?? config.maxRequestImageBytes,
54
+ maxResponseBytes: config.maxResponseBytes,
55
+ nativeRetentionTokenBudget: config.nativeRetentionTokenBudget,
56
+ portableReplayMaxChars: config.portableReplayMaxChars,
57
+ requestImageMaxBytes: route.requestImageMaxBytes ?? config.requestImageMaxBytes,
58
+ requestImagePixelBudget: route.requestImagePixelBudget ?? config.requestImagePixelBudget,
59
+ webMaxResults: config.webMaxResults,
60
+ };
61
+ }
62
+ function grokReplayRoute(route, config) {
63
+ return {
64
+ ...route,
65
+ apiKeyEnv: config.apiKeyEnv,
66
+ headers: config.headers,
67
+ };
68
+ }
69
+ function isRecord(value) {
70
+ return value !== null && typeof value === "object";
71
+ }
72
+ function asAbortSignal(value) {
73
+ return value instanceof AbortSignal ? value : undefined;
74
+ }
75
+ function errorDetails(value) {
76
+ return isRecord(value)
77
+ ? {
78
+ name: value.name,
79
+ code: value.code,
80
+ status: value.status,
81
+ message: value.message,
82
+ }
83
+ : {};
84
+ }
85
+ function dshHome() {
86
+ return process.env.DSH_HOME ?? join(homedir(), ".dsh");
87
+ }
88
+ function defaultAlphaCapabilityPath() {
89
+ return join(dshHome(), "storages", "lcx-codex", "web-alpha-capabilities.json");
90
+ }
91
+ function defaultAlphaRefPath() {
92
+ return join(dshHome(), "storages", "lcx-codex", "web-alpha-refs.json");
93
+ }
105
94
  export const Config = z.object({
106
- provider: z.string().default('lcx'),
107
- baseURL: z.string().default('https://api.lcxbot.com/v1'),
108
- apiKeyEnv: z.string().default('LCX_API_KEY'),
109
- model: z.string().default('gpt-5.6-sol'),
110
- supportsExplicitPromptCacheMode: z.boolean().default(false),
111
- legacyCheckpointPath: z.string().default(''),
112
- checkpointPath: z.string().default(''),
113
- alphaCapabilityPath: z.string().default(''),
114
- alphaRefPath: z.string().default(''),
115
- alphaProfile: z.string().default(''),
116
- alphaGroup: z.string().default(''),
117
- alphaMaxOutputTokens: z.number().default(2500),
118
- webSearchProvider: z.string().default('lcx-responses'),
119
- webMaxResults: z.number().default(8),
120
- timeoutMs: z.number().default(300000),
121
- maxResponseBytes: z.number().default(8 * 1024 * 1024),
122
- maxAttempts: z.number().default(3),
123
- maxRequestImageBytes: z.number().default(DEFAULT_MAX_REQUEST_IMAGE_BYTES),
124
- requestImagePixelBudget: z.number().default(DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET),
125
- requestImageMaxBytes: z.number().default(DEFAULT_REQUEST_IMAGE_MAX_BYTES),
126
- portableReplayMaxChars: z.number().default(80_000),
127
- nativeRetentionTokenBudget: z.number().default(64_000),
128
- assistantRetentionTokenReserve: z.number().default(24_000),
129
- assistantRetentionPerMessageTokenCap: z.number().default(3_000),
130
- webSearchTimeoutMs: z.number().default(240_000),
131
- autoCompactionThresholdPercent: z.number().default(90),
132
- emergencyPruneThresholdPercent: z.number().default(95),
133
- })
134
-
95
+ supportsLongCacheRetention: z.boolean().default(false),
96
+ supportsExplicitPromptCacheMode: z.boolean().default(false),
97
+ alphaCapabilityPath: z.string().default(""),
98
+ alphaRefPath: z.string().default(""),
99
+ alphaProfile: z.string().default(""),
100
+ alphaGroup: z.string().default(""),
101
+ alphaMaxOutputTokens: z.number().default(2500),
102
+ webSearchProvider: z.string().default("lcx-responses"),
103
+ webMaxResults: z.number().default(8),
104
+ timeoutMs: z.number().default(300000),
105
+ maxResponseBytes: z.number().default(8 * 1024 * 1024),
106
+ maxAttempts: z.number().default(3),
107
+ maxRequestImageBytes: z.number().default(DEFAULT_MAX_REQUEST_IMAGE_BYTES),
108
+ requestImagePixelBudget: z
109
+ .number()
110
+ .default(DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET),
111
+ requestImageMaxBytes: z.number().default(DEFAULT_REQUEST_IMAGE_MAX_BYTES),
112
+ portableReplayMaxChars: z.number().default(80_000),
113
+ nativeRetentionTokenBudget: z.number().default(64_000),
114
+ assistantRetentionTokenReserve: z.number().default(24_000),
115
+ assistantRetentionPerMessageTokenCap: z.number().default(3_000),
116
+ });
135
117
  const SettingsSchema = z.object({
136
- enabled: z.boolean().default(false),
137
- webSearch: z.boolean().default(false),
138
- advancedHostedSearch: z.boolean().default(false),
139
- alphaSearch: z.boolean().default(false),
140
- remoteCompaction: z.boolean().default(false),
141
- fallbackToBasicCompaction: z.boolean().default(true),
142
- autoCompaction: z.boolean().default(true),
143
- webSearchTimeoutSeconds: z.number().default(240),
144
- autoCompactionThresholdPercent: z.number().default(90),
145
- emergencyPruneThresholdPercent: z.number().default(95),
146
- provider: z.string().default('lcx'),
147
- baseURL: z.string().default('https://api.lcxbot.com/v1'),
148
- apiKeyEnv: z.string().default('LCX_API_KEY'),
149
- model: z.string().default('gpt-5.6-sol'),
150
- })
151
-
118
+ enabled: z.boolean().default(false),
119
+ webSearch: z.boolean().default(false),
120
+ advancedHostedSearch: z.boolean().default(false),
121
+ alphaSearch: z.boolean().default(false),
122
+ grokNativeWebSearch: z.boolean().default(false),
123
+ grokNativeXSearch: z.boolean().default(false),
124
+ searchMediaPreview: z.boolean().default(false),
125
+ });
152
126
  function normalizeConfig(input = {}) {
153
- const legacy = input.legacyCheckpointPath || input.checkpointPath || defaultLegacyCheckpointPath()
154
- return {
155
- provider: input.provider || 'lcx',
156
- baseURL: String(input.baseURL || 'https://api.lcxbot.com/v1').replace(/\/+$/u, ''),
157
- apiKeyEnv: input.apiKeyEnv || 'LCX_API_KEY',
158
- model: input.model || 'gpt-5.6-sol',
159
- supportsExplicitPromptCacheMode: input.supportsExplicitPromptCacheMode === true,
160
- headers: input.headers && typeof input.headers === 'object' ? { ...input.headers } : {},
161
- legacyCheckpointPath: legacy,
162
- alphaCapabilityPath: input.alphaCapabilityPath || defaultAlphaCapabilityPath(),
163
- alphaRefPath: input.alphaRefPath || defaultAlphaRefPath(),
164
- alphaProfile: String(input.alphaProfile ?? ''),
165
- alphaGroup: String(input.alphaGroup ?? ''),
166
- alphaMaxOutputTokens: Number.isInteger(input.alphaMaxOutputTokens) && input.alphaMaxOutputTokens > 0 ? Math.min(input.alphaMaxOutputTokens, 32_000) : 2500,
167
- webSearchProvider: input.webSearchProvider || 'lcx-responses',
168
- webMaxResults: Number.isInteger(input.webMaxResults) && input.webMaxResults > 0 ? input.webMaxResults : 8,
169
- timeoutMs: Number.isInteger(input.timeoutMs) && input.timeoutMs > 0 ? input.timeoutMs : 300000,
170
- maxResponseBytes: Number.isInteger(input.maxResponseBytes) && input.maxResponseBytes > 0 ? input.maxResponseBytes : 8 * 1024 * 1024,
171
- maxAttempts: Number.isInteger(input.maxAttempts) && input.maxAttempts > 0 ? Math.min(input.maxAttempts, 6) : 3,
172
- maxRequestImageBytes: Number.isSafeInteger(input.maxRequestImageBytes) && input.maxRequestImageBytes > 0 ? input.maxRequestImageBytes : DEFAULT_MAX_REQUEST_IMAGE_BYTES,
173
- requestImagePixelBudget: Number.isSafeInteger(input.requestImagePixelBudget) && input.requestImagePixelBudget > 0 ? input.requestImagePixelBudget : DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET,
174
- requestImageMaxBytes: Number.isSafeInteger(input.requestImageMaxBytes) && input.requestImageMaxBytes > 0 ? input.requestImageMaxBytes : DEFAULT_REQUEST_IMAGE_MAX_BYTES,
175
- portableReplayMaxChars: Number.isSafeInteger(input.portableReplayMaxChars) && input.portableReplayMaxChars > 0 ? input.portableReplayMaxChars : 80_000,
176
- nativeRetentionTokenBudget: Number.isSafeInteger(input.nativeRetentionTokenBudget) && input.nativeRetentionTokenBudget > 0 ? input.nativeRetentionTokenBudget : 64_000,
177
- assistantRetentionTokenReserve: Number.isSafeInteger(input.assistantRetentionTokenReserve) && input.assistantRetentionTokenReserve >= 0 ? input.assistantRetentionTokenReserve : 24_000,
178
- assistantRetentionPerMessageTokenCap: Number.isSafeInteger(input.assistantRetentionPerMessageTokenCap) && input.assistantRetentionPerMessageTokenCap > 0 ? input.assistantRetentionPerMessageTokenCap : 3_000,
179
- webSearchTimeoutMs: Number.isSafeInteger(input.webSearchTimeoutMs) && input.webSearchTimeoutMs >= 30_000 ? Math.min(input.webSearchTimeoutMs, 600_000) : 240_000,
180
- autoCompactionThresholdPercent: Number.isFinite(input.autoCompactionThresholdPercent) ? Math.min(95, Math.max(85, Number(input.autoCompactionThresholdPercent))) : 90,
181
- emergencyPruneThresholdPercent: Number.isFinite(input.emergencyPruneThresholdPercent) ? Math.min(99, Math.max(90, Number(input.emergencyPruneThresholdPercent))) : 95,
182
- }
183
- }
184
-
127
+ return {
128
+ supportsLongCacheRetention: input.supportsLongCacheRetention === true,
129
+ supportsExplicitPromptCacheMode: input.supportsExplicitPromptCacheMode === true,
130
+ alphaCapabilityPath: stringValue(input.alphaCapabilityPath, defaultAlphaCapabilityPath()),
131
+ alphaRefPath: stringValue(input.alphaRefPath, defaultAlphaRefPath()),
132
+ alphaProfile: String(input.alphaProfile ?? ""),
133
+ alphaGroup: String(input.alphaGroup ?? ""),
134
+ alphaMaxOutputTokens: positiveInteger(input.alphaMaxOutputTokens, 2500, 32_000),
135
+ webSearchProvider: stringValue(input.webSearchProvider, "lcx-responses"),
136
+ webMaxResults: positiveInteger(input.webMaxResults, 8),
137
+ timeoutMs: positiveInteger(input.timeoutMs, 300000),
138
+ maxResponseBytes: positiveInteger(input.maxResponseBytes, 8 * 1024 * 1024),
139
+ maxAttempts: positiveInteger(input.maxAttempts, 3, 6),
140
+ maxRequestImageBytes: positiveInteger(input.maxRequestImageBytes, DEFAULT_MAX_REQUEST_IMAGE_BYTES),
141
+ requestImagePixelBudget: positiveInteger(input.requestImagePixelBudget, DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET),
142
+ requestImageMaxBytes: positiveInteger(input.requestImageMaxBytes, DEFAULT_REQUEST_IMAGE_MAX_BYTES),
143
+ portableReplayMaxChars: positiveInteger(input.portableReplayMaxChars, 80_000),
144
+ nativeRetentionTokenBudget: positiveInteger(input.nativeRetentionTokenBudget, 64_000),
145
+ assistantRetentionTokenReserve: typeof input.assistantRetentionTokenReserve === "number" &&
146
+ Number.isSafeInteger(input.assistantRetentionTokenReserve) &&
147
+ input.assistantRetentionTokenReserve >= 0
148
+ ? input.assistantRetentionTokenReserve
149
+ : 24_000,
150
+ assistantRetentionPerMessageTokenCap: positiveInteger(input.assistantRetentionPerMessageTokenCap, 3_000),
151
+ };
152
+ }
185
153
  function webError(message, code, cause) {
186
- const error = new Error(message, cause === undefined ? undefined : { cause })
187
- error.name = 'WebError'; error.code = code; return error
188
- }
189
-
190
- function activeAgentRoute(exec, fallback) {
191
- const route = readAgentRouteState(exec?.agent)
192
- return {
193
- provider: route.requestConfig?.provider ?? route.options?.provider ?? fallback.provider,
194
- model: route.requestConfig?.model ?? route.options?.model ?? fallback.model,
195
- sessionId: route.sessionId,
196
- }
197
- }
198
-
199
- function selectedAgentRoute(agent, fallback) {
200
- const route = readAgentRouteState(agent)
201
- return {
202
- provider: route.options?.provider ?? route.requestConfig?.provider ?? fallback.provider,
203
- model: route.options?.model ?? route.requestConfig?.model ?? fallback.model,
204
- sessionId: route.sessionId,
205
- }
206
- }
207
-
154
+ const error = new Error(message, cause === undefined ? undefined : { cause });
155
+ error.name = "WebError";
156
+ error.code = code;
157
+ return error;
158
+ }
159
+ function routeConfigValue(value, name) {
160
+ return isRecord(value) ? value[name] : undefined;
161
+ }
162
+ function activeAgentRoute(exec) {
163
+ const route = readAgentRouteState(exec.agent);
164
+ return {
165
+ provider: stringValue(routeConfigValue(route.requestConfig, "provider") ??
166
+ routeConfigValue(route.options, "provider"), ""),
167
+ model: stringValue(routeConfigValue(route.requestConfig, "model") ??
168
+ routeConfigValue(route.options, "model"), ""),
169
+ sessionId: route.sessionId,
170
+ };
171
+ }
172
+ function selectedAgentRoute(agent) {
173
+ const route = readAgentRouteState(agent);
174
+ return {
175
+ provider: stringValue(routeConfigValue(route.requestConfig, "provider") ??
176
+ routeConfigValue(route.options, "provider"), ""),
177
+ model: stringValue(routeConfigValue(route.requestConfig, "model") ??
178
+ routeConfigValue(route.options, "model"), ""),
179
+ sessionId: route.sessionId,
180
+ };
181
+ }
208
182
  function requestImageOptions(routeConfig, imageSupport, signal, imageMap, extra = {}) {
209
- return {
210
- imageSupport, signal, imageMap,
211
- maxRequestImageBytes: routeConfig.maxRequestImageBytes,
212
- requestImagePixelBudget: routeConfig.requestImagePixelBudget,
213
- requestImageMaxBytes: routeConfig.requestImageMaxBytes,
214
- ...extra,
215
- }
216
- }
217
-
218
- async function executeHostedSearch(ctx, routeConfig, args, signal, sessionId = '') {
219
- const normalized = normalizeHostedSearchArgs(args)
220
- const requestId = randomUUID()
221
- const route = currentRoute({ provider: routeConfig.provider, model: routeConfig.model, sessionId }, routeConfig)
222
- const headers = await authenticatedHeaders(ctx, routeConfig, sessionId, requestId)
223
- const searchCacheKey = sessionId ? `dsh-lcx-search:${routeFingerprint(route)}` : undefined
224
- const body = buildHostedSearchBody(normalized, routeConfig.model, { promptCacheKey: searchCacheKey })
225
- const response = await fetchJsonWithRetry(`${routeConfig.baseURL}/responses`, body, headers, signal, routeConfig.timeoutMs, { maxAttempts: routeConfig.maxAttempts, maxResponseBytes: routeConfig.maxResponseBytes })
226
- return parseHostedSearchResponse(response, requestId, routeConfig.webMaxResults)
227
- }
228
-
183
+ return {
184
+ imageSupport,
185
+ signal,
186
+ imageMap,
187
+ maxRequestImageBytes: routeConfig.maxRequestImageBytes,
188
+ requestImagePixelBudget: routeConfig.requestImagePixelBudget,
189
+ requestImageMaxBytes: routeConfig.requestImageMaxBytes,
190
+ ...extra,
191
+ };
192
+ }
193
+ async function executeHostedSearch(ctx, routeConfig, args, signal, sessionId = "") {
194
+ const normalized = normalizeHostedSearchArgs(args);
195
+ const requestId = randomUUID();
196
+ const route = currentRoute({ provider: routeConfig.provider, model: routeConfig.model, sessionId }, routeConfig);
197
+ const headers = await authenticatedHeaders(ctx, routeConfig, sessionId, requestId);
198
+ const searchCacheKey = sessionId
199
+ ? `dsh-lcx-search:${routeFingerprint(route)}`
200
+ : undefined;
201
+ const body = buildHostedSearchBody(normalized, routeConfig.model, {
202
+ promptCacheKey: searchCacheKey,
203
+ });
204
+ const response = await fetchJsonWithRetry(`${routeConfig.baseURL}/responses`, body, headers, signal, routeConfig.timeoutMs, {
205
+ maxAttempts: routeConfig.maxAttempts,
206
+ maxResponseBytes: routeConfig.maxResponseBytes,
207
+ });
208
+ recordHostedUsage(response, requestId, routeConfig.provider, routeConfig.model);
209
+ return parseHostedSearchResponse(response, requestId, routeConfig.webMaxResults);
210
+ }
229
211
  class LcxResponsesSearchProvider {
230
- constructor(ctx, getConfig, enabled) { this.ctx = ctx; this.getConfig = getConfig; this.enabled = enabled; this.id = getConfig().webSearchProvider }
231
- available() {
232
- const config = this.getConfig()
233
- const route = resolveResponsesRouteConfig(this.ctx, { provider: config.provider, model: config.model }, config)
234
- return this.enabled() && Boolean(route)
235
- }
236
- async search(request, signal) {
237
- const config = this.getConfig()
238
- const active = hostedSearchRouteContext.getStore()
239
- const requested = active ?? { provider: config.provider, model: config.model, sessionId: '' }
240
- let route = resolveResponsesRouteConfig(this.ctx, requested, config)
241
- let source = active ? 'active-agent' : 'fallback'
242
- if (!route && active) {
243
- route = resolveResponsesRouteConfig(this.ctx, { provider: config.provider, model: config.model }, config)
244
- source = 'fallback'
212
+ ctx;
213
+ getConfig;
214
+ enabled;
215
+ id;
216
+ constructor(ctx, getConfig, enabled) {
217
+ this.ctx = ctx;
218
+ this.getConfig = getConfig;
219
+ this.enabled = enabled;
220
+ this.id = getConfig().webSearchProvider;
245
221
  }
246
- if (!route) throw webError('LCX Hosted Search requires a configured GPT openai-responses route', 'LCX_WEB_ROUTE_UNAVAILABLE')
247
- const sessionId = String((source === 'active-agent' ? active?.sessionId : '') ?? '')
248
- this.ctx.logger?.info?.(`[lcx-codex] web_search route: ${route.provider}/${route.model} (${source})`)
249
- const result = await executeHostedSearch(this.ctx, route, { query: request.query }, signal, sessionId)
250
- const max = Number.isInteger(request.maxResults) && request.maxResults > 0 ? request.maxResults : route.webMaxResults
251
- return { content: result.content || undefined, sources: result.sources.slice(0, max), truncated: result.truncated || result.sources.length > max }
252
- }
253
- }
254
-
222
+ available() {
223
+ const active = hostedSearchRouteContext.getStore();
224
+ if (!active)
225
+ return false;
226
+ const config = this.getConfig();
227
+ const route = resolveResponsesRouteConfig(this.ctx, active, config);
228
+ return this.enabled() && Boolean(route);
229
+ }
230
+ async search(request, signal) {
231
+ const config = this.getConfig();
232
+ const active = hostedSearchRouteContext.getStore();
233
+ const route = active && resolveResponsesRouteConfig(this.ctx, active, config);
234
+ const routeConfig = route && routeWithPolicies(route, config);
235
+ if (!routeConfig)
236
+ throw webError("LCX Hosted Search requires a configured GPT openai-responses route", "LCX_WEB_ROUTE_UNAVAILABLE");
237
+ const sessionId = active?.sessionId ?? "";
238
+ this.ctx.logger?.info?.(`[lcx-codex] web_search route: ${routeConfig.provider}/${routeConfig.model} (active-agent)`);
239
+ const result = await executeHostedSearch(this.ctx, routeConfig, { query: request.query }, signal, sessionId);
240
+ const max = request.maxResults ?? routeConfig.webMaxResults;
241
+ return {
242
+ content: result.content || undefined,
243
+ sources: result.sources.slice(0, max),
244
+ truncated: result.truncated || result.sources.length > max,
245
+ };
246
+ }
247
+ }
248
+ function createScopedGptWebSearchTool(ctx, state, getConfig, provider, fetchEnabled) {
249
+ let definition;
250
+ const composition = {
251
+ web: {
252
+ search: (request, signal) => provider.search(request, signal),
253
+ },
254
+ tools: {
255
+ register(candidate) {
256
+ definition = candidate;
257
+ return () => { };
258
+ },
259
+ },
260
+ systemPrompt: {
261
+ getSectionOrder: () => 0,
262
+ section: () => () => { },
263
+ },
264
+ };
265
+ applyWebSearchTool(composition, getConfig().webMaxResults, WEB_SEARCH_MAX_QUERIES, WEB_SEARCH_TIMEOUT_MS, fetchEnabled);
266
+ if (!definition)
267
+ throw new Error("DSH web_search composition did not register a tool");
268
+ const execute = definition.execute.bind(definition);
269
+ const output = definition.output;
270
+ return withAuxiliaryUsage({
271
+ ...definition,
272
+ output: {
273
+ ...output,
274
+ presentationMeta(args, value) {
275
+ return hostedMediaPresentationMeta(value, "web_search", output.presentationMeta?.(args, value));
276
+ },
277
+ },
278
+ async execute(args, exec) {
279
+ if (!state.enabled || !state.webSearch)
280
+ throw webError("web_search GPT Hosted Search is disabled", "LCX_WEB_DISABLED");
281
+ const active = activeAgentRoute(exec);
282
+ const route = resolveResponsesRouteConfig(ctx, active, getConfig());
283
+ if (!route)
284
+ throw webError("GPT Hosted Search requires the active GPT openai-responses route", "LCX_WEB_ROUTE_UNAVAILABLE");
285
+ return hostedSearchRouteContext.run(active, () => execute(args, exec));
286
+ },
287
+ });
288
+ }
255
289
  function createAdvancedHostedTool(ctx, state, getConfig) {
256
- return {
257
- name: ADVANCED_HOSTED_TOOL,
258
- 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.',
259
- parameters: HOSTED_SEARCH_PARAMETERS,
260
- output: { schema: HOSTED_SEARCH_OUTPUT, render: (_args, value) => renderHostedSearchResult(value) },
261
- async execute(args, exec) {
262
- if (!state.enabled || !state.webSearch || !state.advancedHostedSearch) throw webError(`${ADVANCED_HOSTED_TOOL} is disabled`, 'LCX_WEB_DISABLED')
263
- const config = getConfig(); const active = activeAgentRoute(exec, config)
264
- const route = resolveResponsesRouteConfig(ctx, active, config)
265
- if (!route) throw webError('Advanced Hosted Search requires the active GPT openai-responses route', 'LCX_WEB_ROUTE_UNAVAILABLE')
266
- return executeHostedSearch(ctx, route, args, exec?.signal, active.sessionId)
267
- },
268
- }
269
- }
270
-
290
+ return withAuxiliaryUsage({
291
+ name: ADVANCED_HOSTED_TOOL,
292
+ 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.",
293
+ parameters: HOSTED_SEARCH_PARAMETERS,
294
+ output: {
295
+ schema: HOSTED_SEARCH_OUTPUT,
296
+ render: (_args, value) => renderHostedSearchResult(value),
297
+ presentationMeta: (_args, value) => hostedMediaPresentationMeta(value, ADVANCED_HOSTED_TOOL),
298
+ },
299
+ async execute(args, exec) {
300
+ if (!state.enabled || !state.webSearch || !state.advancedHostedSearch)
301
+ throw webError(`${ADVANCED_HOSTED_TOOL} is disabled`, "LCX_WEB_DISABLED");
302
+ const config = getConfig();
303
+ const active = activeAgentRoute(exec);
304
+ const route = resolveResponsesRouteConfig(ctx, active, config);
305
+ if (!route)
306
+ throw webError("Advanced Hosted Search requires the active GPT openai-responses route", "LCX_WEB_ROUTE_UNAVAILABLE");
307
+ return executeHostedSearch(ctx, routeWithPolicies(route, config), args, exec.signal, active.sessionId);
308
+ },
309
+ });
310
+ }
311
+ function disposeGptToolsForAgent(agent, registrations) {
312
+ const registration = registrations.get(agent);
313
+ if (!registration)
314
+ return;
315
+ registration.dispose();
316
+ registrations.delete(agent);
317
+ }
318
+ function syncGptToolsForAgent(ctx, agent, state, getConfig, provider, registrations) {
319
+ const config = getConfig();
320
+ const active = selectedAgentRoute(agent);
321
+ const route = resolveResponsesRouteConfig(ctx, active, config);
322
+ const webSearch = state.enabled && state.webSearch && Boolean(route);
323
+ const advanced = webSearch && state.advancedHostedSearch;
324
+ if (!webSearch && !advanced) {
325
+ disposeGptToolsForAgent(agent, registrations);
326
+ return false;
327
+ }
328
+ const fingerprint = JSON.stringify({
329
+ provider: route?.provider,
330
+ model: route?.model,
331
+ baseURL: route?.baseURL,
332
+ webSearch,
333
+ advanced,
334
+ webMaxResults: config.webMaxResults,
335
+ });
336
+ if (registrations.get(agent)?.fingerprint === fingerprint)
337
+ return true;
338
+ disposeGptToolsForAgent(agent, registrations);
339
+ const scopedTools = scopedToolRuntime(agent);
340
+ if (!scopedTools)
341
+ return false;
342
+ const disposers = [];
343
+ try {
344
+ if (webSearch) {
345
+ const fetchEnabled = Boolean(scopedTools.get?.("web_fetch", agent));
346
+ disposers.push(scopedTools.register(createScopedGptWebSearchTool(ctx, state, getConfig, provider, fetchEnabled)));
347
+ }
348
+ if (advanced)
349
+ disposers.push(scopedTools.register(createAdvancedHostedTool(ctx, state, getConfig)));
350
+ registrations.set(agent, {
351
+ fingerprint,
352
+ dispose() {
353
+ for (const dispose of disposers.reverse())
354
+ dispose();
355
+ },
356
+ });
357
+ return true;
358
+ }
359
+ catch (error) {
360
+ for (const dispose of disposers.reverse())
361
+ dispose();
362
+ ctx.logger?.warn?.(`[lcx-codex] GPT search tool composition unavailable: ${errorDetails(error).message ?? String(error)}`);
363
+ return false;
364
+ }
365
+ }
271
366
  function alphaCapabilityFor(config, store) {
272
- const fingerprint = alphaCapabilityFingerprint({ baseURL: config.baseURL, provider: config.provider, model: config.model, profile: config.alphaProfile, group: config.alphaGroup, schemaFingerprint: ALPHA_SCHEMA_FINGERPRINT })
273
- return { fingerprint, record: store.get(fingerprint) }
367
+ const fingerprint = alphaCapabilityFingerprint({
368
+ baseURL: config.baseURL,
369
+ provider: config.provider,
370
+ model: config.model,
371
+ profile: config.alphaProfile,
372
+ group: config.alphaGroup,
373
+ schemaFingerprint: ALPHA_SCHEMA_FINGERPRINT,
374
+ });
375
+ return { fingerprint, record: store.get(fingerprint) };
274
376
  }
275
-
276
377
  function verifiedAlphaCapabilityForRoute(ctx, active, config, store) {
277
- const route = resolveResponsesRouteConfig(ctx, active, config)
278
- if (!route) return { route: undefined, usable: false }
279
- const { fingerprint, record } = alphaCapabilityFor(route, store)
280
- return { route, fingerprint, record, usable: alphaCapabilityUsable(record) && record?.schemaFingerprint === ALPHA_SCHEMA_FINGERPRINT }
378
+ const resolvedRoute = resolveResponsesRouteConfig(ctx, active, config);
379
+ if (!resolvedRoute)
380
+ return { route: undefined, usable: false };
381
+ const route = routeWithPolicies(resolvedRoute, config);
382
+ const { fingerprint, record } = alphaCapabilityFor(route, store);
383
+ return {
384
+ route,
385
+ fingerprint,
386
+ record,
387
+ usable: alphaCapabilityUsable(record) &&
388
+ record?.schemaFingerprint === ALPHA_SCHEMA_FINGERPRINT,
389
+ };
281
390
  }
282
-
283
391
  async function executeAlpha(ctx, routeConfig, capability, refStore, args, exec) {
284
- const normalized = normalizeAlphaSearchArgs(args)
285
- if (['open', 'find', 'screenshot'].includes(normalized.action) && isAlphaHttpUrl(normalized.refId) && !isAlphaContinuationUrl(normalized.refId)) {
286
- throw webError('Alpha Search direct URLs must be public HTTP(S) targets', 'LCX_ALPHA_URL_UNAVAILABLE')
287
- }
288
- const sessionId = agentSessionId(exec?.agent)
289
- if (!sessionId) throw webError('Alpha Search requires a DSH session', 'LCX_ALPHA_SESSION_REQUIRED')
290
- const routeFp = routeFingerprint({ provider: routeConfig.provider, model: routeConfig.model, baseURL: routeConfig.baseURL, sessionId })
291
- if (alphaRefRequiresStore(normalized.action, normalized.refId)) refStore.assertUsable(sessionId, routeFp, normalized.refId)
292
- const requestId = randomUUID(); const headers = await authenticatedHeaders(ctx, routeConfig, sessionId, requestId)
293
- let response
294
- try {
295
- 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 })
296
- } catch (error) {
297
- 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)
298
- throw webError('Alpha Search provider request failed', 'LCX_ALPHA_PROVIDER_ERROR', error)
299
- }
300
- const result = parseAlphaSearchResponse(response, { action: normalized.action, capability: capability.classification, requestId })
301
- refStore.record(sessionId, routeFp, result.refRecords ?? result.refs.map((refId) => ({ refId })))
302
- delete result.refRecords
303
- return result
304
- }
305
-
306
- function createAlphaTool(ctx, state, getConfig, capabilityStore, refStore) {
307
- return {
308
- name: ALPHA_TOOL,
309
- 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.',
310
- parameters: ALPHA_SEARCH_PARAMETERS,
311
- output: { schema: ALPHA_SEARCH_OUTPUT, render: (_args, value) => renderAlphaSearchResult(value) },
312
- async execute(args, exec) {
313
- if (!state.enabled || !state.alphaSearch) throw webError(`${ALPHA_TOOL} is disabled`, 'LCX_ALPHA_DISABLED')
314
- const config = getConfig(); const active = activeAgentRoute(exec, config); const route = resolveResponsesRouteConfig(ctx, active, config)
315
- if (!route) throw webError('Alpha Search requires the active GPT openai-responses route', 'LCX_ALPHA_ROUTE_UNAVAILABLE')
316
- const fingerprint = alphaCapabilityFingerprint({ baseURL: route.baseURL, provider: route.provider, model: route.model, profile: route.alphaProfile, group: route.alphaGroup, schemaFingerprint: ALPHA_SCHEMA_FINGERPRINT })
317
- const capability = capabilityStore.get(fingerprint)
318
- 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')
319
- return executeAlpha(ctx, route, capability, refStore, args, exec)
320
- },
321
- }
322
- }
323
-
392
+ const sessionId = agentSessionId(exec?.agent);
393
+ if (!sessionId)
394
+ throw webError("Alpha Search requires a DSH session", "LCX_ALPHA_SESSION_REQUIRED");
395
+ return runWithAlphaSessionLock(sessionId, exec?.signal, async () => {
396
+ const normalized = normalizeAlphaSearchArgs(args);
397
+ assertAlphaActionAllowed(capability, normalized.action);
398
+ if (["open", "find", "screenshot"].includes(normalized.action) &&
399
+ isAlphaHttpUrl(normalized.refId) &&
400
+ !isAlphaContinuationUrl(normalized.refId)) {
401
+ throw webError("Alpha Search direct URLs must be public HTTP(S) targets", "LCX_ALPHA_URL_UNAVAILABLE");
402
+ }
403
+ const routeFp = routeFingerprint({
404
+ provider: routeConfig.provider,
405
+ model: routeConfig.model,
406
+ baseURL: routeConfig.baseURL,
407
+ sessionId,
408
+ });
409
+ const continuedRef = alphaRefRequiresStore(normalized.action, normalized.refId)
410
+ ? refStore.assertUsable(sessionId, routeFp, normalized.refId)
411
+ : undefined;
412
+ const requestId = randomUUID();
413
+ const headers = await authenticatedHeaders(ctx, routeConfig, sessionId, requestId);
414
+ let response;
415
+ try {
416
+ response = await fetchJsonWithRetry(`${routeConfig.baseURL}/alpha/search`, buildAlphaSearchBody(normalized, routeConfig.model, sessionId, true, routeConfig.alphaMaxOutputTokens), headers, exec?.signal, routeConfig.timeoutMs, alphaSearchRetryOptions(routeConfig.maxResponseBytes));
417
+ }
418
+ catch (error) {
419
+ const details = errorDetails(error);
420
+ if ([404, 405].includes(Number(details.status)) ||
421
+ /channel does not support/iu.test(String(details.message ?? "")))
422
+ throw webError("Alpha Search is not supported by this route", "LCX_ALPHA_UNAVAILABLE", error);
423
+ throw webError("Alpha Search provider request failed", "LCX_ALPHA_PROVIDER_ERROR", error);
424
+ }
425
+ const { refRecords: observedRefs, ...result } = parseAlphaSearchResponse(response, {
426
+ action: normalized.action,
427
+ capability: capability.classification,
428
+ requestId,
429
+ });
430
+ const refRecords = observedRefs.map((observation) => {
431
+ if (observation.refId !== normalized.refId || !continuedRef)
432
+ return observation;
433
+ const observedArtifact = observation.provenance.artifactFingerprint;
434
+ const acceptedArtifact = continuedRef.provenance.artifactFingerprint;
435
+ if (observedArtifact && observedArtifact !== acceptedArtifact)
436
+ return observation;
437
+ // An echoed input ref retains its accepted origin; an omitted URL makes no new claim.
438
+ return {
439
+ ...observation,
440
+ ...(observation.url === undefined && continuedRef.url ? { url: continuedRef.url } : {}),
441
+ provenance: { ...continuedRef.provenance },
442
+ };
443
+ });
444
+ refStore.record(sessionId, routeFp, refRecords);
445
+ return result;
446
+ });
447
+ }
448
+ function createAlphaTool(ctx, state, getConfig, capabilityStore, refStore, advertisedRecord) {
449
+ return {
450
+ name: ALPHA_TOOL,
451
+ 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.",
452
+ parameters: advertisedRecord
453
+ ? alphaSearchParametersFor(advertisedRecord)
454
+ : ALPHA_SEARCH_PARAMETERS,
455
+ output: {
456
+ schema: ALPHA_SEARCH_OUTPUT,
457
+ render: (_args, value) => renderAlphaSearchResult(value),
458
+ },
459
+ async execute(args, exec) {
460
+ if (!state.enabled || !state.alphaSearch)
461
+ throw webError(`${ALPHA_TOOL} is disabled`, "LCX_ALPHA_DISABLED");
462
+ const config = getConfig();
463
+ const active = activeAgentRoute(exec);
464
+ const route = resolveResponsesRouteConfig(ctx, active, config);
465
+ if (!route)
466
+ throw webError("Alpha Search requires the active GPT openai-responses route", "LCX_ALPHA_ROUTE_UNAVAILABLE");
467
+ const routeConfig = routeWithPolicies(route, config);
468
+ const fingerprint = alphaCapabilityFingerprint({
469
+ baseURL: routeConfig.baseURL,
470
+ provider: routeConfig.provider,
471
+ model: routeConfig.model,
472
+ profile: routeConfig.alphaProfile,
473
+ group: routeConfig.alphaGroup,
474
+ schemaFingerprint: ALPHA_SCHEMA_FINGERPRINT,
475
+ });
476
+ const capability = capabilityStore.get(fingerprint);
477
+ if (!alphaCapabilityUsable(capability) ||
478
+ capability?.schemaFingerprint !== ALPHA_SCHEMA_FINGERPRINT)
479
+ throw webError("Alpha Search capability has not been verified for this exact route/schema", "LCX_ALPHA_CAPABILITY_UNVERIFIED");
480
+ return executeAlpha(ctx, routeConfig, capability, refStore, args, exec);
481
+ },
482
+ };
483
+ }
324
484
  function syncAlphaToolForAgent(ctx, agent, state, getConfig, capabilityStore, refStore, registrations) {
325
- const previous = registrations.get(agent)
326
- if (!state.enabled || !state.alphaSearch) {
327
- disposeAlphaToolForAgent(agent, registrations)
328
- return false
329
- }
330
- try {
331
- const config = getConfig()
332
- const active = selectedAgentRoute(agent, config)
333
- const capability = verifiedAlphaCapabilityForRoute(ctx, active, config, capabilityStore)
334
- if (!capability.usable) {
335
- disposeAlphaToolForAgent(agent, registrations)
336
- return false
485
+ const previous = registrations.get(agent);
486
+ if (!state.enabled || !state.alphaSearch) {
487
+ disposeAlphaToolForAgent(agent, registrations);
488
+ return false;
489
+ }
490
+ try {
491
+ const config = getConfig();
492
+ const active = selectedAgentRoute(agent);
493
+ const capability = verifiedAlphaCapabilityForRoute(ctx, active, config, capabilityStore);
494
+ if (!capability.usable) {
495
+ disposeAlphaToolForAgent(agent, registrations);
496
+ return false;
497
+ }
498
+ if (previous?.fingerprint === capability.fingerprint)
499
+ return true;
500
+ disposeAlphaToolForAgent(agent, registrations);
501
+ const scopedTools = scopedToolRuntime(agent);
502
+ if (!scopedTools)
503
+ return false;
504
+ // Alpha is route-bound; global registration would advertise a static record to other routes.
505
+ registrations.set(agent, {
506
+ fingerprint: capability.fingerprint,
507
+ dispose: scopedTools.register(createAlphaTool(ctx, state, getConfig, capabilityStore, refStore, capability.record)),
508
+ });
509
+ return true;
337
510
  }
338
- if (previous?.fingerprint === capability.fingerprint) return true
339
- disposeAlphaToolForAgent(agent, registrations)
340
- const scopedTools = resolveScopedService(agent, 'tools')
341
- if (!scopedTools?.register) return false
342
- // Alpha is route-bound; global registration would advertise a static record to other routes.
343
- registrations.set(agent, { fingerprint: capability.fingerprint, dispose: scopedTools.register(createAlphaTool(ctx, state, getConfig, capabilityStore, refStore)) })
344
- return true
345
- } catch (error) {
346
- disposeAlphaToolForAgent(agent, registrations)
347
- ctx.logger?.warn?.(`[lcx-codex] Alpha capability store unavailable: ${error?.message ?? error}`)
348
- return false
349
- }
350
- }
351
-
511
+ catch (error) {
512
+ disposeAlphaToolForAgent(agent, registrations);
513
+ ctx.logger?.warn?.(`[lcx-codex] Alpha capability store unavailable: ${errorDetails(error).message ?? String(error)}`);
514
+ return false;
515
+ }
516
+ }
352
517
  function disposeAlphaToolForAgent(agent, registrations) {
353
- const registration = registrations.get(agent)
354
- if (!registration) return
355
- registration.dispose()
356
- registrations.delete(agent)
518
+ const registration = registrations.get(agent);
519
+ if (!registration)
520
+ return;
521
+ registration.dispose();
522
+ registrations.delete(agent);
357
523
  }
358
-
359
524
  function isDshCompactionDirective(message) {
360
- if (message?.role !== 'user') return false
361
- if (message?.source?.kind === 'plugin' && message?.source?.plugin === 'dsh-compaction-basic') return true
362
- const text = (message?.content ?? []).filter((b) => b?.type === 'text').map((b) => b.text).join('')
363
- return text.includes(COMPACTION_DIRECTIVE)
525
+ return (message?.role === "user" &&
526
+ message?.source?.kind === "plugin" &&
527
+ message?.source?.plugin === "dsh-compaction-basic");
364
528
  }
365
529
  function stripCompactionDirective(messages) {
366
- if (!Array.isArray(messages) || messages.length === 0) return []
367
- return isDshCompactionDirective(messages.at(-1)) ? messages.slice(0, -1) : messages
368
- }
369
-
370
- function mergeMap(target, source) { for (const [key, value] of source ?? []) target.set(key, value) }
371
-
372
- async function serializeNativeAware(messages, route, routeConfig, ctx, options = {}) {
373
- const session = sessionFor(ctx, route.sessionId)
374
- const imageSupport = await resolveModelImageSupport(ctx, route, options.signal)
375
- const input = []; const imageMap = new Map(); let nativeTools; let nativeModel; let grammarToolInputProperties
376
- let normal = []
377
- const serializeOptions = (imageMapOverride, extra = {}) => requestImageOptions(routeConfig, imageSupport, options.signal, imageMapOverride, { route, tools: options.tools, responsesCompat: routeConfig.responsesCompat, ...extra })
378
- const prelude = await serializeDshMessages([], ctx, serializeOptions(undefined, { systemPrompt: options.system, includeSystemPrompt: true }))
379
- const ephemeralPreludeItemCount = prelude.input.length
380
- input.push(...prelude.input); nativeTools = prelude.tools; nativeModel = prelude.model; grammarToolInputProperties = prelude.grammarToolInputProperties
381
- const flush = async () => {
382
- if (!normal.length) return
383
- const serialized = await serializeDshMessages(normal, ctx, serializeOptions())
384
- nativeTools = serialized.tools ?? nativeTools; nativeModel = serialized.model ?? nativeModel; grammarToolInputProperties = serialized.grammarToolInputProperties ?? grammarToolInputProperties
385
- input.push(...serialized.input); mergeMap(imageMap, serialized.imageMap); normal = []
386
- }
387
- for (const message of messages ?? []) {
388
- const state = session ? checkpointStateForMessage(session, message) : undefined
389
- if (state) {
390
- await flush()
391
- if (stateRouteCompatible(state, route, ctx)) {
392
- let nativeOutput = state.nativeOutput
393
- if (state.version === 4) {
394
- const checkpointId = compactCheckpointId(message)
395
- const shadowed = shadowedMessagesForCheckpoint(session, checkpointId)
396
- if (shadowed.length > 0) {
397
- const serializedShadowed = await serializeDshMessages(shadowed, ctx, serializeOptions())
398
- const repairedRetained = retainedConversationInput(serializedShadowed.input, {
399
- tokenBudget: routeConfig.nativeRetentionTokenBudget,
400
- assistantTokenReserve: routeConfig.assistantRetentionTokenReserve,
401
- assistantPerMessageTokenCap: routeConfig.assistantRetentionPerMessageTokenCap,
402
- })
403
- const opaque = state.nativeCompaction ?? nativeOutput.find((item) => item?.type === 'compaction')
404
- if (repairedRetained.length > 0 && opaque) {
405
- nativeOutput = [...repairedRetained, structuredClone(opaque)]
406
- mergeMap(imageMap, serializedShadowed.imageMap)
407
- } else if (state.retainedInputCount === undefined) {
408
- const portable = portableMessagesForCheckpoint(session, checkpointId, { maxChars: routeConfig.portableReplayMaxChars })
409
- const serializedPortable = await serializeDshMessages(portable, ctx, serializeOptions())
410
- input.push(...serializedPortable.input); mergeMap(imageMap, serializedPortable.imageMap)
411
- continue
530
+ if (messages.length === 0)
531
+ return [];
532
+ return isDshCompactionDirective(messages.at(-1))
533
+ ? messages.slice(0, -1)
534
+ : [...messages];
535
+ }
536
+ function mergeMap(target, source) {
537
+ for (const [key, value] of source ?? [])
538
+ target.set(key, value);
539
+ }
540
+ async function serializeNativeAware(messages, route, routeConfig, ctx, options) {
541
+ const session = sessionFor(ctx, route.sessionId);
542
+ const imageSupport = await resolveModelImageSupport(ctx, route, options.signal);
543
+ const input = [];
544
+ const imageMap = new Map();
545
+ let nativeTools;
546
+ let nativeModel;
547
+ let grammarToolInputProperties;
548
+ let normal = [];
549
+ const serializeOptions = (imageMapOverride = undefined, extra = {}) => requestImageOptions(routeConfig, imageSupport, options.signal, imageMapOverride, {
550
+ route,
551
+ tools: options.tools,
552
+ responsesCompat: routeConfig.responsesCompat,
553
+ ...extra,
554
+ });
555
+ const firstMessage = messages[0];
556
+ const systemHead = firstMessage?.role === "system" ? firstMessage : undefined;
557
+ const surfaceMessages = systemHead ? messages.slice(1) : messages;
558
+ const systemPrompt = systemHead?.content
559
+ .filter((block) => block.type === "text")
560
+ .map((block) => block.text)
561
+ .join("") || undefined;
562
+ const prelude = await serializeDshMessages([], ctx, serializeOptions(undefined, {
563
+ systemPrompt,
564
+ includeSystemPrompt: true,
565
+ }));
566
+ const ephemeralPreludeItemCount = prelude.input.length;
567
+ input.push(...prelude.input);
568
+ nativeTools = prelude.tools;
569
+ nativeModel = prelude.model;
570
+ grammarToolInputProperties = prelude.grammarToolInputProperties;
571
+ const flush = async () => {
572
+ if (!normal.length)
573
+ return;
574
+ const batch = normal;
575
+ normal = [];
576
+ const serialized = await serializeDshMessages(batch, ctx, serializeOptions());
577
+ nativeTools = serialized.tools ?? nativeTools;
578
+ nativeModel = serialized.model ?? nativeModel;
579
+ grammarToolInputProperties =
580
+ serialized.grammarToolInputProperties ?? grammarToolInputProperties;
581
+ input.push(...restoreGrokNativeReplay(serialized.input, batch, options.grokNativeReplayRoute));
582
+ mergeMap(imageMap, serialized.imageMap);
583
+ };
584
+ for (const message of surfaceMessages ?? []) {
585
+ assertSupportedCheckpointMessage(message);
586
+ const state = session
587
+ ? checkpointStateForMessage(session, message)
588
+ : undefined;
589
+ if (state && session) {
590
+ const checkpointId = compactCheckpointId(message);
591
+ if (!checkpointId) {
592
+ normal.push(message);
593
+ continue;
594
+ }
595
+ await flush();
596
+ if (stateRouteCompatible(state, route, ctx)) {
597
+ const nativeOutput = state.nativeOutput;
598
+ const hydratedMap = new Map();
599
+ const hydrated = await hydrateNativeImageReferences(nativeOutput, ctx, requestImageOptions(routeConfig, imageSupport, options.signal, hydratedMap));
600
+ input.push(...responseInputItems(hydrated));
601
+ mergeMap(imageMap, hydratedMap);
412
602
  }
413
- } else if (state.retainedInputCount === undefined) {
414
- const portable = portableMessagesForCheckpoint(session, checkpointId, { maxChars: routeConfig.portableReplayMaxChars })
415
- const serializedPortable = await serializeDshMessages(portable, ctx, serializeOptions())
416
- input.push(...serializedPortable.input); mergeMap(imageMap, serializedPortable.imageMap)
417
- continue
418
- }
603
+ else {
604
+ const portable = portableMessagesForCheckpoint(session, checkpointId, { maxChars: routeConfig.portableReplayMaxChars });
605
+ const serialized = await serializeDshMessages(portable, ctx, serializeOptions());
606
+ input.push(...serialized.input);
607
+ mergeMap(imageMap, serialized.imageMap);
608
+ }
609
+ continue;
419
610
  }
420
- const hydratedMap = new Map()
421
- const hydrated = await hydrateNativeImageReferences(nativeOutput, ctx, requestImageOptions(routeConfig, imageSupport, options.signal, hydratedMap))
422
- input.push(...hydrated); mergeMap(imageMap, hydratedMap)
423
- } else {
424
- const portable = portableMessagesForCheckpoint(session, compactCheckpointId(message), { maxChars: routeConfig.portableReplayMaxChars })
425
- const serialized = await serializeDshMessages(portable, ctx, serializeOptions())
426
- input.push(...serialized.input); mergeMap(imageMap, serialized.imageMap)
427
- }
428
- continue
429
- }
430
- const legacyId = legacyV3Id(message)
431
- if (legacyId) {
432
- const legacy = loadLegacyRecord(routeConfig.legacyCheckpointPath, legacyId)
433
- if (legacy) {
434
- await flush()
435
- const legacyItems = legacyRouteCompatible(legacy, route, ctx) ? legacy.nativeOutput : (legacy.portableHistory ?? [])
436
- const hydratedMap = new Map()
437
- const hydrated = await hydrateNativeImageReferences(legacyItems, ctx, requestImageOptions(routeConfig, imageSupport, options.signal, hydratedMap))
438
- input.push(...hydrated); mergeMap(imageMap, hydratedMap)
439
- continue
440
- }
611
+ normal.push(message);
441
612
  }
442
- normal.push(message)
443
- }
444
- await flush()
445
- if (!nativeModel) throw Object.assign(new Error('LCX could not resolve the Pi Responses model descriptor'), { code: 'LCX_RESPONSES_MODEL_UNAVAILABLE' })
446
- return { input, ephemeralPreludeItemCount, imageMap, imageSupport, tools: nativeTools, model: nativeModel, grammarToolInputProperties }
613
+ await flush();
614
+ if (!nativeModel)
615
+ throw Object.assign(new Error("LCX could not resolve the Pi Responses model descriptor"), { code: "LCX_RESPONSES_MODEL_UNAVAILABLE" });
616
+ return {
617
+ input,
618
+ ephemeralPreludeItemCount,
619
+ imageMap,
620
+ imageSupport,
621
+ tools: nativeTools,
622
+ model: nativeModel,
623
+ grammarToolInputProperties,
624
+ };
447
625
  }
448
-
449
626
  function fallbackEligible(error, signal) {
450
- if (signal?.aborted) return false
451
- if (error?.name === 'AbortError' || error?.code === 'LCX_ABORTED') return false
452
- if (error?.name === 'TimeoutError') return true
453
- return ['LCX_HTTP_RETRYABLE', 'LCX_RETRY_EXHAUSTED'].includes(error?.code)
454
- }
455
-
456
- async function* remoteCompactionStream(options, routeConfig, state, ctx, next, requestHeaders) {
457
- const history = stripCompactionDirective(options.messages)
458
- const route = currentRoute(options, routeConfig)
459
- try {
460
- const prepared = await serializeNativeAware(history, route, routeConfig, ctx, { signal: options.signal, system: options.system, tools: options.tools })
461
- const cacheSessionId = promptCacheSessionId(route, routeConfig)
462
- const headers = await authenticatedHeaders(ctx, routeConfig, cacheSessionId, cacheSessionId === undefined ? null : undefined)
463
- const cachedGeneration = generationControlsFromHeader(requestHeaders?.get(route.sessionId), route)
464
- const generation = Object.keys(cachedGeneration).length > 0 ? cachedGeneration : generationControlsFromSession(sessionFor(ctx, route.sessionId), route)
465
- const result = await requestNativeCompaction({
466
- baseURL: routeConfig.baseURL,
467
- model: route.model,
468
- modelDescriptor: prepared.model,
469
- input: prepared.input,
470
- tools: prepared.tools ?? options.tools,
471
- promptCacheKey: promptCacheKey(route, routeConfig),
472
- promptCacheRetention: promptCacheRetention(routeConfig),
473
- cacheRetention: routeConfig.cacheRetention,
474
- reasoningEffort: generation.reasoningEffort,
475
- temperature: generation.temperature,
476
- maxTokens: generation.maxTokens,
477
- idempotencyKey: randomUUID(),
478
- headers,
479
- signal: options.signal,
480
- timeoutMs: routeConfig.timeoutMs,
481
- maxAttempts: routeConfig.maxAttempts,
482
- maxResponseBytes: routeConfig.maxResponseBytes,
483
- })
484
- const session = sessionFor(ctx, route.sessionId)
485
- const block = createNativeCheckpointBlock({
486
- session, route, result, input: prepared.input, ephemeralPreludeItemCount: prepared.ephemeralPreludeItemCount, imageMap: prepared.imageMap,
487
- retentionOptions: {
488
- tokenBudget: routeConfig.nativeRetentionTokenBudget,
489
- assistantTokenReserve: routeConfig.assistantRetentionTokenReserve,
490
- assistantPerMessageTokenCap: routeConfig.assistantRetentionPerMessageTokenCap,
491
- },
492
- })
493
- 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`)
494
- for (const chunk of nativeCheckpointChunks(block, result.usage)) yield chunk
495
- } catch (error) {
496
- const session = sessionFor(ctx, route.sessionId)
497
- const hasExistingCheckpoint = messagesContainNativeCheckpoint(history, session) || messagesContainLegacyCheckpoint(history)
498
- const code = error?.code ?? error?.name ?? 'ERROR'
499
- const status = Number.isInteger(error?.status) ? ` status=${error.status}` : ''
500
- const requestId = error?.requestId ? ` requestId=${String(error.requestId)}` : ''
501
- const providerCode = error?.providerCode ? ` providerCode=${error.providerCode}` : ''
502
- const providerType = error?.providerType ? ` providerType=${error.providerType}` : ''
503
- const providerParam = error?.providerParam ? ` providerParam=${error.providerParam}` : ''
504
- ctx.logger?.warn?.(`[lcx-codex] native V2 compaction failed: code=${code}${status}${requestId}${providerCode}${providerType}${providerParam}`)
505
- if (!state.fallbackToBasicCompaction || hasExistingCheckpoint || !fallbackEligible(error, options.signal)) throw error
506
- ctx.logger?.info?.('[lcx-codex] falling back to DSH basic compaction after allowlisted first-checkpoint native failure')
507
- const stream = await next()
508
- for await (const chunk of stream) yield chunk
509
- }
510
- }
511
-
512
- function routedTargetForAgent(agent, fallback) {
513
- const route = readAgentRouteState(agent)
514
- const provider = route.requestConfig?.provider ?? route.options?.provider ?? fallback.provider
515
- const model = route.requestConfig?.model ?? route.options?.model ?? fallback.model
516
- return provider && model ? { provider, model } : undefined
517
- }
518
-
519
- function clampPercent(value, fallback, min, max) {
520
- const number = Number(value)
521
- return Number.isFinite(number) ? Math.min(max, Math.max(min, number)) : fallback
522
- }
523
-
524
- function pressurePolicy(state, config) {
525
- const auto = clampPercent(state.autoCompactionThresholdPercent, config.autoCompactionThresholdPercent, 85, 95)
526
- const emergencyRaw = clampPercent(state.emergencyPruneThresholdPercent, config.emergencyPruneThresholdPercent, 90, 99)
527
- const emergency = Math.max(auto + 1, emergencyRaw)
528
- return { auto, emergency: Math.min(99, emergency) }
529
- }
530
-
627
+ if (signal?.aborted)
628
+ return false;
629
+ const details = errorDetails(error);
630
+ if (details.name === "AbortError" || details.code === "LCX_ABORTED")
631
+ return false;
632
+ if (details.name === "TimeoutError")
633
+ return true;
634
+ return ["LCX_HTTP_RETRYABLE", "LCX_RETRY_EXHAUSTED"].includes(String(details.code ?? ""));
635
+ }
636
+ async function* remoteCompactionStream(options, routeConfig, ctx, next) {
637
+ const history = stripCompactionDirective(options.messages);
638
+ const route = currentRoute(options, routeConfig);
639
+ try {
640
+ const prepared = await serializeNativeAware(history, route, routeConfig, ctx, { signal: options.signal, tools: options.tools });
641
+ const cacheSessionId = promptCacheSessionId(route, routeConfig, ctx);
642
+ const headers = await authenticatedHeaders(ctx, routeConfig, cacheSessionId, cacheSessionId === undefined ? null : route.sessionId);
643
+ const generation = generationControlsFromSession(sessionFor(ctx, route.sessionId), route);
644
+ const result = await requestNativeCompaction({
645
+ baseURL: routeConfig.baseURL,
646
+ model: route.model,
647
+ modelDescriptor: prepared.model,
648
+ input: prepared.input,
649
+ tools: prepared.tools ?? options.tools,
650
+ promptCacheKey: promptCacheKey(route, routeConfig, ctx),
651
+ promptCacheRetention: promptCacheRetention(routeConfig),
652
+ cacheRetention: routeConfig.cacheRetention,
653
+ reasoningEffort: generation.reasoningEffort,
654
+ temperature: generation.temperature,
655
+ maxTokens: generation.maxTokens,
656
+ idempotencyKey: randomUUID(),
657
+ headers,
658
+ signal: options.signal,
659
+ timeoutMs: routeConfig.timeoutMs,
660
+ maxAttempts: routeConfig.maxAttempts,
661
+ maxResponseBytes: routeConfig.maxResponseBytes,
662
+ });
663
+ const session = sessionFor(ctx, route.sessionId);
664
+ if (session === undefined)
665
+ throw webError("Native compaction requires a live DSH session", "LCX_COMPACT_SESSION_UNAVAILABLE");
666
+ const block = createNativeCheckpointBlock({
667
+ session,
668
+ route,
669
+ result,
670
+ input: prepared.input,
671
+ ephemeralPreludeItemCount: prepared.ephemeralPreludeItemCount,
672
+ imageMap: prepared.imageMap,
673
+ retentionOptions: {
674
+ tokenBudget: routeConfig.nativeRetentionTokenBudget,
675
+ assistantTokenReserve: routeConfig.assistantRetentionTokenReserve,
676
+ assistantPerMessageTokenCap: routeConfig.assistantRetentionPerMessageTokenCap,
677
+ },
678
+ });
679
+ 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`);
680
+ for (const chunk of nativeCheckpointChunks(block, result.usage))
681
+ yield chunk;
682
+ }
683
+ catch (error) {
684
+ const session = sessionFor(ctx, route.sessionId);
685
+ const hasExistingCheckpoint = messagesContainNativeCheckpoint(history, session);
686
+ const details = errorDetails(error);
687
+ const code = details.code ?? details.name ?? "ERROR";
688
+ const status = Number.isInteger(details.status)
689
+ ? ` status=${details.status}`
690
+ : "";
691
+ const requestId = isRecord(error) && error.requestId
692
+ ? ` requestId=${String(error.requestId)}`
693
+ : "";
694
+ const providerCode = isRecord(error) && error.providerCode
695
+ ? ` providerCode=${String(error.providerCode)}`
696
+ : "";
697
+ const providerType = isRecord(error) && error.providerType
698
+ ? ` providerType=${String(error.providerType)}`
699
+ : "";
700
+ const providerParam = isRecord(error) && error.providerParam
701
+ ? ` providerParam=${String(error.providerParam)}`
702
+ : "";
703
+ ctx.logger?.warn?.(`[lcx-codex] native V2 compaction failed: code=${code}${status}${requestId}${providerCode}${providerType}${providerParam}`);
704
+ if (hasExistingCheckpoint || !fallbackEligible(error, options.signal))
705
+ throw error;
706
+ ctx.logger?.info?.("[lcx-codex] falling back to DSH basic compaction after allowlisted first-checkpoint native failure");
707
+ const stream = await next();
708
+ for await (const chunk of stream)
709
+ yield chunk;
710
+ }
711
+ }
712
+ function routedTargetForAgent(agent) {
713
+ const route = readAgentRouteState(agent);
714
+ const provider = stringValue(routeConfigValue(route.requestConfig, "provider") ??
715
+ routeConfigValue(route.options, "provider"), "");
716
+ const model = stringValue(routeConfigValue(route.requestConfig, "model") ??
717
+ routeConfigValue(route.options, "model"), "");
718
+ return provider && model ? { provider, model } : undefined;
719
+ }
531
720
  export function compactionPressureBand(totalTokens, contextWindow, policy) {
532
- const ratioPercent = totalTokens / contextWindow * 100
533
- return { ratioPercent, band: ratioPercent < policy.auto ? 'below' : ratioPercent < policy.emergency ? 'native' : 'emergency' }
721
+ const ratioPercent = (totalTokens / contextWindow) * 100;
722
+ return {
723
+ ratioPercent,
724
+ band: ratioPercent < policy.auto
725
+ ? "below"
726
+ : ratioPercent < policy.emergency
727
+ ? "native"
728
+ : "emergency",
729
+ };
534
730
  }
535
-
536
731
  function adjustedCompactionConfig(config, target, thresholdRatio) {
537
- if (!config || typeof config !== 'object') return config
538
- const modelPolicies = Array.isArray(config.modelPolicies)
539
- ? config.modelPolicies.map((policy) => policy?.provider === target.provider && policy?.model === target.model ? { ...policy, thresholdRatio } : policy)
540
- : config.modelPolicies
541
- return { ...config, thresholdRatio, ...(modelPolicies === undefined ? {} : { modelPolicies }) }
732
+ if (!isRecord(config))
733
+ return config;
734
+ const modelPolicies = Array.isArray(config.modelPolicies)
735
+ ? config.modelPolicies.map((policy) => isRecord(policy) &&
736
+ policy.provider === target.provider &&
737
+ policy.model === target.model
738
+ ? { ...policy, thresholdRatio }
739
+ : policy)
740
+ : config.modelPolicies;
741
+ return {
742
+ ...config,
743
+ thresholdRatio,
744
+ ...(modelPolicies === undefined ? {} : { modelPolicies }),
745
+ };
542
746
  }
543
-
544
747
  function combinedAbortSignal(primary, lifecycle) {
545
- if (!primary) return lifecycle
546
- if (!lifecycle) return primary
547
- if (primary === lifecycle) return primary
548
- return AbortSignal.any([primary, lifecycle])
549
- }
550
-
551
- function patchCompactionPressureService(compactionValue, state, getConfig, ctx, records, requestHeaders) {
552
- const candidate = compactionPatchCandidate(compactionValue, records)
553
- if (!candidate) return false
554
- const { compaction, original } = candidate
555
- const record = { compaction, original, wrapper: undefined, mutex: new ServiceMutex(), lifecycle: new AbortController() }
556
- const wrapper = async function(agentArg, trigger, signal) {
557
- const activeSignal = combinedAbortSignal(signal, record.lifecycle.signal)
558
- const callOriginal = () => {
559
- updateRequestHeaderCache(requestHeaders, agentArg?.session, { type: 'compaction/start' })
560
- return original.call(this, agentArg, trigger, activeSignal)
561
- }
562
- return record.mutex.run(activeSignal, async () => {
563
- if (trigger !== 'pressure' || !state.enabled || !state.autoCompaction) return callOriginal()
564
- const config = getConfig()
565
- const target = routedTargetForAgent(agentArg, config)
566
- if (!target || !resolveResponsesRouteConfig(ctx, target, config)) return callOriginal()
567
- const tokenMeter = resolveScopedService(agentArg, 'tokenMeter') ?? resolveContextService(this?.ctx, 'tokenMeter')
568
- const llm = resolveScopedService(agentArg, 'llm') ?? resolveContextService(ctx, 'llm')
569
- if (!tokenMeter?.measure || !llm?.resolveModelInfo) return callOriginal()
570
- let contextWindow
571
- try { contextWindow = (await llm.resolveModelInfo(target.provider, target.model, activeSignal))?.context?.contextWindow } catch { return callOriginal() }
572
- if (!Number.isFinite(contextWindow) || contextWindow <= 0) return callOriginal()
573
- const totalTokens = Number(tokenMeter.measure(agentArg.session)?.totalTokens ?? 0)
574
- const { auto, emergency } = pressurePolicy(state, config)
575
- const pressure = compactionPressureBand(totalTokens, contextWindow, { auto, emergency })
576
- const ratioPercent = pressure.ratioPercent
577
- if (pressure.band === 'below') return null
578
- const prunerState = toolResultPrunerState(resolveAgentService(ctx, agentArg, 'toolResultPruner') ?? resolveContextService(this?.ctx, 'toolResultPruner'))
579
- const configState = compactionConfigState(this)
580
- const nativeFirst = pressure.band === 'native'
581
- const noOpPrune = () => ({ pruned: [], charsRemoved: 0 })
582
- const prunerPatch = nativeFirst ? patchToolResultPruner(prunerState, noOpPrune) : undefined
583
- const configPatch = patchCompactionConfig(configState, originalConfig => adjustedCompactionConfig(originalConfig, target, auto / 100))
584
- ctx.logger?.info?.(`[lcx-codex] auto pressure ${ratioPercent.toFixed(1)}%: ${nativeFirst ? 'Native V2 first' : 'emergency DSH prune allowed'} (native ${auto}%, emergency ${emergency}%)`)
585
- try { return await callOriginal() }
586
- finally {
587
- restoreCompactionConfig(configPatch)
588
- restoreToolResultPruner(prunerPatch)
589
- }
590
- })
591
- }
592
- record.wrapper = wrapper
593
- return installCompactionPatch(records, record)
594
- }
595
-
596
- function patchCompactionPressureForAgent(agent, state, getConfig, ctx, records, requestHeaders) {
597
- return patchCompactionPressureService(resolveAgentService(ctx, agent, 'compaction'), state, getConfig, ctx, records, requestHeaders)
598
- }
599
-
748
+ return primary === lifecycle ? primary : AbortSignal.any([primary, lifecycle]);
749
+ }
750
+ function patchCompactionPressureService(compactionValue, state, getConfig, ctx, records) {
751
+ const candidate = compactionPatchCandidate(compactionValue, records);
752
+ if (!candidate)
753
+ return false;
754
+ const mutex = new ServiceMutex();
755
+ const policyScope = new InvocationPolicyScope();
756
+ const lifecycle = new AbortController();
757
+ return installCompactionPatch(records, candidate, mutex, policyScope, lifecycle, async (agent, trigger, signal, callOriginal) => {
758
+ const activeSignal = combinedAbortSignal(signal, lifecycle.signal);
759
+ const configScoped = policyScope.ensure(compactionConfigState(candidate.compaction).service, "config");
760
+ const prunerState = toolResultPrunerState(resolveAgentService(ctx, agent, "toolResultPruner") ??
761
+ resolveScopedService(agent, "toolResultPruner") ??
762
+ resolveContextService(ctx, "toolResultPruner"));
763
+ const prunerScoped = !prunerState.pruner ||
764
+ (typeof prunerState.original === "function" &&
765
+ policyScope.ensure(prunerState.pruner, "pruneSession"));
766
+ const accessMode = configScoped && prunerScoped ? "shared" : "exclusive";
767
+ return policyScope.run(activeSignal, accessMode, async () => {
768
+ const invoke = async () => {
769
+ if (trigger !== "pressure" || !state.enabled)
770
+ return callOriginal(activeSignal);
771
+ const config = getConfig();
772
+ const target = routedTargetForAgent(agent);
773
+ if (!target || !resolveResponsesRouteConfig(ctx, target, config))
774
+ return callOriginal(activeSignal);
775
+ const session = sessionFromAgent(agent);
776
+ const tokenMeter = resolveScopedService(agent, "tokenMeter") ??
777
+ resolveContextService(ctx, "tokenMeter");
778
+ if (!session || tokenMeter === undefined)
779
+ return callOriginal(activeSignal);
780
+ let contextWindow;
781
+ try {
782
+ contextWindow = (await ctx.llm.resolveModelInfo(target.provider, target.model, activeSignal)).context?.contextWindow;
783
+ }
784
+ catch {
785
+ return callOriginal(activeSignal);
786
+ }
787
+ const totalTokens = tokenMeterTotal(tokenMeter, session);
788
+ if (contextWindow === undefined ||
789
+ contextWindow <= 0 ||
790
+ totalTokens === undefined)
791
+ return callOriginal(activeSignal);
792
+ const auto = AUTO_COMPACTION_THRESHOLD_PERCENT;
793
+ const emergency = EMERGENCY_PRUNE_THRESHOLD_PERCENT;
794
+ const pressure = compactionPressureBand(totalTokens, contextWindow, {
795
+ auto,
796
+ emergency,
797
+ });
798
+ if (pressure.band === "below")
799
+ return null;
800
+ const nativeFirst = pressure.band === "native";
801
+ const prunerPatch = nativeFirst
802
+ ? patchToolResultPruner(prunerState, () => ({ pruned: [], charsRemoved: 0 }))
803
+ : undefined;
804
+ const configPatch = patchCompactionConfig(compactionConfigState(candidate.compaction), (originalConfig) => adjustedCompactionConfig(originalConfig, target, auto / 100));
805
+ ctx.logger?.info?.(`[lcx-codex] auto pressure ${pressure.ratioPercent.toFixed(1)}%: ${nativeFirst ? "Native V2 first" : "emergency DSH prune allowed"} (native ${auto}%, emergency ${emergency}%)`);
806
+ try {
807
+ return await callOriginal(activeSignal);
808
+ }
809
+ finally {
810
+ restoreCompactionConfig(configPatch);
811
+ restoreToolResultPruner(prunerPatch);
812
+ }
813
+ };
814
+ return accessMode === "shared"
815
+ ? invoke()
816
+ : mutex.run(activeSignal, invoke);
817
+ });
818
+ });
819
+ }
820
+ function patchCompactionPressureForAgent(agent, state, getConfig, ctx, records) {
821
+ return patchCompactionPressureService(resolveAgentService(ctx, agent, "compaction"), state, getConfig, ctx, records);
822
+ }
600
823
  async function restoreCompactionPressure(records) {
601
- const reason = new Error('lcx-codex pressure coordination is shutting down')
602
- const entries = [...records.values()]
603
- for (const record of entries) {
604
- if (!record.lifecycle.signal.aborted) record.lifecycle.abort(reason)
605
- }
606
- await Promise.allSettled(entries.map((record) => record.mutex.close(reason)))
607
- restoreCompactionPatches(records, entries)
608
- }
609
-
610
- function visibleWebSearchTimeout(state, getConfig) {
611
- const config = getConfig()
612
- const timeoutMs = Number.isFinite(state.webSearchTimeoutSeconds) ? Math.min(600_000, Math.max(30_000, Math.round(state.webSearchTimeoutSeconds * 1000))) : config.webSearchTimeoutMs
613
- return state.enabled && state.webSearch ? timeoutMs : undefined
614
- }
615
-
616
- function messagesContainNativeCheckpoint(messages, session) { return Boolean(session && (messages ?? []).some((message) => checkpointStateForMessage(session, message))) }
617
- function messagesContainLegacyCheckpoint(messages) { return (messages ?? []).some((message) => Boolean(legacyCheckpointId(message))) }
618
-
824
+ const reason = new Error("lcx-codex pressure coordination is shutting down");
825
+ const entries = [...records.values()];
826
+ for (const record of entries) {
827
+ if (!record.lifecycle.signal.aborted)
828
+ record.lifecycle.abort(reason);
829
+ }
830
+ await Promise.allSettled(entries.flatMap((record) => [
831
+ record.policyScope.close(reason),
832
+ record.mutex.close(reason),
833
+ ]));
834
+ restoreCompactionPatches(records, entries);
835
+ for (const record of entries)
836
+ record.policyScope.restore();
837
+ }
838
+ function messagesContainNativeCheckpoint(messages, session) {
839
+ return session !== undefined && messages.some((message) => checkpointStateForMessage(session, message));
840
+ }
619
841
  function inputHasNativeState(input) {
620
- return (input ?? []).some((item) => item?.type === 'compaction')
842
+ return input.some((item) => isRecord(item) && item.type === "compaction");
621
843
  }
622
-
623
844
  async function* managedResponsesStream(options, routeConfig, ctx) {
624
- const route = currentRoute(options, routeConfig)
625
- try {
626
- if (options.stop !== undefined) throw Object.assign(new Error('LCX Responses does not support GenerateOptions.stop'), { code: 'LCX_RESPONSES_UNSUPPORTED_OPTION' })
627
- const prepared = await serializeNativeAware(options.messages, route, routeConfig, ctx, { signal: options.signal, system: options.system, tools: options.tools })
628
- const cacheSessionId = promptCacheSessionId(route, routeConfig)
629
- const headers = await authenticatedHeaders(ctx, routeConfig, cacheSessionId, cacheSessionId === undefined ? null : undefined)
630
- const body = buildResponsesBody({
631
- model: prepared.model,
632
- input: prepared.input,
633
- tools: prepared.tools ?? options.tools,
634
- sessionId: cacheSessionId,
635
- promptCacheKey: promptCacheKey(route, routeConfig),
636
- promptCacheRetention: promptCacheRetention(routeConfig),
637
- cacheRetention: routeConfig.cacheRetention,
638
- reasoningEffort: options.reasoningEffort,
639
- temperature: options.temperature,
640
- maxTokens: options.maxTokens,
641
- })
642
- const nativeReplay = inputHasNativeState(prepared.input)
643
- if (nativeReplay) { body.tool_choice = 'auto'; body.parallel_tool_calls = true }
644
- yield* streamResponsesRequest({
645
- baseURL: routeConfig.baseURL,
646
- provider: route.provider,
647
- model: route.model,
648
- piModel: prepared.model,
649
- body,
650
- grammarToolInputProperties: prepared.grammarToolInputProperties,
651
- headers: nativeReplay ? mergeFeatureHeader(headers) : headers,
652
- signal: options.signal,
653
- timeoutMs: routeConfig.timeoutMs,
654
- maxAttempts: 1,
655
- maxResponseBytes: routeConfig.maxResponseBytes,
656
- })
657
- } catch (error) {
658
- yield managedFailureChunk(error, options.signal)
659
- }
660
- }
661
-
662
- async function* unavailableManagedRouteStream(options) {
663
- yield managedFailureChunk(Object.assign(new Error('LCX is enabled but the selected DSH route cannot be resolved as an authenticated OpenAI Responses wire route'), { code: 'LCX_RESPONSES_ROUTE_UNAVAILABLE' }), options.signal)
664
- }
665
-
666
- function installInjected(ctx, configInput = {}) {
667
- const baseConfig = normalizeConfig(configInput)
668
- let runtimeConfig = baseConfig
669
- 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 }
670
- const originalSearchProvider = readWebSearchProvider(ctx)
671
- let warnedWebSelection = false
672
- const provider = new LcxResponsesSearchProvider(ctx, () => runtimeConfig, () => state.enabled && state.webSearch)
673
- ctx.web.registerSearchProvider(provider)
674
- const tools = contextService(ctx, 'tools')
675
- let disposeAdvanced
676
- const capabilityStore = new AlphaCapabilityStore(baseConfig.alphaCapabilityPath)
677
- const refStore = new AlphaRefStore(baseConfig.alphaRefPath)
678
- const alphaAgents = new Set()
679
- const alphaToolRegistrations = new Map()
680
- const compactionPatchRecords = new Map()
681
- const patchedWebSearchDefinitions = new Map()
682
- const requestHeaders = new Map()
683
- const seedRequestHeader = (session) => updateRequestHeaderCache(requestHeaders, session, { type: 'compaction/start' })
684
- for (const session of sessionsService(ctx)?.list?.() ?? []) seedRequestHeader(session)
685
- ctx.on('session/created', (session) => {
686
- seedRequestHeader(session)
687
- }, { global: true })
688
- ctx.on('session/disposed', (session) => {
689
- requestHeaders.delete(String(session?.id ?? ''))
690
- for (const agent of alphaAgents) if (agent?.session === session) {
691
- disposeAlphaToolForAgent(agent, alphaToolRegistrations)
692
- alphaAgents.delete(agent)
845
+ const route = currentRoute(options, routeConfig);
846
+ try {
847
+ if (options.stop !== undefined)
848
+ throw Object.assign(new Error("LCX Responses does not support GenerateOptions.stop"), { code: "LCX_RESPONSES_UNSUPPORTED_OPTION" });
849
+ const prepared = await serializeNativeAware(options.messages, route, routeConfig, ctx, { signal: options.signal, tools: options.tools });
850
+ const cacheSessionId = promptCacheSessionId(route, routeConfig, ctx);
851
+ const headers = await authenticatedHeaders(ctx, routeConfig, cacheSessionId, cacheSessionId === undefined ? null : route.sessionId);
852
+ const body = buildResponsesBody({
853
+ model: prepared.model,
854
+ input: prepared.input,
855
+ tools: prepared.tools ?? options.tools,
856
+ sessionId: cacheSessionId,
857
+ promptCacheKey: promptCacheKey(route, routeConfig, ctx),
858
+ promptCacheRetention: promptCacheRetention(routeConfig),
859
+ cacheRetention: routeConfig.cacheRetention,
860
+ reasoningEffort: options.reasoningEffort,
861
+ temperature: options.temperature,
862
+ maxTokens: options.maxTokens,
863
+ });
864
+ const nativeReplay = inputHasNativeState(prepared.input);
865
+ if (nativeReplay) {
866
+ body.tool_choice = "auto";
867
+ body.parallel_tool_calls = true;
868
+ }
869
+ yield* streamResponsesRequest({
870
+ baseURL: routeConfig.baseURL,
871
+ provider: route.provider,
872
+ model: route.model,
873
+ piModel: prepared.model,
874
+ body,
875
+ grammarToolInputProperties: prepared.grammarToolInputProperties,
876
+ headers: nativeReplay ? mergeFeatureHeader(headers) : headers,
877
+ signal: options.signal,
878
+ timeoutMs: routeConfig.timeoutMs,
879
+ maxAttempts: 1,
880
+ maxResponseBytes: routeConfig.maxResponseBytes,
881
+ });
693
882
  }
694
- }, { global: true })
695
- ctx.on('session/event', (session, event) => {
696
- updateRequestHeaderCache(requestHeaders, session, event)
697
- if (event?.type === 'request/header') for (const agent of alphaAgents) if (agent?.session === session) {
698
- syncAlphaToolForAgent(ctx, agent, state, () => runtimeConfig, capabilityStore, refStore, alphaToolRegistrations)
883
+ catch (error) {
884
+ yield managedFailureChunk(error, options.signal);
699
885
  }
700
- }, { global: true })
701
-
702
- const refreshTools = () => {
703
- if (state.enabled && state.webSearch && state.advancedHostedSearch && !disposeAdvanced && tools?.register) disposeAdvanced = tools.register(createAdvancedHostedTool(ctx, state, () => runtimeConfig))
704
- if ((!state.enabled || !state.webSearch || !state.advancedHostedSearch) && disposeAdvanced) { disposeAdvanced(); disposeAdvanced = undefined }
705
- for (const agent of alphaAgents) syncAlphaToolForAgent(ctx, agent, state, () => runtimeConfig, capabilityStore, refStore, alphaToolRegistrations)
706
- if (ctx.web) {
707
- const target = state.enabled && state.webSearch ? runtimeConfig.webSearchProvider : originalSearchProvider
708
- const selected = writeWebSearchProvider(ctx, target)
709
- if (!selected && state.enabled && state.webSearch && !warnedWebSelection) {
710
- warnedWebSelection = true
711
- 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.')
712
- }
886
+ }
887
+ async function* managedGrokNativeSearchStream(options, routeConfig, nativeSearch, ctx) {
888
+ const route = currentRoute(options, routeConfig);
889
+ const nativeReplayRoute = grokReplayRoute(route, routeConfig);
890
+ try {
891
+ if (options.stop !== undefined)
892
+ throw Object.assign(new Error("LCX Responses does not support GenerateOptions.stop"), { code: "LCX_RESPONSES_UNSUPPORTED_OPTION" });
893
+ const visibleTools = grokVisibleFunctionTools(options.tools, nativeSearch);
894
+ const declaredToolNames = new Set(visibleTools?.map((tool) => tool.name) ?? []);
895
+ const prepared = await serializeNativeAware(options.messages, route, routeConfig, ctx, {
896
+ signal: options.signal,
897
+ tools: visibleTools,
898
+ grokNativeReplayRoute: nativeReplayRoute,
899
+ });
900
+ const cacheSessionId = grokPromptCacheSessionId(route, routeConfig);
901
+ const headers = await authenticatedGrokHeaders(ctx, routeConfig, cacheSessionId);
902
+ const model = {
903
+ ...prepared.model,
904
+ ...routeConfig.modelDefaults,
905
+ ...routeConfig.modelControls,
906
+ id: route.model,
907
+ provider: route.provider,
908
+ baseUrl: routeConfig.baseURL,
909
+ api: "openai-responses",
910
+ };
911
+ const effectiveReasoning = options.reasoningEffort ?? routeConfig.profileReasoning;
912
+ if (effectiveReasoning !== undefined &&
913
+ !getSupportedThinkingLevels(model).some((level) => level === effectiveReasoning))
914
+ throw Object.assign(new Error(`Grok route does not support reasoning effort "${String(effectiveReasoning)}"`), { code: "LCX_RESPONSES_UNSUPPORTED_OPTION" });
915
+ const body = buildResponsesBody({
916
+ model,
917
+ input: prepared.input,
918
+ tools: grokWireTools(prepared.tools ?? visibleTools, nativeSearch),
919
+ sessionId: cacheSessionId,
920
+ promptCacheRetention: promptCacheRetention(routeConfig),
921
+ cacheRetention: routeConfig.cacheRetention,
922
+ reasoningEffort: effectiveReasoning,
923
+ temperature: options.temperature,
924
+ maxTokens: options.maxTokens,
925
+ });
926
+ if (nativeSearch.web) {
927
+ const include = new Set(Array.isArray(body.include) ? body.include : []);
928
+ include.add("web_search_call.action.sources");
929
+ body.include = [...include];
930
+ }
931
+ yield* streamResponsesRequest({
932
+ baseURL: routeConfig.baseURL,
933
+ provider: route.provider,
934
+ model: route.model,
935
+ piModel: model,
936
+ body,
937
+ grammarToolInputProperties: prepared.grammarToolInputProperties,
938
+ headers,
939
+ signal: options.signal,
940
+ timeoutMs: routeConfig.timeoutMs,
941
+ applyDefaultTimeout: false,
942
+ streamIdleTimeoutMs: routeConfig.streamIdleTimeoutMs,
943
+ maxAttempts: 1,
944
+ maxResponseBytes: routeConfig.maxResponseBytes,
945
+ serverToolTypes: GROK_NATIVE_SERVER_TOOL_TYPES,
946
+ isServerToolItem: (item) => isGrokNativeServerToolItem(item, nativeSearch, declaredToolNames),
947
+ declaredToolNames,
948
+ nativeReplayRoute,
949
+ onServerToolUsage(usage) {
950
+ ctx.logger?.info?.(`[lcx-codex] Grok native search used ${usage.total} server-side tool call(s) (web=${usage.webSearchCalls}, x=${usage.xSearchCalls})`);
951
+ },
952
+ });
713
953
  }
714
- }
715
-
716
- let source = () => ({ ...baseConfig, ...state })
717
- installSettingsSection(ctx, SETTINGS_NS, SettingsSchema, {
718
- enabled: false, webSearch: false, advancedHostedSearch: false, alphaSearch: false, remoteCompaction: false, fallbackToBasicCompaction: true, autoCompaction: true,
719
- webSearchTimeoutSeconds: baseConfig.webSearchTimeoutMs / 1000,
720
- autoCompactionThresholdPercent: baseConfig.autoCompactionThresholdPercent,
721
- emergencyPruneThresholdPercent: baseConfig.emergencyPruneThresholdPercent,
722
- provider: baseConfig.provider, baseURL: baseConfig.baseURL, apiKeyEnv: baseConfig.apiKeyEnv, model: baseConfig.model,
723
- }, {
724
- setSource: (current) => { source = current },
725
- onChange: () => {
726
- const value = source()
727
- 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)
728
- state.fallbackToBasicCompaction = value.fallbackToBasicCompaction !== false; state.autoCompaction = value.autoCompaction !== false
729
- state.webSearchTimeoutSeconds = Number.isFinite(value.webSearchTimeoutSeconds) ? value.webSearchTimeoutSeconds : baseConfig.webSearchTimeoutMs / 1000
730
- state.autoCompactionThresholdPercent = clampPercent(value.autoCompactionThresholdPercent, baseConfig.autoCompactionThresholdPercent, 85, 95)
731
- state.emergencyPruneThresholdPercent = clampPercent(value.emergencyPruneThresholdPercent, baseConfig.emergencyPruneThresholdPercent, 90, 99)
732
- runtimeConfig = normalizeConfig({ ...baseConfig, provider: value.provider ?? baseConfig.provider, baseURL: value.baseURL ?? baseConfig.baseURL, apiKeyEnv: value.apiKeyEnv ?? baseConfig.apiKeyEnv, model: value.model ?? baseConfig.model })
733
- refreshTools(); refreshVisibleWebSearchTimeouts(patchedWebSearchDefinitions, visibleWebSearchTimeout(state, () => runtimeConfig))
734
- },
735
- })
736
- try {
737
- const value = source()
738
- 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) })
739
- runtimeConfig = normalizeConfig({ ...baseConfig, provider: value.provider ?? baseConfig.provider, baseURL: value.baseURL ?? baseConfig.baseURL, apiKeyEnv: value.apiKeyEnv ?? baseConfig.apiKeyEnv, model: value.model ?? baseConfig.model })
740
- } catch {}
741
- refreshTools()
742
- ctx.inject(['compaction'], compactionCtx => {
743
- patchCompactionPressureService(resolveContextService(compactionCtx, 'compaction'), state, () => runtimeConfig, ctx, compactionPatchRecords, requestHeaders)
744
- })
745
-
746
- ctx.on('tools/execute', async (exec, next) => {
747
- if (!state.enabled || !state.webSearch || exec?.name !== 'web_search') return next()
748
- const active = activeAgentRoute(exec, runtimeConfig)
749
- return hostedSearchRouteContext.run(active, () => next())
750
- })
751
-
752
- ctx.on('agent/created', ({ agent }) => {
753
- if (!agent) return
754
- alphaAgents.add(agent)
755
- syncAlphaToolForAgent(ctx, agent, state, () => runtimeConfig, capabilityStore, refStore, alphaToolRegistrations)
756
- const installed = patchCompactionPressureForAgent(agent, state, () => runtimeConfig, ctx, compactionPatchRecords, requestHeaders)
757
- if (installed) ctx.logger?.info?.('[lcx-codex] pressure coordination installed through AgentPresets service resolver')
758
- }, { global: true })
759
-
760
- ctx.on('agent/status', ({ agent, status }) => {
761
- if (status !== 'running' || !agent) return
762
- alphaAgents.add(agent)
763
- syncAlphaToolForAgent(ctx, agent, state, () => runtimeConfig, capabilityStore, refStore, alphaToolRegistrations)
764
- const installed = patchCompactionPressureForAgent(agent, state, () => runtimeConfig, ctx, compactionPatchRecords, requestHeaders)
765
- if (installed) ctx.logger?.info?.('[lcx-codex] pressure coordination installed through AgentPresets service resolver')
766
- patchVisibleWebSearchTimeout(agent, () => visibleWebSearchTimeout(state, () => runtimeConfig), patchedWebSearchDefinitions)
767
- }, { global: true })
768
-
769
- ctx.on('llm/stream', (options, next) => {
770
- if (!state.enabled || options.purpose === 'session-title') return next()
771
- const routeConfig = resolveResponsesRouteConfig(ctx, options, runtimeConfig)
772
- if (options.purpose === 'compaction') {
773
- if (!routeConfig) return unavailableManagedRouteStream(options)
774
- return remoteCompactionStream(options, routeConfig, state, ctx, next, requestHeaders)
954
+ catch (error) {
955
+ yield managedFailureChunk(error, options.signal);
956
+ }
957
+ }
958
+ function isGptLifecycleTarget(options) {
959
+ return /^gpt-/iu.test(String(options.model ?? ""));
960
+ }
961
+ async function* unavailableManagedRouteStream(options) {
962
+ yield managedFailureChunk(Object.assign(new Error("LCX is enabled but the selected DSH route cannot be resolved as an authenticated OpenAI Responses wire route"), { code: "LCX_RESPONSES_ROUTE_UNAVAILABLE" }), options.signal);
963
+ }
964
+ function installInjected(ctx, configInput = {}) {
965
+ installSearchUsage(ctx);
966
+ const baseConfig = normalizeConfig(configInput);
967
+ let runtimeConfig = baseConfig;
968
+ const state = {
969
+ enabled: false,
970
+ webSearch: false,
971
+ advancedHostedSearch: false,
972
+ alphaSearch: false,
973
+ grokNativeWebSearch: false,
974
+ grokNativeXSearch: false,
975
+ };
976
+ const provider = new LcxResponsesSearchProvider(ctx, () => runtimeConfig, () => state.enabled && state.webSearch);
977
+ const capabilityStore = new AlphaCapabilityStore(baseConfig.alphaCapabilityPath);
978
+ const refStore = new AlphaRefStore(baseConfig.alphaRefPath);
979
+ const managedAgents = new Set();
980
+ const gptToolRegistrations = new Map();
981
+ const alphaToolRegistrations = new Map();
982
+ const compactionPatchRecords = new Map();
983
+ const syncAgentTools = (agent) => {
984
+ syncGptToolsForAgent(ctx, agent, state, () => runtimeConfig, provider, gptToolRegistrations);
985
+ syncAlphaToolForAgent(ctx, agent, state, () => runtimeConfig, capabilityStore, refStore, alphaToolRegistrations);
986
+ };
987
+ const refreshTools = () => {
988
+ for (const agent of managedAgents)
989
+ syncAgentTools(agent);
990
+ };
991
+ ctx.on("session/disposed", (session) => {
992
+ for (const agent of managedAgents)
993
+ if (agentUsesSession(agent, session)) {
994
+ disposeGptToolsForAgent(agent, gptToolRegistrations);
995
+ disposeAlphaToolForAgent(agent, alphaToolRegistrations);
996
+ managedAgents.delete(agent);
997
+ }
998
+ }, { global: true });
999
+ ctx.on("session/event", (session, event) => {
1000
+ if (event.type === "request/header")
1001
+ for (const agent of managedAgents)
1002
+ if (agentUsesSession(agent, session))
1003
+ syncAgentTools(agent);
1004
+ }, { global: true });
1005
+ const settingsEntry = {
1006
+ enabled: false,
1007
+ webSearch: false,
1008
+ advancedHostedSearch: false,
1009
+ alphaSearch: false,
1010
+ grokNativeWebSearch: false,
1011
+ grokNativeXSearch: false,
1012
+ searchMediaPreview: false,
1013
+ };
1014
+ let source = () => settingsEntry;
1015
+ ctx.settings.installSection(ctx, SETTINGS_NS, SettingsSchema, settingsEntry, {
1016
+ setSource(current) {
1017
+ source = current;
1018
+ },
1019
+ onChange() {
1020
+ const value = source();
1021
+ state.enabled = value.enabled;
1022
+ state.webSearch = value.webSearch;
1023
+ state.advancedHostedSearch = value.advancedHostedSearch;
1024
+ state.alphaSearch = value.alphaSearch;
1025
+ state.grokNativeWebSearch = value.grokNativeWebSearch;
1026
+ state.grokNativeXSearch = value.grokNativeXSearch;
1027
+ runtimeConfig = baseConfig;
1028
+ refreshTools();
1029
+ },
1030
+ });
1031
+ try {
1032
+ const value = source();
1033
+ Object.assign(state, {
1034
+ enabled: Boolean(value.enabled),
1035
+ webSearch: Boolean(value.webSearch),
1036
+ advancedHostedSearch: Boolean(value.advancedHostedSearch),
1037
+ alphaSearch: Boolean(value.alphaSearch),
1038
+ grokNativeWebSearch: Boolean(value.grokNativeWebSearch),
1039
+ grokNativeXSearch: Boolean(value.grokNativeXSearch),
1040
+ });
1041
+ runtimeConfig = baseConfig;
775
1042
  }
776
- if (!routeConfig) return unavailableManagedRouteStream(options)
777
- return managedResponsesStream(options, routeConfig, ctx)
778
- })
779
-
780
- ctx.effect?.(() => async () => {
781
- disposeAdvanced?.()
782
- for (const agent of alphaAgents) disposeAlphaToolForAgent(agent, alphaToolRegistrations)
783
- alphaAgents.clear()
784
- await restoreCompactionPressure(compactionPatchRecords)
785
- restoreVisibleWebSearchTimeouts(patchedWebSearchDefinitions)
786
- writeWebSearchProvider(ctx, originalSearchProvider)
787
- requestHeaders.clear()
788
- }, 'lcx-codex cleanup')
789
- }
790
-
1043
+ catch { }
1044
+ refreshTools();
1045
+ ctx.inject(["compaction"], (compactionCtx) => {
1046
+ patchCompactionPressureService(resolveContextService(compactionCtx, "compaction"), state, () => runtimeConfig, ctx, compactionPatchRecords);
1047
+ });
1048
+ ctx.on("agent/created", ({ agent }) => {
1049
+ if (agent === null || typeof agent !== "object")
1050
+ return;
1051
+ managedAgents.add(agent);
1052
+ syncAgentTools(agent);
1053
+ const installed = patchCompactionPressureForAgent(agent, state, () => runtimeConfig, ctx, compactionPatchRecords);
1054
+ if (installed)
1055
+ ctx.logger?.info?.("[lcx-codex] pressure coordination installed through AgentPresets service resolver");
1056
+ }, { global: true });
1057
+ ctx.on("agent/status", ({ agent, status }) => {
1058
+ if (status !== "running" ||
1059
+ agent === null ||
1060
+ typeof agent !== "object")
1061
+ return;
1062
+ managedAgents.add(agent);
1063
+ syncAgentTools(agent);
1064
+ const installed = patchCompactionPressureForAgent(agent, state, () => runtimeConfig, ctx, compactionPatchRecords);
1065
+ if (installed)
1066
+ ctx.logger?.info?.("[lcx-codex] pressure coordination installed through AgentPresets service resolver");
1067
+ }, { global: true });
1068
+ ctx.on("llm/stream", (options, next) => {
1069
+ if (options.purpose === "session-title")
1070
+ return next();
1071
+ const grokNativeSearch = {
1072
+ web: state.grokNativeWebSearch,
1073
+ x: state.grokNativeXSearch,
1074
+ };
1075
+ if (options.purpose === undefined && grokNativeSearchEnabled(grokNativeSearch)) {
1076
+ const grokRoute = resolveGrokResponsesRouteConfig(ctx, options, runtimeConfig);
1077
+ if (grokRoute)
1078
+ return managedGrokNativeSearchStream(options, routeWithPolicies(grokRoute, runtimeConfig), grokNativeSearch, ctx);
1079
+ }
1080
+ if (!state.enabled || !isGptLifecycleTarget(options))
1081
+ return next();
1082
+ const routeConfig = resolveResponsesRouteConfig(ctx, options, runtimeConfig);
1083
+ if (options.purpose === "compaction") {
1084
+ if (!routeConfig)
1085
+ return unavailableManagedRouteStream(options);
1086
+ return remoteCompactionStream(options, routeWithPolicies(routeConfig, runtimeConfig), ctx, next);
1087
+ }
1088
+ if (!routeConfig)
1089
+ return unavailableManagedRouteStream(options);
1090
+ return managedResponsesStream(options, routeWithPolicies(routeConfig, runtimeConfig), ctx);
1091
+ });
1092
+ ctx.effect?.(() => async () => {
1093
+ for (const agent of managedAgents) {
1094
+ disposeGptToolsForAgent(agent, gptToolRegistrations);
1095
+ disposeAlphaToolForAgent(agent, alphaToolRegistrations);
1096
+ }
1097
+ managedAgents.clear();
1098
+ await restoreCompactionPressure(compactionPatchRecords);
1099
+ }, "lcx-codex cleanup");
1100
+ }
791
1101
  export function apply(ctx, configInput = {}) {
792
- return installInjected(ctx, configInput)
1102
+ return installInjected(ctx, configInput);
793
1103
  }
794
-
795
- apply.inject = inject
796
- apply.Config = Config
797
-
798
- export default apply
1104
+ apply.inject = inject;
1105
+ apply.Config = Config;
1106
+ export default apply;