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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +116 -202
  2. package/cordis.patch.yml +3 -20
  3. package/lib/client.js +273 -146
  4. package/lib/compact-v2.js +218 -199
  5. package/lib/dsh-compat.js +227 -100
  6. package/lib/dsh-responses.js +445 -277
  7. package/lib/index.js +851 -757
  8. package/lib/json-store.js +50 -31
  9. package/lib/native-checkpoint.js +520 -194
  10. package/lib/responses-request.js +106 -121
  11. package/lib/responses-stream.js +972 -443
  12. package/lib/route.js +229 -355
  13. package/lib/service-mutex.js +73 -64
  14. package/lib/token-budget.js +176 -108
  15. package/lib/transport.js +277 -67
  16. package/lib/types/client/index.d.ts +6 -0
  17. package/lib/types/compact-v2.d.ts +104 -0
  18. package/lib/types/dsh-compat.d.ts +78 -0
  19. package/lib/types/dsh-responses.d.ts +82 -0
  20. package/lib/types/index.d.ts +83 -0
  21. package/lib/types/json-store.d.ts +10 -0
  22. package/lib/types/native-checkpoint.d.ts +213 -0
  23. package/lib/types/responses-request.d.ts +58 -0
  24. package/lib/types/responses-stream.d.ts +51 -0
  25. package/lib/types/route.d.ts +132 -0
  26. package/lib/types/service-mutex.d.ts +14 -0
  27. package/lib/types/token-budget.d.ts +50 -0
  28. package/lib/types/transport.d.ts +20 -0
  29. package/lib/types/web-run-output.d.ts +29 -0
  30. package/lib/types/web-search-alpha.d.ts +286 -0
  31. package/lib/types/web-search-capability.d.ts +26 -0
  32. package/lib/types/web-search-hosted.d.ts +246 -0
  33. package/lib/types/web-search-ref-store.d.ts +22 -0
  34. package/lib/web-run-output.js +167 -18
  35. package/lib/web-search-alpha.js +865 -163
  36. package/lib/web-search-capability.js +55 -65
  37. package/lib/web-search-hosted.js +210 -32
  38. package/lib/web-search-ref-store.js +63 -59
  39. package/package.json +79 -27
  40. package/ARCHITECTURE.md +0 -117
  41. package/CHANGELOG.md +0 -224
  42. package/README_EN.md +0 -277
  43. package/assets/dsh-lcx-codex-banner.jpg +0 -0
  44. package/lib/legacy-v3.js +0 -20
  45. package/lib/responses-replay.js +0 -68
  46. package/scripts/probe-alpha.mjs +0 -43
  47. package/scripts/validate-dsh-schema.mjs +0 -31
package/lib/index.js CHANGED
@@ -1,798 +1,892 @@
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 "@deepseek-ai/dsh-agent";
3
+ import "@deepseek-ai/dsh-tools";
4
+ import "@deepseek-ai/dsh-web";
5
+ import { randomUUID } from "node:crypto";
6
+ import { homedir } from "node:os";
7
+ import { join } from "node:path";
8
+ import { AsyncLocalStorage } from "node:async_hooks";
9
+ import { fetchJsonWithRetry } from "./transport.js";
10
+ import { agentSessionId, agentUsesSession, compactionConfigState, compactionPatchCandidate, installCompactionPatch, patchCompactionConfig, patchToolResultPruner, patchVisibleWebSearchTimeout, readAgentRouteState, readWebSearchProvider, refreshVisibleWebSearchTimeouts, resolveAgentService, resolveContextService, resolveScopedService, restoreCompactionConfig, restoreCompactionPatches, restoreToolResultPruner, restoreVisibleWebSearchTimeouts, sessionFor, sessionFromAgent, scopedToolRuntime, tokenMeterTotal, toolResultPrunerState, writeWebSearchProvider, } from "./dsh-compat.js";
11
+ import { authenticatedHeaders, currentRoute, generationControlsFromSession, promptCacheKey, promptCacheRetention, promptCacheSessionId, resolveResponsesRouteConfig, routeFingerprint, } from "./route.js";
12
+ import { DEFAULT_MAX_REQUEST_IMAGE_BYTES, DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET, DEFAULT_REQUEST_IMAGE_MAX_BYTES, hydrateNativeImageReferences, resolveModelImageSupport, responseInputItems, serializeDshMessages, } from "./dsh-responses.js";
13
+ import { mergeFeatureHeader, requestNativeCompaction } from "./compact-v2.js";
14
+ import { buildResponsesBody } from "./responses-request.js";
15
+ import { managedFailureChunk, streamResponsesRequest, } from "./responses-stream.js";
16
+ import { assertSupportedCheckpointMessage, checkpointStateForMessage, compactCheckpointId, createNativeCheckpointBlock, nativeCheckpointChunks, portableMessagesForCheckpoint, stateRouteCompatible, } from "./native-checkpoint.js";
17
+ import { HOSTED_SEARCH_OUTPUT, HOSTED_SEARCH_PARAMETERS, buildHostedSearchBody, normalizeHostedSearchArgs, parseHostedSearchResponse, renderHostedSearchResult, } from "./web-search-hosted.js";
18
+ import { ALPHA_SCHEMA_FINGERPRINT, alphaRefRequiresStore, isAlphaContinuationUrl, isAlphaHttpUrl, ALPHA_SEARCH_OUTPUT, ALPHA_SEARCH_PARAMETERS, buildAlphaSearchBody, normalizeAlphaSearchArgs, parseAlphaSearchResponse, renderAlphaSearchResult, } from "./web-search-alpha.js";
19
+ import { AlphaCapabilityStore, alphaCapabilityFingerprint, alphaCapabilityUsable, } from "./web-search-capability.js";
20
+ import { AlphaRefStore } from "./web-search-ref-store.js";
21
+ import { ServiceMutex } from "./service-mutex.js";
22
+ export const name = "lcx-codex";
23
+ export const inject = ["llm", "web", "sessions", "tools", "settings", "credentials", "attachments", "fs"];
24
+ const SETTINGS_NS = "lcx-codex";
25
+ const ADVANCED_HOSTED_TOOL = "websearch_gpt_advanced";
26
+ const ALPHA_TOOL = "websearch_alpha";
27
+ const hostedSearchRouteContext = new AsyncLocalStorage();
28
+ const WEB_SEARCH_TIMEOUT_MS = 240_000;
29
+ const AUTO_COMPACTION_THRESHOLD_PERCENT = 90;
30
+ const EMERGENCY_PRUNE_THRESHOLD_PERCENT = 95;
31
+ function positiveInteger(value, fallback, maximum) {
32
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0)
33
+ return fallback;
34
+ return maximum === undefined ? value : Math.min(value, maximum);
35
+ }
36
+ function stringValue(value, fallback) {
37
+ return typeof value === "string" && value.trim() ? value : fallback;
38
+ }
39
+ function routeWithPolicies(route, config) {
40
+ return {
41
+ ...route,
42
+ alphaProfile: config.alphaProfile,
43
+ alphaGroup: config.alphaGroup,
44
+ alphaMaxOutputTokens: config.alphaMaxOutputTokens,
45
+ assistantRetentionPerMessageTokenCap: config.assistantRetentionPerMessageTokenCap,
46
+ assistantRetentionTokenReserve: config.assistantRetentionTokenReserve,
47
+ maxRequestImageBytes: route.maxRequestImageBytes ?? config.maxRequestImageBytes,
48
+ maxResponseBytes: config.maxResponseBytes,
49
+ nativeRetentionTokenBudget: config.nativeRetentionTokenBudget,
50
+ portableReplayMaxChars: config.portableReplayMaxChars,
51
+ requestImageMaxBytes: route.requestImageMaxBytes ?? config.requestImageMaxBytes,
52
+ requestImagePixelBudget: route.requestImagePixelBudget ?? config.requestImagePixelBudget,
53
+ webMaxResults: config.webMaxResults,
54
+ };
55
+ }
56
+ function isRecord(value) {
57
+ return value !== null && typeof value === "object";
58
+ }
59
+ function asAbortSignal(value) {
60
+ return value instanceof AbortSignal ? value : undefined;
61
+ }
62
+ function errorDetails(value) {
63
+ return isRecord(value)
64
+ ? {
65
+ name: value.name,
66
+ code: value.code,
67
+ status: value.status,
68
+ message: value.message,
69
+ }
70
+ : {};
71
+ }
72
+ function dshHome() {
73
+ return process.env.DSH_HOME ?? join(homedir(), ".dsh");
74
+ }
75
+ function defaultAlphaCapabilityPath() {
76
+ return join(dshHome(), "storages", "lcx-codex", "web-alpha-capabilities.json");
77
+ }
78
+ function defaultAlphaRefPath() {
79
+ return join(dshHome(), "storages", "lcx-codex", "web-alpha-refs.json");
80
+ }
105
81
  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
