@jskit-ai/assistant-core 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/conversation/package.json +5 -5
- package/package.json +18 -7
- package/src/client/browser.js +21 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
- package/src/client/conversation/AssistantComposerActions.vue +1 -1
- package/src/client/conversation/AssistantConversationElement.vue +14 -52
- package/src/client/conversation/AssistantGoalControl.vue +18 -12
- package/src/client/conversation/AssistantPromptInput.vue +6 -91
- package/src/client/conversation/AssistantTranscript.css +505 -0
- package/src/client/conversation/AssistantTranscript.vue +24 -1197
- package/src/client/conversation/attachmentFiles.js +158 -0
- package/src/client/conversation/attachmentQueueItems.js +121 -0
- package/src/client/conversation/attachmentUploadRows.js +265 -0
- package/src/client/conversation/conversationDisplay.js +54 -0
- package/src/client/conversation/messageDelivery.js +13 -9
- package/src/client/conversation/retainedConversation.js +14 -3
- package/src/client/conversation/submitText.js +13 -2
- package/src/client/conversation/transcriptEntries.js +225 -0
- package/src/client/conversation/useAssistantAttachments.js +86 -388
- package/src/client/conversation/useAssistantQuestions.js +16 -3
- package/src/client/conversation/useAssistantSuggestions.js +16 -2
- package/src/client/conversation/useScrollToBottom.js +17 -19
- package/src/client/conversation/useTextareaAutoGrow.js +115 -0
- package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
- package/src/client/lib/assistantApi.js +192 -161
- package/src/client/types.js +110 -0
- package/src/server/conversation/attachments.js +21 -6
- package/src/server/conversation/boundedToolLoop.js +1 -0
- package/src/server/conversation/claudeClient.js +71 -20
- package/src/server/conversation/claudeCommandRun.js +191 -0
- package/src/server/conversation/claudeHistory.js +7 -0
- package/src/server/conversation/claudeNativeTurn.js +708 -0
- package/src/server/conversation/claudeProcess.js +186 -112
- package/src/server/conversation/claudeProcessStart.js +214 -0
- package/src/server/conversation/claudePromptDelivery.js +183 -0
- package/src/server/conversation/claudeSessionCleanup.js +248 -0
- package/src/server/conversation/claudeTools.js +15 -3
- package/src/server/conversation/claudeTurn.js +212 -1388
- package/src/server/conversation/codexAccountReader.js +168 -0
- package/src/server/conversation/codexAuthPreflight.js +5 -4
- package/src/server/conversation/codexClient.js +18 -6
- package/src/server/conversation/codexConfiguration.js +8 -844
- package/src/server/conversation/codexConversationStops.js +377 -0
- package/src/server/conversation/codexConversationStore.js +119 -85
- package/src/server/conversation/codexConversationTurns.js +378 -0
- package/src/server/conversation/codexConversations.js +134 -866
- package/src/server/conversation/codexDetachedConversation.js +203 -0
- package/src/server/conversation/codexDetachedTurn.js +145 -104
- package/src/server/conversation/codexEvents.js +5 -200
- package/src/server/conversation/codexGoalCommands.js +14 -4
- package/src/server/conversation/codexHelperExecution.js +57 -57
- package/src/server/conversation/codexHelperThreadLedger.js +30 -288
- package/src/server/conversation/codexHelperThreadRecord.js +304 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
- package/src/server/conversation/codexHelperThreads.js +94 -93
- package/src/server/conversation/codexHistoryAdapter.js +13 -8
- package/src/server/conversation/codexIsolation.js +672 -0
- package/src/server/conversation/codexLiveItems.js +379 -0
- package/src/server/conversation/codexMessageCommands.js +68 -286
- package/src/server/conversation/codexMessageDelivery.js +46 -37
- package/src/server/conversation/codexModelCatalog.js +86 -7
- package/src/server/conversation/codexNotificationObserver.js +81 -88
- package/src/server/conversation/codexNotificationQueue.js +21 -10
- package/src/server/conversation/codexObservationControl.js +105 -89
- package/src/server/conversation/codexOutput.js +84 -419
- package/src/server/conversation/codexProcess.js +29 -1053
- package/src/server/conversation/codexProcessLeader.js +1 -0
- package/src/server/conversation/codexPromptDispatch.js +241 -0
- package/src/server/conversation/codexProvider.js +147 -1807
- package/src/server/conversation/codexProviderAuth.js +348 -0
- package/src/server/conversation/codexProviderInvalidation.js +371 -0
- package/src/server/conversation/codexProviderInventories.js +437 -0
- package/src/server/conversation/codexProviderOwner.js +149 -957
- package/src/server/conversation/codexProviderRelease.js +465 -0
- package/src/server/conversation/codexProviderSelection.js +26 -15
- package/src/server/conversation/codexProviderThreads.js +698 -0
- package/src/server/conversation/codexProviderTurns.js +453 -0
- package/src/server/conversation/codexReasoning.js +32 -23
- package/src/server/conversation/codexRenewalCommands.js +18 -6
- package/src/server/conversation/codexRenewalThreads.js +7 -22
- package/src/server/conversation/codexResultSettlement.js +70 -88
- package/src/server/conversation/codexRunJournal.js +91 -97
- package/src/server/conversation/codexRuntime.js +426 -0
- package/src/server/conversation/codexRuntimeInventory.js +113 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
- package/src/server/conversation/codexRuntimeStart.js +431 -0
- package/src/server/conversation/codexRuntimeStop.js +324 -0
- package/src/server/conversation/codexSessionCleanup.js +25 -10
- package/src/server/conversation/codexSessionReconciliation.js +21 -7
- package/src/server/conversation/codexThreadReadiness.js +49 -52
- package/src/server/conversation/codexThreadSnapshot.js +210 -0
- package/src/server/conversation/codexThreadStatus.js +48 -62
- package/src/server/conversation/codexTurn.js +171 -55
- package/src/server/conversation/codexTurnActivity.js +210 -0
- package/src/server/conversation/codexTurnControl.js +44 -48
- package/src/server/conversation/codexTurnEndings.js +610 -0
- package/src/server/conversation/codexTurnRecovery.js +97 -848
- package/src/server/conversation/codexTurnState.js +138 -111
- package/src/server/conversation/completedResponses.js +227 -0
- package/src/server/conversation/configuration.js +2 -2
- package/src/server/conversation/continuity.js +43 -6
- package/src/server/conversation/conversationDelivery.js +394 -0
- package/src/server/conversation/conversationEntries.js +365 -0
- package/src/server/conversation/conversationEvents.js +192 -0
- package/src/server/conversation/conversationGoals.js +187 -0
- package/src/server/conversation/conversationHandles.js +237 -0
- package/src/server/conversation/conversationReads.js +206 -0
- package/src/server/conversation/conversationRefusals.js +58 -0
- package/src/server/conversation/conversationReplacement.js +254 -0
- package/src/server/conversation/conversationSending.js +273 -0
- package/src/server/conversation/conversationStopping.js +121 -0
- package/src/server/conversation/conversationTurnDelivery.js +413 -0
- package/src/server/conversation/conversationTurns.js +511 -0
- package/src/server/conversation/deliveryTiming.js +25 -3
- package/src/server/conversation/fileStorage.js +11 -6
- package/src/server/conversation/hookBridge.js +14 -3
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/localExecution.js +82 -625
- package/src/server/conversation/nativeCommandCallbacks.js +10 -0
- package/src/server/conversation/nativeConversationLifecycle.js +379 -0
- package/src/server/conversation/nativeHistoryExport.js +27 -18
- package/src/server/conversation/normalize.js +9 -0
- package/src/server/conversation/openCodeClient.js +295 -366
- package/src/server/conversation/openCodeNativeHistory.js +141 -0
- package/src/server/conversation/openCodeNativeSession.js +79 -0
- package/src/server/conversation/openCodeProcess.js +109 -53
- package/src/server/conversation/openCodeProcessOwner.js +565 -0
- package/src/server/conversation/openCodeRuntime.js +111 -1814
- package/src/server/conversation/openCodeSessionTurns.js +547 -0
- package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
- package/src/server/conversation/openCodeTools.js +28 -14
- package/src/server/conversation/openCodeTurn.js +80 -74
- package/src/server/conversation/openCodeTurnMonitor.js +388 -0
- package/src/server/conversation/processIdentity.js +582 -0
- package/src/server/conversation/provider.js +21 -6
- package/src/server/conversation/providers/api.js +268 -107
- package/src/server/conversation/providers/claude.js +29 -6
- package/src/server/conversation/providers/claudeDriver.js +325 -250
- package/src/server/conversation/providers/codex.js +69 -33
- package/src/server/conversation/providers/codexDriver.js +503 -1081
- package/src/server/conversation/providers/codexDriverConnection.js +331 -0
- package/src/server/conversation/providers/codexDriverTurns.js +464 -0
- package/src/server/conversation/providers/opencode.js +30 -4
- package/src/server/conversation/providers/opencodeDriver.js +321 -659
- package/src/server/conversation/providers/opencodeStandalone.js +475 -0
- package/src/server/conversation/providers/scoped.js +64 -19
- package/src/server/conversation/runtime.js +171 -2863
- package/src/server/conversation/storage.js +91 -50
- package/src/server/conversation/streams.js +15 -2
- package/src/server/conversation/structuredOutput.js +7 -11
- package/src/server/conversation/systemPrompt.js +13 -1
- package/src/server/conversation/tools.js +47 -15
- package/src/server/conversation/transcript.js +49 -20
- package/src/server/hosts/claudeTurn.js +2 -4
- package/src/server/hosts/codexConfiguration.js +6 -8
- package/src/server/hosts/codexEvents.js +4 -2
- package/src/server/hosts/codexProcess.js +1 -1
- package/src/server/hosts/codexProvider.js +11 -9
- package/src/server/hosts/codexTurn.js +3 -3
- package/src/server/hosts/openCodeClient.js +4 -2
- package/src/server/index.js +0 -2
- package/src/server/lib/aiConnectionClient.js +35 -25
- package/src/server/lib/ndjson.js +1 -0
- package/src/server/lib/providers/anthropicClient.js +15 -6
- package/src/server/lib/providers/common.js +10 -6
- package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
- package/src/server/lib/serviceToolCatalog.js +29 -283
- package/src/server/lib/serviceToolEntries.js +270 -0
- package/src/server/types.js +78 -0
- package/src/shared/assistantResource.js +0 -23
- package/src/shared/conversation/answerChoiceSugar.js +2 -14
- package/src/shared/conversation/messageInputSugar.js +18 -0
- package/src/shared/conversation/numberedQuestionSugar.js +6 -16
- package/src/shared/conversation/pagination.js +1 -1
- package/src/shared/conversation/questions.js +1 -0
- package/src/shared/conversation/streaming.js +1 -0
- package/src/shared/index.js +0 -1
- package/src/testing/conversationStorageContract.js +20 -0
- package/test/aiConnectionClient.test.js +42 -1
- package/test/apiConversationDriver.test.js +217 -38
- package/test/assistantBoundedToolLoop.test.js +1 -1
- package/test/claudeConversationSession.test.js +15 -11
- package/test/claudeInstructionResume.test.js +1 -1
- package/test/claudeProcess.test.js +9 -9
- package/test/claudeTurn.test.js +21 -8
- package/test/codexAccountChange.test.js +5 -3
- package/test/codexConversationSession.test.js +22 -21
- package/test/codexEvents.test.js +3 -1
- package/test/codexHelperDeletion.test.js +3 -7
- package/test/codexIsolation.test.js +6 -6
- package/test/codexModelCatalog.test.js +6 -7
- package/test/codexProcess.test.js +3 -3
- package/test/codexThreadSelection.test.js +4 -1
- package/test/codexTurn.test.js +29 -20
- package/test/conversationApiFacilities.test.js +11 -9
- package/test/conversationAttachments.test.js +3 -3
- package/test/conversationConfiguration.test.js +4 -4
- package/test/conversationNativeStorage.test.js +11 -10
- package/test/conversationReplacement.test.js +13 -13
- package/test/conversationRuntime.test.js +9 -5
- package/test/conversationSession.test.js +78 -22
- package/test/conversationTools.test.js +47 -26
- package/test/fileConversationStorage.test.js +10 -3
- package/test/fixtures/codexHelperThreadLedger.js +3 -3
- package/test/frameworkActions.test.js +33 -18
- package/test/localConversationExecution.test.js +4 -4
- package/test/localExecution.test.js +1 -1
- package/test/openCodeConnectionChange.test.js +3 -2
- package/test/openCodeRuntime.test.js +8 -7
- package/test/openCodeTools.test.js +1 -1
- package/test/opencodeConversationSession.test.js +4 -3
- package/test/serviceToolCatalog.test.js +3 -8
- package/src/server/lib/assistantCompletion.js +0 -190
- package/src/server/lib/assistantToolLoop.js +0 -259
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure } from "./codexTurnState.js";
|
|
2
|
+
import { CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS, createCodexAppServerDetachedTurnWatcher } from "./codexDetachedTurn.js";
|
|
3
|
+
import { assertCodexAppServerHelperAccountIdentity } from "./codexHelperExecution.js";
|
|
4
|
+
import { prepareCodexAppServerConversationThread } from "./codexConversationTurns.js";
|
|
5
|
+
import { codexAppServerDeliveredTurn, sendCodexAppServerPrompt } from "./codexProviderTurns.js";
|
|
6
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
7
|
+
|
|
8
|
+
function ignoreDetachedConversationEvent() {}
|
|
9
|
+
|
|
10
|
+
export async function acquireCodexAppServerDetachedThread(provider, requestedThreadId, threadSettings, input = {}) {
|
|
11
|
+
let thread = null;
|
|
12
|
+
if (requestedThreadId) {
|
|
13
|
+
thread = await provider.resumeThread(requestedThreadId, threadSettings);
|
|
14
|
+
}
|
|
15
|
+
if (!thread) {
|
|
16
|
+
thread = await provider.startThread({
|
|
17
|
+
...threadSettings,
|
|
18
|
+
...(input.ephemeral === true ? { ephemeral: true } : {})
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return thread;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function codexAppServerDetachedThreadId(thread, requestedThreadId) {
|
|
25
|
+
const threadId = normalizeText(thread.id || thread.response?.thread?.id || requestedThreadId);
|
|
26
|
+
if (!threadId) {
|
|
27
|
+
throw new Error("Codex app-server did not return a detached chat thread id.");
|
|
28
|
+
}
|
|
29
|
+
return threadId;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function dispatchCodexAppServerDetachedTurn({ provider, prompt, threadId }, authorized) {
|
|
33
|
+
const delivery = await sendCodexAppServerPrompt({ provider, prompt, threadId }, authorized);
|
|
34
|
+
return { delivery, ...codexAppServerDeliveredTurn(delivery) };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function runDetachedCodexAppServerTurn({
|
|
38
|
+
provider,
|
|
39
|
+
threadId,
|
|
40
|
+
timeoutMs,
|
|
41
|
+
onEvent: emitDetachedEvent,
|
|
42
|
+
onFailure
|
|
43
|
+
} = {}, operation) {
|
|
44
|
+
const watcher = createCodexAppServerDetachedTurnWatcher(provider, threadId, {
|
|
45
|
+
onEvent: (classification) => {
|
|
46
|
+
emitDetachedEvent({
|
|
47
|
+
classification,
|
|
48
|
+
threadId,
|
|
49
|
+
turnId: classification.turnId,
|
|
50
|
+
type: "notification"
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
timeoutMs
|
|
54
|
+
});
|
|
55
|
+
const waitForResult = watcher.wait();
|
|
56
|
+
void waitForResult.catch(() => null);
|
|
57
|
+
const throwWatcherFailure = async (fallbackError, status = "", {
|
|
58
|
+
waitForDetail = false
|
|
59
|
+
} = {}) => {
|
|
60
|
+
if (waitForDetail) {
|
|
61
|
+
watcher.failAfterDetailGrace(fallbackError);
|
|
62
|
+
} else {
|
|
63
|
+
watcher.failNow(fallbackError);
|
|
64
|
+
}
|
|
65
|
+
const error = await waitForResult.then(
|
|
66
|
+
() => fallbackError,
|
|
67
|
+
(watcherError) => watcherError
|
|
68
|
+
);
|
|
69
|
+
throw await onFailure(error, status);
|
|
70
|
+
};
|
|
71
|
+
const { delivery, status, turnId } = await operation(throwWatcherFailure);
|
|
72
|
+
watcher.setTurnId(turnId);
|
|
73
|
+
emitDetachedEvent({
|
|
74
|
+
status,
|
|
75
|
+
threadId,
|
|
76
|
+
turnId,
|
|
77
|
+
type: "turn"
|
|
78
|
+
});
|
|
79
|
+
if (codexAppServerTurnStatusIsProviderFailure(status)) {
|
|
80
|
+
const error = new Error(codexAppServerDeliveredTurn(delivery).error || `Codex app-server turn ${status}.`);
|
|
81
|
+
await throwWatcherFailure(error, status, {
|
|
82
|
+
waitForDetail: true
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
|
|
86
|
+
await watcher.completeNow(status);
|
|
87
|
+
}
|
|
88
|
+
let result = null;
|
|
89
|
+
try {
|
|
90
|
+
result = await waitForResult;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw await onFailure(error, status);
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A detached conversation: one prompt on a resumed or fresh native thread, watched until its turn completes.
|
|
99
|
+
* A Helper's turn runs on its retained Helper thread instead.
|
|
100
|
+
* @param {object} dependencies
|
|
101
|
+
* @param {object} dependencies.conversationPreparation The application's conversation preparation.
|
|
102
|
+
* @param {import("./codexConversations.js").CodexConversationResult} dependencies.codexAppServerConversationResult
|
|
103
|
+
* @param {import("./codexConversations.js").CodexConversationContextReader} dependencies.codexAppServerConversationContext
|
|
104
|
+
* @param {import("./codexHelperThreads.js").CodexHelperThreadLifecycle} dependencies.helperLifecycle
|
|
105
|
+
* @param {string} dependencies.helperOwnershipError
|
|
106
|
+
*/
|
|
107
|
+
export function createCodexDetachedConversation({
|
|
108
|
+
conversationPreparation,
|
|
109
|
+
codexAppServerConversationResult,
|
|
110
|
+
codexAppServerConversationContext,
|
|
111
|
+
helperLifecycle,
|
|
112
|
+
helperOwnershipError
|
|
113
|
+
}) {
|
|
114
|
+
async function runDetachedCodexAppServerConversation(sessionId, input = {}, options = {}) {
|
|
115
|
+
const preparation = conversationPreparation.detached(sessionId, input);
|
|
116
|
+
const { admission } = preparation;
|
|
117
|
+
if (admission.ok === false) return admission;
|
|
118
|
+
try {
|
|
119
|
+
return await codexAppServerConversationResult(async () => {
|
|
120
|
+
const unavailable = preparation.result;
|
|
121
|
+
if (unavailable) return unavailable;
|
|
122
|
+
const prompt = preparation.prompt;
|
|
123
|
+
const context = await codexAppServerConversationContext(sessionId, input, options);
|
|
124
|
+
if (context.ok === false) return context;
|
|
125
|
+
const { provider, runtime, workdir } = context;
|
|
126
|
+
const executionProfile = input.executionProfile && typeof input.executionProfile === "object" &&
|
|
127
|
+
!Array.isArray(input.executionProfile) ? input.executionProfile : null;
|
|
128
|
+
const helperTurn = Boolean(executionProfile);
|
|
129
|
+
const emitDetachedEvent = options.onEvent || ignoreDetachedConversationEvent;
|
|
130
|
+
const onRetired = ({ threadId }) => emitDetachedEvent({ threadId, type: "thread-retired" });
|
|
131
|
+
if (helperTurn) {
|
|
132
|
+
await assertCodexAppServerHelperAccountIdentity(provider, input.expectedAccountIdentitySignature, helperOwnershipError);
|
|
133
|
+
}
|
|
134
|
+
const execution = preparation.execution(context);
|
|
135
|
+
const threadSettings = helperTurn ? null : await prepareCodexAppServerConversationThread(
|
|
136
|
+
provider, execution.threadPreparation, conversationPreparation.isolation
|
|
137
|
+
);
|
|
138
|
+
const requestedThreadId = normalizeText(input.threadId || input.codexSessionId);
|
|
139
|
+
let helper = null;
|
|
140
|
+
let thread;
|
|
141
|
+
if (helperTurn) {
|
|
142
|
+
const scope = { executionProfile, onRetired, projectRuntimeRoot: normalizeText(runtime?.stateRoot),
|
|
143
|
+
projectContextRoot: normalizeText(runtime?.projectContextRoot), provider, sessionId, requestedThreadId, workdir,
|
|
144
|
+
...(requestedThreadId ? { record: helperLifecycle.threadForOperation({ executionProfile,
|
|
145
|
+
projectRuntimeRoot: normalizeText(runtime?.stateRoot), provider, sessionId,
|
|
146
|
+
threadId: requestedThreadId, workdir }) } : {}) };
|
|
147
|
+
helper = await helperLifecycle.prepareDetached(scope, execution.helperPreparation);
|
|
148
|
+
thread = helper.thread;
|
|
149
|
+
} else {
|
|
150
|
+
thread = await acquireCodexAppServerDetachedThread(provider, requestedThreadId, threadSettings, input);
|
|
151
|
+
}
|
|
152
|
+
const threadId = codexAppServerDetachedThreadId(thread, requestedThreadId);
|
|
153
|
+
emitDetachedEvent({ threadId, type: "thread" });
|
|
154
|
+
const requestedTimeoutMs = Number(input.timeoutMs || 0);
|
|
155
|
+
const profileTimeoutMs = Number(executionProfile?.limits?.timeoutMs || 0);
|
|
156
|
+
let turnId = "";
|
|
157
|
+
const result = await runDetachedCodexAppServerTurn({
|
|
158
|
+
provider, threadId, onEvent: emitDetachedEvent,
|
|
159
|
+
timeoutMs: helperTurn
|
|
160
|
+
? Math.min(requestedTimeoutMs > 0 ? requestedTimeoutMs : profileTimeoutMs, profileTimeoutMs)
|
|
161
|
+
: requestedTimeoutMs > 0 ? requestedTimeoutMs : CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS,
|
|
162
|
+
async onFailure(error, status) {
|
|
163
|
+
await helper?.discard();
|
|
164
|
+
return execution.failure(error, status);
|
|
165
|
+
}
|
|
166
|
+
}, async failDispatch => {
|
|
167
|
+
let dispatched;
|
|
168
|
+
if (helperTurn) {
|
|
169
|
+
dispatched = await helper.dispatch(threadId, failDispatch);
|
|
170
|
+
} else {
|
|
171
|
+
try {
|
|
172
|
+
dispatched = await dispatchCodexAppServerDetachedTurn({ provider, prompt, threadId }, execution.authorized);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
await failDispatch(error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
turnId = dispatched.turnId;
|
|
178
|
+
return dispatched;
|
|
179
|
+
});
|
|
180
|
+
if (helperTurn) {
|
|
181
|
+
try {
|
|
182
|
+
await assertCodexAppServerHelperAccountIdentity(provider, input.expectedAccountIdentitySignature, helperOwnershipError);
|
|
183
|
+
execution.validateOutput(result.text);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
await helper.discard();
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
await helper.complete();
|
|
189
|
+
}
|
|
190
|
+
emitDetachedEvent({ status: result.status || "completed", text: result.text,
|
|
191
|
+
threadId, turnId: result.turnId || turnId, type: "completed" });
|
|
192
|
+
return { ok: true, text: result.text, threadId, turnId: result.turnId || turnId,
|
|
193
|
+
...(helperTurn ? { inputCharacters: prompt.length, outputCharacters: result.text.length, usage: result.usage || null } : {}) };
|
|
194
|
+
});
|
|
195
|
+
} finally {
|
|
196
|
+
admission.release();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return Object.freeze({ runDetachedCodexAppServerConversation });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** @typedef {ReturnType<typeof createCodexDetachedConversation>} CodexDetachedConversation */
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
import { normalizeNullableText } from "./normalize.js";
|
|
3
|
-
import { classifyCodexAppServerEvent, codexAppServerNotificationParams, codexAppServerNotificationThreadId, codexAppServerNotificationTurnId, codexAppServerNotificationTurnStatus, codexAppServerNotificationError, codexAppServerStatusFromValue
|
|
3
|
+
import { classifyCodexAppServerEvent, codexAppServerNotificationParams, codexAppServerNotificationThreadId, codexAppServerNotificationTurnId, codexAppServerNotificationTurnStatus, codexAppServerNotificationError, codexAppServerStatusFromValue } from "./codexEvents.js";
|
|
4
|
+
import {
|
|
5
|
+
codexAppServerProviderThreadAssistantSegments,
|
|
6
|
+
codexAppServerProviderTurnId,
|
|
7
|
+
codexAppServerProviderTurnStatus,
|
|
8
|
+
codexAppServerProviderTurnForOperation,
|
|
9
|
+
codexAppServerProviderTurnText,
|
|
10
|
+
codexAppServerProviderTurnError
|
|
11
|
+
} from "./codexThreadSnapshot.js";
|
|
4
12
|
import { codexAppServerTurnStatusIsProviderFailure, codexAppServerTurnStatusIsSuccessfulComplete } from "./codexTurnState.js";
|
|
5
13
|
|
|
6
14
|
const CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS = 180_000;
|
|
@@ -30,10 +38,33 @@ function codexAppServerTurnTokenUsage(notification = {}) {
|
|
|
30
38
|
return Object.keys(snapshot).length ? Object.freeze(snapshot) : null;
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The provider methods a detached turn watcher reads.
|
|
43
|
+
* @typedef {Pick<import("./codexProvider.js").CodexAppServerAgentProvider,
|
|
44
|
+
* "subscribe" | "readThread" | "isAvailable" | "currentConnectionGeneration">} CodexDetachedTurnProvider
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The observer told about each classified notification of the watched turn.
|
|
49
|
+
* @typedef {(classification: object, notification: object) => void} CodexDetachedTurnEventObserver
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
function ignoreCodexDetachedTurnEvent() {}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Watches one native turn of a thread until it completes, fails or times out.
|
|
56
|
+
* @param {CodexDetachedTurnProvider} provider
|
|
57
|
+
* @param {string} threadId
|
|
58
|
+
* @param {object} [options]
|
|
59
|
+
* @param {boolean} [options.includeThreadHistory]
|
|
60
|
+
* @param {boolean} [options.allowEmptyResponse]
|
|
61
|
+
* @param {CodexDetachedTurnEventObserver} [options.onEvent]
|
|
62
|
+
* @param {number} [options.timeoutMs]
|
|
63
|
+
*/
|
|
64
|
+
function createCodexAppServerDetachedTurnWatcher(provider, threadId = "", {
|
|
34
65
|
includeThreadHistory = true,
|
|
35
66
|
allowEmptyResponse = false,
|
|
36
|
-
onEvent =
|
|
67
|
+
onEvent = ignoreCodexDetachedTurnEvent,
|
|
37
68
|
timeoutMs = CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS
|
|
38
69
|
} = {}) {
|
|
39
70
|
const normalizedThreadId = normalizeNullableText(threadId);
|
|
@@ -79,8 +110,8 @@ function createCodexAppServerDetachedTurnWatcher(provider = null, threadId = "",
|
|
|
79
110
|
rejectWaiter?.(error);
|
|
80
111
|
}
|
|
81
112
|
|
|
82
|
-
function emitWatcherEvent(classification
|
|
83
|
-
if (
|
|
113
|
+
function emitWatcherEvent(classification, notification) {
|
|
114
|
+
if (!classification.kind) {
|
|
84
115
|
return;
|
|
85
116
|
}
|
|
86
117
|
onEvent({
|
|
@@ -94,8 +125,7 @@ function createCodexAppServerDetachedTurnWatcher(provider = null, threadId = "",
|
|
|
94
125
|
if (
|
|
95
126
|
!includeThreadHistory ||
|
|
96
127
|
!normalizedThreadId ||
|
|
97
|
-
!targetTurnId
|
|
98
|
-
typeof provider?.readThread !== "function"
|
|
128
|
+
!targetTurnId
|
|
99
129
|
) {
|
|
100
130
|
return {
|
|
101
131
|
status: "",
|
|
@@ -165,104 +195,101 @@ function createCodexAppServerDetachedTurnWatcher(provider = null, threadId = "",
|
|
|
165
195
|
return !targetTurnId || !notificationTurnId || notificationTurnId === targetTurnId;
|
|
166
196
|
}
|
|
167
197
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
198
|
+
function setTurnId(turnId = "") {
|
|
199
|
+
targetTurnId = normalizeNullableText(turnId);
|
|
200
|
+
if (pendingFailure) {
|
|
201
|
+
failAfterDetailGrace(pendingFailure);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (pendingCompletionStatus) {
|
|
205
|
+
const status = pendingCompletionStatus;
|
|
206
|
+
pendingCompletionStatus = "";
|
|
207
|
+
void finishFromCompletion(status);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function wait() {
|
|
212
|
+
if (settled) {
|
|
213
|
+
return Promise.reject(new Error("Codex app-server detached turn watcher was already settled."));
|
|
214
|
+
}
|
|
215
|
+
return new Promise((resolve, reject) => {
|
|
216
|
+
resolveWaiter = resolve;
|
|
217
|
+
rejectWaiter = reject;
|
|
218
|
+
if (timeoutMs > 0) {
|
|
219
|
+
timeout = setTimeout(() => {
|
|
220
|
+
fail(new Error("Timed out waiting for Codex app-server response."));
|
|
221
|
+
}, timeoutMs);
|
|
222
|
+
} else {
|
|
223
|
+
// Interactive repair turns may take longer than bounded helper jobs.
|
|
224
|
+
// Retain their watcher until completion, interruption, or connection loss.
|
|
225
|
+
const generation = provider.currentConnectionGeneration();
|
|
226
|
+
connectionCheck = setInterval(() => {
|
|
227
|
+
if (provider.isAvailable() === false ||
|
|
228
|
+
provider.currentConnectionGeneration() !== generation) {
|
|
229
|
+
fail(new Error("Connection to Codex was lost while the temporary AI was working."));
|
|
230
|
+
}
|
|
231
|
+
}, 1000);
|
|
232
|
+
connectionCheck.unref();
|
|
193
233
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
234
|
+
unsubscribe = provider.subscribe((notification = {}) => {
|
|
235
|
+
if (!notificationMatches(notification)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
usage = codexAppServerTurnTokenUsage(notification) || usage;
|
|
239
|
+
const classification = classifyCodexAppServerEvent(notification);
|
|
240
|
+
emitWatcherEvent(classification, notification);
|
|
241
|
+
if (
|
|
242
|
+
classification.kind === "provider_error" &&
|
|
243
|
+
classification.text &&
|
|
244
|
+
codexAppServerNotificationParams(notification).willRetry !== true
|
|
245
|
+
) {
|
|
246
|
+
const error = new Error(classification.text);
|
|
247
|
+
if (!targetTurnId) {
|
|
248
|
+
pendingFailure = error;
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
fail(error);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (classification.kind === "final_assistant_result" && classification.text) {
|
|
255
|
+
finalText = classification.text;
|
|
256
|
+
if (pendingCompletionStatus && targetTurnId) {
|
|
257
|
+
const status = pendingCompletionStatus;
|
|
258
|
+
pendingCompletionStatus = "";
|
|
259
|
+
clearTimeout(failureDetailTimeout);
|
|
260
|
+
failureDetailTimeout = null;
|
|
261
|
+
void finishFromCompletion(status);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const method = normalizeNullableText(notification.method);
|
|
265
|
+
if (method !== "turn/completed" && method !== "thread/status/changed") {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const status = codexAppServerNotificationTurnStatus(notification) || "completed";
|
|
269
|
+
if (codexAppServerTurnStatusIsProviderFailure(status)) {
|
|
270
|
+
const error = new Error(codexAppServerNotificationError(notification) || `Codex app-server turn ${status}.`);
|
|
271
|
+
if (!targetTurnId) {
|
|
272
|
+
pendingFailure = error;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
failAfterDetailGrace(error);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
|
|
279
|
+
void finishFromCompletion(status);
|
|
212
280
|
}
|
|
213
|
-
unsubscribe = typeof provider?.subscribe === "function"
|
|
214
|
-
? provider.subscribe((notification = {}) => {
|
|
215
|
-
if (!notificationMatches(notification)) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
usage = codexAppServerTurnTokenUsage(notification) || usage;
|
|
219
|
-
const classification = classifyCodexAppServerEvent(notification);
|
|
220
|
-
emitWatcherEvent(classification, notification);
|
|
221
|
-
if (
|
|
222
|
-
classification.kind === "provider_error" &&
|
|
223
|
-
classification.text &&
|
|
224
|
-
codexAppServerNotificationParams(notification).willRetry !== true
|
|
225
|
-
) {
|
|
226
|
-
const error = new Error(classification.text);
|
|
227
|
-
if (!targetTurnId) {
|
|
228
|
-
pendingFailure = error;
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
fail(error);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
if (classification.kind === "final_assistant_result" && classification.text) {
|
|
235
|
-
finalText = classification.text;
|
|
236
|
-
if (pendingCompletionStatus && targetTurnId) {
|
|
237
|
-
const status = pendingCompletionStatus;
|
|
238
|
-
pendingCompletionStatus = "";
|
|
239
|
-
clearTimeout(failureDetailTimeout);
|
|
240
|
-
failureDetailTimeout = null;
|
|
241
|
-
void finishFromCompletion(status);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
const method = normalizeNullableText(notification.method);
|
|
245
|
-
if (method !== "turn/completed" && method !== "thread/status/changed") {
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
const status = codexAppServerNotificationTurnStatus(notification) || "completed";
|
|
249
|
-
if (codexAppServerTurnStatusIsProviderFailure(status)) {
|
|
250
|
-
const error = new Error(codexAppServerNotificationError(notification) || `Codex app-server turn ${status}.`);
|
|
251
|
-
if (!targetTurnId) {
|
|
252
|
-
pendingFailure = error;
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
failAfterDetailGrace(error);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
|
|
259
|
-
void finishFromCompletion(status);
|
|
260
|
-
}
|
|
261
|
-
})
|
|
262
|
-
: null;
|
|
263
281
|
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return Object.freeze({
|
|
286
|
+
completeNow: finishFromCompletion,
|
|
287
|
+
failNow: fail,
|
|
288
|
+
failAfterDetailGrace,
|
|
289
|
+
// readable-check: allow setter-seam - not a slot: setTurnId records the turn identity that startup acknowledges after wait() has subscribed
|
|
290
|
+
setTurnId,
|
|
291
|
+
wait
|
|
292
|
+
});
|
|
266
293
|
}
|
|
267
294
|
|
|
268
295
|
function codexAppServerTurnError(reason, details = {}, providerMessage = "") {
|
|
@@ -276,6 +303,15 @@ function codexAppServerTurnError(reason, details = {}, providerMessage = "") {
|
|
|
276
303
|
return error;
|
|
277
304
|
}
|
|
278
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Waits for an already started native turn and returns its final answer.
|
|
308
|
+
* @param {CodexDetachedTurnProvider} provider
|
|
309
|
+
* @param {string} threadId
|
|
310
|
+
* @param {object | null} turn
|
|
311
|
+
* @param {object} [options]
|
|
312
|
+
* @param {number} [options.timeoutMs]
|
|
313
|
+
* @param {typeof codexAppServerTurnError} [options.createError]
|
|
314
|
+
*/
|
|
279
315
|
async function waitForCodexAppServerTurn(provider, threadId = "", turn = null, {
|
|
280
316
|
timeoutMs = CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS,
|
|
281
317
|
createError = codexAppServerTurnError
|
|
@@ -313,7 +349,7 @@ async function waitForCodexAppServerTurn(provider, threadId = "", turn = null, {
|
|
|
313
349
|
void pending.catch(() => null);
|
|
314
350
|
if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
|
|
315
351
|
await watcher.completeNow(status);
|
|
316
|
-
} else
|
|
352
|
+
} else {
|
|
317
353
|
const latestThread = await provider.readThread(normalizedThreadId);
|
|
318
354
|
const latestTurn = codexAppServerProviderTurnForOperation(latestThread, { turnId });
|
|
319
355
|
const latestStatus = codexAppServerProviderTurnStatus(latestTurn || {});
|
|
@@ -330,4 +366,9 @@ async function waitForCodexAppServerTurn(provider, threadId = "", turn = null, {
|
|
|
330
366
|
return pending;
|
|
331
367
|
}
|
|
332
368
|
|
|
333
|
-
export {
|
|
369
|
+
export {
|
|
370
|
+
CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS,
|
|
371
|
+
createCodexAppServerDetachedTurnWatcher,
|
|
372
|
+
ignoreCodexDetachedTurnEvent,
|
|
373
|
+
waitForCodexAppServerTurn
|
|
374
|
+
};
|