@jskit-ai/assistant-core 0.2.7 → 0.2.9
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 +13 -10
- 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,12 +1,63 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { codexAppServerThreadStatus, codexAppServerConversationTurnIsActive, codexAppServerNativeOuterTurnId } from "./codexTurnState.js";
|
|
2
|
+
import { ignoreCodexDetachedTurnEvent } from "./codexDetachedTurn.js";
|
|
3
|
+
import { readCodexToolFreeConfiguration } from "./codexIsolation.js";
|
|
4
|
+
import { codexAppServerAssistantItemText, codexAppServerContentText, classifyCodexAppServerEvent, codexAppServerNotificationThreadId, codexAppServerNotificationTurnId, codexAppServerNotificationTurnStatus } from "./codexEvents.js";
|
|
5
|
+
import {
|
|
6
|
+
codexAppServerProviderThreadAssistantSegments,
|
|
7
|
+
codexAppServerThreadRawValue,
|
|
8
|
+
codexAppServerProviderTurnId,
|
|
9
|
+
codexAppServerProviderTurnStatus,
|
|
10
|
+
codexAppServerProviderTurnItems,
|
|
11
|
+
codexAppServerProviderTurnClientIds,
|
|
12
|
+
codexAppServerThreadTurnId,
|
|
13
|
+
codexAppServerThreadError
|
|
14
|
+
} from "./codexThreadSnapshot.js";
|
|
15
|
+
import {
|
|
16
|
+
codexAppServerEphemeralConversationSnapshot,
|
|
17
|
+
createCodexConversationTurns,
|
|
18
|
+
ignoreCodexConversationEvent,
|
|
19
|
+
prepareCodexAppServerConversationThread
|
|
20
|
+
} from "./codexConversationTurns.js";
|
|
21
|
+
import {
|
|
22
|
+
acquireCodexAppServerDetachedThread,
|
|
23
|
+
codexAppServerDetachedThreadId,
|
|
24
|
+
createCodexDetachedConversation,
|
|
25
|
+
dispatchCodexAppServerDetachedTurn,
|
|
26
|
+
runDetachedCodexAppServerTurn
|
|
27
|
+
} from "./codexDetachedConversation.js";
|
|
28
|
+
import { createCodexConversationStops } from "./codexConversationStops.js";
|
|
29
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
30
|
+
|
|
31
|
+
/** @typedef {Map<string, Map<string, object>>} CodexConversationRegistry Each session's native conversations, by id. */
|
|
32
|
+
/** @typedef {(sessionId?: string, conversationId?: string) => object | null} CodexConversationLookup */
|
|
33
|
+
/** @typedef {(state: object) => void} CodexConversationStateUpdate */
|
|
34
|
+
/** @typedef {(sessionId: string, conversationId: string, provider: import("./codexProvider.js").CodexAppServerAgentProvider) => object} CodexConversationObserver */
|
|
35
|
+
/** @typedef {{ release(): void }} CodexProviderBorrowHandle */
|
|
36
|
+
/** @typedef {(sessionId: string, input: object, options: object, borrow?: CodexProviderBorrowHandle | null) => Promise<any>} CodexConversationContextReader */
|
|
37
|
+
/** @typedef {(operation: () => Promise<any>) => Promise<any>} CodexConversationResult */
|
|
38
|
+
/** @typedef {(borrowing: boolean, operation: (borrow: CodexProviderBorrowHandle | null) => Promise<any>) => Promise<any>} CodexProviderBorrow */
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The scoped and Temporary conversation commands of a run owner: native conversations this server tracks, their
|
|
42
|
+
* observation, and creating, starting, reading, waiting for, stopping and deleting them. The conversation and
|
|
43
|
+
* turn-start collections keep the run owner's lifetime.
|
|
44
|
+
* @param {object} dependencies
|
|
45
|
+
* @param {import("./codexTurn.js").CodexCheckpoint} dependencies.checkpoint
|
|
46
|
+
* @param {typeof import("./codexTurnState.js").codexAppServerConversationMessageId} dependencies.codexAppServerConversationMessageId
|
|
47
|
+
* @param {import("./codexOutput.js").CodexAppServerOutput["streamMessage"]} dependencies.codexAppServerStreamMessage
|
|
48
|
+
* @param {object} dependencies.conversationPreparation The application's conversation preparation.
|
|
49
|
+
* @param {import("./codexProviderSelection.js").CodexProviderSelection["ensureCodexAppServerProviderForActiveTurn"]} dependencies.ensureCodexAppServerProviderForActiveTurn
|
|
50
|
+
* @param {string} dependencies.errorPrefix
|
|
51
|
+
* @param {import("./codexHelperThreads.js").CodexHelperThreadLifecycle} dependencies.helperLifecycle
|
|
52
|
+
* @param {string} dependencies.helperOwnershipError
|
|
53
|
+
* @param {string} dependencies.interruptFailedCode
|
|
54
|
+
* @param {import("./codexTurn.js").CodexSessionNamespace} dependencies.namespace
|
|
55
|
+
* @param {import("./codexTurn.js").CodexProviderSessions} dependencies.providerSessions
|
|
56
|
+
* @param {import("./codexProviderSelection.js").CodexProviderSelection["ensureSessionProvider"]} dependencies.ensureSessionProvider
|
|
57
|
+
* @param {import("./codexProviderSelection.js").CodexProviderSelection["borrowSessionProvider"]} dependencies.borrowSessionProvider
|
|
58
|
+
* @param {string} dependencies.steerFailedCode
|
|
59
|
+
* @param {import("./codexTurn.js").CodexTurnOutcomes} dependencies.turnOutcomes
|
|
60
|
+
*/
|
|
10
61
|
export function createCodexConversationCommands({
|
|
11
62
|
checkpoint,
|
|
12
63
|
codexAppServerConversationMessageId,
|
|
@@ -26,6 +77,53 @@ export function createCodexConversationCommands({
|
|
|
26
77
|
}) {
|
|
27
78
|
const codexAppServerConversations = new Map();
|
|
28
79
|
const codexAppServerConversationTurnStarts = new Map();
|
|
80
|
+
// Turns, detached runs and stops live in their own modules. Each receives the
|
|
81
|
+
// registry above and the registry and context functions declared below.
|
|
82
|
+
const {
|
|
83
|
+
createPreparedCodexAppServerConversation,
|
|
84
|
+
startPreparedCodexAppServerConversationTurn,
|
|
85
|
+
waitForPreparedCodexAppServerConversationTurn
|
|
86
|
+
} = createCodexConversationTurns({
|
|
87
|
+
codexAppServerConversations,
|
|
88
|
+
codexAppServerConversation,
|
|
89
|
+
observeCodexConversation,
|
|
90
|
+
holdWhileActive,
|
|
91
|
+
namespace,
|
|
92
|
+
errorPrefix,
|
|
93
|
+
helperOwnershipError,
|
|
94
|
+
steerFailedCode,
|
|
95
|
+
interruptFailedCode
|
|
96
|
+
});
|
|
97
|
+
const { runDetachedCodexAppServerConversation } = createCodexDetachedConversation({
|
|
98
|
+
conversationPreparation,
|
|
99
|
+
codexAppServerConversationResult,
|
|
100
|
+
codexAppServerConversationContext,
|
|
101
|
+
helperLifecycle,
|
|
102
|
+
helperOwnershipError
|
|
103
|
+
});
|
|
104
|
+
const {
|
|
105
|
+
stopCodexAppServerConversation,
|
|
106
|
+
stopPersistentCodexConversation,
|
|
107
|
+
interruptDetachedCodexAppServerConversationTurn,
|
|
108
|
+
deleteScopedCodexAppServerConversation,
|
|
109
|
+
deleteDetachedCodexAppServerConversationThread,
|
|
110
|
+
closeCodexAppServerConversations
|
|
111
|
+
} = createCodexConversationStops({
|
|
112
|
+
codexAppServerConversations,
|
|
113
|
+
codexAppServerConversation,
|
|
114
|
+
codexAppServerConversationContext,
|
|
115
|
+
codexAppServerConversationResult,
|
|
116
|
+
withProviderBorrow,
|
|
117
|
+
forgetObservation,
|
|
118
|
+
holdWhileActive,
|
|
119
|
+
conversationPreparation,
|
|
120
|
+
helperLifecycle,
|
|
121
|
+
providerSessions,
|
|
122
|
+
namespace,
|
|
123
|
+
errorPrefix,
|
|
124
|
+
interruptFailedCode,
|
|
125
|
+
turnOutcomes
|
|
126
|
+
});
|
|
29
127
|
|
|
30
128
|
// Reads and Temporary chat commands borrow their provider instead of claiming
|
|
31
129
|
// it: the operation returns its borrow when it finishes, and an unused
|
|
@@ -54,7 +152,7 @@ export function createCodexConversationCommands({
|
|
|
54
152
|
}
|
|
55
153
|
|
|
56
154
|
function forgetObservation(state) {
|
|
57
|
-
state.unsubscribe
|
|
155
|
+
if (state.unsubscribe) state.unsubscribe();
|
|
58
156
|
state.unsubscribe = null;
|
|
59
157
|
holdWhileActive(state);
|
|
60
158
|
}
|
|
@@ -76,9 +174,9 @@ export function createCodexConversationCommands({
|
|
|
76
174
|
}
|
|
77
175
|
|
|
78
176
|
async function withCodexAppServerConversationTurnStart(sessionId, input, operation) {
|
|
79
|
-
const messageId =
|
|
177
|
+
const messageId = normalizeText(input.messageId);
|
|
80
178
|
const deliveryKey = messageId
|
|
81
|
-
? `${namespace(sessionId)}\0${
|
|
179
|
+
? `${namespace(sessionId)}\0${normalizeText(input.conversationId)}\0${messageId}`
|
|
82
180
|
: "";
|
|
83
181
|
const existing = deliveryKey ? codexAppServerConversationTurnStarts.get(deliveryKey) : null;
|
|
84
182
|
if (existing) {
|
|
@@ -103,7 +201,8 @@ export function createCodexConversationCommands({
|
|
|
103
201
|
let state = conversations.get(conversationId);
|
|
104
202
|
if (state?.provider === provider && state.unsubscribe) return state;
|
|
105
203
|
if (state) forgetObservation(state);
|
|
106
|
-
state = { conversationId, provider, persistent: true, status: "ready", runId: "", error: "", turnOutcome: ""
|
|
204
|
+
state = { conversationId, provider, persistent: true, status: "ready", runId: "", error: "", turnOutcome: "",
|
|
205
|
+
onEvent: ignoreCodexConversationEvent };
|
|
107
206
|
state.unsubscribe = provider.subscribe((notification) => {
|
|
108
207
|
if (codexAppServerNotificationThreadId(notification) !== conversationId) return;
|
|
109
208
|
const turnId = codexAppServerNotificationTurnId(notification);
|
|
@@ -112,18 +211,18 @@ export function createCodexConversationCommands({
|
|
|
112
211
|
if (status) state.status = status;
|
|
113
212
|
const classification = classifyCodexAppServerEvent(notification);
|
|
114
213
|
if (notification.method === "item/agentMessage/delta") {
|
|
115
|
-
state.onEvent
|
|
214
|
+
state.onEvent({ type: "text", text: notification.params.delta,
|
|
116
215
|
messageId: notification.params.itemId, threadId: conversationId, turnId,
|
|
117
216
|
message: codexAppServerStreamMessage(classification) });
|
|
118
217
|
} else if (["assistant_started", "live_progress", "final_assistant_result"].includes(classification.kind)) {
|
|
119
|
-
state.onEvent
|
|
218
|
+
state.onEvent({ type: "message", threadId: conversationId, turnId,
|
|
120
219
|
message: { ...codexAppServerStreamMessage(classification),
|
|
121
220
|
...(classification.kind !== "assistant_started" ? { text: classification.text } : {}) } });
|
|
122
221
|
}
|
|
123
222
|
if (notification.method === "thread/goal/updated") state.goal = notification.params.goal;
|
|
124
223
|
if (notification.method === "thread/goal/cleared") state.goal = null;
|
|
125
224
|
if (notification.method === "turn/completed") {
|
|
126
|
-
state.onEvent =
|
|
225
|
+
state.onEvent = ignoreCodexConversationEvent;
|
|
127
226
|
const completedTurnId = state.runId;
|
|
128
227
|
const completedStatus = state.status;
|
|
129
228
|
// A turn the person stopped reaches the checkpoint as cancelled, as Main turns do.
|
|
@@ -145,7 +244,7 @@ export function createCodexConversationCommands({
|
|
|
145
244
|
}
|
|
146
245
|
|
|
147
246
|
async function readPersistentCodexConversation(sessionId, input, context) {
|
|
148
|
-
const conversationId =
|
|
247
|
+
const conversationId = normalizeText(input.conversationId);
|
|
149
248
|
const previous = codexAppServerConversation(sessionId, conversationId);
|
|
150
249
|
const state = observeCodexConversation(sessionId, conversationId, context.provider);
|
|
151
250
|
const thread = await context.provider.readThreadStatus(conversationId);
|
|
@@ -200,7 +299,7 @@ export function createCodexConversationCommands({
|
|
|
200
299
|
}
|
|
201
300
|
else continue;
|
|
202
301
|
const text = role === "thinking"
|
|
203
|
-
?
|
|
302
|
+
? normalizeText(codexAppServerContentText(item.summary)) || codexAppServerContentText(item.content)
|
|
204
303
|
: codexAppServerAssistantItemText(item);
|
|
205
304
|
if (text) messages.push({
|
|
206
305
|
id: codexAppServerConversationMessageId(conversationId, turnId, role, item.id || text),
|
|
@@ -226,27 +325,6 @@ export function createCodexConversationCommands({
|
|
|
226
325
|
};
|
|
227
326
|
}
|
|
228
327
|
|
|
229
|
-
function codexAppServerEphemeralConversationSnapshot(state = {}) {
|
|
230
|
-
return {
|
|
231
|
-
conversationId: normalizeCodexRunText(state.conversationId),
|
|
232
|
-
error: normalizeCodexRunText(state.error),
|
|
233
|
-
message: normalizeCodexRunText(state.message),
|
|
234
|
-
ok: true,
|
|
235
|
-
outcome: state.outcome || null,
|
|
236
|
-
progressUpdates: Array.isArray(state.progressUpdates)
|
|
237
|
-
? state.progressUpdates.map((update = {}) => ({
|
|
238
|
-
id: normalizeCodexRunText(update.id),
|
|
239
|
-
text: normalizeCodexRunText(update.text)
|
|
240
|
-
})).filter((update) => update.id && update.text)
|
|
241
|
-
: [],
|
|
242
|
-
rawText: normalizeCodexRunText(state.rawText),
|
|
243
|
-
messageId: normalizeCodexRunText(state.messageId),
|
|
244
|
-
runId: normalizeCodexRunText(state.runId),
|
|
245
|
-
status: normalizeCodexRunText(state.status) || "ready",
|
|
246
|
-
turnMetadata: state.turnMetadata || null
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
328
|
async function codexAppServerConversationResult(operation) {
|
|
251
329
|
try {
|
|
252
330
|
return await operation();
|
|
@@ -255,17 +333,6 @@ export function createCodexConversationCommands({
|
|
|
255
333
|
}
|
|
256
334
|
}
|
|
257
335
|
|
|
258
|
-
async function prepareCodexAppServerConversationThread(provider, preparation, helperIsolation) {
|
|
259
|
-
if (preparation.inspection) {
|
|
260
|
-
const enforcement = await helperIsolation.inspect(provider, preparation.inspection);
|
|
261
|
-
return preparation.prepared(enforcement);
|
|
262
|
-
}
|
|
263
|
-
const config = preparation.projectHooks
|
|
264
|
-
? await codexAppServerProjectHookTrustConfig(provider, preparation.workdir)
|
|
265
|
-
: null;
|
|
266
|
-
return preparation.settings(config);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
336
|
async function acquireCodexAppServerConversationContext(sessionId, prepared, input = {}, borrow = null) {
|
|
270
337
|
if (prepared.result) return prepared.result;
|
|
271
338
|
const { context } = prepared;
|
|
@@ -285,7 +352,7 @@ export function createCodexConversationCommands({
|
|
|
285
352
|
await helperLifecycle.restoreThreads(restoration.projectRuntimeRoot, restoration.context)
|
|
286
353
|
);
|
|
287
354
|
} else {
|
|
288
|
-
const threadId =
|
|
355
|
+
const threadId = normalizeText(input.threadId || input.codexSessionId || input.conversationId);
|
|
289
356
|
if (threadId) {
|
|
290
357
|
await helperLifecycle.assertOrdinaryThreadAvailable(prepared.projectRuntimeRoot, threadId);
|
|
291
358
|
}
|
|
@@ -343,99 +410,11 @@ export function createCodexConversationCommands({
|
|
|
343
410
|
}));
|
|
344
411
|
}
|
|
345
412
|
|
|
346
|
-
async function runDetachedCodexAppServerConversation(sessionId, input = {}, options = {}) {
|
|
347
|
-
const preparation = conversationPreparation.detached(sessionId, input);
|
|
348
|
-
const { admission } = preparation;
|
|
349
|
-
if (admission.ok === false) return admission;
|
|
350
|
-
try {
|
|
351
|
-
return await codexAppServerConversationResult(async () => {
|
|
352
|
-
const unavailable = preparation.result;
|
|
353
|
-
if (unavailable) return unavailable;
|
|
354
|
-
const prompt = preparation.prompt;
|
|
355
|
-
const context = await codexAppServerConversationContext(sessionId, input, options);
|
|
356
|
-
if (context.ok === false) return context;
|
|
357
|
-
const { provider, runtime, workdir } = context;
|
|
358
|
-
const executionProfile = input.executionProfile && typeof input.executionProfile === "object" &&
|
|
359
|
-
!Array.isArray(input.executionProfile) ? input.executionProfile : null;
|
|
360
|
-
const helperTurn = Boolean(executionProfile);
|
|
361
|
-
const emitDetachedEvent = event => {
|
|
362
|
-
if (typeof options.onEvent === "function") options.onEvent(event);
|
|
363
|
-
};
|
|
364
|
-
const onRetired = ({ threadId }) => emitDetachedEvent({ threadId, type: "thread-retired" });
|
|
365
|
-
if (helperTurn) {
|
|
366
|
-
await assertCodexAppServerHelperAccountIdentity(provider, input.expectedAccountIdentitySignature, helperOwnershipError);
|
|
367
|
-
}
|
|
368
|
-
const execution = preparation.execution(context);
|
|
369
|
-
const threadSettings = helperTurn ? null : await prepareCodexAppServerConversationThread(
|
|
370
|
-
provider, execution.threadPreparation, conversationPreparation.isolation
|
|
371
|
-
);
|
|
372
|
-
const requestedThreadId = normalizeCodexRunText(input.threadId || input.codexSessionId);
|
|
373
|
-
let helper = null;
|
|
374
|
-
let thread;
|
|
375
|
-
if (helperTurn) {
|
|
376
|
-
const scope = { executionProfile, onRetired, projectRuntimeRoot: normalizeCodexRunText(runtime?.stateRoot),
|
|
377
|
-
projectContextRoot: normalizeCodexRunText(runtime?.projectContextRoot), provider, sessionId, requestedThreadId, workdir,
|
|
378
|
-
...(requestedThreadId ? { record: helperLifecycle.threadForOperation({ executionProfile,
|
|
379
|
-
projectRuntimeRoot: normalizeCodexRunText(runtime?.stateRoot), provider, sessionId,
|
|
380
|
-
threadId: requestedThreadId, workdir }) } : {}) };
|
|
381
|
-
helper = await helperLifecycle.prepareDetached(scope, execution.helperPreparation);
|
|
382
|
-
thread = helper.thread;
|
|
383
|
-
} else {
|
|
384
|
-
thread = await acquireCodexAppServerDetachedThread(provider, requestedThreadId, threadSettings, input);
|
|
385
|
-
}
|
|
386
|
-
const threadId = codexAppServerDetachedThreadId(thread, requestedThreadId);
|
|
387
|
-
emitDetachedEvent({ threadId, type: "thread" });
|
|
388
|
-
const requestedTimeoutMs = Number(input.timeoutMs || 0);
|
|
389
|
-
const profileTimeoutMs = Number(executionProfile?.limits?.timeoutMs || 0);
|
|
390
|
-
let turnId = "";
|
|
391
|
-
const result = await runDetachedCodexAppServerTurn({
|
|
392
|
-
provider, threadId, onEvent: emitDetachedEvent,
|
|
393
|
-
timeoutMs: helperTurn
|
|
394
|
-
? Math.min(requestedTimeoutMs > 0 ? requestedTimeoutMs : profileTimeoutMs, profileTimeoutMs)
|
|
395
|
-
: requestedTimeoutMs > 0 ? requestedTimeoutMs : CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS,
|
|
396
|
-
async onFailure(error, status) {
|
|
397
|
-
await helper?.discard();
|
|
398
|
-
return execution.failure(error, status);
|
|
399
|
-
}
|
|
400
|
-
}, async failDispatch => {
|
|
401
|
-
let dispatched;
|
|
402
|
-
if (helperTurn) {
|
|
403
|
-
dispatched = await helper.dispatch(threadId, failDispatch);
|
|
404
|
-
} else {
|
|
405
|
-
try {
|
|
406
|
-
dispatched = await dispatchCodexAppServerDetachedTurn({ provider, prompt, threadId }, execution.authorized);
|
|
407
|
-
} catch (error) {
|
|
408
|
-
await failDispatch(error);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
turnId = dispatched.turnId;
|
|
412
|
-
return dispatched;
|
|
413
|
-
});
|
|
414
|
-
if (helperTurn) {
|
|
415
|
-
try {
|
|
416
|
-
await assertCodexAppServerHelperAccountIdentity(provider, input.expectedAccountIdentitySignature, helperOwnershipError);
|
|
417
|
-
execution.validateOutput(result.text);
|
|
418
|
-
} catch (error) {
|
|
419
|
-
await helper.discard();
|
|
420
|
-
throw error;
|
|
421
|
-
}
|
|
422
|
-
await helper.complete();
|
|
423
|
-
}
|
|
424
|
-
emitDetachedEvent({ status: result.status || "completed", text: result.text,
|
|
425
|
-
threadId, turnId: result.turnId || turnId, type: "completed" });
|
|
426
|
-
return { ok: true, text: result.text, threadId, turnId: result.turnId || turnId,
|
|
427
|
-
...(helperTurn ? { inputCharacters: prompt.length, outputCharacters: result.text.length, usage: result.usage || null } : {}) };
|
|
428
|
-
});
|
|
429
|
-
} finally {
|
|
430
|
-
admission.release();
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
413
|
async function startCodexAppServerConversationTurn(sessionId, input = {}, options = {}) {
|
|
435
|
-
const messageId =
|
|
414
|
+
const messageId = normalizeText(input.messageId);
|
|
436
415
|
return withCodexAppServerConversationTurnStart(sessionId, input, () => codexAppServerConversationResult(async () => {
|
|
437
|
-
const conversationId =
|
|
438
|
-
const prompt =
|
|
416
|
+
const conversationId = normalizeText(input.conversationId);
|
|
417
|
+
const prompt = normalizeText(input.message || input.prompt);
|
|
439
418
|
if (!conversationId || !prompt) {
|
|
440
419
|
return {
|
|
441
420
|
code: `${errorPrefix}agent_conversation_turn_input_required`,
|
|
@@ -459,8 +438,8 @@ export function createCodexConversationCommands({
|
|
|
459
438
|
ok: false,
|
|
460
439
|
};
|
|
461
440
|
}
|
|
462
|
-
//
|
|
463
|
-
// checks and Helper isolation capability checks
|
|
441
|
+
// Native preparation runs only after the duplicate and active-turn
|
|
442
|
+
// checks and the Helper isolation capability checks.
|
|
464
443
|
return startPreparedCodexAppServerConversationTurn(sessionId, input, {
|
|
465
444
|
executionProfile: execution.executionProfile,
|
|
466
445
|
helperIsolation: conversationPreparation.isolation,
|
|
@@ -468,7 +447,7 @@ export function createCodexConversationCommands({
|
|
|
468
447
|
authorized: execution.authorized,
|
|
469
448
|
readOnly: execution.readOnly,
|
|
470
449
|
actorMetadata: execution.actorMetadata,
|
|
471
|
-
onEvent: execution.onEvent,
|
|
450
|
+
onEvent: execution.onEvent || ignoreCodexConversationEvent,
|
|
472
451
|
projectResult: execution.projectResult,
|
|
473
452
|
conversationId,
|
|
474
453
|
messageId,
|
|
@@ -483,7 +462,7 @@ export function createCodexConversationCommands({
|
|
|
483
462
|
|
|
484
463
|
async function readCodexAppServerConversation(sessionId, input = {}, options = {}) {
|
|
485
464
|
return codexAppServerConversationResult(async () => {
|
|
486
|
-
const conversationId =
|
|
465
|
+
const conversationId = normalizeText(input.conversationId);
|
|
487
466
|
if (!conversationId) {
|
|
488
467
|
return {
|
|
489
468
|
code: `${errorPrefix}agent_conversation_id_required`,
|
|
@@ -498,7 +477,7 @@ export function createCodexConversationCommands({
|
|
|
498
477
|
if (input.ephemeral === true) {
|
|
499
478
|
return conversationPreparation.expired(conversationId, input);
|
|
500
479
|
}
|
|
501
|
-
if (input.persistent && conversationState?.persistent && conversationState.provider.isAvailable
|
|
480
|
+
if (input.persistent && conversationState?.persistent && conversationState.provider.isAvailable() &&
|
|
502
481
|
!conversationState.provider.observationFailure && !conversationPreparation.admissionError(sessionId)) {
|
|
503
482
|
// Poll the already observed native thread. Execution environment setup
|
|
504
483
|
// belongs to Send and reconnection, not every history read. The read
|
|
@@ -518,7 +497,7 @@ export function createCodexConversationCommands({
|
|
|
518
497
|
}
|
|
519
498
|
if (input.persistent) return readPreparedPersistentCodexConversation(sessionId, input, context);
|
|
520
499
|
const thread = await context.provider.readThread(conversationId);
|
|
521
|
-
const runId =
|
|
500
|
+
const runId = normalizeText(input.runId) || codexAppServerThreadTurnId(thread);
|
|
522
501
|
const status = codexAppServerThreadStatus(thread);
|
|
523
502
|
const text = runId
|
|
524
503
|
? codexAppServerProviderThreadAssistantSegments(thread, runId)
|
|
@@ -539,11 +518,11 @@ export function createCodexConversationCommands({
|
|
|
539
518
|
|
|
540
519
|
async function waitForCodexAppServerConversationTurn(sessionId, input = {}, {
|
|
541
520
|
assistantScope = null,
|
|
542
|
-
onEvent =
|
|
521
|
+
onEvent = ignoreCodexDetachedTurnEvent
|
|
543
522
|
} = {}) {
|
|
544
523
|
return codexAppServerConversationResult(async () => {
|
|
545
|
-
const conversationId =
|
|
546
|
-
const runId =
|
|
524
|
+
const conversationId = normalizeText(input.conversationId);
|
|
525
|
+
const runId = normalizeText(input.runId);
|
|
547
526
|
if (!conversationId || !runId) {
|
|
548
527
|
return {
|
|
549
528
|
code: `${errorPrefix}agent_conversation_run_required`,
|
|
@@ -573,724 +552,13 @@ export function createCodexConversationCommands({
|
|
|
573
552
|
});
|
|
574
553
|
}
|
|
575
554
|
|
|
576
|
-
|
|
577
|
-
async function stopThroughObservationFailure(state) {
|
|
578
|
-
await state.provider.failObservation(state.provider.observationFailure);
|
|
579
|
-
if (codexAppServerConversationTurnIsActive(state.status)) {
|
|
580
|
-
throw new Error("Codex's stop is not yet confirmed.");
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
async function stopCodexAppServerConversation(sessionId, input = {}, options = {}, {
|
|
585
|
-
turnOutcome = turnOutcomes.USER_CANCELLED
|
|
586
|
-
} = {}) {
|
|
587
|
-
const conversationId = normalizeCodexRunText(input.conversationId);
|
|
588
|
-
const conversationState = codexAppServerConversation(sessionId, conversationId);
|
|
589
|
-
if (conversationState?.provider.observationFailure) {
|
|
590
|
-
await stopThroughObservationFailure(conversationState);
|
|
591
|
-
return { conversationId, ok: true, runId: conversationState.runId, status: "interrupted" };
|
|
592
|
-
}
|
|
593
|
-
if (input.ephemeral === true && !conversationState) {
|
|
594
|
-
return {
|
|
595
|
-
conversationExpired: true,
|
|
596
|
-
conversationId,
|
|
597
|
-
ok: true,
|
|
598
|
-
runId: normalizeCodexRunText(input.runId),
|
|
599
|
-
status: "interrupted"
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
if (input.persistent) {
|
|
603
|
-
return withProviderBorrow(true, async (borrow) => {
|
|
604
|
-
const context = await codexAppServerConversationContext(sessionId, input, options, borrow);
|
|
605
|
-
if (context.ok === false) return context;
|
|
606
|
-
return stopPersistentCodexConversation({
|
|
607
|
-
conversationId,
|
|
608
|
-
conversationState,
|
|
609
|
-
provider: context.provider,
|
|
610
|
-
turnOutcome
|
|
611
|
-
});
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
const result = await interruptDetachedCodexAppServerConversationTurn(sessionId, {
|
|
615
|
-
agentSettings: input.agentSettings,
|
|
616
|
-
threadId: input.conversationId,
|
|
617
|
-
turnId: input.runId
|
|
618
|
-
}, options, conversationState);
|
|
619
|
-
return {
|
|
620
|
-
...result,
|
|
621
|
-
conversationId,
|
|
622
|
-
runId: normalizeCodexRunText(input.runId)
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// A scope's delete stops only what this server runs for it: its live providers,
|
|
627
|
-
// and a runtime whose earlier stop could not prove its exit. It never acquires
|
|
628
|
-
// a provider, which would start a Codex app-server only to stop it, so it needs
|
|
629
|
-
// no account, tool home or workdir. A runtime other providers share stays theirs.
|
|
630
|
-
async function stopCodexScopeRuntimes(sessionKey) {
|
|
631
|
-
const owner = providerSessions.owner;
|
|
632
|
-
const attempted = new Set([...owner.providers]
|
|
633
|
-
.filter(([providerKey]) => owner.providerOwners.get(providerKey)?.sessionKey === sessionKey)
|
|
634
|
-
.map(([, provider]) => provider));
|
|
635
|
-
const stopped = await owner.stopCachedProvidersForSession(sessionKey, { requireStopped: true });
|
|
636
|
-
const failed = [...stopped.failed];
|
|
637
|
-
const results = [...stopped.results];
|
|
638
|
-
for (const record of [...owner.ownedRuntimes.values()]) {
|
|
639
|
-
if (record.sessionKey !== sessionKey || attempted.has(record.provider) ||
|
|
640
|
-
owner.runtimeIsShared(record.providerKey, record.providerOptions)) continue;
|
|
641
|
-
try {
|
|
642
|
-
// Its provider is no longer cached, so the runtime's own options name its lock.
|
|
643
|
-
results.push(await owner.withLifecycle(() => owner.stopOwnedRuntime(record),
|
|
644
|
-
record.providerKey, record.providerOptions));
|
|
645
|
-
} catch (error) {
|
|
646
|
-
failed.push({ code: normalizeCodexRunText(error?.code), error: codexAppServerErrorMessage(error),
|
|
647
|
-
providerKey: record.providerKey, retryable: error?.retryable === true });
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
if (failed.length) {
|
|
651
|
-
const error = new Error(failed.map((failure) => failure.error).join(" "));
|
|
652
|
-
error.code = failed[0].code;
|
|
653
|
-
error.retryable = failed.every((failure) => failure.retryable);
|
|
654
|
-
error.details = { failed };
|
|
655
|
-
throw error;
|
|
656
|
-
}
|
|
657
|
-
return { ok: true, results, stopped: results.some((result) => result?.stopped === true) };
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
async function deleteScopedCodexAppServerConversation(sessionId, input = {}, options = {}) {
|
|
661
|
-
const conversationId = normalizeCodexRunText(input.conversationId);
|
|
662
|
-
const sessionKey = namespace(sessionId);
|
|
663
|
-
const conversations = codexAppServerConversations.get(sessionKey);
|
|
664
|
-
// An ephemeral native thread lives only in the process that created it, so
|
|
665
|
-
// one this server no longer tracks has nothing left to delete.
|
|
666
|
-
const conversationExpired = input.ephemeral === true && !conversations?.has(conversationId);
|
|
667
|
-
let result;
|
|
668
|
-
if (conversationExpired) {
|
|
669
|
-
result = {
|
|
670
|
-
conversationExpired: true
|
|
671
|
-
};
|
|
672
|
-
} else {
|
|
673
|
-
result = await deleteDetachedCodexAppServerConversationThread(sessionId, {
|
|
674
|
-
agentSettings: input.agentSettings,
|
|
675
|
-
threadId: input.conversationId
|
|
676
|
-
}, options, { conversationId, sessionKey, conversations });
|
|
677
|
-
if (result.ok === false) {
|
|
678
|
-
return { ...result, conversationId };
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
const providerExit = options.assistantScope ? await stopCodexScopeRuntimes(sessionKey) : null;
|
|
682
|
-
return {
|
|
683
|
-
...result,
|
|
684
|
-
conversationId,
|
|
685
|
-
ok: conversationExpired ? providerExit?.ok !== false : result?.ok !== false,
|
|
686
|
-
...(providerExit ? { providerExit } : {})
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// Close stops each persistent conversation through the provider that observes
|
|
691
|
-
// it. A closing session refuses a rebuilt context, and a new connection could
|
|
692
|
-
// not see the running turn anyway. One conversation's failure does not skip
|
|
693
|
-
// the others; it stays registered so that a retried close stops it.
|
|
694
|
-
async function stopObservedCodexConversation(state) {
|
|
695
|
-
const { provider } = state;
|
|
696
|
-
if (provider.observationFailure) {
|
|
697
|
-
await stopThroughObservationFailure(state);
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
// A provider its owner has since closed observes nothing left to stop here:
|
|
701
|
-
// the owner stopped or detached its process, and reconnecting it would
|
|
702
|
-
// start a process nothing owns.
|
|
703
|
-
if (![...providerSessions.owner.providers].some(([, owned]) => owned === provider)) return;
|
|
704
|
-
// Closing is not the person stopping the turn; its status alone decides the outcome.
|
|
705
|
-
await stopPersistentCodexConversation({
|
|
706
|
-
conversationId: state.conversationId, conversationState: state, provider, turnOutcome: ""
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
async function closeCodexAppServerConversations(sessionId) {
|
|
711
|
-
const key = namespace(sessionId);
|
|
712
|
-
const conversations = codexAppServerConversations.get(key);
|
|
713
|
-
const failed = [];
|
|
714
|
-
for (const state of [...(conversations?.values() || [])]) {
|
|
715
|
-
if (!state.persistent) {
|
|
716
|
-
await deleteScopedCodexAppServerConversation(sessionId, { conversationId: state.conversationId }).catch(() => null);
|
|
717
|
-
conversations.delete(state.conversationId);
|
|
718
|
-
continue;
|
|
719
|
-
}
|
|
720
|
-
try {
|
|
721
|
-
await stopObservedCodexConversation(state);
|
|
722
|
-
} catch (error) {
|
|
723
|
-
failed.push({ conversationId: state.conversationId, error: codexAppServerErrorMessage(error) });
|
|
724
|
-
continue;
|
|
725
|
-
}
|
|
726
|
-
forgetObservation(state);
|
|
727
|
-
conversations.delete(state.conversationId);
|
|
728
|
-
}
|
|
729
|
-
if (conversations && codexAppServerConversations.get(key) === conversations && !conversations.size) {
|
|
730
|
-
codexAppServerConversations.delete(key);
|
|
731
|
-
}
|
|
732
|
-
if (failed.length) {
|
|
733
|
-
const error = new Error(`Codex could not confirm that ${failed.length === 1 ? "a conversation" : `${failed.length} conversations`} stopped: ${
|
|
734
|
-
failed.map(({ conversationId, error: reason }) => `${conversationId} (${reason})`).join("; ")}. Retry closing the session.`);
|
|
735
|
-
error.code = `${errorPrefix}codex_conversation_close_failed`;
|
|
736
|
-
error.retryable = true;
|
|
737
|
-
error.details = { failed, sessionId: normalizeCodexRunText(sessionId) };
|
|
738
|
-
throw error;
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
async function codexAppServerConversationControl(sessionId, input, {
|
|
743
|
-
assistantScope = null,
|
|
744
|
-
runtime = null,
|
|
745
|
-
session = null
|
|
746
|
-
} = {}, operation, dispatch) {
|
|
747
|
-
return codexAppServerConversationResult(async () => {
|
|
748
|
-
const prepared = conversationPreparation.control(sessionId, input, operation);
|
|
749
|
-
if (prepared.result) return prepared.result;
|
|
750
|
-
const { admission, threadId, turnId } = prepared;
|
|
751
|
-
// Stopping or deleting a thread is cleanup, not a use that keeps Codex running.
|
|
752
|
-
const borrow = { release() {} };
|
|
753
|
-
try {
|
|
754
|
-
let context = null;
|
|
755
|
-
try {
|
|
756
|
-
context = await codexAppServerConversationContext(sessionId, input, { assistantScope, runtime, session }, borrow);
|
|
757
|
-
} catch (error) {
|
|
758
|
-
const unavailable = prepared.unavailable();
|
|
759
|
-
if (unavailable) return unavailable;
|
|
760
|
-
throw error;
|
|
761
|
-
}
|
|
762
|
-
if (context.ok === false) return context;
|
|
763
|
-
const unavailable = prepared.unavailable();
|
|
764
|
-
if (unavailable) return unavailable;
|
|
765
|
-
const policyResult = helperLifecycle?.inspectControl(operation, input, {
|
|
766
|
-
sessionId, threadId, turnId,
|
|
767
|
-
get retiredThreadIds() { return context.helperRestore?.retiredThreadIds; },
|
|
768
|
-
get projectRuntimeRoot() { return context.runtime?.stateRoot; },
|
|
769
|
-
get provider() { return context.provider; },
|
|
770
|
-
get workdir() { return context.workdir; }
|
|
771
|
-
});
|
|
772
|
-
// The original Helper-retirement return releases admission before its
|
|
773
|
-
// promise settles; only the native dispatch below is awaited here.
|
|
774
|
-
if (policyResult !== undefined) return policyResult;
|
|
775
|
-
if (operation === "interrupt") {
|
|
776
|
-
const unavailable = prepared.unavailable();
|
|
777
|
-
if (unavailable) return unavailable;
|
|
778
|
-
} else if (typeof context.provider.deleteThread !== "function") {
|
|
779
|
-
return {
|
|
780
|
-
code: `${errorPrefix}codex_detached_thread_delete_unavailable`,
|
|
781
|
-
error: "Codex app-server thread deletion is not available.",
|
|
782
|
-
messageCode: "assistant.codex.threadDeleteUnavailable",
|
|
783
|
-
messageParams: {},
|
|
784
|
-
ok: false,
|
|
785
|
-
statusCode: 409,
|
|
786
|
-
threadId
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
return await dispatch({ provider: context.provider, threadId, turnId });
|
|
790
|
-
} finally {
|
|
791
|
-
borrow.release();
|
|
792
|
-
admission.release();
|
|
793
|
-
}
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
async function interruptDetachedCodexAppServerConversationTurn(sessionId, input = {}, options = {}, conversationState = null) {
|
|
798
|
-
return interruptCodexAppServerConversation(conversationState, dispatch =>
|
|
799
|
-
codexAppServerConversationControl(sessionId, input, options, "interrupt", dispatch));
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
async function deleteDetachedCodexAppServerConversationThread(sessionId, input = {}, options = {}, conversation = null) {
|
|
803
|
-
return deleteCodexAppServerConversation(conversation, dispatch =>
|
|
804
|
-
codexAppServerConversationControl(sessionId, input, options, "delete", dispatch));
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
async function createPreparedCodexAppServerConversation(sessionId, input, {
|
|
808
|
-
provider,
|
|
809
|
-
executionProfile,
|
|
810
|
-
helperIsolation,
|
|
811
|
-
threadPreparation
|
|
812
|
-
} = {}) {
|
|
813
|
-
const prepareThread = () => prepareCodexAppServerConversationThread(provider, threadPreparation(), helperIsolation);
|
|
814
|
-
const thread = executionProfile
|
|
815
|
-
? (await helperIsolation.start(provider, prepareThread)).thread
|
|
816
|
-
: await provider.startThread({
|
|
817
|
-
...await prepareThread(),
|
|
818
|
-
...(input.ephemeral === true ? { ephemeral: true } : {})
|
|
819
|
-
});
|
|
820
|
-
const conversationId = normalizeCodexRunText(thread.id || thread.response?.thread?.id);
|
|
821
|
-
if (!conversationId) {
|
|
822
|
-
throw new Error("Codex app-server did not return a conversation id.");
|
|
823
|
-
}
|
|
824
|
-
if (input.persistent === true) observeCodexConversation(sessionId, conversationId, provider);
|
|
825
|
-
if (input.ephemeral === true) {
|
|
826
|
-
const sessionKey = namespace(sessionId);
|
|
827
|
-
const conversations = codexAppServerConversations.get(sessionKey) || new Map();
|
|
828
|
-
conversations.set(conversationId, {
|
|
829
|
-
conversationId,
|
|
830
|
-
executionProfile,
|
|
831
|
-
provider,
|
|
832
|
-
error: "",
|
|
833
|
-
message: "",
|
|
834
|
-
messageId: "",
|
|
835
|
-
nextProgressSequence: 0,
|
|
836
|
-
outcome: null,
|
|
837
|
-
progressUpdates: [],
|
|
838
|
-
rawText: "",
|
|
839
|
-
runId: "",
|
|
840
|
-
status: "ready",
|
|
841
|
-
turnMetadata: null,
|
|
842
|
-
watcher: null
|
|
843
|
-
});
|
|
844
|
-
codexAppServerConversations.set(sessionKey, conversations);
|
|
845
|
-
}
|
|
846
|
-
return {
|
|
847
|
-
conversationId,
|
|
848
|
-
ok: true,
|
|
849
|
-
status: "ready"
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
async function steerCodexAppServerConversationTurn(input, {
|
|
854
|
-
conversationId,
|
|
855
|
-
conversationState,
|
|
856
|
-
messageId,
|
|
857
|
-
prompt,
|
|
858
|
-
provider
|
|
859
|
-
} = {}) {
|
|
860
|
-
const images = codexLocalImageInput(input.attachments || []);
|
|
861
|
-
const result = await provider.steerTurn(
|
|
862
|
-
conversationId,
|
|
863
|
-
conversationState.runId,
|
|
864
|
-
images.length ? [prompt, ...images] : prompt,
|
|
865
|
-
{ clientUserMessageId: messageId }
|
|
866
|
-
);
|
|
867
|
-
const failure = codexAppServerSteerFailure(result, steerFailedCode);
|
|
868
|
-
if (failure) return { ...failure, ok: false };
|
|
869
|
-
conversationState.messageId = messageId;
|
|
870
|
-
return {
|
|
871
|
-
conversationId,
|
|
872
|
-
messageId,
|
|
873
|
-
ok: true,
|
|
874
|
-
runId: conversationState.runId,
|
|
875
|
-
status: "inProgress",
|
|
876
|
-
deliveryMode: "steer"
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
async function acquireCodexAppServerDetachedThread(provider, requestedThreadId, threadSettings, input = {}) {
|
|
881
|
-
let thread = null;
|
|
882
|
-
if (requestedThreadId) {
|
|
883
|
-
thread = await provider.resumeThread(requestedThreadId, threadSettings);
|
|
884
|
-
}
|
|
885
|
-
if (!thread) {
|
|
886
|
-
thread = await provider.startThread({
|
|
887
|
-
...threadSettings,
|
|
888
|
-
...(input.ephemeral === true ? { ephemeral: true } : {})
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
|
-
return thread;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
function codexAppServerDetachedThreadId(thread, requestedThreadId) {
|
|
895
|
-
const threadId = normalizeCodexRunText(thread.id || thread.response?.thread?.id || requestedThreadId);
|
|
896
|
-
if (!threadId) {
|
|
897
|
-
throw new Error("Codex app-server did not return a detached chat thread id.");
|
|
898
|
-
}
|
|
899
|
-
return threadId;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
async function dispatchCodexAppServerDetachedTurn({ provider, prompt, threadId }, authorized) {
|
|
903
|
-
const delivery = await sendCodexAppServerPrompt({ provider, prompt, threadId }, authorized);
|
|
904
|
-
return { delivery, ...codexAppServerDeliveredTurn(delivery) };
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
async function runDetachedCodexAppServerTurn({
|
|
908
|
-
provider,
|
|
909
|
-
threadId,
|
|
910
|
-
timeoutMs,
|
|
911
|
-
onEvent: emitDetachedEvent,
|
|
912
|
-
onFailure
|
|
913
|
-
} = {}, operation) {
|
|
914
|
-
const watcher = createCodexAppServerDetachedTurnWatcher(provider, threadId, {
|
|
915
|
-
onEvent: (classification) => {
|
|
916
|
-
emitDetachedEvent({
|
|
917
|
-
classification,
|
|
918
|
-
threadId,
|
|
919
|
-
turnId: classification.turnId,
|
|
920
|
-
type: "notification"
|
|
921
|
-
});
|
|
922
|
-
},
|
|
923
|
-
timeoutMs
|
|
924
|
-
});
|
|
925
|
-
const waitForResult = watcher.wait();
|
|
926
|
-
void waitForResult.catch(() => null);
|
|
927
|
-
const throwWatcherFailure = async (fallbackError, status = "", {
|
|
928
|
-
waitForDetail = false
|
|
929
|
-
} = {}) => {
|
|
930
|
-
if (waitForDetail) {
|
|
931
|
-
watcher.failAfterDetailGrace(fallbackError);
|
|
932
|
-
} else {
|
|
933
|
-
watcher.failNow(fallbackError);
|
|
934
|
-
}
|
|
935
|
-
const error = await waitForResult.then(
|
|
936
|
-
() => fallbackError,
|
|
937
|
-
(watcherError) => watcherError
|
|
938
|
-
);
|
|
939
|
-
throw await onFailure(error, status);
|
|
940
|
-
};
|
|
941
|
-
const { delivery, status, turnId } = await operation(throwWatcherFailure);
|
|
942
|
-
watcher.setTurnId(turnId);
|
|
943
|
-
emitDetachedEvent({
|
|
944
|
-
status,
|
|
945
|
-
threadId,
|
|
946
|
-
turnId,
|
|
947
|
-
type: "turn"
|
|
948
|
-
});
|
|
949
|
-
if (codexAppServerTurnStatusIsProviderFailure(status)) {
|
|
950
|
-
const error = new Error(codexAppServerDeliveredTurn(delivery).error || `Codex app-server turn ${status}.`);
|
|
951
|
-
await throwWatcherFailure(error, status, {
|
|
952
|
-
waitForDetail: true
|
|
953
|
-
});
|
|
954
|
-
}
|
|
955
|
-
if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
|
|
956
|
-
await watcher.completeNow(status);
|
|
957
|
-
}
|
|
958
|
-
let result = null;
|
|
959
|
-
try {
|
|
960
|
-
result = await waitForResult;
|
|
961
|
-
} catch (error) {
|
|
962
|
-
throw await onFailure(error, status);
|
|
963
|
-
}
|
|
964
|
-
return result;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
async function startPreparedCodexAppServerConversationTurn(sessionId, input, {
|
|
968
|
-
conversationId,
|
|
969
|
-
messageId,
|
|
970
|
-
prompt,
|
|
971
|
-
provider,
|
|
972
|
-
conversationState,
|
|
973
|
-
executionProfile,
|
|
974
|
-
actorMetadata,
|
|
975
|
-
onEvent,
|
|
976
|
-
projectResult,
|
|
977
|
-
helperIsolation,
|
|
978
|
-
threadPreparation,
|
|
979
|
-
prepareTurn,
|
|
980
|
-
authorized,
|
|
981
|
-
readOnly
|
|
982
|
-
} = {}) {
|
|
983
|
-
if (conversationState && messageId && conversationState.messageId === messageId) {
|
|
984
|
-
return codexAppServerEphemeralConversationSnapshot(conversationState);
|
|
985
|
-
}
|
|
986
|
-
if (conversationState && codexAppServerConversationTurnIsActive(conversationState.status)) {
|
|
987
|
-
if (input.steer === true) {
|
|
988
|
-
return steerCodexAppServerConversationTurn(input, {
|
|
989
|
-
conversationId,
|
|
990
|
-
conversationState,
|
|
991
|
-
messageId,
|
|
992
|
-
prompt,
|
|
993
|
-
provider
|
|
994
|
-
});
|
|
995
|
-
}
|
|
996
|
-
return {
|
|
997
|
-
code: `${errorPrefix}temporary_conversation_turn_active`,
|
|
998
|
-
error: "Temporary AI is already working on this conversation.",
|
|
999
|
-
messageCode: "assistant.codex.temporaryConversationBusy",
|
|
1000
|
-
messageParams: {},
|
|
1001
|
-
ok: false
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
if (!conversationState || input.persistent === true) {
|
|
1005
|
-
const prepareThread = () => prepareCodexAppServerConversationThread(provider, threadPreparation(), helperIsolation);
|
|
1006
|
-
if (executionProfile) await helperIsolation.resume(provider, conversationId, prepareThread);
|
|
1007
|
-
else await provider.resumeThread(conversationId, await prepareThread());
|
|
1008
|
-
}
|
|
1009
|
-
let admittedAccountIdentitySignature = "";
|
|
1010
|
-
if (executionProfile) {
|
|
1011
|
-
admittedAccountIdentitySignature = await readCodexAppServerAccountIdentity(provider, helperOwnershipError);
|
|
1012
|
-
}
|
|
1013
|
-
let watcher = null;
|
|
1014
|
-
let waitForResult = null;
|
|
1015
|
-
if (conversationState && !input.persistent) {
|
|
1016
|
-
Object.assign(conversationState, {
|
|
1017
|
-
error: "",
|
|
1018
|
-
message: "",
|
|
1019
|
-
messageId,
|
|
1020
|
-
outcome: null,
|
|
1021
|
-
progressUpdates: [],
|
|
1022
|
-
rawText: "",
|
|
1023
|
-
runId: "",
|
|
1024
|
-
status: "starting",
|
|
1025
|
-
turnMetadata: actorMetadata
|
|
1026
|
-
});
|
|
1027
|
-
watcher = createCodexAppServerDetachedTurnWatcher(provider, conversationId, {
|
|
1028
|
-
includeThreadHistory: false,
|
|
1029
|
-
timeoutMs: executionProfile?.limits.timeoutMs || 0,
|
|
1030
|
-
onEvent(classification = {}) {
|
|
1031
|
-
const current = codexAppServerConversation(sessionId, conversationId);
|
|
1032
|
-
if (!current || (classification.turnId && current.runId && classification.turnId !== current.runId)) {
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
onEvent?.(classification, current);
|
|
1036
|
-
}
|
|
1037
|
-
});
|
|
1038
|
-
waitForResult = watcher.wait();
|
|
1039
|
-
void waitForResult.catch(() => null);
|
|
1040
|
-
conversationState.watcher = watcher;
|
|
1041
|
-
}
|
|
1042
|
-
let delivery = null;
|
|
1043
|
-
if (input.persistent && conversationState) Object.assign(conversationState, { onEvent, turnOutcome: "" });
|
|
1044
|
-
try {
|
|
1045
|
-
await input.onPromptSending?.({ threadId: conversationId });
|
|
1046
|
-
if (executionProfile) {
|
|
1047
|
-
const prepared = prepareTurn();
|
|
1048
|
-
delivery = await sendPreparedCodexAppServerHelperTurn(provider, prepared, helperIsolation);
|
|
1049
|
-
} else {
|
|
1050
|
-
delivery = await sendCodexAppServerPrompt({
|
|
1051
|
-
clientUserMessageId: input.messageId,
|
|
1052
|
-
outputSchema: input.outputSchema,
|
|
1053
|
-
prompt,
|
|
1054
|
-
attachments: input.attachments,
|
|
1055
|
-
provider,
|
|
1056
|
-
readOnly,
|
|
1057
|
-
threadId: conversationId
|
|
1058
|
-
}, authorized);
|
|
1059
|
-
}
|
|
1060
|
-
} catch (error) {
|
|
1061
|
-
if (input.persistent && conversationState) conversationState.onEvent = null;
|
|
1062
|
-
watcher?.failNow(error);
|
|
1063
|
-
await waitForResult?.catch(() => null);
|
|
1064
|
-
if (conversationState) {
|
|
1065
|
-
Object.assign(conversationState, {
|
|
1066
|
-
error: codexAppServerErrorMessage(error, "Temporary AI message could not be sent."),
|
|
1067
|
-
status: "failed",
|
|
1068
|
-
watcher: null
|
|
1069
|
-
});
|
|
1070
|
-
holdWhileActive(conversationState);
|
|
1071
|
-
}
|
|
1072
|
-
throw error;
|
|
1073
|
-
}
|
|
1074
|
-
const { turnId: runId, status } = codexAppServerDeliveredTurn(delivery);
|
|
1075
|
-
if (!runId) {
|
|
1076
|
-
watcher?.failNow(new Error("Codex app-server accepted a conversation turn without returning its id."));
|
|
1077
|
-
await waitForResult?.catch(() => null);
|
|
1078
|
-
throw new Error("Codex app-server accepted a conversation turn without returning its id.");
|
|
1079
|
-
}
|
|
1080
|
-
if (conversationState && !input.persistent) {
|
|
1081
|
-
let nativeCompleted = false;
|
|
1082
|
-
Object.assign(conversationState, {
|
|
1083
|
-
runId,
|
|
1084
|
-
status: !executionProfile && codexAppServerTurnStatusIsSuccessfulComplete(status) ? status : "inProgress"
|
|
1085
|
-
});
|
|
1086
|
-
watcher.setTurnId(runId);
|
|
1087
|
-
if (codexAppServerTurnStatusIsProviderFailure(status)) {
|
|
1088
|
-
watcher.failNow(new Error(`Codex app-server turn ${status}.`));
|
|
1089
|
-
} else if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
|
|
1090
|
-
await watcher.completeNow(status);
|
|
1091
|
-
}
|
|
1092
|
-
conversationState.completion = waitForResult.then(async (result = {}) => {
|
|
1093
|
-
const current = codexAppServerConversation(sessionId, conversationId);
|
|
1094
|
-
if (!current || current.runId !== runId) {
|
|
1095
|
-
return;
|
|
1096
|
-
}
|
|
1097
|
-
nativeCompleted = true;
|
|
1098
|
-
if (!executionProfile) current.status = result.status || "completed";
|
|
1099
|
-
if (executionProfile) {
|
|
1100
|
-
const completingRunId = current.runId;
|
|
1101
|
-
await assertCodexAppServerHelperAccountIdentity(provider, admittedAccountIdentitySignature, helperOwnershipError);
|
|
1102
|
-
if (codexAppServerConversation(sessionId, conversationId) !== current ||
|
|
1103
|
-
current.runId !== completingRunId || !codexAppServerConversationTurnIsActive(current.status)) {
|
|
1104
|
-
throw new Error("The scoped helper turn is unavailable.");
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
const response = projectResult(result);
|
|
1108
|
-
Object.assign(current, {
|
|
1109
|
-
error: "",
|
|
1110
|
-
...response,
|
|
1111
|
-
status: result.status || "completed",
|
|
1112
|
-
watcher: null
|
|
1113
|
-
});
|
|
1114
|
-
return codexAppServerEphemeralConversationSnapshot(current);
|
|
1115
|
-
}).catch(async (error) => {
|
|
1116
|
-
const current = codexAppServerConversation(sessionId, conversationId);
|
|
1117
|
-
if (!current || current.runId !== runId || current.status === "interrupted") {
|
|
1118
|
-
throw error;
|
|
1119
|
-
}
|
|
1120
|
-
if (executionProfile && !nativeCompleted && codexAppServerConversationTurnIsActive(current.status)) {
|
|
1121
|
-
try {
|
|
1122
|
-
const interrupted = await provider.interruptTurn(conversationId, runId);
|
|
1123
|
-
const failure = codexAppServerInterruptFailure(interrupted, interruptFailedCode);
|
|
1124
|
-
if (failure) throw new Error(failure.error);
|
|
1125
|
-
} catch {
|
|
1126
|
-
current.error = "The helper deadline ended, but Stop could not be confirmed. Retry Stop before reusing this helper.";
|
|
1127
|
-
current.watcher = null;
|
|
1128
|
-
throw new Error(current.error);
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
Object.assign(current, {
|
|
1132
|
-
error: codexAppServerErrorMessage(error, "Temporary AI turn failed."),
|
|
1133
|
-
status: "failed",
|
|
1134
|
-
watcher: null
|
|
1135
|
-
});
|
|
1136
|
-
throw error;
|
|
1137
|
-
});
|
|
1138
|
-
void conversationState.completion.catch(() => null);
|
|
1139
|
-
}
|
|
1140
|
-
if (input.persistent && conversationState) {
|
|
1141
|
-
Object.assign(conversationState, { runId, status, messageId });
|
|
1142
|
-
holdWhileActive(conversationState);
|
|
1143
|
-
}
|
|
1144
|
-
return { conversationId, messageId, ok: true, runId, status };
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
async function interruptCodexAppServerConversation(conversationState, operation) {
|
|
1148
|
-
const result = await operation(async ({ provider, threadId, turnId }) => {
|
|
1149
|
-
const result = await provider.interruptTurn(threadId, turnId);
|
|
1150
|
-
const interruptFailure = codexAppServerInterruptFailure(result, interruptFailedCode);
|
|
1151
|
-
if (interruptFailure) {
|
|
1152
|
-
return {
|
|
1153
|
-
...interruptFailure,
|
|
1154
|
-
result,
|
|
1155
|
-
threadId,
|
|
1156
|
-
turnId
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
return {
|
|
1160
|
-
ok: true,
|
|
1161
|
-
result,
|
|
1162
|
-
status: "interrupted",
|
|
1163
|
-
threadId,
|
|
1164
|
-
turnId
|
|
1165
|
-
};
|
|
1166
|
-
});
|
|
1167
|
-
if (result.ok !== false && conversationState) {
|
|
1168
|
-
conversationState.status = "interrupted";
|
|
1169
|
-
conversationState.watcher?.failNow(new Error("Temporary AI turn was stopped."));
|
|
1170
|
-
conversationState.watcher = null;
|
|
1171
|
-
holdWhileActive(conversationState);
|
|
1172
|
-
}
|
|
1173
|
-
return result;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
async function deleteCodexAppServerConversation(conversation, operation) {
|
|
1177
|
-
const result = await operation(async ({ provider, threadId }) => {
|
|
1178
|
-
try {
|
|
1179
|
-
const result = await provider.deleteThread(threadId);
|
|
1180
|
-
return {
|
|
1181
|
-
ok: true,
|
|
1182
|
-
result,
|
|
1183
|
-
status: "deleted",
|
|
1184
|
-
threadId
|
|
1185
|
-
};
|
|
1186
|
-
} catch (error) {
|
|
1187
|
-
if (
|
|
1188
|
-
codexAppServerRequestIsInvalid(error, "thread/delete") &&
|
|
1189
|
-
!await codexAppServerThreadHasReadableHistory(provider, threadId)
|
|
1190
|
-
) {
|
|
1191
|
-
return {
|
|
1192
|
-
ok: true,
|
|
1193
|
-
status: "notFound",
|
|
1194
|
-
threadId
|
|
1195
|
-
};
|
|
1196
|
-
}
|
|
1197
|
-
throw error;
|
|
1198
|
-
}
|
|
1199
|
-
});
|
|
1200
|
-
if (result.ok !== false && conversation) {
|
|
1201
|
-
const { conversationId, sessionKey, conversations } = conversation;
|
|
1202
|
-
const state = conversations?.get(conversationId);
|
|
1203
|
-
state?.watcher?.failNow(new Error("Temporary AI conversation was closed."));
|
|
1204
|
-
if (state) forgetObservation(state);
|
|
1205
|
-
conversations?.delete(conversationId);
|
|
1206
|
-
if (conversations?.size === 0) {
|
|
1207
|
-
codexAppServerConversations.delete(sessionKey);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
return result;
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
async function stopPersistentCodexConversation({
|
|
1214
|
-
conversationId,
|
|
1215
|
-
conversationState,
|
|
1216
|
-
provider,
|
|
1217
|
-
turnOutcome = ""
|
|
1218
|
-
} = {}) {
|
|
1219
|
-
// Set before stopping: the provider can report turn/completed while it stops.
|
|
1220
|
-
if (conversationState && codexAppServerConversationTurnIsActive(conversationState.status)) {
|
|
1221
|
-
conversationState.turnOutcome = turnOutcome;
|
|
1222
|
-
}
|
|
1223
|
-
try {
|
|
1224
|
-
await provider.readThreadStatus(conversationId);
|
|
1225
|
-
await provider.stopThreadForObservationLoss(conversationId, "");
|
|
1226
|
-
} catch (error) {
|
|
1227
|
-
if (!codexAppServerThreadIsMissing(error, conversationId) || error.message.toLowerCase().startsWith("thread not loaded:")) {
|
|
1228
|
-
if (conversationState) conversationState.turnOutcome = "";
|
|
1229
|
-
throw error;
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
if (conversationState) {
|
|
1233
|
-
conversationState.status = "interrupted";
|
|
1234
|
-
if (conversationState.goal?.status === "active") conversationState.goal = { ...conversationState.goal, status: "paused" };
|
|
1235
|
-
conversationState.watcher?.failNow(new Error("Temporary AI turn was stopped."));
|
|
1236
|
-
conversationState.watcher = null;
|
|
1237
|
-
holdWhileActive(conversationState);
|
|
1238
|
-
}
|
|
1239
|
-
return { ok: true, conversationId, status: "interrupted" };
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
async function waitForPreparedCodexAppServerConversationTurn(sessionId, input, {
|
|
1243
|
-
provider,
|
|
1244
|
-
onEvent = null
|
|
1245
|
-
} = {}) {
|
|
1246
|
-
const conversationId = normalizeCodexRunText(input.conversationId);
|
|
1247
|
-
const runId = normalizeCodexRunText(input.runId);
|
|
1248
|
-
const watcher = createCodexAppServerDetachedTurnWatcher(provider, conversationId, {
|
|
1249
|
-
onEvent,
|
|
1250
|
-
timeoutMs: Number(input.timeoutMs || 0) > 0
|
|
1251
|
-
? Number(input.timeoutMs)
|
|
1252
|
-
: input.persistent === true ? 0 : CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS
|
|
1253
|
-
});
|
|
1254
|
-
const conversationState = codexAppServerConversation(sessionId, conversationId);
|
|
1255
|
-
if (input.persistent && conversationState) conversationState.watcher = watcher;
|
|
1256
|
-
const waitForResult = watcher.wait();
|
|
1257
|
-
void waitForResult.catch(() => {});
|
|
1258
|
-
watcher.setTurnId(runId);
|
|
1259
|
-
try {
|
|
1260
|
-
const current = await provider.readThread(conversationId);
|
|
1261
|
-
const status = codexAppServerThreadStatus(current);
|
|
1262
|
-
if (codexAppServerTurnStatusIsProviderFailure(status)) {
|
|
1263
|
-
watcher.failNow(new Error(
|
|
1264
|
-
codexAppServerThreadError(current) || `Codex app-server turn ${status}.`
|
|
1265
|
-
));
|
|
1266
|
-
} else if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
|
|
1267
|
-
await watcher.completeNow(status);
|
|
1268
|
-
}
|
|
1269
|
-
const result = await waitForResult;
|
|
1270
|
-
return {
|
|
1271
|
-
conversationId,
|
|
1272
|
-
ok: true,
|
|
1273
|
-
runId,
|
|
1274
|
-
status: result.status || status || "completed",
|
|
1275
|
-
rawText: result.text
|
|
1276
|
-
};
|
|
1277
|
-
} catch (error) {
|
|
1278
|
-
watcher.failNow(error);
|
|
1279
|
-
throw error;
|
|
1280
|
-
} finally {
|
|
1281
|
-
if (conversationState?.watcher === watcher) conversationState.watcher = null;
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
return {
|
|
555
|
+
return Object.freeze({
|
|
1286
556
|
codexAppServerConversations,
|
|
1287
557
|
codexAppServerConversationTurnStarts,
|
|
1288
558
|
codexAppServerConversation,
|
|
1289
559
|
hasActiveConversation,
|
|
1290
|
-
withCodexAppServerConversationTurnStart,
|
|
1291
560
|
observeCodexConversation,
|
|
1292
561
|
readPersistentCodexConversation,
|
|
1293
|
-
codexAppServerEphemeralConversationSnapshot,
|
|
1294
562
|
prepareCodexAppServerConversationThread,
|
|
1295
563
|
codexAppServerConversationContext,
|
|
1296
564
|
codexAppServerEphemeralScopeContext,
|
|
@@ -1304,12 +572,12 @@ export function createCodexConversationCommands({
|
|
|
1304
572
|
closeCodexAppServerConversations,
|
|
1305
573
|
interruptDetachedCodexAppServerConversationTurn,
|
|
1306
574
|
deleteDetachedCodexAppServerConversationThread,
|
|
1307
|
-
steerCodexAppServerConversationTurn,
|
|
1308
575
|
acquireCodexAppServerDetachedThread,
|
|
1309
576
|
codexAppServerDetachedThreadId,
|
|
1310
577
|
dispatchCodexAppServerDetachedTurn,
|
|
1311
578
|
runDetachedCodexAppServerTurn,
|
|
1312
|
-
interruptCodexAppServerConversation,
|
|
1313
579
|
stopPersistentCodexConversation
|
|
1314
|
-
};
|
|
580
|
+
});
|
|
1315
581
|
}
|
|
582
|
+
|
|
583
|
+
/** @typedef {ReturnType<typeof createCodexConversationCommands>} CodexConversationCommands */
|