-
82
+ supportsLongCacheRetention: z.boolean().default(false),
83
+ supportsExplicitPromptCacheMode: z.boolean().default(false),
84
+ alphaCapabilityPath: z.string().default(""),
85
+ alphaRefPath: z.string().default(""),
86
+ alphaProfile: z.string().default(""),
87
+ alphaGroup: z.string().default(""),
88
+ alphaMaxOutputTokens: z.number().default(2500),
89
+ webSearchProvider: z.string().default("lcx-responses"),
90
+ webMaxResults: z.number().default(8),
91
+ timeoutMs: z.number().default(300000),
92
+ maxResponseBytes: z.number().default(8 * 1024 * 1024),
93
+ maxAttempts: z.number().default(3),
94
+ maxRequestImageBytes: z.number().default(DEFAULT_MAX_REQUEST_IMAGE_BYTES),
95
+ requestImagePixelBudget: z
96
+ .number()
97
+ .default(DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET),
98
+ requestImageMaxBytes: z.number().default(DEFAULT_REQUEST_IMAGE_MAX_BYTES),
99
+ portableReplayMaxChars: z.number().default(80_000),
100
+ nativeRetentionTokenBudget: z.number().default(64_000),
101
+ assistantRetentionTokenReserve: z.number().default(24_000),
102
+ assistantRetentionPerMessageTokenCap: z.number().default(3_000),
103
+ });
135
104
  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
-
105
+ enabled: z.boolean().default(false),
106
+ webSearch: z.boolean().default(false),
107
+ advancedHostedSearch: z.boolean().default(false),
108
+ alphaSearch: z.boolean().default(false),
109
+ });
152
110
  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
-
111
+ return {
112
+ supportsLongCacheRetention: input.supportsLongCacheRetention === true,
113
+ supportsExplicitPromptCacheMode: input.supportsExplicitPromptCacheMode === true,
114
+ alphaCapabilityPath: stringValue(input.alphaCapabilityPath, defaultAlphaCapabilityPath()),
115
+ alphaRefPath: stringValue(input.alphaRefPath, defaultAlphaRefPath()),
116
+ alphaProfile: String(input.alphaProfile ?? ""),
117
+ alphaGroup: String(input.alphaGroup ?? ""),
118
+ alphaMaxOutputTokens: positiveInteger(input.alphaMaxOutputTokens, 2500, 32_000),
119
+ webSearchProvider: stringValue(input.webSearchProvider, "lcx-responses"),
120
+ webMaxResults: positiveInteger(input.webMaxResults, 8),
121
+ timeoutMs: positiveInteger(input.timeoutMs, 300000),
122
+ maxResponseBytes: positiveInteger(input.maxResponseBytes, 8 * 1024 * 1024),
123
+ maxAttempts: positiveInteger(input.maxAttempts, 3, 6),
124
+ maxRequestImageBytes: positiveInteger(input.maxRequestImageBytes, DEFAULT_MAX_REQUEST_IMAGE_BYTES),
125
+ requestImagePixelBudget: positiveInteger(input.requestImagePixelBudget, DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET),
126
+ requestImageMaxBytes: positiveInteger(input.requestImageMaxBytes, DEFAULT_REQUEST_IMAGE_MAX_BYTES),
127
+ portableReplayMaxChars: positiveInteger(input.portableReplayMaxChars, 80_000),
128
+ nativeRetentionTokenBudget: positiveInteger(input.nativeRetentionTokenBudget, 64_000),
129
+ assistantRetentionTokenReserve: typeof input.assistantRetentionTokenReserve === "number" &&
130
+ Number.isSafeInteger(input.assistantRetentionTokenReserve) &&
131
+ input.assistantRetentionTokenReserve >= 0
132
+ ? input.assistantRetentionTokenReserve
133
+ : 24_000,
134
+ assistantRetentionPerMessageTokenCap: positiveInteger(input.assistantRetentionPerMessageTokenCap, 3_000),
135
+ };
136
+ }
185
137
  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
