@jskit-ai/assistant-core 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/conversation/package.json +5 -5
- package/package.json +18 -7
- package/src/client/browser.js +21 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
- package/src/client/conversation/AssistantComposerActions.vue +1 -1
- package/src/client/conversation/AssistantConversationElement.vue +14 -52
- package/src/client/conversation/AssistantGoalControl.vue +18 -12
- package/src/client/conversation/AssistantPromptInput.vue +6 -91
- package/src/client/conversation/AssistantTranscript.css +505 -0
- package/src/client/conversation/AssistantTranscript.vue +24 -1197
- package/src/client/conversation/attachmentFiles.js +158 -0
- package/src/client/conversation/attachmentQueueItems.js +121 -0
- package/src/client/conversation/attachmentUploadRows.js +265 -0
- package/src/client/conversation/conversationDisplay.js +54 -0
- package/src/client/conversation/messageDelivery.js +13 -9
- package/src/client/conversation/retainedConversation.js +14 -3
- package/src/client/conversation/submitText.js +13 -2
- package/src/client/conversation/transcriptEntries.js +225 -0
- package/src/client/conversation/useAssistantAttachments.js +86 -388
- package/src/client/conversation/useAssistantQuestions.js +16 -3
- package/src/client/conversation/useAssistantSuggestions.js +16 -2
- package/src/client/conversation/useScrollToBottom.js +17 -19
- package/src/client/conversation/useTextareaAutoGrow.js +115 -0
- package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
- package/src/client/lib/assistantApi.js +192 -161
- package/src/client/types.js +110 -0
- package/src/server/conversation/attachments.js +21 -6
- package/src/server/conversation/boundedToolLoop.js +1 -0
- package/src/server/conversation/claudeClient.js +71 -20
- package/src/server/conversation/claudeCommandRun.js +191 -0
- package/src/server/conversation/claudeHistory.js +7 -0
- package/src/server/conversation/claudeNativeTurn.js +708 -0
- package/src/server/conversation/claudeProcess.js +186 -112
- package/src/server/conversation/claudeProcessStart.js +214 -0
- package/src/server/conversation/claudePromptDelivery.js +183 -0
- package/src/server/conversation/claudeSessionCleanup.js +248 -0
- package/src/server/conversation/claudeTools.js +15 -3
- package/src/server/conversation/claudeTurn.js +212 -1388
- package/src/server/conversation/codexAccountReader.js +168 -0
- package/src/server/conversation/codexAuthPreflight.js +5 -4
- package/src/server/conversation/codexClient.js +18 -6
- package/src/server/conversation/codexConfiguration.js +8 -844
- package/src/server/conversation/codexConversationStops.js +377 -0
- package/src/server/conversation/codexConversationStore.js +119 -85
- package/src/server/conversation/codexConversationTurns.js +378 -0
- package/src/server/conversation/codexConversations.js +134 -866
- package/src/server/conversation/codexDetachedConversation.js +203 -0
- package/src/server/conversation/codexDetachedTurn.js +145 -104
- package/src/server/conversation/codexEvents.js +5 -200
- package/src/server/conversation/codexGoalCommands.js +14 -4
- package/src/server/conversation/codexHelperExecution.js +57 -57
- package/src/server/conversation/codexHelperThreadLedger.js +30 -288
- package/src/server/conversation/codexHelperThreadRecord.js +304 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
- package/src/server/conversation/codexHelperThreads.js +94 -93
- package/src/server/conversation/codexHistoryAdapter.js +13 -8
- package/src/server/conversation/codexIsolation.js +672 -0
- package/src/server/conversation/codexLiveItems.js +379 -0
- package/src/server/conversation/codexMessageCommands.js +68 -286
- package/src/server/conversation/codexMessageDelivery.js +46 -37
- package/src/server/conversation/codexModelCatalog.js +86 -7
- package/src/server/conversation/codexNotificationObserver.js +81 -88
- package/src/server/conversation/codexNotificationQueue.js +21 -10
- package/src/server/conversation/codexObservationControl.js +105 -89
- package/src/server/conversation/codexOutput.js +84 -419
- package/src/server/conversation/codexProcess.js +29 -1053
- package/src/server/conversation/codexProcessLeader.js +1 -0
- package/src/server/conversation/codexPromptDispatch.js +241 -0
- package/src/server/conversation/codexProvider.js +147 -1807
- package/src/server/conversation/codexProviderAuth.js +348 -0
- package/src/server/conversation/codexProviderInvalidation.js +371 -0
- package/src/server/conversation/codexProviderInventories.js +437 -0
- package/src/server/conversation/codexProviderOwner.js +149 -957
- package/src/server/conversation/codexProviderRelease.js +465 -0
- package/src/server/conversation/codexProviderSelection.js +26 -15
- package/src/server/conversation/codexProviderThreads.js +698 -0
- package/src/server/conversation/codexProviderTurns.js +453 -0
- package/src/server/conversation/codexReasoning.js +32 -23
- package/src/server/conversation/codexRenewalCommands.js +18 -6
- package/src/server/conversation/codexRenewalThreads.js +7 -22
- package/src/server/conversation/codexResultSettlement.js +70 -88
- package/src/server/conversation/codexRunJournal.js +91 -97
- package/src/server/conversation/codexRuntime.js +426 -0
- package/src/server/conversation/codexRuntimeInventory.js +113 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
- package/src/server/conversation/codexRuntimeStart.js +431 -0
- package/src/server/conversation/codexRuntimeStop.js +324 -0
- package/src/server/conversation/codexSessionCleanup.js +25 -10
- package/src/server/conversation/codexSessionReconciliation.js +21 -7
- package/src/server/conversation/codexThreadReadiness.js +49 -52
- package/src/server/conversation/codexThreadSnapshot.js +210 -0
- package/src/server/conversation/codexThreadStatus.js +48 -62
- package/src/server/conversation/codexTurn.js +171 -55
- package/src/server/conversation/codexTurnActivity.js +210 -0
- package/src/server/conversation/codexTurnControl.js +44 -48
- package/src/server/conversation/codexTurnEndings.js +610 -0
- package/src/server/conversation/codexTurnRecovery.js +97 -848
- package/src/server/conversation/codexTurnState.js +138 -111
- package/src/server/conversation/completedResponses.js +227 -0
- package/src/server/conversation/configuration.js +2 -2
- package/src/server/conversation/continuity.js +43 -6
- package/src/server/conversation/conversationDelivery.js +394 -0
- package/src/server/conversation/conversationEntries.js +365 -0
- package/src/server/conversation/conversationEvents.js +192 -0
- package/src/server/conversation/conversationGoals.js +187 -0
- package/src/server/conversation/conversationHandles.js +237 -0
- package/src/server/conversation/conversationReads.js +206 -0
- package/src/server/conversation/conversationRefusals.js +58 -0
- package/src/server/conversation/conversationReplacement.js +254 -0
- package/src/server/conversation/conversationSending.js +273 -0
- package/src/server/conversation/conversationStopping.js +121 -0
- package/src/server/conversation/conversationTurnDelivery.js +413 -0
- package/src/server/conversation/conversationTurns.js +511 -0
- package/src/server/conversation/deliveryTiming.js +25 -3
- package/src/server/conversation/fileStorage.js +11 -6
- package/src/server/conversation/hookBridge.js +14 -3
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/localExecution.js +82 -625
- package/src/server/conversation/nativeCommandCallbacks.js +10 -0
- package/src/server/conversation/nativeConversationLifecycle.js +379 -0
- package/src/server/conversation/nativeHistoryExport.js +27 -18
- package/src/server/conversation/normalize.js +9 -0
- package/src/server/conversation/openCodeClient.js +295 -366
- package/src/server/conversation/openCodeNativeHistory.js +141 -0
- package/src/server/conversation/openCodeNativeSession.js +79 -0
- package/src/server/conversation/openCodeProcess.js +109 -53
- package/src/server/conversation/openCodeProcessOwner.js +565 -0
- package/src/server/conversation/openCodeRuntime.js +111 -1814
- package/src/server/conversation/openCodeSessionTurns.js +547 -0
- package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
- package/src/server/conversation/openCodeTools.js +28 -14
- package/src/server/conversation/openCodeTurn.js +80 -74
- package/src/server/conversation/openCodeTurnMonitor.js +388 -0
- package/src/server/conversation/processIdentity.js +582 -0
- package/src/server/conversation/provider.js +21 -6
- package/src/server/conversation/providers/api.js +268 -107
- package/src/server/conversation/providers/claude.js +29 -6
- package/src/server/conversation/providers/claudeDriver.js +325 -250
- package/src/server/conversation/providers/codex.js +69 -33
- package/src/server/conversation/providers/codexDriver.js +503 -1081
- package/src/server/conversation/providers/codexDriverConnection.js +331 -0
- package/src/server/conversation/providers/codexDriverTurns.js +464 -0
- package/src/server/conversation/providers/opencode.js +30 -4
- package/src/server/conversation/providers/opencodeDriver.js +321 -659
- package/src/server/conversation/providers/opencodeStandalone.js +475 -0
- package/src/server/conversation/providers/scoped.js +64 -19
- package/src/server/conversation/runtime.js +171 -2863
- package/src/server/conversation/storage.js +91 -50
- package/src/server/conversation/streams.js +15 -2
- package/src/server/conversation/structuredOutput.js +7 -11
- package/src/server/conversation/systemPrompt.js +13 -1
- package/src/server/conversation/tools.js +47 -15
- package/src/server/conversation/transcript.js +49 -20
- package/src/server/hosts/claudeTurn.js +2 -4
- package/src/server/hosts/codexConfiguration.js +6 -8
- package/src/server/hosts/codexEvents.js +4 -2
- package/src/server/hosts/codexProcess.js +1 -1
- package/src/server/hosts/codexProvider.js +11 -9
- package/src/server/hosts/codexTurn.js +3 -3
- package/src/server/hosts/openCodeClient.js +4 -2
- package/src/server/index.js +0 -2
- package/src/server/lib/aiConnectionClient.js +35 -25
- package/src/server/lib/ndjson.js +1 -0
- package/src/server/lib/providers/anthropicClient.js +15 -6
- package/src/server/lib/providers/common.js +10 -6
- package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
- package/src/server/lib/serviceToolCatalog.js +29 -283
- package/src/server/lib/serviceToolEntries.js +270 -0
- package/src/server/types.js +78 -0
- package/src/shared/assistantResource.js +0 -23
- package/src/shared/conversation/answerChoiceSugar.js +2 -14
- package/src/shared/conversation/messageInputSugar.js +18 -0
- package/src/shared/conversation/numberedQuestionSugar.js +6 -16
- package/src/shared/conversation/pagination.js +1 -1
- package/src/shared/conversation/questions.js +1 -0
- package/src/shared/conversation/streaming.js +1 -0
- package/src/shared/index.js +0 -1
- package/src/testing/conversationStorageContract.js +20 -0
- package/test/aiConnectionClient.test.js +42 -1
- package/test/apiConversationDriver.test.js +217 -38
- package/test/assistantBoundedToolLoop.test.js +1 -1
- package/test/claudeConversationSession.test.js +15 -11
- package/test/claudeInstructionResume.test.js +1 -1
- package/test/claudeProcess.test.js +9 -9
- package/test/claudeTurn.test.js +21 -8
- package/test/codexAccountChange.test.js +5 -3
- package/test/codexConversationSession.test.js +22 -21
- package/test/codexEvents.test.js +3 -1
- package/test/codexHelperDeletion.test.js +3 -7
- package/test/codexIsolation.test.js +6 -6
- package/test/codexModelCatalog.test.js +6 -7
- package/test/codexProcess.test.js +3 -3
- package/test/codexThreadSelection.test.js +4 -1
- package/test/codexTurn.test.js +29 -20
- package/test/conversationApiFacilities.test.js +11 -9
- package/test/conversationAttachments.test.js +3 -3
- package/test/conversationConfiguration.test.js +4 -4
- package/test/conversationNativeStorage.test.js +11 -10
- package/test/conversationReplacement.test.js +13 -13
- package/test/conversationRuntime.test.js +9 -5
- package/test/conversationSession.test.js +78 -22
- package/test/conversationTools.test.js +47 -26
- package/test/fileConversationStorage.test.js +10 -3
- package/test/fixtures/codexHelperThreadLedger.js +3 -3
- package/test/frameworkActions.test.js +33 -18
- package/test/localConversationExecution.test.js +4 -4
- package/test/localExecution.test.js +1 -1
- package/test/openCodeConnectionChange.test.js +3 -2
- package/test/openCodeRuntime.test.js +8 -7
- package/test/openCodeTools.test.js +1 -1
- package/test/opencodeConversationSession.test.js +4 -3
- package/test/serviceToolCatalog.test.js +3 -8
- package/src/server/lib/assistantCompletion.js +0 -190
- package/src/server/lib/assistantToolLoop.js +0 -259
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
|
-
import {
|
|
4
|
-
import os from "node:os";
|
|
3
|
+
import { readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import { promisify } from "node:util";
|
|
7
|
-
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
8
6
|
import { normalizeNullableText } from "./normalize.js";
|
|
9
7
|
import { codexDisabledProcessFeatureArguments } from "./codexHomeStorage.js";
|
|
10
8
|
|
|
@@ -116,11 +114,12 @@ export function codexInteractiveArguments({
|
|
|
116
114
|
];
|
|
117
115
|
}
|
|
118
116
|
|
|
117
|
+
/** @param {import("./codexProvider.js").CodexAppServerAgentProvider} provider */
|
|
119
118
|
export async function codexAppServerProjectHookTrustConfig(provider, cwd = "", {
|
|
120
119
|
persist = false
|
|
121
120
|
} = {}) {
|
|
122
121
|
const normalizedCwd = normalizeNullableText(cwd);
|
|
123
|
-
if (!normalizedCwd
|
|
122
|
+
if (!normalizedCwd) {
|
|
124
123
|
return null;
|
|
125
124
|
}
|
|
126
125
|
await provider.trustProject(normalizedCwd);
|
|
@@ -148,8 +147,7 @@ export async function codexAppServerProjectHookTrustConfig(provider, cwd = "", {
|
|
|
148
147
|
]));
|
|
149
148
|
if (
|
|
150
149
|
persist &&
|
|
151
|
-
trustedHooks.some(({ trustStatus }) => !["managed", "trusted"].includes(trustStatus))
|
|
152
|
-
typeof provider?.writeHookTrustState === "function"
|
|
150
|
+
trustedHooks.some(({ trustStatus }) => !["managed", "trusted"].includes(trustStatus))
|
|
153
151
|
) {
|
|
154
152
|
await provider.writeHookTrustState(state);
|
|
155
153
|
}
|
|
@@ -182,10 +180,8 @@ async function codexModelCatalogRevision({ command, codexHome, bundled, addition
|
|
|
182
180
|
// Export from the installed CLI so native model definitions stay CLI-owned.
|
|
183
181
|
// An app-server start reuses the runtime's exported catalogue until its
|
|
184
182
|
// revision changes, so reconnects and idle restarts run no exporter.
|
|
185
|
-
export async function prepareCodexModelCatalog({
|
|
186
|
-
|
|
187
|
-
codexHome = process.env.CODEX_HOME || path.join(os.homedir(), ".codex")
|
|
188
|
-
}) {
|
|
183
|
+
export async function prepareCodexModelCatalog({ command, runtimeDir, bundled = false, signal, additionalModels = [], codexHome }) {
|
|
184
|
+
if (!codexHome) throw new TypeError("prepareCodexModelCatalog requires the Codex home whose configuration the catalogue reads.");
|
|
189
185
|
const catalogPath = path.join(runtimeDir, "models.json");
|
|
190
186
|
const revisionPath = path.join(runtimeDir, "models.revision");
|
|
191
187
|
const revisionInput = { command, codexHome, bundled, additionalModels };
|
|
@@ -258,38 +254,6 @@ export async function readCodexModelContextWindow({ runtimeDir, model } = {}) {
|
|
|
258
254
|
}
|
|
259
255
|
}
|
|
260
256
|
|
|
261
|
-
const DISABLED_FEATURES = Object.freeze([
|
|
262
|
-
"apps",
|
|
263
|
-
"artifact",
|
|
264
|
-
"browser_use",
|
|
265
|
-
"browser_use_external",
|
|
266
|
-
"browser_use_full_cdp_access",
|
|
267
|
-
"code_mode",
|
|
268
|
-
"code_mode_host",
|
|
269
|
-
"computer_use",
|
|
270
|
-
"default_mode_request_user_input",
|
|
271
|
-
"deferred_executor",
|
|
272
|
-
"goals",
|
|
273
|
-
"hooks",
|
|
274
|
-
"image_generation",
|
|
275
|
-
"in_app_browser",
|
|
276
|
-
"memories",
|
|
277
|
-
"multi_agent",
|
|
278
|
-
"multi_agent_v2",
|
|
279
|
-
"plugins",
|
|
280
|
-
"psp",
|
|
281
|
-
"recommended_plugins",
|
|
282
|
-
"request_permissions_tool",
|
|
283
|
-
"shell_tool",
|
|
284
|
-
"skill_mcp_dependency_install",
|
|
285
|
-
"skill_search",
|
|
286
|
-
"tool_call_mcp_elicitation",
|
|
287
|
-
"tool_suggest",
|
|
288
|
-
"unified_exec",
|
|
289
|
-
"unified_exec_zsh_fork",
|
|
290
|
-
"view_image"
|
|
291
|
-
]);
|
|
292
|
-
|
|
293
257
|
export function codexAppServerTextHasControlCharacters(value = "") {
|
|
294
258
|
return Array.from(String(value)).some((character) => {
|
|
295
259
|
const codePoint = character.codePointAt(0);
|
|
@@ -297,645 +261,6 @@ export function codexAppServerTextHasControlCharacters(value = "") {
|
|
|
297
261
|
});
|
|
298
262
|
}
|
|
299
263
|
|
|
300
|
-
function isPlainRecord(value) {
|
|
301
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
const prototype = Object.getPrototypeOf(value);
|
|
305
|
-
return prototype === Object.prototype || prototype === null;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function deepFreezeCodexAppServerHelperConfig(value) {
|
|
309
|
-
if (!value || typeof value !== "object" || Object.isFrozen(value)) {
|
|
310
|
-
return value;
|
|
311
|
-
}
|
|
312
|
-
for (const nested of Object.values(value)) {
|
|
313
|
-
deepFreezeCodexAppServerHelperConfig(nested);
|
|
314
|
-
}
|
|
315
|
-
return Object.freeze(value);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function codexAppServerIsolationPolicyError(message = "", details = {}) {
|
|
319
|
-
const error = new Error(normalizeNullableText(message) || "Codex cannot prove the helper execution policy.");
|
|
320
|
-
error.code = "codex_policy_unenforceable";
|
|
321
|
-
error.details = Object.freeze({ ...details });
|
|
322
|
-
for (const [name, value] of Object.entries(error.details)) {
|
|
323
|
-
if (!["code", "details", "message", "name"].includes(name)) {
|
|
324
|
-
error[name] = value;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return error;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/** Native helper isolation verification; hosts supply their profile policy. */
|
|
331
|
-
export function createCodexAppServerIsolation({
|
|
332
|
-
clientName = "jskit",
|
|
333
|
-
minimumVersion = "0.151.0",
|
|
334
|
-
disabledFeatures = DISABLED_FEATURES,
|
|
335
|
-
limits: {
|
|
336
|
-
userAgentMaxLength = 512,
|
|
337
|
-
mcpServerMaxCount = 128,
|
|
338
|
-
mcpServerNameMaxLength = 256,
|
|
339
|
-
configResponseMaxBytes = 256 * 1024,
|
|
340
|
-
hookMaxCount = 256,
|
|
341
|
-
hookErrorMaxCount = 256,
|
|
342
|
-
hookFieldMaxLength = 2048,
|
|
343
|
-
hookFingerprintMaxLength = 256 * 1024,
|
|
344
|
-
hookResponseMaxBytes = 512 * 1024
|
|
345
|
-
} = {},
|
|
346
|
-
createError = codexAppServerIsolationPolicyError
|
|
347
|
-
} = {}) {
|
|
348
|
-
const codexAppServerHelperIsolationConfigs = new WeakSet();
|
|
349
|
-
|
|
350
|
-
function assertCodexAppServerHelperResponseBounded(value, maxBytes, label) {
|
|
351
|
-
let bytes = 0;
|
|
352
|
-
try {
|
|
353
|
-
bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
354
|
-
} catch {
|
|
355
|
-
throw createError(
|
|
356
|
-
`Codex helper execution received an invalid ${label}.`
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
if (bytes > maxBytes) {
|
|
360
|
-
throw createError(
|
|
361
|
-
`Codex helper execution received an oversized ${label}.`
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function codexAppServerHelperConnectionGeneration(provider) {
|
|
367
|
-
if (typeof provider?.currentConnectionGeneration !== "function") {
|
|
368
|
-
throw createError(
|
|
369
|
-
"Codex helper execution cannot verify the app-server connection generation."
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
const generation = provider.currentConnectionGeneration();
|
|
373
|
-
if (!Number.isSafeInteger(generation) || generation <= 0) {
|
|
374
|
-
throw createError(
|
|
375
|
-
"Codex helper execution found no active app-server connection."
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
return generation;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
async function codexAppServerHelperExecutionContext(provider) {
|
|
382
|
-
if (typeof provider?.currentHelperExecutionContext !== "function") {
|
|
383
|
-
throw createError(
|
|
384
|
-
"Codex helper execution requires its dedicated isolated provider."
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
const context = await provider.currentHelperExecutionContext();
|
|
388
|
-
const cwd = normalizeNullableText(context?.cwd);
|
|
389
|
-
const accountIdentitySignature = normalizeNullableText(context?.accountIdentitySignature);
|
|
390
|
-
if (
|
|
391
|
-
context?.executionMode !== "helper" ||
|
|
392
|
-
!cwd ||
|
|
393
|
-
!path.isAbsolute(cwd) ||
|
|
394
|
-
!/^sha256:[a-f0-9]{64}$/u.test(accountIdentitySignature)
|
|
395
|
-
) {
|
|
396
|
-
throw createError(
|
|
397
|
-
"Codex helper runtime isolation could not be verified."
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
return Object.freeze({
|
|
401
|
-
accountIdentitySignature,
|
|
402
|
-
cwd,
|
|
403
|
-
executionMode: "helper"
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function codexAppServerSemanticVersionParts(value = "") {
|
|
408
|
-
const match = normalizeNullableText(value).match(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u);
|
|
409
|
-
if (!match) {
|
|
410
|
-
return null;
|
|
411
|
-
}
|
|
412
|
-
const parts = match.slice(1).map((part) => Number.parseInt(part, 10));
|
|
413
|
-
return parts.every(Number.isSafeInteger) ? parts : null;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function codexAppServerUserAgentVersionParts(value = "") {
|
|
417
|
-
const userAgent = normalizeNullableText(value);
|
|
418
|
-
if (
|
|
419
|
-
!userAgent ||
|
|
420
|
-
userAgent.length > userAgentMaxLength ||
|
|
421
|
-
codexAppServerTextHasControlCharacters(userAgent)
|
|
422
|
-
) {
|
|
423
|
-
return null;
|
|
424
|
-
}
|
|
425
|
-
const prefix = `${clientName}/`;
|
|
426
|
-
if (!userAgent.startsWith(prefix)) {
|
|
427
|
-
return null;
|
|
428
|
-
}
|
|
429
|
-
const match = userAgent.slice(prefix.length).match(/^((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*))(?:$|[ \t].*$)/u);
|
|
430
|
-
return match ? codexAppServerSemanticVersionParts(match[1]) : null;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function assertCodexAppServerHelperCompatibility(provider) {
|
|
434
|
-
if (typeof provider?.currentServerInfo !== "function") {
|
|
435
|
-
throw createError(
|
|
436
|
-
"Codex helper execution cannot verify the app-server version. Update Codex and retry.",
|
|
437
|
-
{ minimumVersion }
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
const userAgent = normalizeNullableText(provider.currentServerInfo()?.userAgent);
|
|
441
|
-
const actualParts = codexAppServerUserAgentVersionParts(userAgent);
|
|
442
|
-
if (!actualParts) {
|
|
443
|
-
throw createError(
|
|
444
|
-
"Codex helper execution received an unrecognised app-server version. Update Codex and retry.",
|
|
445
|
-
{
|
|
446
|
-
minimumVersion
|
|
447
|
-
}
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
const actualVersion = actualParts.join(".");
|
|
451
|
-
const minimumParts = codexAppServerSemanticVersionParts(minimumVersion);
|
|
452
|
-
const differingPart = actualParts.findIndex((part, index) => part !== minimumParts[index]);
|
|
453
|
-
if (differingPart !== -1 && actualParts[differingPart] < minimumParts[differingPart]) {
|
|
454
|
-
throw createError(
|
|
455
|
-
`Codex helper execution requires app-server ${minimumVersion} or newer; current version is ${actualVersion}. Update Codex and retry.`,
|
|
456
|
-
{
|
|
457
|
-
actualVersion,
|
|
458
|
-
minimumVersion
|
|
459
|
-
}
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
return Object.freeze({
|
|
463
|
-
minimumVersion,
|
|
464
|
-
version: actualVersion
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function codexAppServerHelperMcpServerNames(configResult = null) {
|
|
469
|
-
if (!isPlainRecord(configResult?.config)) {
|
|
470
|
-
throw createError(
|
|
471
|
-
"Codex helper execution could not read the effective app-server configuration."
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
const servers = configResult.config.mcp_servers;
|
|
475
|
-
if (servers === undefined || servers === null) {
|
|
476
|
-
return [];
|
|
477
|
-
}
|
|
478
|
-
// Only the MCP inventory participates in this isolation check. The native
|
|
479
|
-
// response can also contain large model catalogues and configuration layers.
|
|
480
|
-
assertCodexAppServerHelperResponseBounded(
|
|
481
|
-
servers,
|
|
482
|
-
configResponseMaxBytes,
|
|
483
|
-
"MCP configuration"
|
|
484
|
-
);
|
|
485
|
-
if (!isPlainRecord(servers)) {
|
|
486
|
-
throw createError(
|
|
487
|
-
"Codex helper execution received an invalid MCP server configuration."
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
const names = Object.keys(servers);
|
|
491
|
-
if (
|
|
492
|
-
names.length > mcpServerMaxCount ||
|
|
493
|
-
names.some((name) => (
|
|
494
|
-
!name ||
|
|
495
|
-
name.length > mcpServerNameMaxLength ||
|
|
496
|
-
codexAppServerTextHasControlCharacters(name)
|
|
497
|
-
))
|
|
498
|
-
) {
|
|
499
|
-
throw createError(
|
|
500
|
-
"Codex helper execution received an oversized or invalid MCP server inventory."
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
return names.sort();
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function codexAppServerHelperHookState(result = null, cwd = "") {
|
|
507
|
-
assertCodexAppServerHelperResponseBounded(
|
|
508
|
-
result,
|
|
509
|
-
hookResponseMaxBytes,
|
|
510
|
-
"hook inventory"
|
|
511
|
-
);
|
|
512
|
-
if (!Array.isArray(result?.data)) {
|
|
513
|
-
throw createError(
|
|
514
|
-
"Codex helper execution could not enumerate app-server hooks."
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
if (result.data.length !== 1) {
|
|
518
|
-
throw createError(
|
|
519
|
-
"Codex helper execution received an unexpected hook inventory."
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
const record = result.data[0];
|
|
523
|
-
if (
|
|
524
|
-
normalizeNullableText(record?.cwd) !== cwd ||
|
|
525
|
-
normalizeNullableText(record?.cwd).length > hookFieldMaxLength ||
|
|
526
|
-
!Array.isArray(record.hooks) ||
|
|
527
|
-
!Array.isArray(record.errors) ||
|
|
528
|
-
record.hooks.length > hookMaxCount ||
|
|
529
|
-
record.errors.length > hookErrorMaxCount
|
|
530
|
-
) {
|
|
531
|
-
throw createError(
|
|
532
|
-
"Codex helper execution received an incomplete hook inventory."
|
|
533
|
-
);
|
|
534
|
-
}
|
|
535
|
-
if (record.errors.length > 0) {
|
|
536
|
-
throw createError(
|
|
537
|
-
"Codex helper execution cannot continue while hook discovery has errors.",
|
|
538
|
-
{ hookErrorCount: record.errors.length }
|
|
539
|
-
);
|
|
540
|
-
}
|
|
541
|
-
const hooks = record.hooks.map((hook) => {
|
|
542
|
-
const key = normalizeNullableText(hook?.key);
|
|
543
|
-
const currentHash = normalizeNullableText(hook?.currentHash);
|
|
544
|
-
const handlerType = normalizeNullableText(hook?.handlerType);
|
|
545
|
-
const sourcePath = normalizeNullableText(hook?.sourcePath);
|
|
546
|
-
if (
|
|
547
|
-
!key ||
|
|
548
|
-
[key, currentHash, handlerType, sourcePath].some((field) => (
|
|
549
|
-
field.length > hookFieldMaxLength ||
|
|
550
|
-
codexAppServerTextHasControlCharacters(field)
|
|
551
|
-
))
|
|
552
|
-
) {
|
|
553
|
-
throw createError(
|
|
554
|
-
"Codex helper execution found an invalid hook inventory entry."
|
|
555
|
-
);
|
|
556
|
-
}
|
|
557
|
-
if (hook?.isManaged === true && hook?.enabled === true) {
|
|
558
|
-
throw createError(
|
|
559
|
-
"Codex helper execution cannot disable a managed hook."
|
|
560
|
-
);
|
|
561
|
-
}
|
|
562
|
-
return {
|
|
563
|
-
currentHash,
|
|
564
|
-
enabled: hook?.enabled === true,
|
|
565
|
-
handlerType,
|
|
566
|
-
isManaged: hook?.isManaged === true,
|
|
567
|
-
key,
|
|
568
|
-
sourcePath
|
|
569
|
-
};
|
|
570
|
-
}).sort((left, right) => left.key.localeCompare(right.key));
|
|
571
|
-
const fingerprint = JSON.stringify(hooks);
|
|
572
|
-
if (fingerprint.length > hookFingerprintMaxLength) {
|
|
573
|
-
throw createError(
|
|
574
|
-
"Codex helper execution received an oversized hook inventory."
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
return {
|
|
578
|
-
fingerprint,
|
|
579
|
-
hookKeys: hooks.filter((hook) => !hook.isManaged).map((hook) => hook.key)
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
function codexAppServerHelperIsolationConfig({
|
|
584
|
-
effort = "",
|
|
585
|
-
summary = "",
|
|
586
|
-
hookKeys = [],
|
|
587
|
-
mcpServerNames = []
|
|
588
|
-
} = {}) {
|
|
589
|
-
const config = {
|
|
590
|
-
features: Object.fromEntries(
|
|
591
|
-
disabledFeatures.map((feature) => [feature, false])
|
|
592
|
-
),
|
|
593
|
-
hooks: {
|
|
594
|
-
state: Object.fromEntries(hookKeys.map((key) => [key, { enabled: false }]))
|
|
595
|
-
},
|
|
596
|
-
include_apps_instructions: false,
|
|
597
|
-
include_collaboration_mode_instructions: false,
|
|
598
|
-
include_environment_context: false,
|
|
599
|
-
include_permissions_instructions: false,
|
|
600
|
-
...(effort ? { model_reasoning_effort: effort } : {}),
|
|
601
|
-
...(summary ? { model_reasoning_summary: summary } : {}),
|
|
602
|
-
mcp_servers: Object.fromEntries(
|
|
603
|
-
mcpServerNames.map((name) => [name, { enabled: false }])
|
|
604
|
-
),
|
|
605
|
-
memories: {
|
|
606
|
-
dedicated_tools: false,
|
|
607
|
-
generate_memories: false,
|
|
608
|
-
use_memories: false
|
|
609
|
-
},
|
|
610
|
-
notify: [],
|
|
611
|
-
orchestrator: {
|
|
612
|
-
mcp: {
|
|
613
|
-
enabled: false
|
|
614
|
-
},
|
|
615
|
-
skills: {
|
|
616
|
-
enabled: false
|
|
617
|
-
}
|
|
618
|
-
},
|
|
619
|
-
project_doc_max_bytes: 0,
|
|
620
|
-
shell_environment_policy: {
|
|
621
|
-
inherit: "none",
|
|
622
|
-
set: {}
|
|
623
|
-
},
|
|
624
|
-
skills: {
|
|
625
|
-
include_instructions: false
|
|
626
|
-
},
|
|
627
|
-
tools: {
|
|
628
|
-
experimental_request_user_input: {
|
|
629
|
-
enabled: false
|
|
630
|
-
},
|
|
631
|
-
update_plan: {
|
|
632
|
-
enabled: false
|
|
633
|
-
}
|
|
634
|
-
},
|
|
635
|
-
web_search: "disabled"
|
|
636
|
-
};
|
|
637
|
-
const frozenConfig = deepFreezeCodexAppServerHelperConfig(config);
|
|
638
|
-
codexAppServerHelperIsolationConfigs.add(frozenConfig);
|
|
639
|
-
return frozenConfig;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
function isolationConfigFrom(configResult, hookResult, cwd, { effort = "", summary = "" } = {}) {
|
|
643
|
-
const mcpServerNames = codexAppServerHelperMcpServerNames(configResult);
|
|
644
|
-
const hookState = codexAppServerHelperHookState(hookResult, cwd);
|
|
645
|
-
return {
|
|
646
|
-
mcpServerNames,
|
|
647
|
-
hookState,
|
|
648
|
-
config: codexAppServerHelperIsolationConfig({
|
|
649
|
-
effort,
|
|
650
|
-
summary,
|
|
651
|
-
hookKeys: hookState.hookKeys,
|
|
652
|
-
mcpServerNames
|
|
653
|
-
})
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
async function codexAppServerHelperIsolationState(provider, { effort = "", summary = "" } = {}) {
|
|
658
|
-
assertCodexAppServerHelperCompatibility(provider);
|
|
659
|
-
if (
|
|
660
|
-
typeof provider?.readConfig !== "function" ||
|
|
661
|
-
typeof provider?.listHooks !== "function"
|
|
662
|
-
) {
|
|
663
|
-
throw createError(
|
|
664
|
-
"Codex helper execution cannot inventory configuration and hooks."
|
|
665
|
-
);
|
|
666
|
-
}
|
|
667
|
-
const executionContext = await codexAppServerHelperExecutionContext(provider);
|
|
668
|
-
const generation = codexAppServerHelperConnectionGeneration(provider);
|
|
669
|
-
const configResult = await provider.readConfig({
|
|
670
|
-
cwd: executionContext.cwd,
|
|
671
|
-
includeLayers: false
|
|
672
|
-
});
|
|
673
|
-
if (codexAppServerHelperConnectionGeneration(provider) !== generation) {
|
|
674
|
-
throw createError(
|
|
675
|
-
"Codex app-server reconnected during helper policy verification."
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
const hookResult = await provider.listHooks([executionContext.cwd]);
|
|
679
|
-
if (codexAppServerHelperConnectionGeneration(provider) !== generation) {
|
|
680
|
-
throw createError(
|
|
681
|
-
"Codex app-server reconnected during helper policy verification."
|
|
682
|
-
);
|
|
683
|
-
}
|
|
684
|
-
const { mcpServerNames, hookState, config } = isolationConfigFrom(configResult, hookResult, executionContext.cwd, {
|
|
685
|
-
effort,
|
|
686
|
-
summary
|
|
687
|
-
});
|
|
688
|
-
return Object.freeze({
|
|
689
|
-
accountIdentitySignature: executionContext.accountIdentitySignature,
|
|
690
|
-
config,
|
|
691
|
-
connectionGeneration: generation,
|
|
692
|
-
executionCwd: executionContext.cwd,
|
|
693
|
-
hookFingerprint: hookState.fingerprint,
|
|
694
|
-
hookKeys: Object.freeze([...hookState.hookKeys]),
|
|
695
|
-
mcpServerNames: Object.freeze([...mcpServerNames])
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
function codexAppServerHelperIsolationMatches(left = null, right = null) {
|
|
700
|
-
return left?.accountIdentitySignature === right?.accountIdentitySignature &&
|
|
701
|
-
left?.connectionGeneration === right?.connectionGeneration &&
|
|
702
|
-
left?.executionCwd === right?.executionCwd &&
|
|
703
|
-
left?.hookFingerprint === right?.hookFingerprint &&
|
|
704
|
-
JSON.stringify(left?.mcpServerNames || []) === JSON.stringify(right?.mcpServerNames || []);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
function codexAppServerHelperVerificationFailure(error, cleanupError, threadId = "") {
|
|
708
|
-
const normalizedThreadId = normalizeNullableText(threadId);
|
|
709
|
-
if (!cleanupError) {
|
|
710
|
-
error.codexAppServerHelperThreadId = normalizedThreadId;
|
|
711
|
-
error.codexAppServerHelperThreadRetired = true;
|
|
712
|
-
return error;
|
|
713
|
-
}
|
|
714
|
-
const failure = createError(
|
|
715
|
-
"Codex could not retire a helper thread after policy verification failed.",
|
|
716
|
-
{
|
|
717
|
-
cleanupFailed: true,
|
|
718
|
-
threadId: normalizedThreadId
|
|
719
|
-
}
|
|
720
|
-
);
|
|
721
|
-
failure.codexAppServerHelperThreadCleanupRequired = true;
|
|
722
|
-
failure.codexAppServerHelperThreadId = normalizedThreadId;
|
|
723
|
-
return failure;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
async function throwAfterCodexAppServerHelperVerificationFailure({
|
|
727
|
-
error = null,
|
|
728
|
-
provider = null,
|
|
729
|
-
threadId = ""
|
|
730
|
-
} = {}) {
|
|
731
|
-
let cleanupError = null;
|
|
732
|
-
try {
|
|
733
|
-
await provider.deleteThread(threadId);
|
|
734
|
-
} catch (caught) {
|
|
735
|
-
cleanupError = caught;
|
|
736
|
-
}
|
|
737
|
-
throw codexAppServerHelperVerificationFailure(error, cleanupError, threadId);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// Re-inspect after the native thread exists; a changed surface retires it.
|
|
741
|
-
async function verifyHelperThread(provider, enforcement, thread, threadId, phase) {
|
|
742
|
-
try {
|
|
743
|
-
const verified = await codexAppServerHelperIsolationState(
|
|
744
|
-
provider,
|
|
745
|
-
{
|
|
746
|
-
effort: enforcement.config.model_reasoning_effort,
|
|
747
|
-
summary: enforcement.config.model_reasoning_summary
|
|
748
|
-
}
|
|
749
|
-
);
|
|
750
|
-
if (!codexAppServerHelperIsolationMatches(enforcement, verified)) {
|
|
751
|
-
throw createError(
|
|
752
|
-
`Codex execution surfaces changed while the helper thread was ${phase}.`
|
|
753
|
-
);
|
|
754
|
-
}
|
|
755
|
-
return Object.freeze({
|
|
756
|
-
enforcement: verified,
|
|
757
|
-
thread,
|
|
758
|
-
threadId
|
|
759
|
-
});
|
|
760
|
-
} catch (error) {
|
|
761
|
-
await throwAfterCodexAppServerHelperVerificationFailure({
|
|
762
|
-
error,
|
|
763
|
-
provider,
|
|
764
|
-
threadId
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
async function startCodexAppServerHelperThread(provider, prepare) {
|
|
770
|
-
if (
|
|
771
|
-
typeof provider?.startThread !== "function" ||
|
|
772
|
-
typeof provider?.deleteThread !== "function"
|
|
773
|
-
) {
|
|
774
|
-
throw createError(
|
|
775
|
-
"Codex helper execution cannot own and clean up its app-server thread."
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
const prepared = await prepare();
|
|
779
|
-
const thread = await provider.startThread(prepared.settings);
|
|
780
|
-
const threadId = normalizeNullableText(thread?.id);
|
|
781
|
-
if (!threadId) {
|
|
782
|
-
throw createError(
|
|
783
|
-
"Codex app-server did not return a helper thread id."
|
|
784
|
-
);
|
|
785
|
-
}
|
|
786
|
-
return verifyHelperThread(provider, prepared.enforcement, thread, threadId, "starting");
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
async function resumeCodexAppServerHelperThread(provider, threadId, prepare) {
|
|
790
|
-
if (
|
|
791
|
-
typeof provider?.resumeThread !== "function" ||
|
|
792
|
-
typeof provider?.deleteThread !== "function"
|
|
793
|
-
) {
|
|
794
|
-
throw createError(
|
|
795
|
-
"Codex helper execution cannot safely resume and clean up its app-server thread."
|
|
796
|
-
);
|
|
797
|
-
}
|
|
798
|
-
const normalizedThreadId = normalizeNullableText(threadId);
|
|
799
|
-
if (!normalizedThreadId) {
|
|
800
|
-
throw createError(
|
|
801
|
-
"Codex helper resume requires a controller-owned thread id."
|
|
802
|
-
);
|
|
803
|
-
}
|
|
804
|
-
const prepared = await prepare();
|
|
805
|
-
const enforcement = prepared.enforcement;
|
|
806
|
-
const thread = await provider.resumeThread(
|
|
807
|
-
normalizedThreadId,
|
|
808
|
-
prepared.settings
|
|
809
|
-
);
|
|
810
|
-
return verifyHelperThread(provider, enforcement, thread, normalizedThreadId, "resuming");
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
function configuration({ configResult, hookResult, workdir, effort = "", summary = "" }) {
|
|
814
|
-
return isolationConfigFrom(configResult, hookResult, workdir, { effort, summary }).config;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// Native Helper requests retain their verified configuration object and the
|
|
818
|
-
// distinct Start/Resume field sets. Profile policy is validated by the host.
|
|
819
|
-
function codexAppServerHelperThreadSettings({
|
|
820
|
-
approvalPolicy,
|
|
821
|
-
baseInstructions,
|
|
822
|
-
config,
|
|
823
|
-
cwd,
|
|
824
|
-
effectiveSettings,
|
|
825
|
-
sandbox,
|
|
826
|
-
systemPrompt
|
|
827
|
-
}) {
|
|
828
|
-
return {
|
|
829
|
-
allowProviderModelFallback: false,
|
|
830
|
-
approvalPolicy,
|
|
831
|
-
baseInstructions,
|
|
832
|
-
config,
|
|
833
|
-
cwd,
|
|
834
|
-
systemPrompt: systemPrompt || null,
|
|
835
|
-
dynamicTools: [],
|
|
836
|
-
environments: [],
|
|
837
|
-
model: effectiveSettings.model,
|
|
838
|
-
runtimeWorkspaceRoots: [],
|
|
839
|
-
sandbox,
|
|
840
|
-
selectedCapabilityRoots: []
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
function codexAppServerHelperThreadStartSettings(settings, threadSource) {
|
|
845
|
-
return {
|
|
846
|
-
...settings,
|
|
847
|
-
sessionStartSource: "startup",
|
|
848
|
-
threadSource
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
function codexAppServerHelperThreadResumeSettings(settings) {
|
|
853
|
-
return {
|
|
854
|
-
approvalPolicy: settings.approvalPolicy,
|
|
855
|
-
baseInstructions: settings.baseInstructions,
|
|
856
|
-
config: settings.config,
|
|
857
|
-
cwd: settings.cwd,
|
|
858
|
-
systemPrompt: settings.systemPrompt,
|
|
859
|
-
model: settings.model,
|
|
860
|
-
runtimeWorkspaceRoots: settings.runtimeWorkspaceRoots,
|
|
861
|
-
sandbox: settings.sandbox
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
function codexAppServerHelperTurnSettings({
|
|
866
|
-
approvalPolicy,
|
|
867
|
-
cwd,
|
|
868
|
-
effectiveSettings,
|
|
869
|
-
outputSchema
|
|
870
|
-
}) {
|
|
871
|
-
const settings = {
|
|
872
|
-
approvalPolicy,
|
|
873
|
-
cwd,
|
|
874
|
-
environments: [],
|
|
875
|
-
model: effectiveSettings.model,
|
|
876
|
-
outputSchema,
|
|
877
|
-
runtimeWorkspaceRoots: [],
|
|
878
|
-
sandboxPolicy: {
|
|
879
|
-
networkAccess: false,
|
|
880
|
-
type: "readOnly"
|
|
881
|
-
},
|
|
882
|
-
summary: "none"
|
|
883
|
-
};
|
|
884
|
-
if (effectiveSettings.request.reasoning) {
|
|
885
|
-
settings.effort = effectiveSettings.thinking;
|
|
886
|
-
}
|
|
887
|
-
return settings;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
return Object.freeze({
|
|
891
|
-
assertCompatibility: assertCodexAppServerHelperCompatibility,
|
|
892
|
-
executionContext: codexAppServerHelperExecutionContext,
|
|
893
|
-
inspect: codexAppServerHelperIsolationState,
|
|
894
|
-
configuration,
|
|
895
|
-
hasConfiguration: (config) => codexAppServerHelperIsolationConfigs.has(config),
|
|
896
|
-
threadSettings: codexAppServerHelperThreadSettings,
|
|
897
|
-
threadStartSettings: codexAppServerHelperThreadStartSettings,
|
|
898
|
-
threadResumeSettings: codexAppServerHelperThreadResumeSettings,
|
|
899
|
-
turnSettings: codexAppServerHelperTurnSettings,
|
|
900
|
-
start: startCodexAppServerHelperThread,
|
|
901
|
-
resume: resumeCodexAppServerHelperThread
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
const codexConversationIsolation = createCodexAppServerIsolation();
|
|
906
|
-
|
|
907
|
-
/** Disable ambient tools while allowing the caller to add its authorized tools. */
|
|
908
|
-
export function codexToolFreeConfiguration(options) {
|
|
909
|
-
return structuredClone(codexConversationIsolation.configuration(options));
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
export async function readCodexToolFreeConfiguration(
|
|
913
|
-
provider = null,
|
|
914
|
-
workdir = "",
|
|
915
|
-
assertCompatibility = codexConversationIsolation.assertCompatibility
|
|
916
|
-
) {
|
|
917
|
-
assertCompatibility(provider);
|
|
918
|
-
if (
|
|
919
|
-
typeof provider?.readConfig !== "function" ||
|
|
920
|
-
typeof provider?.listHooks !== "function" ||
|
|
921
|
-
typeof provider?.currentConnectionGeneration !== "function"
|
|
922
|
-
) {
|
|
923
|
-
throw new Error("Codex cannot verify tool isolation for this ephemeral conversation.");
|
|
924
|
-
}
|
|
925
|
-
const connectionGeneration = provider.currentConnectionGeneration();
|
|
926
|
-
if (!Number.isSafeInteger(connectionGeneration) || connectionGeneration <= 0) {
|
|
927
|
-
throw new Error("Codex has no active connection for this ephemeral conversation.");
|
|
928
|
-
}
|
|
929
|
-
const [configResult, hookResult] = await Promise.all([
|
|
930
|
-
provider.readConfig({ cwd: workdir, includeLayers: false }),
|
|
931
|
-
provider.listHooks([workdir])
|
|
932
|
-
]);
|
|
933
|
-
if (provider.currentConnectionGeneration() !== connectionGeneration) {
|
|
934
|
-
throw new Error("Codex reconnected while verifying this ephemeral conversation.");
|
|
935
|
-
}
|
|
936
|
-
return codexToolFreeConfiguration({ configResult, hookResult, workdir });
|
|
937
|
-
}
|
|
938
|
-
|
|
939
264
|
/** Interpret native catalogue responses without choosing application offerings. */
|
|
940
265
|
export function codexCatalogRows(value = null, createError = message => new Error(message)) {
|
|
941
266
|
const rows = Array.isArray(value) ? value : value?.data;
|
|
@@ -945,7 +270,7 @@ export function codexCatalogRows(value = null, createError = message => new Erro
|
|
|
945
270
|
return rows;
|
|
946
271
|
}
|
|
947
272
|
|
|
948
|
-
export function codexCatalogReasoningEfforts(model
|
|
273
|
+
export function codexCatalogReasoningEfforts(model) {
|
|
949
274
|
return new Set((Array.isArray(model?.supportedReasoningEfforts)
|
|
950
275
|
? model.supportedReasoningEfforts
|
|
951
276
|
: [])
|
|
@@ -1083,6 +408,7 @@ export async function verifyCodexProviderKey(provider, apiKey, fetchImpl = fetch
|
|
|
1083
408
|
}
|
|
1084
409
|
// Never expose a provider's raw response, which can contain request details.
|
|
1085
410
|
if (!response.ok) {
|
|
411
|
+
// readable-check: allow optional-call - platform object: a fetch Response, whose body may be absent or lack cancel
|
|
1086
412
|
await response.body?.cancel?.();
|
|
1087
413
|
throw new Error(response.status === 401 || response.status === 403
|
|
1088
414
|
? `${provider.label} rejected this key. Check the key and the required account plan.`
|
|
@@ -1094,165 +420,3 @@ export async function verifyCodexProviderKey(provider, apiKey, fetchImpl = fetch
|
|
|
1094
420
|
throw new Error(`${provider.label} did not return a usable Responses API result. The key was not saved.`);
|
|
1095
421
|
}
|
|
1096
422
|
}
|
|
1097
|
-
|
|
1098
|
-
const CODEX_APP_SERVER_SELECTED_AUTH_MAX_BYTES = 1024 * 1024;
|
|
1099
|
-
const CODEX_APP_SERVER_CHATGPT_ACCESS_TOKEN_MAX_LENGTH = 256 * 1024;
|
|
1100
|
-
export const CODEX_APP_SERVER_ACCOUNT_ID_MAX_LENGTH = 512;
|
|
1101
|
-
const CODEX_APP_SERVER_PLAN_TYPE_MAX_LENGTH = 128;
|
|
1102
|
-
const CODEX_APP_SERVER_API_KEY_MAX_LENGTH = 16 * 1024;
|
|
1103
|
-
|
|
1104
|
-
/** Native account records and secret-change detection. Hosts select the credential home. */
|
|
1105
|
-
export function createCodexAccountReader({
|
|
1106
|
-
errorPrefix = "",
|
|
1107
|
-
identityNamespace = "codex-account-v1",
|
|
1108
|
-
secretNamespace = "codex-auth-secret-v1"
|
|
1109
|
-
} = {}) {
|
|
1110
|
-
function codexAppServerHelperAuthError(code = "", message = "") {
|
|
1111
|
-
const error = new Error(
|
|
1112
|
-
normalizeNullableText(message) || "Codex helper authentication is unavailable. Reconnect Codex and retry."
|
|
1113
|
-
);
|
|
1114
|
-
error.code = normalizeNullableText(code) || `${errorPrefix}codex_helper_auth_unavailable`;
|
|
1115
|
-
return error;
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
function boundedCodexAuthText(value, {
|
|
1119
|
-
label = "Codex authentication value",
|
|
1120
|
-
maxLength = 0
|
|
1121
|
-
} = {}) {
|
|
1122
|
-
if (typeof value !== "string" || value.length === 0 || value.length > maxLength) {
|
|
1123
|
-
throw codexAppServerHelperAuthError(
|
|
1124
|
-
`${errorPrefix}codex_helper_auth_invalid`,
|
|
1125
|
-
`${label} is missing or invalid. Reconnect Codex and retry.`
|
|
1126
|
-
);
|
|
1127
|
-
}
|
|
1128
|
-
return value;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
function codexAccountIdentitySignature(authMode = "", identity = "") {
|
|
1132
|
-
return `sha256:${createHash("sha256")
|
|
1133
|
-
.update(`${identityNamespace}\0`, "utf8")
|
|
1134
|
-
.update(normalizeNullableText(authMode), "utf8")
|
|
1135
|
-
.update("\0", "utf8")
|
|
1136
|
-
.update(String(identity || ""), "utf8")
|
|
1137
|
-
.digest("hex")}`;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
function codexAuthSecretSignature(secret = "") {
|
|
1141
|
-
return createHash("sha256")
|
|
1142
|
-
.update(`${secretNamespace}\0`, "utf8")
|
|
1143
|
-
.update(String(secret || ""), "utf8")
|
|
1144
|
-
.digest("hex");
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
async function readBoundedCodexAuthJson(filePath = "") {
|
|
1148
|
-
let handle = null;
|
|
1149
|
-
try {
|
|
1150
|
-
handle = await open(filePath, "r");
|
|
1151
|
-
const fileStat = await handle.stat();
|
|
1152
|
-
if (!fileStat.isFile() || fileStat.size > CODEX_APP_SERVER_SELECTED_AUTH_MAX_BYTES) {
|
|
1153
|
-
throw codexAppServerHelperAuthError(
|
|
1154
|
-
`${errorPrefix}codex_helper_auth_invalid`,
|
|
1155
|
-
"The selected Codex authentication record is invalid. Reconnect Codex and retry."
|
|
1156
|
-
);
|
|
1157
|
-
}
|
|
1158
|
-
const buffer = Buffer.alloc(CODEX_APP_SERVER_SELECTED_AUTH_MAX_BYTES + 1);
|
|
1159
|
-
let offset = 0;
|
|
1160
|
-
while (offset < buffer.length) {
|
|
1161
|
-
const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, null);
|
|
1162
|
-
if (bytesRead === 0) {
|
|
1163
|
-
break;
|
|
1164
|
-
}
|
|
1165
|
-
offset += bytesRead;
|
|
1166
|
-
}
|
|
1167
|
-
if (offset > CODEX_APP_SERVER_SELECTED_AUTH_MAX_BYTES) {
|
|
1168
|
-
throw codexAppServerHelperAuthError(
|
|
1169
|
-
`${errorPrefix}codex_helper_auth_invalid`,
|
|
1170
|
-
"The selected Codex authentication record is too large. Reconnect Codex and retry."
|
|
1171
|
-
);
|
|
1172
|
-
}
|
|
1173
|
-
const parsed = JSON.parse(buffer.subarray(0, offset).toString("utf8"));
|
|
1174
|
-
if (!isRecord(parsed)) {
|
|
1175
|
-
throw new Error("invalid authentication record");
|
|
1176
|
-
}
|
|
1177
|
-
return parsed;
|
|
1178
|
-
} catch (error) {
|
|
1179
|
-
if (error?.code?.startsWith?.(`${errorPrefix}codex_helper_auth_`)) {
|
|
1180
|
-
throw error;
|
|
1181
|
-
}
|
|
1182
|
-
throw codexAppServerHelperAuthError(
|
|
1183
|
-
`${errorPrefix}codex_helper_auth_unavailable`,
|
|
1184
|
-
"The selected Codex authentication record could not be read. Reconnect Codex and retry."
|
|
1185
|
-
);
|
|
1186
|
-
} finally {
|
|
1187
|
-
await handle?.close?.().catch(() => null);
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
async function readCodexSelectedAccountAuth(options = {}) {
|
|
1192
|
-
const toolHomeSource = normalizeNullableText(options.toolHomeSource);
|
|
1193
|
-
if (!toolHomeSource || !path.isAbsolute(toolHomeSource)) {
|
|
1194
|
-
throw codexAppServerHelperAuthError(
|
|
1195
|
-
`${errorPrefix}codex_helper_auth_unavailable`,
|
|
1196
|
-
"The selected Codex account is unavailable. Reconnect Codex and retry."
|
|
1197
|
-
);
|
|
1198
|
-
}
|
|
1199
|
-
const auth = await readBoundedCodexAuthJson(path.join(toolHomeSource, ".codex", "auth.json"));
|
|
1200
|
-
const storedMode = normalizeNullableText(auth.auth_mode).toLowerCase();
|
|
1201
|
-
if (storedMode === "chatgpt") {
|
|
1202
|
-
const tokens = isRecord(auth.tokens) ? auth.tokens : {};
|
|
1203
|
-
const accountId = boundedCodexAuthText(tokens.account_id, {
|
|
1204
|
-
label: "Codex account identity",
|
|
1205
|
-
maxLength: CODEX_APP_SERVER_ACCOUNT_ID_MAX_LENGTH
|
|
1206
|
-
});
|
|
1207
|
-
const accessToken = boundedCodexAuthText(tokens.access_token, {
|
|
1208
|
-
label: "Codex access token",
|
|
1209
|
-
maxLength: CODEX_APP_SERVER_CHATGPT_ACCESS_TOKEN_MAX_LENGTH
|
|
1210
|
-
});
|
|
1211
|
-
const planTypeValue = tokens.chatgpt_plan_type ?? tokens.plan_type;
|
|
1212
|
-
const planType = planTypeValue === undefined || planTypeValue === null || planTypeValue === ""
|
|
1213
|
-
? ""
|
|
1214
|
-
: boundedCodexAuthText(planTypeValue, {
|
|
1215
|
-
label: "Codex plan type",
|
|
1216
|
-
maxLength: CODEX_APP_SERVER_PLAN_TYPE_MAX_LENGTH
|
|
1217
|
-
});
|
|
1218
|
-
return Object.freeze({
|
|
1219
|
-
accessToken,
|
|
1220
|
-
accountId,
|
|
1221
|
-
authMode: "chatgpt",
|
|
1222
|
-
identitySignature: codexAccountIdentitySignature("chatgpt", accountId),
|
|
1223
|
-
planType,
|
|
1224
|
-
secretSignature: codexAuthSecretSignature(accessToken)
|
|
1225
|
-
});
|
|
1226
|
-
}
|
|
1227
|
-
if (["apikey", "api_key"].includes(storedMode)) {
|
|
1228
|
-
const apiKey = boundedCodexAuthText(auth.OPENAI_API_KEY, {
|
|
1229
|
-
label: "Codex API key",
|
|
1230
|
-
maxLength: CODEX_APP_SERVER_API_KEY_MAX_LENGTH
|
|
1231
|
-
});
|
|
1232
|
-
return Object.freeze({
|
|
1233
|
-
apiKey,
|
|
1234
|
-
authMode: "apiKey",
|
|
1235
|
-
identitySignature: codexAccountIdentitySignature("apiKey", apiKey),
|
|
1236
|
-
secretSignature: codexAuthSecretSignature(apiKey)
|
|
1237
|
-
});
|
|
1238
|
-
}
|
|
1239
|
-
throw codexAppServerHelperAuthError(
|
|
1240
|
-
`${errorPrefix}codex_helper_auth_invalid`,
|
|
1241
|
-
"The selected Codex authentication mode is unsupported. Reconnect Codex and retry."
|
|
1242
|
-
);
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
return {
|
|
1246
|
-
error: codexAppServerHelperAuthError,
|
|
1247
|
-
identitySignature: codexAccountIdentitySignature,
|
|
1248
|
-
readRecord: readBoundedCodexAuthJson,
|
|
1249
|
-
readSelected: readCodexSelectedAccountAuth
|
|
1250
|
-
};
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
const CODEX_AUTH_INVALIDATED_PATTERN =
|
|
1254
|
-
/\b(?:token_invalidated|refresh_token_invalidated)\b|authentication token has been invalidated|HTTP error:\s*401 Unauthorized|401 Unauthorized/iu;
|
|
1255
|
-
|
|
1256
|
-
export function codexAuthOutputRequiresReconnect(output = "") {
|
|
1257
|
-
return CODEX_AUTH_INVALIDATED_PATTERN.test(String(output || ""));
|
|
1258
|
-
}
|