@openclaw/codex 2026.9.2 → 2026.9.4
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 +2 -0
- package/dist/api.js +29 -26
- package/dist/apply-CvT8tW5i.js +356 -0
- package/dist/{attempt-client-cleanup-Bp1ZOnk2.js → attempt-client-cleanup-DKHzbwt5.js} +38 -214
- package/dist/{attempt-context-CSazkWns.js → attempt-context-D44yeDII.js} +9 -7
- package/dist/{event-projector-usage-ZqEVbAEY.js → attempt-notifications-C9XufG6l.js} +3 -106
- package/dist/{auth-binding-Ds_QVm-X.js → auth-binding-CWlKVccJ.js} +2 -2
- package/dist/auth-cache-key-7orQae_j.js +498 -0
- package/dist/auth-profile-CwzO4_RG.js +43 -0
- package/dist/auth-profile-selection-DFu9e167.js +45 -0
- package/dist/binding-connection-D3udKbIE.js +60 -0
- package/dist/{bounded-turn-DxGJRYbY.js → bounded-turn-C_Hneydj.js} +28 -125
- package/dist/build-state-BCmg9-5p.js +19 -0
- package/dist/{capabilities-B1mgvpP8.js → capabilities-8vx68WLH.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/client-B57KWPQx.js +4005 -0
- package/dist/command-dispatch-BpduNGVf.js +21 -0
- package/dist/{command-formatters-DSbGMXA0.js → command-formatters-lxlkNZgU.js} +4 -3
- package/dist/{command-handlers-DOJvGv6-.js → command-handlers-tiL9iWj7.js} +788 -135
- package/dist/command-rpc-CR5EOEZX.js +233 -0
- package/dist/{compact-Y_xBqrHN.js → compact-B_H3AOd7.js} +19 -14
- package/dist/config-oIORaQ5T.js +106 -0
- package/dist/{config-CyapYSji.js → config-options-CXMq1T-C.js} +205 -593
- package/dist/config-runtime-D0zGNIHu.js +11 -0
- package/dist/{config-utils-C57JiJrU.js → config-utils-DujwEnhg.js} +2 -4
- package/dist/conversation-binding-DsB4-O7U.js +900 -0
- package/dist/{plugin-marketplace-discovery-B7gHXJ3d.js → conversation-control-CtUu1b3m.js} +11 -143
- package/dist/doctor-contract-api.js +5 -5
- package/dist/{dynamic-tools-BwpvTxaX.js → dynamic-tools-CmN5RAip.js} +133 -76
- package/dist/{effective-mcp-catalog-CY9Jn4bA.js → effective-mcp-catalog-Bd73bqA8.js} +3 -3
- package/dist/{elicitation-input-DMkeki8d.js → elicitation-input-DPoK8F7w.js} +8 -22
- package/dist/ephemeral-turn-D78_VmzO.js +101 -0
- package/dist/{event-projector-assistant-message-B9kp6iGC.js → event-projector-assistant-message-BmBM4qBV.js} +12 -25
- package/dist/event-projector-items-5VhsECCO.js +200 -0
- package/dist/event-projector-usage-BfXmpOec.js +102 -0
- package/dist/{harness-Czh2Sdoc.js → harness-C7rRa7bm.js} +48 -24
- package/dist/harness.js +1 -1
- package/dist/history-rejection-B4nOXNXF.js +13 -0
- package/dist/incognito-session-uhrBF6wJ.js +15 -0
- package/dist/index.js +406 -2915
- package/dist/inference-context-Dr_mzvk4.js +94 -0
- package/dist/inference-proxy-HGXKUIre.js +311 -0
- package/dist/{isolated-completion-CRh-51AK.js → isolated-completion-DWVb5U2J.js} +7 -4
- package/dist/launch-args-RXQwn8zV.js +90 -0
- package/dist/{managed-binary-DcpnJF9x.js → managed-binary-D22rNEcR.js} +13 -103
- package/dist/managed-thread-store-D1t5jh3G.js +66 -0
- package/dist/media-understanding-provider.js +4 -1
- package/dist/migration-provider-api.js +2 -0
- package/dist/{model-catalog-DT5vo6qo.js → model-catalog-Dq2n8f_p.js} +6 -5
- package/dist/{models-CcrgSMQR.js → models-PCKOim-h.js} +3 -3
- package/dist/native-execution-policy-umd0tgh4.js +128 -0
- package/dist/native-subagent-history-DjtPloaU.js +140 -0
- package/dist/native-subagent-history-owner-Pcp7JuVF.js +34 -0
- package/dist/{native-subagent-monitor-B3bvQGcg.js → native-subagent-monitor-fQ7eIXEf.js} +115 -460
- package/dist/native-subagent-task-ids-BzTdT0Uw.js +387 -0
- package/dist/node-cli-sessions-DKaOmVUh.js +527 -0
- package/dist/{node-exec-server.runtime-nfbUOtUB.js → node-exec-server.runtime-Cl2vapEi.js} +55 -24
- package/dist/plan-7uG-Fp36.js +947 -0
- package/dist/plugin-activation-BYQculil.js +138 -0
- package/dist/{thread-resume-BLgqkUc1.js → plugin-app-cache-key-d0eKxy7b.js} +4 -52
- package/dist/{plugin-approval-roundtrip-aQVdyL5g.js → plugin-approval-roundtrip-Bz3FT8W5.js} +1 -1
- package/dist/plugin-inventory-oL4Na1ZG.js +328 -0
- package/dist/plugin-marketplace-discovery-P-Vndfqq.js +137 -0
- package/dist/{plugin-metadata-cache-BwCLTY_U.js → plugin-metadata-cache-ClPJpRio.js} +17 -9
- package/dist/protocol-5bh1G-H7.js +275 -0
- package/dist/provider-MQ8PVkuE.js +50 -0
- package/dist/{provider-capabilities-BpG9Ltc5.js → provider-capabilities-BvCUL69A.js} +2 -2
- package/dist/{rate-limits-Fx_l4eNa.js → rate-limits-CBMmWBr9.js} +1 -1
- package/dist/request-B7hEkBGq.js +212 -0
- package/dist/rpc-error-ttN_QjEm.js +30 -0
- package/dist/{run-attempt-BzthFQSM.js → run-attempt-CvsAAGa0.js} +664 -377
- package/dist/{runtime-artifact-Bh_gf6-t.js → runtime-artifact-BJ0rXBZ_.js} +27 -27
- package/dist/sandbox-guard-BxgyoM8M.js +166 -0
- package/dist/{session-binding-4Ki04sW-.js → session-binding-CI8UuilT.js} +30 -48
- package/dist/{session-binding-record-CyaXZvTe.js → session-binding-record-Bcvslnhf.js} +12 -26
- package/dist/session-catalog-adoption-tcZi1IfX.js +251 -0
- package/dist/session-catalog-archive-CwKg8Xz2.js +56 -0
- package/dist/session-catalog-ct-AH01I.js +1477 -0
- package/dist/session-catalog-home-id-CwhLdC03.js +18 -0
- package/dist/session-catalog-node-adoption-B1tgDW-m.js +182 -0
- package/dist/session-catalog-node-continue-Ci-DXyJP.js +238 -0
- package/dist/session-catalog-provenance-D-dIfRAH.js +96 -0
- package/dist/{session-cli-BjLhpmS1.js → session-cli-DNN_jhGc.js} +3 -2
- package/dist/{session-history-DWmMqiqW.js → session-history-Cz-tOOYj.js} +6 -3
- package/dist/{session-upstream-marker-DReTpGz6.js → session-history-import-DwyijFI6.js} +5 -50
- package/dist/{session-history-read-LHldwvyh.js → session-history-read-cxPUxyVV.js} +21 -7
- package/dist/session-history-worker-runtime.js +18 -6
- package/dist/session-history.worker-BeK1YOyT.js +378 -0
- package/dist/session-history.worker.js +1 -1
- package/dist/{session-initialization-BCFVnLL4.js → session-initialization-Ct0_Hzmg.js} +2 -2
- package/dist/{session-permission-policy-BaueHmSj.js → session-permission-policy-BBmXA8Eg.js} +4 -7
- package/dist/{session-retirement-DNdmYdFc.js → session-retirement-B2YfbAAT.js} +6 -5
- package/dist/session-upstream-marker-D15C9NHp.js +46 -0
- package/dist/{settled-turn-context-BOM3Y1pl.js → settled-turn-context-Du6gdUDd.js} +9 -9
- package/dist/{settled-turn-finalizer-BI-b1sry.js → settled-turn-finalizer-Dn91stmc.js} +8 -7
- package/dist/shared-client-CscigXXL.js +3109 -0
- package/dist/{side-question-B6DWAw1A.js → side-question-BGMJQf4U.js} +65 -170
- package/dist/source-CElXU0Oy.js +518 -0
- package/dist/thread-archive-guard-DfpBr-Sz.js +53 -0
- package/dist/thread-history-page-D0JIp48V.js +361 -0
- package/dist/{thread-lifecycle-DPK30jad.js → thread-lifecycle-DXl1-I6b.js} +187 -639
- package/dist/{thread-ownership-CbucAmmp.js → thread-ownership-BsDRTsyp.js} +13 -15
- package/dist/thread-policy-BuEQfE0g.js +54 -0
- package/dist/thread-resume-DsDOZpFg.js +52 -0
- package/dist/timeout-BVw8aaM1.js +33 -0
- package/dist/transcript-history-projection-v17wXgdK.js +238 -0
- package/dist/{transcript-mirror-_njEB84z.js → transcript-mirror-C5CKgNy6.js} +50 -260
- package/dist/{transcript-mirror-attestation-Byl34PC-.js → transcript-mirror-attestation-DibE0krH.js} +17 -4
- package/dist/{transport-stdio-CzTngV0j.js → transport-stdio-BGjHoYLr.js} +73 -43
- package/dist/{upstream-session-fork-1fwbNKzQ.js → upstream-session-fork-BZerMte_.js} +27 -19
- package/dist/{usage-i9fv5-4m.js → usage-tCk-mg_x.js} +4 -3
- package/dist/version-omm6IPI3.js +12 -0
- package/dist/{web-search-provider.runtime-DKQn67A7.js → web-search-provider.runtime-B7bGbGWq.js} +3 -3
- package/openclaw.plugin.json +23 -1
- package/package.json +4 -4
- package/dist/request-C8H22rPi.js +0 -475
- package/dist/session-catalog-B47uwekb.js +0 -3272
- package/dist/session-history.worker-zZVcx0ME.js +0 -267
- package/dist/shared-client-CKhX2kFt.js +0 -7505
- package/dist/thread-policy-CTalhWFy.js +0 -105
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
import { l as sessionBindingIdentity, n as assertCodexBindingMayBeReplaced } from "./session-binding-record-Bcvslnhf.js";
|
|
2
|
+
import { b as readCodexRequirementsToml, g as readCodexEffectiveConfig, n as codexSandboxPolicyForTurn } from "./config-options-CXMq1T-C.js";
|
|
3
|
+
import { o as isJsonObject, x as readCodexPluginConfig } from "./protocol-5bh1G-H7.js";
|
|
4
|
+
import { _ as assertCodexThreadAcceptsDirectInput, b as assertCodexThreadStartResponse, l as isCodexAppServerOverloadError, m as CodexThreadDirectInputError, s as isCodexAppServerIndeterminateRequestCancellationError } from "./client-B57KWPQx.js";
|
|
5
|
+
import { o as resolveCodexDefaultWorkspaceDir } from "./conversation-binding-data-BiodpIzq.js";
|
|
6
|
+
import { n as projectBoundedCodexVisibleSessionHistory } from "./transcript-history-projection-v17wXgdK.js";
|
|
7
|
+
import { n as resolveOpenClawExecPolicyForCodexAppServer, t as canUseCodexModelBackedApprovalsReviewerForModel } from "./config-oIORaQ5T.js";
|
|
8
|
+
import { n as resolveCodexAppServerRuntimeOptions } from "./config-runtime-D0zGNIHu.js";
|
|
9
|
+
import { n as normalizeCodexAppServerBindingModelProvider, t as isCodexAppServerNativeAuthProfile } from "./auth-profile-CwzO4_RG.js";
|
|
10
|
+
import { H as isCodexAppServerClientRuntimeLive, R as consumeCodexAppServerLiveThread, U as isCodexAppServerLiveThreadClaimed, V as hasCodexAppServerLiveThread, _ as retainSharedCodexAppServerClientByInstanceId, c as getLeasedSharedCodexAppServerClient, m as releaseCodexAppServerClientLease, q as releaseCodexAppServerLiveThread, x as withLeasedCodexAppServerClientStartSelectionRetry } from "./shared-client-CscigXXL.js";
|
|
11
|
+
import { a as closeCodexStartupClientBestEffort, d as unsubscribeCodexThreadBestEffort, h as isCodexNotificationForTurn, l as retireUnsafeCodexTurnClientBestEffort, o as interruptCodexTurnAndWaitBestEffort, p as getCodexAppServerTurnRouter, s as isCodexAppServerUnsafeSubscriptionError } from "./attempt-client-cleanup-DKHzbwt5.js";
|
|
12
|
+
import { n as isAssistantCommentaryCompletionNotification } from "./attempt-notifications-C9XufG6l.js";
|
|
13
|
+
import { D as resolveCodexAppServerRequestModelSelection, Et as mergeCodexThreadConfigs, M as buildCodexProjectDocThreadConfig, T as CODEX_NATIVE_PERSONALITY_NONE, Tt as buildDisabledAppsConfigPatch } from "./thread-lifecycle-DXl1-I6b.js";
|
|
14
|
+
import "./session-binding-CI8UuilT.js";
|
|
15
|
+
import { t as resumeCodexAppServerThread } from "./thread-resume-DsDOZpFg.js";
|
|
16
|
+
import { a as rollbackCodexAppServerBindingSubscription, n as releaseCodexAppServerBindingSubscription, r as retainCodexAppServerBindingSubscription, s as withExclusiveCodexAppServerThread, t as isSameCodexAppServerThreadOwner } from "./thread-ownership-BsDRTsyp.js";
|
|
17
|
+
import { a as resolveCodexAppServerForModelProvider, i as resolveCodexSessionPermissionCwd, n as applyCodexSessionPermissionPolicy, t as CODEX_SESSION_PERMISSION_EXEC_MODES } from "./session-permission-policy-BBmXA8Eg.js";
|
|
18
|
+
import { u as trackCodexConversationActiveTurn } from "./conversation-control-CtUu1b3m.js";
|
|
19
|
+
import { asOptionalRecord, normalizeOptionalString, normalizeSingleOrTrimmedStringList } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
20
|
+
import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
|
|
21
|
+
import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { resolveAgentWorkspaceDir } from "openclaw/plugin-sdk/agent-runtime";
|
|
25
|
+
import { embeddedAgentLog, formatErrorMessage, resolveActiveEmbeddedRunSessionId, resolveSandboxContext } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
26
|
+
import { loadExecApprovals } from "openclaw/plugin-sdk/exec-approvals-runtime";
|
|
27
|
+
import { getSessionEntry, resolveStorePath, resolveTranscriptSessionKeyBySessionId } from "openclaw/plugin-sdk/session-store-runtime";
|
|
28
|
+
import { readVisibleSessionTranscriptMessageEntries } from "openclaw/plugin-sdk/session-transcript-runtime";
|
|
29
|
+
//#region extensions/codex/src/conversation-binding-preparation.ts
|
|
30
|
+
/** Canonical conversation thread preparation, policy, and binding ownership. */
|
|
31
|
+
const NATIVE_CONVERSATION_INTERACTIVE_APPROVALS_UNAVAILABLE = "OpenClaw native Codex conversation binding cannot route interactive approvals yet; use the Codex harness or explicit /acp spawn codex for that workflow.";
|
|
32
|
+
async function resolveConversationAppServerRuntime(params) {
|
|
33
|
+
const source = params.source;
|
|
34
|
+
const agentId = source?.agentId ?? params.agentId ?? (params.config ? resolveSessionAgentIdsStrict({
|
|
35
|
+
sessionKey: params.sessionKey,
|
|
36
|
+
config: params.config
|
|
37
|
+
}).sessionAgentId : void 0);
|
|
38
|
+
const storePath = agentId && (source || params.sessionKey) ? resolveStorePath(params.config?.session?.store, { agentId }) : void 0;
|
|
39
|
+
const sessionKey = source ? source.sessionKey ?? (storePath ? resolveTranscriptSessionKeyBySessionId({
|
|
40
|
+
agentId: source.agentId,
|
|
41
|
+
sessionId: source.sessionId,
|
|
42
|
+
storePath
|
|
43
|
+
}) : void 0) : params.sessionKey;
|
|
44
|
+
const storedEntry = sessionKey && storePath ? getSessionEntry({
|
|
45
|
+
agentId,
|
|
46
|
+
storePath,
|
|
47
|
+
sessionKey,
|
|
48
|
+
readConsistency: "latest"
|
|
49
|
+
}) : void 0;
|
|
50
|
+
const entry = !source || storedEntry?.sessionId === source.sessionId ? storedEntry : void 0;
|
|
51
|
+
if (source && !entry) throw new Error("Codex conversation source session is missing or no longer current; rebind this conversation before retrying.");
|
|
52
|
+
const permissionMode = entry?.permissionMode;
|
|
53
|
+
const sessionRoot = permissionMode ? entry?.sessionRoot : void 0;
|
|
54
|
+
const agentWorkspaceDir = params.config && agentId ? resolveAgentWorkspaceDir(params.config, agentId) : resolveCodexDefaultWorkspaceDir(params.pluginConfig);
|
|
55
|
+
const execPolicy = resolveOpenClawExecPolicyForCodexAppServer({
|
|
56
|
+
config: params.config,
|
|
57
|
+
agentId,
|
|
58
|
+
permissionMode,
|
|
59
|
+
execOverrides: permissionMode ? { mode: CODEX_SESSION_PERMISSION_EXEC_MODES[permissionMode] } : void 0,
|
|
60
|
+
approvals: permissionMode === "full" ? void 0 : loadExecApprovals()
|
|
61
|
+
});
|
|
62
|
+
const sandboxForPolicy = execPolicy.touched && execPolicy.security === "full" && execPolicy.ask !== "off" ? await resolveSandboxContext({
|
|
63
|
+
config: params.config,
|
|
64
|
+
sessionKey,
|
|
65
|
+
workspaceDir: agentWorkspaceDir
|
|
66
|
+
}) : void 0;
|
|
67
|
+
const configuredRuntime = resolveCodexAppServerRuntimeOptions({
|
|
68
|
+
pluginConfig: params.pluginConfig,
|
|
69
|
+
execPolicy,
|
|
70
|
+
modelProvider: params.modelProvider,
|
|
71
|
+
model: params.model,
|
|
72
|
+
config: params.config,
|
|
73
|
+
agentDir: params.agentDir,
|
|
74
|
+
openClawSandboxActive: Boolean(sandboxForPolicy?.enabled)
|
|
75
|
+
});
|
|
76
|
+
const canUseAutoReview = canUseCodexModelBackedApprovalsReviewerForModel({
|
|
77
|
+
modelProvider: params.modelProvider,
|
|
78
|
+
model: params.model,
|
|
79
|
+
config: params.config,
|
|
80
|
+
env: process.env,
|
|
81
|
+
agentDir: params.agentDir
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
runtime: applyCodexSessionPermissionPolicy({
|
|
85
|
+
appServer: configuredRuntime,
|
|
86
|
+
permissionMode,
|
|
87
|
+
sessionRoot,
|
|
88
|
+
defaultRoot: agentWorkspaceDir,
|
|
89
|
+
pluginConfig: readCodexPluginConfig(params.pluginConfig),
|
|
90
|
+
canUseAutoReview,
|
|
91
|
+
requirementsToml: readCodexRequirementsToml({}),
|
|
92
|
+
execMode: execPolicy.mode
|
|
93
|
+
}),
|
|
94
|
+
workspaceDir: resolveCodexSessionPermissionCwd({
|
|
95
|
+
permissionMode,
|
|
96
|
+
sessionRoot,
|
|
97
|
+
defaultRoot: agentWorkspaceDir,
|
|
98
|
+
requestedCwd: params.workspaceDir,
|
|
99
|
+
fallbackCwd: params.workspaceDir
|
|
100
|
+
})
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS = "This Codex thread is bound to an OpenClaw conversation. Answer normally; OpenClaw will deliver your final response back to the conversation.";
|
|
104
|
+
async function resolveThreadBindingRuntime(params) {
|
|
105
|
+
const agentLookup = buildCodexConversationAgentLookup({
|
|
106
|
+
agentDir: params.agentDir,
|
|
107
|
+
config: params.config
|
|
108
|
+
});
|
|
109
|
+
const modelProvider = resolveThreadRequestModelProvider({
|
|
110
|
+
authProfileId: params.authProfileId,
|
|
111
|
+
modelProvider: params.modelProvider,
|
|
112
|
+
...agentLookup
|
|
113
|
+
});
|
|
114
|
+
const modelSelection = resolveOptionalThreadRequestModelSelection({
|
|
115
|
+
model: params.model,
|
|
116
|
+
modelProvider,
|
|
117
|
+
authProfileId: params.authProfileId,
|
|
118
|
+
...agentLookup
|
|
119
|
+
});
|
|
120
|
+
const reviewerModelProvider = resolveModelBackedReviewerPolicyProvider({
|
|
121
|
+
authProfileId: params.authProfileId,
|
|
122
|
+
modelProvider: params.modelProvider,
|
|
123
|
+
...agentLookup
|
|
124
|
+
});
|
|
125
|
+
const { runtime, workspaceDir } = await resolveConversationAppServerRuntime({
|
|
126
|
+
pluginConfig: params.pluginConfig,
|
|
127
|
+
config: params.config,
|
|
128
|
+
agentId: params.agentId,
|
|
129
|
+
sessionKey: params.sessionKey,
|
|
130
|
+
source: params.source,
|
|
131
|
+
workspaceDir: params.workspaceDir,
|
|
132
|
+
modelProvider: reviewerModelProvider,
|
|
133
|
+
model: params.model,
|
|
134
|
+
agentDir: params.agentDir
|
|
135
|
+
});
|
|
136
|
+
const modelScopedRuntime = resolveCodexAppServerForModelProvider({
|
|
137
|
+
appServer: runtime,
|
|
138
|
+
provider: reviewerModelProvider,
|
|
139
|
+
model: params.model,
|
|
140
|
+
config: params.config,
|
|
141
|
+
env: process.env,
|
|
142
|
+
agentDir: params.agentDir
|
|
143
|
+
});
|
|
144
|
+
assertNativeConversationApprovalPolicySupported(modelScopedRuntime);
|
|
145
|
+
const clientOptions = {
|
|
146
|
+
startOptions: runtime.start,
|
|
147
|
+
timeoutMs: runtime.requestTimeoutMs,
|
|
148
|
+
authProfileId: params.authProfileId,
|
|
149
|
+
...agentLookup
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
runtime: modelScopedRuntime,
|
|
153
|
+
workspaceDir,
|
|
154
|
+
agentLookup,
|
|
155
|
+
model: modelSelection?.model,
|
|
156
|
+
modelProvider: modelSelection?.modelProvider ?? modelProvider,
|
|
157
|
+
clientOptions
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function buildConversationThreadRequest(resolved, serviceTier, effectiveNativeConfig) {
|
|
161
|
+
return {
|
|
162
|
+
cwd: resolved.workspaceDir,
|
|
163
|
+
...resolved.model ? { model: resolved.model } : {},
|
|
164
|
+
...resolved.modelProvider ? { modelProvider: resolved.modelProvider } : {},
|
|
165
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
166
|
+
approvalPolicy: resolved.runtime.approvalPolicy,
|
|
167
|
+
approvalsReviewer: resolved.runtime.approvalsReviewer,
|
|
168
|
+
...resolved.runtime.sessionRoot ? { runtimeWorkspaceRoots: [resolved.runtime.sessionRoot] } : {},
|
|
169
|
+
...codexConversationSandboxOrPermissions(resolved.runtime, resolved.runtime.sandbox, effectiveNativeConfig),
|
|
170
|
+
...serviceTier ? { serviceTier } : {}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async function buildConversationThreadRequestForClient(client, resolved, serviceTier, requestOptions) {
|
|
174
|
+
const effectiveConfig = await readCodexEffectiveConfig(client, resolved.workspaceDir, requestOptions());
|
|
175
|
+
requestOptions();
|
|
176
|
+
return buildConversationThreadRequest(resolved, serviceTier, effectiveConfig);
|
|
177
|
+
}
|
|
178
|
+
function codexConversationSandboxOrPermissions(runtime, sandbox, effectiveNativeConfig) {
|
|
179
|
+
const networkProxy = runtime.networkProxy;
|
|
180
|
+
const config = buildCodexProjectDocThreadConfig(mergeCodexThreadConfigs(networkProxy?.configPatch, buildDisabledAppsConfigPatch()), effectiveNativeConfig);
|
|
181
|
+
return networkProxy ? { config } : {
|
|
182
|
+
sandbox,
|
|
183
|
+
config
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async function writeThreadBindingFromResponse(params, resolved, client, response, requestOptions) {
|
|
187
|
+
let retained = false;
|
|
188
|
+
let sameOwner = false;
|
|
189
|
+
try {
|
|
190
|
+
const current = params.bindingStore.read(params.identity);
|
|
191
|
+
assertCodexBindingMayBeReplaced(current, "storing a conversation-bound Codex thread");
|
|
192
|
+
const trackSubscription = !params.incognito && isCodexAppServerClientRuntimeLive(client);
|
|
193
|
+
sameOwner = isSameCodexAppServerThreadOwner(current, {
|
|
194
|
+
threadId: response.thread.id,
|
|
195
|
+
clientId: client.getInstanceId()
|
|
196
|
+
});
|
|
197
|
+
requestOptions();
|
|
198
|
+
assertCodexThreadAcceptsDirectInput(response.thread);
|
|
199
|
+
if (trackSubscription) {
|
|
200
|
+
retained = await retainCodexAppServerBindingSubscription(client, response.thread.id);
|
|
201
|
+
if (!retained) throw new Error("Codex conversation thread lost its native subscription owner.");
|
|
202
|
+
}
|
|
203
|
+
if (current && !sameOwner) {
|
|
204
|
+
const { assertCurrent } = requestOptions();
|
|
205
|
+
await releaseCodexAppServerBindingSubscription(current, { assertCurrent });
|
|
206
|
+
}
|
|
207
|
+
requestOptions();
|
|
208
|
+
if (!await params.bindingStore.mutate(params.identity, {
|
|
209
|
+
kind: "set",
|
|
210
|
+
binding: {
|
|
211
|
+
threadId: response.thread.id,
|
|
212
|
+
clientId: client.getInstanceId(),
|
|
213
|
+
cwd: resolved.workspaceDir,
|
|
214
|
+
authProfileId: params.authProfileId,
|
|
215
|
+
model: response.model ?? resolved.model ?? params.model,
|
|
216
|
+
modelProvider: normalizeCodexAppServerBindingModelProvider({
|
|
217
|
+
authProfileId: params.authProfileId,
|
|
218
|
+
modelProvider: response.modelProvider ?? resolved.modelProvider ?? params.modelProvider,
|
|
219
|
+
...resolved.agentLookup
|
|
220
|
+
}),
|
|
221
|
+
serviceTier: params.serviceTier ?? resolved.runtime.serviceTier ?? void 0,
|
|
222
|
+
networkProxyProfileName: resolved.runtime.networkProxy?.profileName,
|
|
223
|
+
networkProxyConfigFingerprint: resolved.runtime.networkProxy?.configFingerprint
|
|
224
|
+
}
|
|
225
|
+
}, requestOptions)) throw new Error("Codex conversation binding changed while storing its thread.");
|
|
226
|
+
} catch (error) {
|
|
227
|
+
if (retained && !sameOwner || !hasCodexAppServerLiveThread(client, response.thread.id)) await rollbackCodexAppServerBindingSubscription(client, response.thread.id, retained);
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async function bindThread(params, threadId) {
|
|
232
|
+
const current = params.bindingStore.read(params.identity);
|
|
233
|
+
assertCodexBindingMayBeReplaced(current, "binding a conversation-bound Codex thread");
|
|
234
|
+
const resolved = await resolveThreadBindingRuntime(params);
|
|
235
|
+
const clientLease = { client: await getLeasedSharedCodexAppServerClient(resolved.clientOptions) };
|
|
236
|
+
try {
|
|
237
|
+
await withLeasedCodexAppServerClientStartSelectionRetry({
|
|
238
|
+
lease: clientLease,
|
|
239
|
+
options: resolved.clientOptions,
|
|
240
|
+
run: async (client, requestOptions) => {
|
|
241
|
+
const request = await buildConversationThreadRequestForClient(client, resolved, params.serviceTier ?? resolved.runtime.serviceTier, requestOptions);
|
|
242
|
+
let response;
|
|
243
|
+
if (threadId && !resolved.runtime.networkProxy) {
|
|
244
|
+
if (isCodexAppServerLiveThreadClaimed(client, threadId)) throw new Error(`Codex thread ${threadId} has an active run; stop it before binding its conversation.`);
|
|
245
|
+
const { thread } = await client.request("thread/read", {
|
|
246
|
+
threadId,
|
|
247
|
+
includeTurns: false
|
|
248
|
+
}, requestOptions());
|
|
249
|
+
assertCodexThreadAcceptsDirectInput(thread);
|
|
250
|
+
const { assertCurrent } = requestOptions();
|
|
251
|
+
await releaseCodexAppServerLiveThread(client, threadId, assertCurrent);
|
|
252
|
+
if (isCodexAppServerLiveThreadClaimed(client, threadId)) throw new Error(`Codex thread ${threadId} has an active run; stop it before binding its conversation.`);
|
|
253
|
+
response = await resumeCodexAppServerThread({
|
|
254
|
+
client,
|
|
255
|
+
abandonClient: () => closeCodexStartupClientBestEffort(client),
|
|
256
|
+
request: {
|
|
257
|
+
...request,
|
|
258
|
+
threadId
|
|
259
|
+
},
|
|
260
|
+
requestResume: (resumeRequest) => client.request("thread/resume", resumeRequest, requestOptions())
|
|
261
|
+
});
|
|
262
|
+
} else response = await client.request("thread/start", {
|
|
263
|
+
...request,
|
|
264
|
+
developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
|
|
265
|
+
experimentalRawEvents: true,
|
|
266
|
+
...params.incognito ? { ephemeral: true } : {}
|
|
267
|
+
}, requestOptions());
|
|
268
|
+
await writeThreadBindingFromResponse(params, resolved, client, response, requestOptions);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
} finally {
|
|
272
|
+
releaseCodexAppServerClientLease(clientLease);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function assertNativeConversationApprovalPolicySupported(runtime) {
|
|
276
|
+
if (runtime.approvalPolicy !== "never" && runtime.approvalsReviewer === "user") throw new Error(NATIVE_CONVERSATION_INTERACTIVE_APPROVALS_UNAVAILABLE);
|
|
277
|
+
}
|
|
278
|
+
async function prepareCodexConversationBinding(params, options = {}) {
|
|
279
|
+
const identity = {
|
|
280
|
+
kind: "conversation",
|
|
281
|
+
bindingId: params.data.bindingId
|
|
282
|
+
};
|
|
283
|
+
const snapshot = params.bindingStore.read(identity);
|
|
284
|
+
const run = () => params.bindingStore.withLease(identity, async () => {
|
|
285
|
+
const current = params.bindingStore.read(identity);
|
|
286
|
+
if (current?.threadId !== snapshot?.threadId || current?.clientId !== snapshot?.clientId) throw new Error("Codex conversation binding changed before preparation.");
|
|
287
|
+
const requested = params.data.start && current?.conversationStartId !== params.data.start.id ? params.data.start : void 0;
|
|
288
|
+
if (current && !requested && !options.forceNew) return;
|
|
289
|
+
const sourceIdentity = params.data.source ? sessionBindingIdentity({
|
|
290
|
+
agentId: params.data.source.agentId,
|
|
291
|
+
sessionId: params.data.source.sessionId,
|
|
292
|
+
sessionKey: params.data.source.sessionKey,
|
|
293
|
+
config: params.config
|
|
294
|
+
}) : void 0;
|
|
295
|
+
const sourceBinding = sourceIdentity ? params.bindingStore.read(sourceIdentity) : void 0;
|
|
296
|
+
assertCodexBindingMayBeReplaced(current, "initializing a conversation-bound Codex thread");
|
|
297
|
+
assertCodexBindingMayBeReplaced(sourceBinding, "transferring a session into a conversation-bound Codex thread");
|
|
298
|
+
const inherited = current ?? sourceBinding;
|
|
299
|
+
const agentLookup = buildCodexConversationAgentLookup({
|
|
300
|
+
agentDir: params.data.agentDir,
|
|
301
|
+
config: params.config
|
|
302
|
+
});
|
|
303
|
+
const bindingParams = {
|
|
304
|
+
bindingStore: params.bindingStore,
|
|
305
|
+
identity,
|
|
306
|
+
pluginConfig: params.pluginConfig,
|
|
307
|
+
workspaceDir: requested ? params.data.workspaceDir : inherited?.cwd ?? params.data.workspaceDir,
|
|
308
|
+
...agentLookup,
|
|
309
|
+
model: requested?.model ?? inherited?.model,
|
|
310
|
+
modelProvider: requested?.modelProvider ?? inherited?.modelProvider,
|
|
311
|
+
authProfileId: requested?.authProfileId ?? inherited?.authProfileId,
|
|
312
|
+
serviceTier: inherited?.serviceTier,
|
|
313
|
+
config: params.config,
|
|
314
|
+
sessionKey: params.data.legacyBinding ? params.sessionKey : params.data.source?.sessionKey,
|
|
315
|
+
source: params.data.source,
|
|
316
|
+
incognito: params.incognito,
|
|
317
|
+
agentId: params.data.source?.agentId ?? params.data.agentId
|
|
318
|
+
};
|
|
319
|
+
const threadId = requested?.threadId;
|
|
320
|
+
await bindThread(bindingParams, options.forceNew ? void 0 : threadId);
|
|
321
|
+
const stored = params.bindingStore.read(identity);
|
|
322
|
+
if (!stored) throw new Error("Codex conversation binding disappeared while initializing its thread.");
|
|
323
|
+
if (sourceIdentity && params.data.source && !current?.conversationSourceTransferComplete) await params.bindingStore.withLease(sourceIdentity, async () => {
|
|
324
|
+
const source = params.bindingStore.read(sourceIdentity);
|
|
325
|
+
if (source && source.threadId === params.data.source?.threadId) {
|
|
326
|
+
const sourceSessionKey = sourceIdentity.sessionKey ?? resolveTranscriptSessionKeyBySessionId({
|
|
327
|
+
agentId: sourceIdentity.agentId,
|
|
328
|
+
sessionId: sourceIdentity.sessionId,
|
|
329
|
+
storePath: resolveStorePath(params.config?.session?.store, { agentId: sourceIdentity.agentId })
|
|
330
|
+
});
|
|
331
|
+
if (sourceSessionKey && resolveActiveEmbeddedRunSessionId(sourceSessionKey) === sourceIdentity.sessionId) throw new Error("Codex source session has an active run; stop it before binding this conversation.");
|
|
332
|
+
if (source.threadId !== stored.threadId) {
|
|
333
|
+
await releaseCodexAppServerBindingSubscription(source);
|
|
334
|
+
await projectConversationSourceHistory(params.data.source, stored, params.config);
|
|
335
|
+
}
|
|
336
|
+
await params.bindingStore.mutate(sourceIdentity, {
|
|
337
|
+
kind: "clear",
|
|
338
|
+
threadId: source.threadId
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
if (!await params.bindingStore.mutate(identity, {
|
|
343
|
+
kind: "patch",
|
|
344
|
+
threadId: stored.threadId,
|
|
345
|
+
patch: {
|
|
346
|
+
...params.data.start ? { conversationStartId: params.data.start.id } : {},
|
|
347
|
+
...sourceIdentity ? { conversationSourceTransferComplete: true } : {}
|
|
348
|
+
}
|
|
349
|
+
})) throw new Error("Codex conversation binding changed while initializing its thread.");
|
|
350
|
+
});
|
|
351
|
+
const threadId = params.data.start?.threadId ?? snapshot?.threadId;
|
|
352
|
+
if (threadId) await withExclusiveCodexAppServerThread({
|
|
353
|
+
bindingStore: params.bindingStore,
|
|
354
|
+
identity,
|
|
355
|
+
threadId,
|
|
356
|
+
run
|
|
357
|
+
});
|
|
358
|
+
else await run();
|
|
359
|
+
}
|
|
360
|
+
async function projectConversationSourceHistory(source, target, config) {
|
|
361
|
+
const storePath = resolveStorePath(config?.session?.store, { agentId: source.agentId });
|
|
362
|
+
const sessionKey = source.sessionKey ?? resolveTranscriptSessionKeyBySessionId({
|
|
363
|
+
agentId: source.agentId,
|
|
364
|
+
sessionId: source.sessionId,
|
|
365
|
+
storePath
|
|
366
|
+
});
|
|
367
|
+
if (!sessionKey) return;
|
|
368
|
+
const entries = await readVisibleSessionTranscriptMessageEntries({
|
|
369
|
+
agentId: source.agentId,
|
|
370
|
+
sessionId: source.sessionId,
|
|
371
|
+
sessionKey,
|
|
372
|
+
storePath
|
|
373
|
+
});
|
|
374
|
+
const history = projectBoundedCodexVisibleSessionHistory(entries);
|
|
375
|
+
if (history.length === 0) return;
|
|
376
|
+
const clientLease = retainSharedCodexAppServerClientByInstanceId(target.clientId);
|
|
377
|
+
if (!clientLease) throw new Error("Codex conversation source history lost its bound client owner.");
|
|
378
|
+
try {
|
|
379
|
+
await clientLease.client.request("thread/inject_items", {
|
|
380
|
+
threadId: target.threadId,
|
|
381
|
+
items: history
|
|
382
|
+
});
|
|
383
|
+
} finally {
|
|
384
|
+
clientLease.release();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function resolveThreadRequestModelProvider(params) {
|
|
388
|
+
const modelProvider = params.modelProvider?.trim();
|
|
389
|
+
if (!modelProvider || modelProvider.toLowerCase() === "codex") return;
|
|
390
|
+
if (isCodexAppServerNativeAuthProfile(params) && modelProvider.toLowerCase() === "openai") return;
|
|
391
|
+
return modelProvider.toLowerCase() === "openai" ? "openai" : modelProvider;
|
|
392
|
+
}
|
|
393
|
+
function resolveOptionalThreadRequestModelSelection(params) {
|
|
394
|
+
if (!params.model?.trim()) return;
|
|
395
|
+
return resolveCodexAppServerRequestModelSelection({
|
|
396
|
+
model: params.model,
|
|
397
|
+
modelProvider: params.modelProvider,
|
|
398
|
+
authProfileId: params.authProfileId,
|
|
399
|
+
agentDir: params.agentDir,
|
|
400
|
+
config: params.config
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
function resolveModelBackedReviewerPolicyProvider(params) {
|
|
404
|
+
const modelProvider = params.modelProvider?.trim();
|
|
405
|
+
if (modelProvider && modelProvider.toLowerCase() !== "codex") return modelProvider.toLowerCase() === "openai" ? "openai" : modelProvider;
|
|
406
|
+
return isCodexAppServerNativeAuthProfile(params) ? "openai" : void 0;
|
|
407
|
+
}
|
|
408
|
+
function buildCodexConversationAgentLookup(params) {
|
|
409
|
+
const agentDir = params.agentDir?.trim();
|
|
410
|
+
return {
|
|
411
|
+
...agentDir ? { agentDir } : {},
|
|
412
|
+
...params.config ? { config: params.config } : {}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
//#endregion
|
|
416
|
+
//#region extensions/codex/src/conversation-turn-collector.ts
|
|
417
|
+
/** Identifies a timer that expired in the bound-turn collector itself. */
|
|
418
|
+
var CodexConversationTurnTimeoutError = class extends Error {
|
|
419
|
+
constructor() {
|
|
420
|
+
super("codex app-server bound turn timed out");
|
|
421
|
+
this.name = "CodexConversationTurnTimeoutError";
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
function createCodexConversationTurnCollector(threadId) {
|
|
425
|
+
let turnId;
|
|
426
|
+
let completed = false;
|
|
427
|
+
let failedError;
|
|
428
|
+
let timeout;
|
|
429
|
+
const assistantTextByItem = /* @__PURE__ */ new Map();
|
|
430
|
+
let resolveCompletion;
|
|
431
|
+
let rejectCompletion;
|
|
432
|
+
const collectReplyText = () => {
|
|
433
|
+
return [...assistantTextByItem.values()].map((text) => text.trim()).filter(Boolean).at(-1) ?? "";
|
|
434
|
+
};
|
|
435
|
+
const clearWaitState = () => {
|
|
436
|
+
if (timeout) {
|
|
437
|
+
clearTimeout(timeout);
|
|
438
|
+
timeout = void 0;
|
|
439
|
+
}
|
|
440
|
+
resolveCompletion = void 0;
|
|
441
|
+
rejectCompletion = void 0;
|
|
442
|
+
};
|
|
443
|
+
const finish = () => {
|
|
444
|
+
if (completed) return;
|
|
445
|
+
completed = true;
|
|
446
|
+
if (failedError) rejectCompletion?.(new Error(failedError));
|
|
447
|
+
else resolveCompletion?.({ replyText: collectReplyText() });
|
|
448
|
+
clearWaitState();
|
|
449
|
+
};
|
|
450
|
+
const handleNotification = (notification) => {
|
|
451
|
+
const params = isJsonObject(notification.params) ? notification.params : void 0;
|
|
452
|
+
if (!params || !turnId || !isCodexNotificationForTurn(params, threadId, turnId)) return;
|
|
453
|
+
if (notification.method === "item/agentMessage/delta") {
|
|
454
|
+
const itemId = normalizeOptionalString(params.itemId) ?? "assistant";
|
|
455
|
+
const delta = readTextString(params, "delta");
|
|
456
|
+
if (!delta) return;
|
|
457
|
+
assistantTextByItem.set(itemId, `${assistantTextByItem.get(itemId) ?? ""}${delta}`);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (notification.method === "item/completed") {
|
|
461
|
+
const item = isJsonObject(params.item) ? params.item : void 0;
|
|
462
|
+
if (item?.type === "agentMessage") {
|
|
463
|
+
const itemId = normalizeOptionalString(item.id) ?? normalizeOptionalString(params.itemId) ?? "assistant";
|
|
464
|
+
assistantTextByItem.delete(itemId);
|
|
465
|
+
if (isAssistantCommentaryCompletionNotification(notification)) return;
|
|
466
|
+
const text = readTextString(item, "text");
|
|
467
|
+
if (text?.trim()) assistantTextByItem.set(itemId, text);
|
|
468
|
+
}
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (notification.method === "turn/completed") {
|
|
472
|
+
const turn = isJsonObject(params.turn) ? params.turn : void 0;
|
|
473
|
+
const status = normalizeOptionalString(turn?.status);
|
|
474
|
+
if (status === "failed") failedError = normalizeOptionalString(asOptionalRecord(turn?.error)?.message) ?? "codex app-server turn failed";
|
|
475
|
+
else if (status === "interrupted") failedError = "codex app-server turn interrupted";
|
|
476
|
+
else if (status !== "completed") failedError = "codex app-server turn completed without a valid terminal status";
|
|
477
|
+
if (status === "completed") {
|
|
478
|
+
const items = Array.isArray(turn?.items) ? turn.items : [];
|
|
479
|
+
for (const item of items) {
|
|
480
|
+
if (!isJsonObject(item) || item.type !== "agentMessage") continue;
|
|
481
|
+
const itemId = normalizeOptionalString(item.id) ?? `assistant-${assistantTextByItem.size + 1}`;
|
|
482
|
+
assistantTextByItem.delete(itemId);
|
|
483
|
+
const text = item.phase === "commentary" ? void 0 : readTextString(item, "text");
|
|
484
|
+
if (text?.trim()) assistantTextByItem.set(itemId, text);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
finish();
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
return {
|
|
491
|
+
setTurnId(nextTurnId) {
|
|
492
|
+
turnId = nextTurnId;
|
|
493
|
+
},
|
|
494
|
+
handleNotification,
|
|
495
|
+
wait(params) {
|
|
496
|
+
if (completed) return failedError ? Promise.reject(new Error(failedError)) : Promise.resolve({ replyText: collectReplyText() });
|
|
497
|
+
return new Promise((resolve, reject) => {
|
|
498
|
+
resolveCompletion = resolve;
|
|
499
|
+
rejectCompletion = reject;
|
|
500
|
+
timeout = setTimeout(() => {
|
|
501
|
+
completed = true;
|
|
502
|
+
reject(new CodexConversationTurnTimeoutError());
|
|
503
|
+
clearWaitState();
|
|
504
|
+
}, resolveTimerTimeoutMs(params.timeoutMs, 100, 100));
|
|
505
|
+
timeout.unref?.();
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function readTextString(record, key) {
|
|
511
|
+
const value = record?.[key];
|
|
512
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
513
|
+
}
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region extensions/codex/src/conversation-turn-input.ts
|
|
516
|
+
const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
517
|
+
".avif",
|
|
518
|
+
".gif",
|
|
519
|
+
".jpeg",
|
|
520
|
+
".jpg",
|
|
521
|
+
".png",
|
|
522
|
+
".webp"
|
|
523
|
+
]);
|
|
524
|
+
function buildCodexConversationTurnInput(params) {
|
|
525
|
+
return [{
|
|
526
|
+
type: "text",
|
|
527
|
+
text: params.prompt,
|
|
528
|
+
text_elements: []
|
|
529
|
+
}, ...extractInboundMedia(params.event).map(toCodexImageInput).filter((item) => item !== void 0)];
|
|
530
|
+
}
|
|
531
|
+
function extractInboundMedia(event) {
|
|
532
|
+
const metadata = event.metadata ?? {};
|
|
533
|
+
const paths = normalizeSingleOrTrimmedStringList(metadata.mediaPaths).concat(normalizeSingleOrTrimmedStringList(metadata.mediaPath));
|
|
534
|
+
const urls = normalizeSingleOrTrimmedStringList(metadata.mediaUrls).concat(normalizeSingleOrTrimmedStringList(metadata.mediaUrl));
|
|
535
|
+
const mimeTypes = normalizeSingleOrTrimmedStringList(metadata.mediaTypes).concat(normalizeSingleOrTrimmedStringList(metadata.mediaType));
|
|
536
|
+
const count = Math.max(paths.length, urls.length, mimeTypes.length);
|
|
537
|
+
const media = [];
|
|
538
|
+
for (let index = 0; index < count; index += 1) media.push({
|
|
539
|
+
path: paths[index],
|
|
540
|
+
url: urls[index],
|
|
541
|
+
mimeType: mimeTypes[index] ?? mimeTypes[0]
|
|
542
|
+
});
|
|
543
|
+
return media;
|
|
544
|
+
}
|
|
545
|
+
function toCodexImageInput(media) {
|
|
546
|
+
if (!isImageMedia(media)) return;
|
|
547
|
+
const localPath = media.path ?? readLocalMediaPath(media.url);
|
|
548
|
+
if (localPath) {
|
|
549
|
+
const normalized = normalizeFileUrl(localPath);
|
|
550
|
+
return normalized ? {
|
|
551
|
+
type: "localImage",
|
|
552
|
+
path: normalized
|
|
553
|
+
} : void 0;
|
|
554
|
+
}
|
|
555
|
+
return media.url ? {
|
|
556
|
+
type: "image",
|
|
557
|
+
url: media.url
|
|
558
|
+
} : void 0;
|
|
559
|
+
}
|
|
560
|
+
function isImageMedia(media) {
|
|
561
|
+
if (media.mimeType?.toLowerCase().startsWith("image/")) return true;
|
|
562
|
+
const candidate = media.path ?? media.url;
|
|
563
|
+
if (!candidate) return false;
|
|
564
|
+
return IMAGE_EXTENSIONS.has(path.extname(candidate.split(/[?#]/, 1)[0] ?? "").toLowerCase());
|
|
565
|
+
}
|
|
566
|
+
function normalizeFileUrl(value) {
|
|
567
|
+
if (!/^file:\/\//iu.test(value)) return value;
|
|
568
|
+
try {
|
|
569
|
+
const fileUrl = new URL(value);
|
|
570
|
+
decodeURIComponent(fileUrl.pathname);
|
|
571
|
+
return fileURLToPath(fileUrl);
|
|
572
|
+
} catch {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
function readLocalMediaPath(value) {
|
|
577
|
+
if (!value) return;
|
|
578
|
+
if (/^file:\/\//iu.test(value)) return value;
|
|
579
|
+
if (value.startsWith("//")) return;
|
|
580
|
+
if (path.isAbsolute(value) || path.win32.isAbsolute(value)) return value;
|
|
581
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(value) ? void 0 : value;
|
|
582
|
+
}
|
|
583
|
+
//#endregion
|
|
584
|
+
//#region extensions/codex/src/conversation-binding.ts
|
|
585
|
+
const DEFAULT_BOUND_TURN_TIMEOUT_MS = 12e5;
|
|
586
|
+
async function runBoundTurn(params) {
|
|
587
|
+
const agentLookup = buildCodexConversationAgentLookup({
|
|
588
|
+
agentDir: params.data.agentDir,
|
|
589
|
+
config: params.config
|
|
590
|
+
});
|
|
591
|
+
const identity = {
|
|
592
|
+
kind: "conversation",
|
|
593
|
+
bindingId: params.data.bindingId
|
|
594
|
+
};
|
|
595
|
+
const binding = params.bindingStore.read(identity);
|
|
596
|
+
if (!binding?.threadId) throw new Error("bound Codex conversation has no thread binding");
|
|
597
|
+
return await withExclusiveCodexAppServerThread({
|
|
598
|
+
bindingStore: params.bindingStore,
|
|
599
|
+
identity,
|
|
600
|
+
threadId: binding.threadId,
|
|
601
|
+
run: async () => {
|
|
602
|
+
const current = params.bindingStore.read(identity);
|
|
603
|
+
if (!isSameCodexAppServerThreadOwner(current, binding)) throw new Error("Codex conversation binding changed before its turn.");
|
|
604
|
+
assertCodexBindingMayBeReplaced(binding, "running a conversation-bound Codex thread");
|
|
605
|
+
let threadId = binding.threadId;
|
|
606
|
+
const requestedWorkspaceDir = binding.cwd || params.data.workspaceDir;
|
|
607
|
+
const reviewerModelProvider = resolveModelBackedReviewerPolicyProvider({
|
|
608
|
+
authProfileId: binding.authProfileId,
|
|
609
|
+
modelProvider: binding.modelProvider,
|
|
610
|
+
...agentLookup
|
|
611
|
+
});
|
|
612
|
+
const { runtime, workspaceDir } = await resolveConversationAppServerRuntime({
|
|
613
|
+
pluginConfig: params.pluginConfig,
|
|
614
|
+
config: params.config,
|
|
615
|
+
agentId: params.data.source?.agentId ?? params.data.agentId,
|
|
616
|
+
sessionKey: params.data.legacyBinding ? params.sessionKey : params.data.source?.sessionKey,
|
|
617
|
+
source: params.data.source,
|
|
618
|
+
workspaceDir: requestedWorkspaceDir,
|
|
619
|
+
modelProvider: reviewerModelProvider,
|
|
620
|
+
model: binding.model,
|
|
621
|
+
agentDir: params.data.agentDir
|
|
622
|
+
});
|
|
623
|
+
const modelScopedRuntime = resolveCodexAppServerForModelProvider({
|
|
624
|
+
appServer: runtime,
|
|
625
|
+
provider: reviewerModelProvider,
|
|
626
|
+
model: binding.model,
|
|
627
|
+
config: params.config,
|
|
628
|
+
env: process.env,
|
|
629
|
+
agentDir: params.data.agentDir
|
|
630
|
+
});
|
|
631
|
+
const sessionRoot = modelScopedRuntime.sessionRoot;
|
|
632
|
+
const approvalPolicy = modelScopedRuntime.approvalPolicy;
|
|
633
|
+
const sandbox = modelScopedRuntime.sandbox;
|
|
634
|
+
const permissionProfile = modelScopedRuntime.networkProxy?.profileName;
|
|
635
|
+
const networkProxyConfigFingerprint = modelScopedRuntime.networkProxy?.configFingerprint;
|
|
636
|
+
const networkProxyBindingChanged = binding.networkProxyProfileName !== permissionProfile || binding.networkProxyConfigFingerprint !== networkProxyConfigFingerprint;
|
|
637
|
+
const serviceTier = binding.serviceTier ?? runtime.serviceTier;
|
|
638
|
+
let useStickyNetworkProfile = permissionProfile !== void 0 && binding.networkProxyProfileName === permissionProfile && binding.networkProxyConfigFingerprint === networkProxyConfigFingerprint;
|
|
639
|
+
assertNativeConversationApprovalPolicySupported(modelScopedRuntime);
|
|
640
|
+
const modelSelection = binding.model ? resolveCodexAppServerRequestModelSelection({
|
|
641
|
+
model: binding.model,
|
|
642
|
+
modelProvider: binding.modelProvider,
|
|
643
|
+
authProfileId: binding.authProfileId,
|
|
644
|
+
...agentLookup
|
|
645
|
+
}) : void 0;
|
|
646
|
+
const threadRequestRuntime = {
|
|
647
|
+
runtime: modelScopedRuntime,
|
|
648
|
+
workspaceDir,
|
|
649
|
+
...modelSelection
|
|
650
|
+
};
|
|
651
|
+
const clientOptions = {
|
|
652
|
+
startOptions: runtime.start,
|
|
653
|
+
timeoutMs: runtime.requestTimeoutMs,
|
|
654
|
+
authProfileId: binding.authProfileId,
|
|
655
|
+
...agentLookup
|
|
656
|
+
};
|
|
657
|
+
let client = await getLeasedSharedCodexAppServerClient(clientOptions);
|
|
658
|
+
const clientLease = { client };
|
|
659
|
+
let activeTurnId;
|
|
660
|
+
let activeTurnCleanup = () => void 0;
|
|
661
|
+
let isolatedSubscriptionClient;
|
|
662
|
+
let turnRoute;
|
|
663
|
+
let liveThreadOwnership;
|
|
664
|
+
let ownsNativeSubscription = false;
|
|
665
|
+
let turnSucceeded = false;
|
|
666
|
+
const assertResumeInputAllowed = async () => {
|
|
667
|
+
const { thread } = await client.request("thread/read", {
|
|
668
|
+
threadId,
|
|
669
|
+
includeTurns: false
|
|
670
|
+
}, { timeoutMs: runtime.requestTimeoutMs });
|
|
671
|
+
assertCodexThreadAcceptsDirectInput(thread);
|
|
672
|
+
};
|
|
673
|
+
try {
|
|
674
|
+
if (!networkProxyBindingChanged && binding.clientId !== client.getInstanceId()) await assertResumeInputAllowed();
|
|
675
|
+
if (!params.incognito && isCodexAppServerClientRuntimeLive(client)) {
|
|
676
|
+
const ownership = await consumeCodexAppServerLiveThread(client, threadId);
|
|
677
|
+
if (ownership) {
|
|
678
|
+
liveThreadOwnership = {
|
|
679
|
+
client,
|
|
680
|
+
threadId,
|
|
681
|
+
ownership
|
|
682
|
+
};
|
|
683
|
+
ownsNativeSubscription = true;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (networkProxyBindingChanged) {
|
|
687
|
+
const response = assertCodexThreadStartResponse(await withLeasedCodexAppServerClientStartSelectionRetry({
|
|
688
|
+
lease: clientLease,
|
|
689
|
+
options: clientOptions,
|
|
690
|
+
run: async (requestClient, requestOptions) => {
|
|
691
|
+
const threadRequest = await buildConversationThreadRequestForClient(requestClient, threadRequestRuntime, serviceTier, requestOptions);
|
|
692
|
+
return await requestClient.request("thread/start", {
|
|
693
|
+
...threadRequest,
|
|
694
|
+
developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
|
|
695
|
+
experimentalRawEvents: true,
|
|
696
|
+
...params.incognito ? { ephemeral: true } : {}
|
|
697
|
+
}, requestOptions());
|
|
698
|
+
},
|
|
699
|
+
onClientChange: (nextClient) => {
|
|
700
|
+
client = nextClient;
|
|
701
|
+
}
|
|
702
|
+
}));
|
|
703
|
+
threadId = response.thread.id;
|
|
704
|
+
ownsNativeSubscription = true;
|
|
705
|
+
assertCodexThreadAcceptsDirectInput(response.thread);
|
|
706
|
+
if (liveThreadOwnership && (liveThreadOwnership.threadId !== threadId || liveThreadOwnership.client !== client)) {
|
|
707
|
+
const previousOwnership = liveThreadOwnership;
|
|
708
|
+
try {
|
|
709
|
+
await previousOwnership.ownership.release(previousOwnership.threadId);
|
|
710
|
+
} catch (error) {
|
|
711
|
+
if (!(isCodexAppServerClientRuntimeLive(previousOwnership.client) && await retainCodexAppServerBindingSubscription(previousOwnership.client, previousOwnership.threadId, previousOwnership.ownership).catch(() => false))) await closeCodexStartupClientBestEffort(previousOwnership.client);
|
|
712
|
+
liveThreadOwnership = void 0;
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
liveThreadOwnership = void 0;
|
|
716
|
+
} else if (binding.threadId !== threadId) await releaseCodexAppServerBindingSubscription(binding);
|
|
717
|
+
if (!await params.bindingStore.mutate(identity, {
|
|
718
|
+
kind: "set",
|
|
719
|
+
binding: {
|
|
720
|
+
threadId,
|
|
721
|
+
clientId: client.getInstanceId(),
|
|
722
|
+
cwd: response.thread.cwd ?? workspaceDir,
|
|
723
|
+
authProfileId: binding.authProfileId,
|
|
724
|
+
model: response.model ?? modelSelection?.model ?? binding.model,
|
|
725
|
+
modelProvider: normalizeCodexAppServerBindingModelProvider({
|
|
726
|
+
authProfileId: binding.authProfileId,
|
|
727
|
+
modelProvider: response.modelProvider ?? modelSelection?.modelProvider ?? binding.modelProvider,
|
|
728
|
+
...agentLookup
|
|
729
|
+
}),
|
|
730
|
+
serviceTier: serviceTier ?? void 0,
|
|
731
|
+
networkProxyProfileName: modelScopedRuntime.networkProxy?.profileName,
|
|
732
|
+
networkProxyConfigFingerprint: modelScopedRuntime.networkProxy?.configFingerprint,
|
|
733
|
+
conversationStartId: binding.conversationStartId,
|
|
734
|
+
conversationSourceTransferComplete: binding.conversationSourceTransferComplete,
|
|
735
|
+
historyCoveredThrough: binding.historyCoveredThrough
|
|
736
|
+
}
|
|
737
|
+
})) throw new Error("Codex conversation binding changed while rotating its thread.");
|
|
738
|
+
useStickyNetworkProfile = modelScopedRuntime.networkProxy !== void 0;
|
|
739
|
+
} else if (binding.clientId !== client.getInstanceId() || isCodexAppServerClientRuntimeLive(client) && !params.incognito && !liveThreadOwnership) {
|
|
740
|
+
if (binding.clientId === client.getInstanceId()) await assertResumeInputAllowed();
|
|
741
|
+
const response = await withLeasedCodexAppServerClientStartSelectionRetry({
|
|
742
|
+
lease: clientLease,
|
|
743
|
+
options: clientOptions,
|
|
744
|
+
run: async (requestClient, requestOptions) => {
|
|
745
|
+
const threadRequest = await buildConversationThreadRequestForClient(requestClient, threadRequestRuntime, serviceTier, requestOptions);
|
|
746
|
+
return await resumeCodexAppServerThread({
|
|
747
|
+
client: requestClient,
|
|
748
|
+
onSubscriptionReleased: () => {
|
|
749
|
+
isolatedSubscriptionClient = requestClient;
|
|
750
|
+
},
|
|
751
|
+
abandonClient: async () => {
|
|
752
|
+
await closeCodexStartupClientBestEffort(requestClient);
|
|
753
|
+
isolatedSubscriptionClient = requestClient;
|
|
754
|
+
},
|
|
755
|
+
request: {
|
|
756
|
+
threadId,
|
|
757
|
+
...threadRequest
|
|
758
|
+
},
|
|
759
|
+
requestResume: (request) => requestClient.request("thread/resume", request, requestOptions())
|
|
760
|
+
});
|
|
761
|
+
},
|
|
762
|
+
onClientChange: (nextClient) => {
|
|
763
|
+
client = nextClient;
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
threadId = response.thread.id;
|
|
767
|
+
ownsNativeSubscription = true;
|
|
768
|
+
assertCodexThreadAcceptsDirectInput(response.thread);
|
|
769
|
+
if (!isSameCodexAppServerThreadOwner(binding, {
|
|
770
|
+
threadId,
|
|
771
|
+
clientId: client.getInstanceId()
|
|
772
|
+
})) await releaseCodexAppServerBindingSubscription(binding);
|
|
773
|
+
if (!await params.bindingStore.mutate(identity, {
|
|
774
|
+
kind: "patch",
|
|
775
|
+
threadId: binding.threadId,
|
|
776
|
+
patch: {
|
|
777
|
+
clientId: client.getInstanceId(),
|
|
778
|
+
cwd: response.thread.cwd ?? binding.cwd,
|
|
779
|
+
model: response.model ?? modelSelection?.model ?? binding.model,
|
|
780
|
+
modelProvider: normalizeCodexAppServerBindingModelProvider({
|
|
781
|
+
authProfileId: binding.authProfileId,
|
|
782
|
+
modelProvider: response.modelProvider ?? modelSelection?.modelProvider ?? binding.modelProvider,
|
|
783
|
+
...agentLookup
|
|
784
|
+
})
|
|
785
|
+
}
|
|
786
|
+
})) throw new Error("Codex conversation binding changed while resuming on a new client.");
|
|
787
|
+
}
|
|
788
|
+
const turnCollector = createCodexConversationTurnCollector(threadId);
|
|
789
|
+
turnRoute = getCodexAppServerTurnRouter(client).reserveThread({
|
|
790
|
+
threadId,
|
|
791
|
+
onNotification: turnCollector.handleNotification
|
|
792
|
+
});
|
|
793
|
+
turnRoute.armTurn();
|
|
794
|
+
activeTurnId = (await client.request("turn/start", {
|
|
795
|
+
threadId,
|
|
796
|
+
input: buildCodexConversationTurnInput({
|
|
797
|
+
prompt: params.prompt,
|
|
798
|
+
event: params.event
|
|
799
|
+
}),
|
|
800
|
+
cwd: workspaceDir,
|
|
801
|
+
...sessionRoot ? { runtimeWorkspaceRoots: [sessionRoot] } : {},
|
|
802
|
+
approvalPolicy,
|
|
803
|
+
approvalsReviewer: modelScopedRuntime.approvalsReviewer,
|
|
804
|
+
...useStickyNetworkProfile ? {} : { sandboxPolicy: codexSandboxPolicyForTurn(sandbox, sessionRoot ?? workspaceDir) },
|
|
805
|
+
...modelSelection?.model ? { model: modelSelection.model } : {},
|
|
806
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
807
|
+
...serviceTier ? { serviceTier } : {}
|
|
808
|
+
}, { timeoutMs: runtime.requestTimeoutMs })).turn.id;
|
|
809
|
+
activeTurnCleanup = trackCodexConversationActiveTurn({
|
|
810
|
+
identity,
|
|
811
|
+
client,
|
|
812
|
+
threadId,
|
|
813
|
+
turnId: activeTurnId
|
|
814
|
+
});
|
|
815
|
+
turnCollector.setTurnId(activeTurnId);
|
|
816
|
+
await turnRoute.bindTurn(activeTurnId);
|
|
817
|
+
const replyText = (await turnCollector.wait({ timeoutMs: params.timeoutMs ?? DEFAULT_BOUND_TURN_TIMEOUT_MS })).replyText.trim();
|
|
818
|
+
turnSucceeded = true;
|
|
819
|
+
return { reply: { text: replyText || "Codex completed without a text reply." } };
|
|
820
|
+
} catch (error) {
|
|
821
|
+
if (isCodexAppServerOverloadError(error) && error.method === "thread/resume") throw error;
|
|
822
|
+
if (error instanceof CodexThreadDirectInputError) {
|
|
823
|
+
if (params.incognito && ownsNativeSubscription) {
|
|
824
|
+
if (!await unsubscribeCodexThreadBestEffort(client, {
|
|
825
|
+
threadId,
|
|
826
|
+
timeoutMs: 5e3
|
|
827
|
+
})) await retireUnsafeCodexTurnClientBestEffort(client, "parent-owned thread unsubscribe");
|
|
828
|
+
}
|
|
829
|
+
throw error;
|
|
830
|
+
}
|
|
831
|
+
if (error instanceof CodexConversationTurnTimeoutError && activeTurnId || turnRoute && isCodexAppServerIndeterminateRequestCancellationError(error)) {
|
|
832
|
+
if (!await interruptCodexTurnAndWaitBestEffort(client, {
|
|
833
|
+
threadId,
|
|
834
|
+
turnId: activeTurnId ?? ""
|
|
835
|
+
})) {
|
|
836
|
+
await retireUnsafeCodexTurnClientBestEffort(client, "turn interrupt");
|
|
837
|
+
isolatedSubscriptionClient = client;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (params.incognito) {
|
|
841
|
+
if (await params.bindingStore.mutate(identity, {
|
|
842
|
+
kind: "clear",
|
|
843
|
+
threadId
|
|
844
|
+
}) && isolatedSubscriptionClient !== client) {
|
|
845
|
+
if (!await unsubscribeCodexThreadBestEffort(client, {
|
|
846
|
+
threadId,
|
|
847
|
+
timeoutMs: 5e3
|
|
848
|
+
})) await retireUnsafeCodexTurnClientBestEffort(client, "thread unsubscribe");
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
throw error;
|
|
852
|
+
} finally {
|
|
853
|
+
activeTurnCleanup();
|
|
854
|
+
turnRoute?.release();
|
|
855
|
+
try {
|
|
856
|
+
if (ownsNativeSubscription && isolatedSubscriptionClient !== client && !params.incognito && isCodexAppServerClientRuntimeLive(client)) {
|
|
857
|
+
const currentLiveThreadOwnership = liveThreadOwnership?.client === client && liveThreadOwnership.threadId === threadId ? liveThreadOwnership.ownership : void 0;
|
|
858
|
+
let retained = false;
|
|
859
|
+
if (turnSucceeded) retained = await params.bindingStore.withLease(identity, async () => {
|
|
860
|
+
const latest = params.bindingStore.read(identity);
|
|
861
|
+
if (latest?.threadId !== threadId || latest.clientId !== client.getInstanceId()) return false;
|
|
862
|
+
return await retainCodexAppServerBindingSubscription(client, threadId, currentLiveThreadOwnership);
|
|
863
|
+
});
|
|
864
|
+
if (!retained) {
|
|
865
|
+
if (!(currentLiveThreadOwnership ? await currentLiveThreadOwnership.release(threadId).then(() => true) : await unsubscribeCodexThreadBestEffort(client, {
|
|
866
|
+
threadId,
|
|
867
|
+
timeoutMs: 5e3
|
|
868
|
+
}))) await closeCodexStartupClientBestEffort(client);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
} catch (error) {
|
|
872
|
+
embeddedAgentLog.warn("codex conversation subscription cleanup failed", {
|
|
873
|
+
threadId,
|
|
874
|
+
reason: formatErrorMessage(error)
|
|
875
|
+
});
|
|
876
|
+
await closeCodexStartupClientBestEffort(client);
|
|
877
|
+
} finally {
|
|
878
|
+
releaseCodexAppServerClientLease(clientLease);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
async function runBoundTurnWithMissingThreadRecovery(params) {
|
|
885
|
+
await prepareCodexConversationBinding(params);
|
|
886
|
+
try {
|
|
887
|
+
return await runBoundTurn(params);
|
|
888
|
+
} catch (error) {
|
|
889
|
+
if (!isCodexThreadNotFoundError(error)) throw error;
|
|
890
|
+
await prepareCodexConversationBinding(params, { forceNew: true });
|
|
891
|
+
return await runBoundTurn(params);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
function isCodexThreadNotFoundError(error) {
|
|
895
|
+
if (isCodexAppServerOverloadError(error) || isCodexAppServerUnsafeSubscriptionError(error)) return false;
|
|
896
|
+
const message = formatErrorMessage(error);
|
|
897
|
+
return /\bthread not found:/iu.test(message) || /\bbound Codex conversation has no thread binding\b/u.test(message);
|
|
898
|
+
}
|
|
899
|
+
//#endregion
|
|
900
|
+
export { runBoundTurnWithMissingThreadRecovery };
|