-
138
+ const error = new Error(message, cause === undefined ? undefined : { cause });
139
+ error.name = "WebError";
140
+ error.code = code;
141
+ return error;
142
+ }
143
+ function routeConfigValue(value, name) {
144
+ return isRecord(value) ? value[name] : undefined;
145
+ }
146
+ function activeAgentRoute(exec) {
147
+ const route = readAgentRouteState(exec.agent);
148
+ return {
149
+ provider: stringValue(routeConfigValue(route.requestConfig, "provider") ??
150
+ routeConfigValue(route.options, "provider"), ""),
151
+ model: stringValue(routeConfigValue(route.requestConfig, "model") ??
152
+ routeConfigValue(route.options, "model"), ""),
153
+ sessionId: route.sessionId,
154
+ };
155
+ }
156
+ function selectedAgentRoute(agent) {
157
+ const route = readAgentRouteState(agent);
158
+ return {
159
+ provider: stringValue(routeConfigValue(route.options, "provider") ??
160
+ routeConfigValue(route.requestConfig, "provider"), ""),
161
+ model: stringValue(routeConfigValue(route.options, "model") ??
162
+ routeConfigValue(route.requestConfig, "model"), ""),
163
+ sessionId: route.sessionId,
164
+ };
165
+ }
208
166
  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
-
167
+ return {
168
+ imageSupport,
169
+ signal,
170
+ imageMap,
171
+ maxRequestImageBytes: routeConfig.maxRequestImageBytes,
172
+ requestImagePixelBudget: routeConfig.requestImagePixelBudget,
173
+ requestImageMaxBytes: routeConfig.requestImageMaxBytes,
174
+ ...extra,
175
+ };
176
+ }
177
+ async function executeHostedSearch(ctx, routeConfig, args, signal, sessionId = "") {
178
+ const normalized = normalizeHostedSearchArgs(args);
179
+ const requestId = randomUUID();
180
+ const route = currentRoute({ provider: routeConfig.provider, model: routeConfig.model, sessionId }, routeConfig);
181
+ const headers = await authenticatedHeaders(ctx, routeConfig, sessionId, requestId);
182
+ const searchCacheKey = sessionId
183
+ ? `dsh-lcx-search:${routeFingerprint(route)}`
184
+ : undefined;
185
+ const body = buildHostedSearchBody(normalized, routeConfig.model, {
186
+ promptCacheKey: searchCacheKey,
187
+ });
188
+ const response = await fetchJsonWithRetry(`${routeConfig.baseURL}/responses`, body, headers, signal, routeConfig.timeoutMs, {
189
+ maxAttempts: routeConfig.maxAttempts,
190
+ maxResponseBytes: routeConfig.maxResponseBytes,
191
+ });
192
+ return parseHostedSearchResponse(response, requestId, routeConfig.webMaxResults);
193
+ }
229
194
  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'
195
+ ctx;
196
+ getConfig;
197
+ enabled;
198
+ id;
199
+ constructor(ctx, getConfig, enabled) {
200
+ this.ctx = ctx;
201
+ this.getConfig = getConfig;
202
+ this.enabled = enabled;
203
+ this.id = getConfig().webSearchProvider;
204
+ }
205
+ available() {
206
+ const active = hostedSearchRouteContext.getStore();
207
+ if (!active)
208
+ return false;
209
+ const config = this.getConfig();
210
+ const route = resolveResponsesRouteConfig(this.ctx, active, config);
211
+ return this.enabled() && Boolean(route);
212
+ }
213
+ async search(request, signal) {
214
+ const config = this.getConfig();
215
+ const active = hostedSearchRouteContext.getStore();
216
+ const route = active && resolveResponsesRouteConfig(this.ctx, active, config);
217
+ const routeConfig = route && routeWithPolicies(route, config);
218
+ if (!routeConfig)
219
+ throw webError("LCX Hosted Search requires a configured GPT openai-responses route", "LCX_WEB_ROUTE_UNAVAILABLE");
220
+ const sessionId = active?.sessionId ?? "";
221
+ this.ctx.logger?.info?.(`[lcx-codex] web_search route: ${routeConfig.provider}/${routeConfig.model} (active-agent)`);
222
+ const result = await executeHostedSearch(this.ctx, routeConfig, { query: request.query }, signal, sessionId);
223
+ const max = request.maxResults ?? routeConfig.webMaxResults;
224
+ return {
225
+ content: result.content || undefined,
226
+ sources: result.sources.slice(0, max),
227
+ truncated: result.truncated || result.sources.length > max,
228
+ };
245
229
  }
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
-
230
+ }
255
231
  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
-
232
+ return {
233
+ name: ADVANCED_HOSTED_TOOL,
234
+ 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.",
235
+ parameters: HOSTED_SEARCH_PARAMETERS,
236
+ output: {
237
+ schema: HOSTED_SEARCH_OUTPUT,
238
+ render: (_args, value) => renderHostedSearchResult(value),
239
+ },
240
+ async execute(args, exec) {
241
+ if (!state.enabled || !state.webSearch || !state.advancedHostedSearch)
242
+ throw webError(`${ADVANCED_HOSTED_TOOL} is disabled`, "LCX_WEB_DISABLED");
243
+ const config = getConfig();
244
+ const active = activeAgentRoute(exec);
245
+ const route = resolveResponsesRouteConfig(ctx, active, config);
246
+ if (!route)
247
+ throw webError("Advanced Hosted Search requires the active GPT openai-responses route", "LCX_WEB_ROUTE_UNAVAILABLE");
248
+ return executeHostedSearch(ctx, routeWithPolicies(route, config), args, exec.signal, active.sessionId);
249
+ },
250
+ };
251
+ }
271
252
  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) }
253
+ const fingerprint = alphaCapabilityFingerprint({
254
+ baseURL: config.baseURL,
255
+ provider: config.provider,
256
+ model: config.model,
257
+ profile: config.alphaProfile,
258
+ group: config.alphaGroup,
259
+ schemaFingerprint: ALPHA_SCHEMA_FINGERPRINT,
260
+ });
261
+ return { fingerprint, record: store.get(fingerprint) };
274
262
  }
275
-
276
263
  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 }
