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.
- package/README.md +116 -202
- package/cordis.patch.yml +3 -20
- package/lib/client.js +273 -146
- package/lib/compact-v2.js +218 -199
- package/lib/dsh-compat.js +227 -100
- package/lib/dsh-responses.js +445 -277
- package/lib/index.js +851 -757
- package/lib/json-store.js +50 -31
- package/lib/native-checkpoint.js +520 -194
- package/lib/responses-request.js +106 -121
- package/lib/responses-stream.js +972 -443
- package/lib/route.js +229 -355
- package/lib/service-mutex.js +73 -64
- package/lib/token-budget.js +176 -108
- package/lib/transport.js +277 -67
- package/lib/types/client/index.d.ts +6 -0
- package/lib/types/compact-v2.d.ts +104 -0
- package/lib/types/dsh-compat.d.ts +78 -0
- package/lib/types/dsh-responses.d.ts +82 -0
- package/lib/types/index.d.ts +83 -0
- package/lib/types/json-store.d.ts +10 -0
- package/lib/types/native-checkpoint.d.ts +213 -0
- package/lib/types/responses-request.d.ts +58 -0
- package/lib/types/responses-stream.d.ts +51 -0
- package/lib/types/route.d.ts +132 -0
- package/lib/types/service-mutex.d.ts +14 -0
- package/lib/types/token-budget.d.ts +50 -0
- package/lib/types/transport.d.ts +20 -0
- package/lib/types/web-run-output.d.ts +29 -0
- package/lib/types/web-search-alpha.d.ts +286 -0
- package/lib/types/web-search-capability.d.ts +26 -0
- package/lib/types/web-search-hosted.d.ts +246 -0
- package/lib/types/web-search-ref-store.d.ts +22 -0
- package/lib/web-run-output.js +167 -18
- package/lib/web-search-alpha.js +865 -163
- package/lib/web-search-capability.js +55 -65
- package/lib/web-search-hosted.js +210 -32
- package/lib/web-search-ref-store.js +63 -59
- package/package.json +79 -27
- package/ARCHITECTURE.md +0 -117
- package/CHANGELOG.md +0 -224
- package/README_EN.md +0 -277
- package/assets/dsh-lcx-codex-banner.jpg +0 -0
- package/lib/legacy-v3.js +0 -20
- package/lib/responses-replay.js +0 -68
- package/scripts/probe-alpha.mjs +0 -43
- package/scripts/validate-dsh-schema.mjs +0 -31
package/lib/index.js
CHANGED
|
@@ -1,798 +1,892 @@
|
|
|
1
|
-
import z from
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
533
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
function visibleWebSearchTimeout(state
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
689
|
+
return input.some((item) => isRecord(item) && item.type === "compaction");
|
|
621
690
|
}
|
|
622
|
-
|
|
623
691
|
async function* managedResponsesStream(options, routeConfig, ctx) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
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
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
-
|
|
888
|
+
return installInjected(ctx, configInput);
|
|
793
889
|
}
|
|
794
|
-
|
|
795
|
-
apply.
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
export default apply
|
|
890
|
+
apply.inject = inject;
|
|
891
|
+
apply.Config = Config;
|
|
892
|
+
export default apply;
|