264
+ const resolvedRoute = resolveResponsesRouteConfig(ctx, active, config);
265
+ if (!resolvedRoute)
266
+ return { route: undefined, usable: false };
267
+ const route = routeWithPolicies(resolvedRoute, config);
268
+ const { fingerprint, record } = alphaCapabilityFor(route, store);
269
+ return {
270
+ route,
271
+ fingerprint,
272
+ record,
273
+ usable: alphaCapabilityUsable(record) &&
274
+ record?.schemaFingerprint === ALPHA_SCHEMA_FINGERPRINT,
275
+ };
281
276
  }
282
-
283
277
  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
-
278
+ const normalized = normalizeAlphaSearchArgs(args);
279
+ if (["open", "find", "screenshot"].includes(normalized.action) &&
280
+ isAlphaHttpUrl(normalized.refId) &&
281
+ !isAlphaContinuationUrl(normalized.refId)) {
282
+ throw webError("Alpha Search direct URLs must be public HTTP(S) targets", "LCX_ALPHA_URL_UNAVAILABLE");
283
+ }
284
+ const sessionId = agentSessionId(exec?.agent);
285
+ if (!sessionId)
286
+ throw webError("Alpha Search requires a DSH session", "LCX_ALPHA_SESSION_REQUIRED");
287
+ const routeFp = routeFingerprint({
288
+ provider: routeConfig.provider,
289
+ model: routeConfig.model,
290
+ baseURL: routeConfig.baseURL,
291
+ sessionId,
292
+ });
293
+ if (alphaRefRequiresStore(normalized.action, normalized.refId))
294
+ refStore.assertUsable(sessionId, routeFp, normalized.refId);
295
+ const requestId = randomUUID();
296
+ const headers = await authenticatedHeaders(ctx, routeConfig, sessionId, requestId);
297
+ let response;
298
+ try {
299
+ response = await fetchJsonWithRetry(`${routeConfig.baseURL}/alpha/search`, buildAlphaSearchBody(normalized, routeConfig.model, sessionId, true, routeConfig.alphaMaxOutputTokens), headers, exec?.signal, routeConfig.timeoutMs, {
300
+ maxAttempts: routeConfig.maxAttempts,
301
+ maxResponseBytes: routeConfig.maxResponseBytes,
302
+ });
303
+ }
304
+ catch (error) {
305
+ const details = errorDetails(error);
306
+ if ([404, 405].includes(Number(details.status)) ||
307
+ /channel does not support/iu.test(String(details.message ?? "")))
308
+ throw webError("Alpha Search is not supported by this route", "LCX_ALPHA_UNAVAILABLE", error);
309
+ throw webError("Alpha Search provider request failed", "LCX_ALPHA_PROVIDER_ERROR", error);
310
+ }
311
+ const { refRecords, ...result } = parseAlphaSearchResponse(response, {
312
+ action: normalized.action,
313
+ capability: capability.classification,
314
+ requestId,
315
+ });
316
+ refStore.record(sessionId, routeFp, refRecords);
317
+ return result;
318
+ }
306
319
  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
-
320
+ return {
321
+ name: ALPHA_TOOL,
322
+ 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.",
323
+ parameters: ALPHA_SEARCH_PARAMETERS,
324
+ output: {
325
+ schema: ALPHA_SEARCH_OUTPUT,
326
+ render: (_args, value) => renderAlphaSearchResult(value),
327
+ },
328
+ async execute(args, exec) {
329
+ if (!state.enabled || !state.alphaSearch)
330
+ throw webError(`${ALPHA_TOOL} is disabled`, "LCX_ALPHA_DISABLED");
331
+ const config = getConfig();
332
+ const active = activeAgentRoute(exec);
333
+ const route = resolveResponsesRouteConfig(ctx, active, config);
334
+ if (!route)
335
+ throw webError("Alpha Search requires the active GPT openai-responses route", "LCX_ALPHA_ROUTE_UNAVAILABLE");
336
+ const routeConfig = routeWithPolicies(route, config);
337
+ const fingerprint = alphaCapabilityFingerprint({
338
+ baseURL: routeConfig.baseURL,
339
+ provider: routeConfig.provider,
340
+ model: routeConfig.model,
341
+ profile: routeConfig.alphaProfile,
342
+ group: routeConfig.alphaGroup,
343
+ schemaFingerprint: ALPHA_SCHEMA_FINGERPRINT,
344
+ });
345
+ const capability = capabilityStore.get(fingerprint);
346
+ if (!alphaCapabilityUsable(capability) ||
347
+ capability?.schemaFingerprint !== ALPHA_SCHEMA_FINGERPRINT)
348
+ throw webError("Alpha Search capability has not been verified for this exact route/schema", "LCX_ALPHA_CAPABILITY_UNVERIFIED");
349
+ return executeAlpha(ctx, routeConfig, capability, refStore, args, exec);
350
+ },
351
+ };
352
+ }
324
353
  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
354
+ const previous = registrations.get(agent);
355
+ if (!state.enabled || !state.alphaSearch) {
356
+ disposeAlphaToolForAgent(agent, registrations);
357
+ return false;
358
+ }
359
+ try {
360
+ const config = getConfig();
361
+ const active = selectedAgentRoute(agent);
362
+ const capability = verifiedAlphaCapabilityForRoute(ctx, active, config, capabilityStore);
363
+ if (!capability.usable) {
364
+ disposeAlphaToolForAgent(agent, registrations);
365
+ return false;
366
+ }
367
+ if (previous?.fingerprint === capability.fingerprint)
368
+ return true;
369
+ disposeAlphaToolForAgent(agent, registrations);
370
+ const scopedTools = scopedToolRuntime(agent);
371
+ if (!scopedTools)
372
+ return false;
373
+ // Alpha is route-bound; global registration would advertise a static record to other routes.
374
+ registrations.set(agent, {
375
+ fingerprint: capability.fingerprint,
376
+ dispose: scopedTools.register(createAlphaTool(ctx, state, getConfig, capabilityStore, refStore)),
377
+ });
378
+ return true;
379
+ }
380
+ catch (error) {
381
+ disposeAlphaToolForAgent(agent, registrations);
382
+ ctx.logger?.warn?.(`[lcx-codex] Alpha capability store unavailable: ${errorDetails(error).message ?? String(error)}`);
383
+ return false;
337
384
  }
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
-
385
+ }
352
386
  function disposeAlphaToolForAgent(agent, registrations) {
353
- const registration = registrations.get(agent)
354
- if (!registration) return
355
- registration.dispose()
356
- registrations.delete(agent)
387
+ const registration = registrations.get(agent);
388
+ if (!registration)
389
+ return;
390
+ registration.dispose();
391
+ registrations.delete(agent);
357
392
  }
358
-
359
393
  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)
394
+ return (message?.role === "user" &&
395
+ message?.source?.kind === "plugin" &&
396
+ message?.source?.plugin === "dsh-compaction-basic");
364
397
  }
365
398
  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
399
+ if (messages.length === 0)
400
+ return [];
401
+ return isDshCompactionDirective(messages.at(-1))
402
+ ? messages.slice(0, -1)
403
+ : [...messages];
404
+ }
405
+ function mergeMap(target, source) {
406
+ for (const [key, value] of source ?? [])
407
+ target.set(key, value);
408
+ }
409
+ async function serializeNativeAware(messages, route, routeConfig, ctx, options) {
410
+ const session = sessionFor(ctx, route.sessionId);
411
+ const imageSupport = await resolveModelImageSupport(ctx, route, options.signal);
412
+ const input = [];
413
+ const imageMap = new Map();
414
+ let nativeTools;
415
+ let nativeModel;
416
+ let grammarToolInputProperties;
417
+ let normal = [];
418
+ const serializeOptions = (imageMapOverride = undefined, extra = {}) => requestImageOptions(routeConfig, imageSupport, options.signal, imageMapOverride, {
419
+ route,
420
+ tools: options.tools,
421
+ responsesCompat: routeConfig.responsesCompat,
422
+ ...extra,
423
+ });
424
+ const prelude = await serializeDshMessages([], ctx, serializeOptions(undefined, {
425
+ systemPrompt: options.system,
426
+ includeSystemPrompt: true,
427
+ }));
428
+ const ephemeralPreludeItemCount = prelude.input.length;
429
+ input.push(...prelude.input);
430
+ nativeTools = prelude.tools;
431
+ nativeModel = prelude.model;
432
+ grammarToolInputProperties = prelude.grammarToolInputProperties;
433
+ const flush = async () => {
434
+ if (!normal.length)
435
+ return;
436
+ const serialized = await serializeDshMessages(normal, ctx, serializeOptions());
437
+ nativeTools = serialized.tools ?? nativeTools;
438
+ nativeModel = serialized.model ?? nativeModel;
439
+ grammarToolInputProperties =
440
+ serialized.grammarToolInputProperties ?? grammarToolInputProperties;
441
+ input.push(...serialized.input);
442
+ mergeMap(imageMap, serialized.imageMap);
443
+ normal = [];
444
+ };
445
+ for (const message of messages ?? []) {
446
+ assertSupportedCheckpointMessage(message);
447
+ const state = session
448
+ ? checkpointStateForMessage(session, message)
449
+ : undefined;
450
+ if (state && session) {
451
+ const checkpointId = compactCheckpointId(message);
452
+ if (!checkpointId) {
453
+ normal.push(message);
454
+ continue;
455
+ }
456
+ await flush();
457
+ if (stateRouteCompatible(state, route, ctx)) {
458
+ const nativeOutput = state.nativeOutput;
459
+ const hydratedMap = new Map();
460
+ const hydrated = await hydrateNativeImageReferences(nativeOutput, ctx, requestImageOptions(routeConfig, imageSupport, options.signal, hydratedMap));
461
+ input.push(...responseInputItems(hydrated));
462
+ mergeMap(imageMap, hydratedMap);
463
+ }
464
+ else {
465
+ const portable = portableMessagesForCheckpoint(session, checkpointId, { maxChars: routeConfig.portableReplayMaxChars });
466
+ const serialized = await serializeDshMessages(portable, ctx, serializeOptions());
467
+ input.push(...serialized.input);
468
+ mergeMap(imageMap, serialized.imageMap);
412
469
  }
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
- }
470
+ continue;
419
471
  }
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
472
+ normal.push(message);
429
473
  }
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
- }
441
- }
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 }
474
+ await flush();
475
+ if (!nativeModel)
476
+ throw Object.assign(new Error("LCX could not resolve the Pi Responses model descriptor"), { code: "LCX_RESPONSES_MODEL_UNAVAILABLE" });
477
+ return {
478
+ input,
479
+ ephemeralPreludeItemCount,
480
+ imageMap,
481
+ imageSupport,
482
+ tools: nativeTools,
483
+ model: nativeModel,
484
+ grammarToolInputProperties,
485
+ };
447
486
  }
448
-
449
487
  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
-
488
+ if (signal?.aborted)
489
+ return false;
490
+ const details = errorDetails(error);
491
+ if (details.name === "AbortError" || details.code === "LCX_ABORTED")
492
+ return false;
493
+ if (details.name === "TimeoutError")
494
+ return true;
495
+ return ["LCX_HTTP_RETRYABLE", "LCX_RETRY_EXHAUSTED"].includes(String(details.code ?? ""));
496
+ }
497
+ async function* remoteCompactionStream(options, routeConfig, ctx, next) {
498
+ const history = stripCompactionDirective(options.messages);
499
+ const route = currentRoute(options, routeConfig);
500
+ try {
501
+ const prepared = await serializeNativeAware(history, route, routeConfig, ctx, { signal: options.signal, system: options.system, tools: options.tools });
502
+ const cacheSessionId = promptCacheSessionId(route, routeConfig, ctx);
503
+ const headers = await authenticatedHeaders(ctx, routeConfig, cacheSessionId, cacheSessionId === undefined ? null : route.sessionId);
504
+ const generation = generationControlsFromSession(sessionFor(ctx, route.sessionId), route);
505
+ const result = await requestNativeCompaction({
506
+ baseURL: routeConfig.baseURL,
507
+ model: route.model,
508
+ modelDescriptor: prepared.model,
509
+ input: prepared.input,
510
+ tools: prepared.tools ?? options.tools,
511
+ promptCacheKey: promptCacheKey(route, routeConfig, ctx),
512
+ promptCacheRetention: promptCacheRetention(routeConfig),
513
+ cacheRetention: routeConfig.cacheRetention,
514
+ reasoningEffort: generation.reasoningEffort,
515
+ temperature: generation.temperature,
516
+ maxTokens: generation.maxTokens,
517
+ idempotencyKey: randomUUID(),
518
+ headers,
519
+ signal: options.signal,
520
+ timeoutMs: routeConfig.timeoutMs,
521
+ maxAttempts: routeConfig.maxAttempts,
522
+ maxResponseBytes: routeConfig.maxResponseBytes,
523
+ });
524
+ const session = sessionFor(ctx, route.sessionId);
525
+ if (session === undefined)
526
+ throw webError("Native compaction requires a live DSH session", "LCX_COMPACT_SESSION_UNAVAILABLE");
527
+ const block = createNativeCheckpointBlock({
528
+ session,
529
+ route,
530
+ result,
531
+ input: prepared.input,
532
+ ephemeralPreludeItemCount: prepared.ephemeralPreludeItemCount,
533
+ imageMap: prepared.imageMap,
534
+ retentionOptions: {
535
+ tokenBudget: routeConfig.nativeRetentionTokenBudget,
536
+ assistantTokenReserve: routeConfig.assistantRetentionTokenReserve,
537
+ assistantPerMessageTokenCap: routeConfig.assistantRetentionPerMessageTokenCap,
538
+ },
539
+ });
540
+ 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`);
541
+ for (const chunk of nativeCheckpointChunks(block, result.usage))
542
+ yield chunk;
543
+ }
544
+ catch (error) {
545
+ const session = sessionFor(ctx, route.sessionId);
546
+ const hasExistingCheckpoint = messagesContainNativeCheckpoint(history, session);
547
+ const details = errorDetails(error);
548
+ const code = details.code ?? details.name ?? "ERROR";
549
+ const status = Number.isInteger(details.status)
550
+ ? ` status=${details.status}`
551
+ : "";
552
+ const requestId = isRecord(error) && error.requestId
553
+ ? ` requestId=${String(error.requestId)}`
554
+ : "";
555
+ const providerCode = isRecord(error) && error.providerCode
556
+ ? ` providerCode=${String(error.providerCode)}`
557
+ : "";
558
+ const providerType = isRecord(error) && error.providerType
559
+ ? ` providerType=${String(error.providerType)}`
560
+ : "";
561
+ const providerParam = isRecord(error) && error.providerParam
562
+ ? ` providerParam=${String(error.providerParam)}`
563
+ : "";
564
+ ctx.logger?.warn?.(`[lcx-codex] native V2 compaction failed: code=${code}${status}${requestId}${providerCode}${providerType}${providerParam}`);
565
+ if (hasExistingCheckpoint || !fallbackEligible(error, options.signal))
566
+ throw error;
567
+ ctx.logger?.info?.("[lcx-codex] falling back to DSH basic compaction after allowlisted first-checkpoint native failure");
568
+ const stream = await next();
569
+ for await (const chunk of stream)
570
+ yield chunk;
571
+ }
572
+ }
573
+ function routedTargetForAgent(agent) {
574
+ const route = readAgentRouteState(agent);
575
+ const provider = stringValue(routeConfigValue(route.requestConfig, "provider") ??
576
+ routeConfigValue(route.options, "provider"), "");
577
+ const model = stringValue(routeConfigValue(route.requestConfig, "model") ??
578
+ routeConfigValue(route.options, "model"), "");
579
+ return provider && model ? { provider, model } : undefined;
580
+ }
531
581
  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' }
582
+ const ratioPercent = (totalTokens / contextWindow) * 100;
583
+ return {
584
+ ratioPercent,
585
+ band: ratioPercent < policy.auto
586
+ ? "below"
587
+ : ratioPercent < policy.emergency
588
+ ? "native"
589
+ : "emergency",
590
+ };
534
591
  }
535
-
536
592
  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 }) }
593
+ if (!isRecord(config))
594
+ return config;
595
+ const modelPolicies = Array.isArray(config.modelPolicies)
596
+ ? config.modelPolicies.map((policy) => isRecord(policy) &&
597
+ policy.provider === target.provider &&
598
+ policy.model === target.model
599
+ ? { ...policy, thresholdRatio }
600
+ : policy)
601
+ : config.modelPolicies;
602
+ return {
603
+ ...config,
604
+ thresholdRatio,
605
+ ...(modelPolicies === undefined ? {} : { modelPolicies }),
606
+ };
542
607
  }
543
-
544
608
  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
-
609
+ return primary === lifecycle ? primary : AbortSignal.any([primary, lifecycle]);
610
+ }
611
+ function patchCompactionPressureService(compactionValue, state, getConfig, ctx, records) {
612
+ const candidate = compactionPatchCandidate(compactionValue, records);
613
+ if (!candidate)
614
+ return false;
615
+ const mutex = new ServiceMutex();
616
+ const lifecycle = new AbortController();
617
+ return installCompactionPatch(records, candidate, mutex, lifecycle, async (agent, trigger, signal, callOriginal) => {
618
+ const activeSignal = combinedAbortSignal(signal, lifecycle.signal);
619
+ return mutex.run(activeSignal, async () => {
620
+ if (trigger !== "pressure" || !state.enabled)
621
+ return callOriginal(activeSignal);
622
+ const config = getConfig();
623
+ const target = routedTargetForAgent(agent);
624
+ if (!target || !resolveResponsesRouteConfig(ctx, target, config))
625
+ return callOriginal(activeSignal);
626
+ const session = sessionFromAgent(agent);
627
+ const tokenMeter = resolveScopedService(agent, "tokenMeter") ??
628
+ resolveContextService(ctx, "tokenMeter");
629
+ if (!session || tokenMeter === undefined)
630
+ return callOriginal(activeSignal);
631
+ let contextWindow;
632
+ try {
633
+ contextWindow = (await ctx.llm.resolveModelInfo(target.provider, target.model, activeSignal)).context?.contextWindow;
634
+ }
635
+ catch {
636
+ return callOriginal(activeSignal);
637
+ }
638
+ const totalTokens = tokenMeterTotal(tokenMeter, session);
639
+ if (contextWindow === undefined ||
640
+ contextWindow <= 0 ||
641
+ totalTokens === undefined)
642
+ return callOriginal(activeSignal);
643
+ const auto = AUTO_COMPACTION_THRESHOLD_PERCENT;
644
+ const emergency = EMERGENCY_PRUNE_THRESHOLD_PERCENT;
645
+ const pressure = compactionPressureBand(totalTokens, contextWindow, {
646
+ auto,
647
+ emergency,
648
+ });
649
+ if (pressure.band === "below")
650
+ return null;
651
+ const prunerState = toolResultPrunerState(resolveAgentService(ctx, agent, "toolResultPruner"));
652
+ const configState = compactionConfigState(compactionValue);
653
+ const nativeFirst = pressure.band === "native";
654
+ const prunerPatch = nativeFirst
655
+ ? patchToolResultPruner(prunerState, () => ({ pruned: [], charsRemoved: 0 }))
656
+ : undefined;
657
+ const configPatch = patchCompactionConfig(configState, (originalConfig) => adjustedCompactionConfig(originalConfig, target, auto / 100));
658
+ ctx.logger?.info?.(`[lcx-codex] auto pressure ${pressure.ratioPercent.toFixed(1)}%: ${nativeFirst ? "Native V2 first" : "emergency DSH prune allowed"} (native ${auto}%, emergency ${emergency}%)`);
659
+ try {
660
+ return await callOriginal(activeSignal);
661
+ }
662
+ finally {
663
+ restoreCompactionConfig(configPatch);
664
+ restoreToolResultPruner(prunerPatch);
665
+ }
666
+ });
667
+ });
668
+ }
669
+ function patchCompactionPressureForAgent(agent, state, getConfig, ctx, records) {
670
+ return patchCompactionPressureService(resolveAgentService(ctx, agent, "compaction"), state, getConfig, ctx, records);
671
+ }
600
672
  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
-
673
+ const reason = new Error("lcx-codex pressure coordination is shutting down");
674
+ const entries = [...records.values()];
675
+ for (const record of entries) {
676
+ if (!record.lifecycle.signal.aborted)
677
+ record.lifecycle.abort(reason);
678
+ }
679
+ await Promise.allSettled(entries.map((record) => record.mutex.close(reason)));
680
+ restoreCompactionPatches(records, entries);
681
+ }
682
+ function visibleWebSearchTimeout(state) {
683
+ return state.enabled && state.webSearch ? WEB_SEARCH_TIMEOUT_MS : undefined;
684
+ }
685
+ function messagesContainNativeCheckpoint(messages, session) {
686
+ return session !== undefined && messages.some((message) => checkpointStateForMessage(session, message));
687
+ }
619
688
  function inputHasNativeState(input) {
620
- return (input ?? []).some((item) => item?.type === 'compaction')
689
+ return input.some((item) => isRecord(item) && item.type === "compaction");
621
690
  }
622
-
623
691
  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
-
692
+ const route = currentRoute(options, routeConfig);
693
+ try {
694
+ if (options.stop !== undefined)
695
+ throw Object.assign(new Error("LCX Responses does not support GenerateOptions.stop"), { code: "LCX_RESPONSES_UNSUPPORTED_OPTION" });
696
+ const prepared = await serializeNativeAware(options.messages, route, routeConfig, ctx, { signal: options.signal, system: options.system, tools: options.tools });
697
+ const cacheSessionId = promptCacheSessionId(route, routeConfig, ctx);
698
+ const headers = await authenticatedHeaders(ctx, routeConfig, cacheSessionId, cacheSessionId === undefined ? null : route.sessionId);
699
+ const body = buildResponsesBody({
700
+ model: prepared.model,
701
+ input: prepared.input,
702
+ tools: prepared.tools ?? options.tools,
703
+ sessionId: cacheSessionId,
704
+ promptCacheKey: promptCacheKey(route, routeConfig, ctx),
705
+ promptCacheRetention: promptCacheRetention(routeConfig),
706
+ cacheRetention: routeConfig.cacheRetention,
707
+ reasoningEffort: options.reasoningEffort,
708
+ temperature: options.temperature,
709
+ maxTokens: options.maxTokens,
710
+ });
711
+ const nativeReplay = inputHasNativeState(prepared.input);
712
+ if (nativeReplay) {
713
+ body.tool_choice = "auto";
714
+ body.parallel_tool_calls = true;
715
+ }
716
+ yield* streamResponsesRequest({
717
+ baseURL: routeConfig.baseURL,
718
+ provider: route.provider,
719
+ model: route.model,
720
+ piModel: prepared.model,
721
+ body,
722
+ grammarToolInputProperties: prepared.grammarToolInputProperties,
723
+ headers: nativeReplay ? mergeFeatureHeader(headers) : headers,
724
+ signal: options.signal,
725
+ timeoutMs: routeConfig.timeoutMs,
726
+ maxAttempts: 1,
727
+ maxResponseBytes: routeConfig.maxResponseBytes,
728
+ });
729
+ }
730
+ catch (error) {
731
+ yield managedFailureChunk(error, options.signal);
732
+ }
733
+ }
662
734
  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)
735
+ 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
736
  }
665
-
666
737
  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)
693
- }
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)
699
- }
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
- }
713
- }
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)
738
+ const baseConfig = normalizeConfig(configInput);
739
+ let runtimeConfig = baseConfig;
740
+ const state = {
741
+ enabled: false,
742
+ webSearch: false,
743
+ advancedHostedSearch: false,
744
+ alphaSearch: false,
745
+ };
746
+ const originalSearchProvider = readWebSearchProvider(ctx);
747
+ let warnedWebSelection = false;
748
+ const provider = new LcxResponsesSearchProvider(ctx, () => runtimeConfig, () => state.enabled && state.webSearch);
749
+ ctx.web.registerSearchProvider(provider);
750
+ const tools = ctx.tools;
751
+ let disposeAdvanced;
752
+ const capabilityStore = new AlphaCapabilityStore(baseConfig.alphaCapabilityPath);
753
+ const refStore = new AlphaRefStore(baseConfig.alphaRefPath);
754
+ const alphaAgents = new Set();
755
+ const alphaToolRegistrations = new Map();
756
+ const compactionPatchRecords = new Map();
757
+ const patchedWebSearchDefinitions = new Map();
758
+ ctx.on("session/disposed", (session) => {
759
+ for (const agent of alphaAgents)
760
+ if (agentUsesSession(agent, session)) {
761
+ disposeAlphaToolForAgent(agent, alphaToolRegistrations);
762
+ alphaAgents.delete(agent);
763
+ }
764
+ }, { global: true });
765
+ ctx.on("session/event", (session, event) => {
766
+ if (event.type === "request/header")
767
+ for (const agent of alphaAgents)
768
+ if (agentUsesSession(agent, session)) {
769
+ syncAlphaToolForAgent(ctx, agent, state, () => runtimeConfig, capabilityStore, refStore, alphaToolRegistrations);
770
+ }
771
+ }, { global: true });
772
+ const refreshTools = () => {
773
+ if (state.enabled &&
774
+ state.webSearch &&
775
+ state.advancedHostedSearch &&
776
+ !disposeAdvanced &&
777
+ tools?.register)
778
+ disposeAdvanced = tools.register(createAdvancedHostedTool(ctx, state, () => runtimeConfig));
779
+ if ((!state.enabled || !state.webSearch || !state.advancedHostedSearch) &&
780
+ disposeAdvanced) {
781
+ disposeAdvanced();
782
+ disposeAdvanced = undefined;
783
+ }
784
+ for (const agent of alphaAgents)
785
+ syncAlphaToolForAgent(ctx, agent, state, () => runtimeConfig, capabilityStore, refStore, alphaToolRegistrations);
786
+ if (ctx.web) {
787
+ const target = state.enabled && state.webSearch
788
+ ? runtimeConfig.webSearchProvider
789
+ : originalSearchProvider;
790
+ const selected = writeWebSearchProvider(ctx, target);
791
+ if (!selected &&
792
+ state.enabled &&
793
+ state.webSearch &&
794
+ !warnedWebSelection) {
795
+ warnedWebSelection = true;
796
+ 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.");
797
+ }
798
+ }
799
+ };
800
+ const settingsEntry = {
801
+ enabled: false,
802
+ webSearch: false,
803
+ advancedHostedSearch: false,
804
+ alphaSearch: false,
805
+ };
806
+ let source = () => settingsEntry;
807
+ ctx.settings.installSection(ctx, SETTINGS_NS, SettingsSchema, settingsEntry, {
808
+ setSource(current) {
809
+ source = current;
810
+ },
811
+ onChange() {
812
+ const value = source();
813
+ state.enabled = value.enabled;
814
+ state.webSearch = value.webSearch;
815
+ state.advancedHostedSearch = value.advancedHostedSearch;
816
+ state.alphaSearch = value.alphaSearch;
817
+ runtimeConfig = baseConfig;
818
+ refreshTools();
819
+ refreshVisibleWebSearchTimeouts(patchedWebSearchDefinitions, visibleWebSearchTimeout(state));
820
+ },
821
+ });
822
+ try {
823
+ const value = source();
824
+ Object.assign(state, {
825
+ enabled: Boolean(value.enabled),
826
+ webSearch: Boolean(value.webSearch),
827
+ advancedHostedSearch: Boolean(value.advancedHostedSearch),
828
+ alphaSearch: Boolean(value.alphaSearch),
829
+ });
830
+ runtimeConfig = baseConfig;
775
831
  }
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
-
832
+ catch { }
833
+ refreshTools();
834
+ ctx.inject(["compaction"], (compactionCtx) => {
835
+ patchCompactionPressureService(resolveContextService(compactionCtx, "compaction"), state, () => runtimeConfig, ctx, compactionPatchRecords);
836
+ });
837
+ ctx.on("tools/execute", async (exec, next) => {
838
+ if (!state.enabled || !state.webSearch || exec.name !== "web_search")
839
+ return next();
840
+ const active = activeAgentRoute(exec);
841
+ return hostedSearchRouteContext.run(active, () => next());
842
+ });
843
+ ctx.on("agent/created", ({ agent }) => {
844
+ if (agent === null || typeof agent !== "object")
845
+ return;
846
+ alphaAgents.add(agent);
847
+ syncAlphaToolForAgent(ctx, agent, state, () => runtimeConfig, capabilityStore, refStore, alphaToolRegistrations);
848
+ const installed = patchCompactionPressureForAgent(agent, state, () => runtimeConfig, ctx, compactionPatchRecords);
849
+ if (installed)
850
+ ctx.logger?.info?.("[lcx-codex] pressure coordination installed through AgentPresets service resolver");
851
+ }, { global: true });
852
+ ctx.on("agent/status", ({ agent, status }) => {
853
+ if (status !== "running" ||
854
+ agent === null ||
855
+ typeof agent !== "object")
856
+ return;
857
+ alphaAgents.add(agent);
858
+ syncAlphaToolForAgent(ctx, agent, state, () => runtimeConfig, capabilityStore, refStore, alphaToolRegistrations);
859
+ const installed = patchCompactionPressureForAgent(agent, state, () => runtimeConfig, ctx, compactionPatchRecords);
860
+ if (installed)
861
+ ctx.logger?.info?.("[lcx-codex] pressure coordination installed through AgentPresets service resolver");
862
+ patchVisibleWebSearchTimeout(agent, () => visibleWebSearchTimeout(state), patchedWebSearchDefinitions);
863
+ }, { global: true });
864
+ ctx.on("llm/stream", (options, next) => {
865
+ if (!state.enabled || options.purpose === "session-title")
866
+ return next();
867
+ const routeConfig = resolveResponsesRouteConfig(ctx, options, runtimeConfig);
868
+ if (options.purpose === "compaction") {
869
+ if (!routeConfig)
870
+ return unavailableManagedRouteStream(options);
871
+ return remoteCompactionStream(options, routeWithPolicies(routeConfig, runtimeConfig), ctx, next);
872
+ }
873
+ if (!routeConfig)
874
+ return unavailableManagedRouteStream(options);
875
+ return managedResponsesStream(options, routeWithPolicies(routeConfig, runtimeConfig), ctx);
876
+ });
877
+ ctx.effect?.(() => async () => {
878
+ disposeAdvanced?.();
879
+ for (const agent of alphaAgents)
880
+ disposeAlphaToolForAgent(agent, alphaToolRegistrations);
881
+ alphaAgents.clear();
882
+ await restoreCompactionPressure(compactionPatchRecords);
883
+ restoreVisibleWebSearchTimeouts(patchedWebSearchDefinitions);
884
+ writeWebSearchProvider(ctx, originalSearchProvider);
885
+ }, "lcx-codex cleanup");
886
+ }
791
887
  export function apply(ctx, configInput = {}) {
792
- return installInjected(ctx, configInput)
888
+ return installInjected(ctx, configInput);
793
889
  }
794
-
795
- apply.inject = inject
796
- apply.Config = Config
797
-
798
- export default apply
890
+ apply.inject = inject;
891
+ apply.Config = Config;
892
+ export default apply;