@jskit-ai/assistant-core 0.2.7 → 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 +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,14 +1,20 @@
|
|
|
1
|
+
import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
1
2
|
import { setTimeout as delay } from "node:timers/promises";
|
|
2
3
|
import { openCodeAssistantMessageText as assistantMessageText } from "./openCodeClient.js";
|
|
3
|
-
import {
|
|
4
|
+
import { normalizeObject } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
5
|
import { normalizeNullableText } from "./normalize.js";
|
|
5
6
|
|
|
6
|
-
const text = normalizeNullableText;
|
|
7
7
|
export const OPENCODE_INTERRUPT_TIMEOUT_MS = 5_000;
|
|
8
8
|
const OPENCODE_EVENT_READY_TIMEOUT_MS = 120_000;
|
|
9
9
|
|
|
10
|
+
/** A final-answer recovery whose caller keeps no projection of the reasoning-only completion. */
|
|
11
|
+
export function recoverWithoutProjection() {}
|
|
12
|
+
|
|
13
|
+
/** A detached turn whose caller keeps no state after observation closes. */
|
|
14
|
+
function ignoreObservationClose() {}
|
|
15
|
+
|
|
10
16
|
export function openCodeDetachedPrompt(input = {}) {
|
|
11
|
-
const prompt =
|
|
17
|
+
const prompt = normalizeNullableText(input.prompt || input.message);
|
|
12
18
|
if (!input.outputSchema) {
|
|
13
19
|
return prompt;
|
|
14
20
|
}
|
|
@@ -26,45 +32,44 @@ export function openCodeStructuredOutput(value = "") {
|
|
|
26
32
|
return match ? match[1].trim() : original;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
const record = value => isRecord(value) ? value : {};
|
|
30
35
|
|
|
31
36
|
function openCodeEventSummary(event = {}) {
|
|
32
|
-
const payload =
|
|
33
|
-
const properties =
|
|
34
|
-
const data = Object.keys(properties).length ? properties :
|
|
35
|
-
const info =
|
|
36
|
-
const part =
|
|
37
|
-
const model =
|
|
37
|
+
const payload = normalizeObject(event.data);
|
|
38
|
+
const properties = normalizeObject(payload.properties);
|
|
39
|
+
const data = Object.keys(properties).length ? properties : normalizeObject(payload.data);
|
|
40
|
+
const info = normalizeObject(data.info);
|
|
41
|
+
const part = normalizeObject(data.part);
|
|
42
|
+
const model = normalizeObject(info.model || data.model);
|
|
38
43
|
return {
|
|
39
|
-
agent:
|
|
44
|
+
agent: normalizeNullableText(info.agent || data.agent),
|
|
40
45
|
at: Number(data.timestamp || part.time?.start || part.time?.created) || Date.now(),
|
|
41
|
-
eventId:
|
|
42
|
-
messageId:
|
|
43
|
-
modelId:
|
|
44
|
-
modelProviderId:
|
|
45
|
-
partId:
|
|
46
|
+
eventId: normalizeNullableText(event.id || payload.id || part.id),
|
|
47
|
+
messageId: normalizeNullableText(part.messageID || data.messageID || info.id),
|
|
48
|
+
modelId: normalizeNullableText(model.id || model.modelID || info.modelID),
|
|
49
|
+
modelProviderId: normalizeNullableText(model.providerID || info.providerID),
|
|
50
|
+
partId: normalizeNullableText(part.id || data.partID),
|
|
46
51
|
...(part.type === "text" && typeof part.text === "string" ? { textSnapshot: part.text } : {}),
|
|
47
52
|
...(payload.type === "message.part.delta" && data.field === "text" && typeof data.delta === "string"
|
|
48
53
|
? { textDelta: data.delta } : {}),
|
|
49
|
-
partType:
|
|
50
|
-
|
|
54
|
+
partType: normalizeNullableText(part.type) || (
|
|
55
|
+
normalizeNullableText(payload.type) === "session.next.reasoning.ended"
|
|
51
56
|
? "reasoning"
|
|
52
|
-
:
|
|
57
|
+
: normalizeNullableText(payload.type) === "session.next.text.ended"
|
|
53
58
|
? "text"
|
|
54
59
|
: ""
|
|
55
60
|
),
|
|
56
|
-
text: ["reasoning", "text"].includes(
|
|
57
|
-
?
|
|
58
|
-
: ["session.next.text.ended", "session.next.reasoning.ended"].includes(
|
|
59
|
-
?
|
|
61
|
+
text: ["reasoning", "text"].includes(normalizeNullableText(part.type))
|
|
62
|
+
? normalizeNullableText(part.text || data.delta).slice(0, 32_000)
|
|
63
|
+
: ["session.next.text.ended", "session.next.reasoning.ended"].includes(normalizeNullableText(payload.type))
|
|
64
|
+
? normalizeNullableText(data.text).slice(0, 32_000)
|
|
60
65
|
: "",
|
|
61
|
-
tool:
|
|
62
|
-
type:
|
|
66
|
+
tool: normalizeNullableText(part.tool || data.tool),
|
|
67
|
+
type: normalizeNullableText(payload.type || event.event)
|
|
63
68
|
};
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
async function consumeOpenCodeEvents(client, conversationId, {
|
|
67
|
-
eventStartedAt = null, onEvent = null, onError = null, onReady
|
|
72
|
+
eventStartedAt = null, onEvent = null, onError = null, onReady,
|
|
68
73
|
readError = openCodeMessageError, signal
|
|
69
74
|
} = {}) {
|
|
70
75
|
for await (const event of client.events(conversationId, { onReady, signal })) {
|
|
@@ -73,23 +78,21 @@ async function consumeOpenCodeEvents(client, conversationId, {
|
|
|
73
78
|
const current = eventStartedAt === null || Number(summary.at) >= Number(eventStartedAt);
|
|
74
79
|
if (
|
|
75
80
|
current && summary.type === "session.error" &&
|
|
76
|
-
|
|
81
|
+
normalizeNullableText(event.data?.properties?.sessionID) === conversationId &&
|
|
77
82
|
typeof onError === "function"
|
|
78
83
|
) {
|
|
79
|
-
const failure =
|
|
84
|
+
const failure = normalizeObject(event.data?.properties?.error);
|
|
80
85
|
onError(Object.assign(new Error(readError({ error: failure }) || "OpenCode turn failed."), {
|
|
81
|
-
name:
|
|
86
|
+
name: normalizeNullableText(failure.name) || "Error"
|
|
82
87
|
}));
|
|
83
88
|
}
|
|
84
89
|
if (summary.type && current && typeof onEvent === "function") {
|
|
85
90
|
await onEvent(summary, event);
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
error.statusCode = 503;
|
|
92
|
-
throw error;
|
|
93
|
+
throw new AppError(503, "OpenCode's event connection ended before observation was closed.", {
|
|
94
|
+
code: "assistant_opencode_observation_lost", details: {}
|
|
95
|
+
});
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
export function observeOpenCodeEvents(client, conversationId, {
|
|
@@ -115,9 +118,9 @@ export function observeOpenCodeEvents(client, conversationId, {
|
|
|
115
118
|
completion: events,
|
|
116
119
|
readFailure: () => eventFailure,
|
|
117
120
|
waitUntilReady({ timeoutMs = OPENCODE_EVENT_READY_TIMEOUT_MS, timeoutError } = {}) {
|
|
118
|
-
const timeout = setTimeout(() => eventReady.reject(timeoutError ||
|
|
119
|
-
|
|
120
|
-
{ code: "assistant_opencode_events_timeout", details: {}
|
|
121
|
+
const timeout = setTimeout(() => eventReady.reject(timeoutError || new AppError(504,
|
|
122
|
+
"OpenCode's event connection did not become ready.",
|
|
123
|
+
{ code: "assistant_opencode_events_timeout", details: {} }
|
|
121
124
|
)), timeoutMs);
|
|
122
125
|
void eventReady.promise.then(() => clearTimeout(timeout), () => clearTimeout(timeout));
|
|
123
126
|
return eventReady.promise;
|
|
@@ -129,8 +132,8 @@ export function observeOpenCodeEvents(client, conversationId, {
|
|
|
129
132
|
};
|
|
130
133
|
}
|
|
131
134
|
|
|
132
|
-
export function openCodeMessageError(message
|
|
133
|
-
return
|
|
135
|
+
export function openCodeMessageError(message) {
|
|
136
|
+
return normalizeNullableText(message?.error?.message || message?.error?.data?.message || message?.error?.name || message?.error);
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
export async function inspectOpenCodeMessageAdmission(client, conversationId, inputMessageId, {
|
|
@@ -146,7 +149,7 @@ export async function inspectOpenCodeMessageAdmission(client, conversationId, in
|
|
|
146
149
|
const messages = await readOpenCodeMessagesUntil(client, conversationId, hasMessage(inputMessageId), { signal: readSignal });
|
|
147
150
|
if (wait) signal.throwIfAborted();
|
|
148
151
|
const accepted = openCodeMessageRows(messages).some((message) =>
|
|
149
|
-
message.type === "user" &&
|
|
152
|
+
message.type === "user" && normalizeNullableText(message.id) === inputMessageId);
|
|
150
153
|
if (accepted || !wait) return { accepted, messages };
|
|
151
154
|
} catch {
|
|
152
155
|
if (!wait) return null;
|
|
@@ -223,7 +226,7 @@ export async function readOpenCodeMessagesUntil(client, conversationId, found, {
|
|
|
223
226
|
}
|
|
224
227
|
}
|
|
225
228
|
|
|
226
|
-
const hasMessage = id => rows => rows.some(message =>
|
|
229
|
+
const hasMessage = id => rows => rows.some(message => normalizeNullableText(message?.id) === normalizeNullableText(id));
|
|
227
230
|
|
|
228
231
|
export function openCodeMessageRows(value = null) {
|
|
229
232
|
const rows = Array.isArray(value) ? value : Array.isArray(value?.data) ? value.data : [];
|
|
@@ -239,7 +242,7 @@ export function openCodeMessageRows(value = null) {
|
|
|
239
242
|
export function openCodePersistentConversationMessages(rows, conversationMessageId) {
|
|
240
243
|
return rows.filter((row) => row.type === "assistant").flatMap((row) => {
|
|
241
244
|
const complete = Boolean(row.time?.completed || row.finish || row.error);
|
|
242
|
-
const reasoning = (row.content || []).filter((part) => part.type === "reasoning" &&
|
|
245
|
+
const reasoning = (row.content || []).filter((part) => part.type === "reasoning" && normalizeNullableText(part.text))
|
|
243
246
|
.map((part, index) => ({ id: conversationMessageId(row.id, part.id || index, "reasoning"), role: "thinking", text: part.text, complete }));
|
|
244
247
|
const answer = assistantMessageText(row);
|
|
245
248
|
return [...reasoning, ...(answer ? [{ id: conversationMessageId(row.id, "assistant"), role: "assistant", text: answer, complete }] : [])];
|
|
@@ -278,13 +281,13 @@ export async function projectOpenCodeConversationMessages(messages, expectedId,
|
|
|
278
281
|
}
|
|
279
282
|
}
|
|
280
283
|
const result = openCodeLastAssistantResult(rows);
|
|
281
|
-
return { failure: result.error, providerApiFailure:
|
|
284
|
+
return { failure: result.error, providerApiFailure: normalizeNullableText(result.message?.error?.name) === "APIError" };
|
|
282
285
|
}
|
|
283
286
|
|
|
284
287
|
// Native automatic compaction and its continuation belong to the admitted turn.
|
|
285
288
|
export function openCodeRowsForInput(value = null, inputMessageId = "") {
|
|
286
289
|
const rows = openCodeMessageRows(value);
|
|
287
|
-
const index = rows.findIndex((message) =>
|
|
290
|
+
const index = rows.findIndex((message) => normalizeNullableText(message?.id) === normalizeNullableText(inputMessageId));
|
|
288
291
|
if (index < 0) {
|
|
289
292
|
return [];
|
|
290
293
|
}
|
|
@@ -309,7 +312,7 @@ export function openCodeRowsForInput(value = null, inputMessageId = "") {
|
|
|
309
312
|
function openCodeMessageResultForInput(value = null, inputMessageId = "", { readError = openCodeMessageError } = {}) {
|
|
310
313
|
const rows = openCodeRowsForInput(value, inputMessageId);
|
|
311
314
|
if (!rows.length && !openCodeMessageRows(value).some((message) => (
|
|
312
|
-
|
|
315
|
+
normalizeNullableText(message?.id) === normalizeNullableText(inputMessageId)
|
|
313
316
|
))) {
|
|
314
317
|
return null;
|
|
315
318
|
}
|
|
@@ -323,7 +326,7 @@ function openCodeMessageResultForInput(value = null, inputMessageId = "", { read
|
|
|
323
326
|
(result.message?.time?.completed || result.message?.finish))),
|
|
324
327
|
error: result.error,
|
|
325
328
|
text: result.text,
|
|
326
|
-
turnId:
|
|
329
|
+
turnId: normalizeNullableText(result.message?.id)
|
|
327
330
|
};
|
|
328
331
|
}
|
|
329
332
|
|
|
@@ -349,7 +352,7 @@ function latestOpenCodeMessageResult(value, readError) {
|
|
|
349
352
|
(result.message.time?.completed || result.message.finish))),
|
|
350
353
|
error: result.error,
|
|
351
354
|
text: result.text,
|
|
352
|
-
turnId:
|
|
355
|
+
turnId: normalizeNullableText(result.message.id)
|
|
353
356
|
};
|
|
354
357
|
}
|
|
355
358
|
|
|
@@ -361,18 +364,18 @@ export async function waitForOpenCodeMessages(client, conversationId = "", input
|
|
|
361
364
|
} = {}) {
|
|
362
365
|
const resolveInputMessageId = typeof inputMessageId === "function"
|
|
363
366
|
? inputMessageId
|
|
364
|
-
: () =>
|
|
367
|
+
: () => normalizeNullableText(inputMessageId);
|
|
365
368
|
let completedInputMessageId = null;
|
|
366
369
|
while (true) {
|
|
367
370
|
signal?.throwIfAborted();
|
|
368
|
-
const expectedInputMessageId =
|
|
371
|
+
const expectedInputMessageId = normalizeNullableText(resolveInputMessageId());
|
|
369
372
|
const messages = await readOpenCodeMessagesUntil(client, conversationId,
|
|
370
373
|
expectedInputMessageId ? hasMessage(expectedInputMessageId) : () => true, { signal });
|
|
371
|
-
if (expectedInputMessageId !==
|
|
374
|
+
if (expectedInputMessageId !== normalizeNullableText(resolveInputMessageId())) continue;
|
|
372
375
|
if (typeof onMessages === "function") {
|
|
373
376
|
await onMessages(messages, expectedInputMessageId);
|
|
374
377
|
}
|
|
375
|
-
if (expectedInputMessageId !==
|
|
378
|
+
if (expectedInputMessageId !== normalizeNullableText(resolveInputMessageId())) continue;
|
|
376
379
|
const result = expectedInputMessageId
|
|
377
380
|
? openCodeMessageResultForInput(messages, expectedInputMessageId, { readError })
|
|
378
381
|
: latestOpenCodeMessageResult(messages, readError);
|
|
@@ -383,7 +386,7 @@ export async function waitForOpenCodeMessages(client, conversationId = "", input
|
|
|
383
386
|
// An assistant message can finish with tool calls while the native turn
|
|
384
387
|
// continues. Only an idle session acknowledges the final answer.
|
|
385
388
|
if (result?.complete && (await client.sessionStatus(conversationId, { signal })).type === "idle" &&
|
|
386
|
-
expectedInputMessageId ===
|
|
389
|
+
expectedInputMessageId === normalizeNullableText(resolveInputMessageId())) {
|
|
387
390
|
if (completedInputMessageId === expectedInputMessageId) {
|
|
388
391
|
return { messages, result, inputMessageId: expectedInputMessageId };
|
|
389
392
|
}
|
|
@@ -396,7 +399,7 @@ export async function waitForOpenCodeMessages(client, conversationId = "", input
|
|
|
396
399
|
}
|
|
397
400
|
|
|
398
401
|
export async function waitForOpenCodeFinalResponse(client, conversationId, turn, {
|
|
399
|
-
agent, model, recoveryMessageId, beforeRecovery =
|
|
402
|
+
agent, model, recoveryMessageId, beforeRecovery = recoverWithoutProjection, ...options
|
|
400
403
|
} = {}) {
|
|
401
404
|
const waitForCompletion = async () => {
|
|
402
405
|
for (;;) {
|
|
@@ -408,7 +411,7 @@ export async function waitForOpenCodeFinalResponse(client, conversationId, turn,
|
|
|
408
411
|
const admission = turn.admission;
|
|
409
412
|
await admission?.promise;
|
|
410
413
|
options.signal?.throwIfAborted();
|
|
411
|
-
if (admission !== turn.admission || completion.inputMessageId !==
|
|
414
|
+
if (admission !== turn.admission || completion.inputMessageId !== normalizeNullableText(turn.inputMessageId)) {
|
|
412
415
|
continue;
|
|
413
416
|
}
|
|
414
417
|
return completion;
|
|
@@ -418,12 +421,12 @@ export async function waitForOpenCodeFinalResponse(client, conversationId, turn,
|
|
|
418
421
|
const completion = await waitForCompletion();
|
|
419
422
|
// Work around https://github.com/anomalyco/opencode/issues/37073. Some
|
|
420
423
|
// reasoning models complete successfully without a user-facing text part.
|
|
421
|
-
if (turn.interruptRequested || completion.result?.error ||
|
|
422
|
-
await beforeRecovery
|
|
424
|
+
if (turn.interruptRequested || completion.result?.error || normalizeNullableText(completion.result?.text)) return completion;
|
|
425
|
+
await beforeRecovery(completion);
|
|
423
426
|
const admission = turn.admission;
|
|
424
427
|
await admission?.promise;
|
|
425
428
|
options.signal?.throwIfAborted();
|
|
426
|
-
if (admission !== turn.admission || completion.inputMessageId !==
|
|
429
|
+
if (admission !== turn.admission || completion.inputMessageId !== normalizeNullableText(turn.inputMessageId)) {
|
|
427
430
|
continue;
|
|
428
431
|
}
|
|
429
432
|
// The async native prompt ACK does not acknowledge user-row creation.
|
|
@@ -437,7 +440,7 @@ export async function waitForOpenCodeFinalResponse(client, conversationId, turn,
|
|
|
437
440
|
signal: receiptSignal, wait: true, readFailure: options.readFailure
|
|
438
441
|
}).then(observation => {
|
|
439
442
|
const message = openCodeMessageRows(observation.messages).find(message =>
|
|
440
|
-
message.type === "user" &&
|
|
443
|
+
message.type === "user" && normalizeNullableText(message.id) === recoveryMessageId);
|
|
441
444
|
recoveryAdmission.resolve(message);
|
|
442
445
|
return message;
|
|
443
446
|
}, error => {
|
|
@@ -458,8 +461,8 @@ export async function waitForOpenCodeFinalResponse(client, conversationId, turn,
|
|
|
458
461
|
}, { signal: options.signal });
|
|
459
462
|
await receipt;
|
|
460
463
|
options.signal?.throwIfAborted();
|
|
461
|
-
if (admission === turn.admission && completion.inputMessageId ===
|
|
462
|
-
turn.inputMessageId =
|
|
464
|
+
if (admission === turn.admission && completion.inputMessageId === normalizeNullableText(turn.inputMessageId)) {
|
|
465
|
+
turn.inputMessageId = normalizeNullableText(admitted?.id) || recoveryMessageId;
|
|
463
466
|
turn.updatedAt = new Date().toISOString();
|
|
464
467
|
}
|
|
465
468
|
} catch (error) {
|
|
@@ -472,10 +475,13 @@ export async function waitForOpenCodeFinalResponse(client, conversationId, turn,
|
|
|
472
475
|
}
|
|
473
476
|
}
|
|
474
477
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
478
|
+
/**
|
|
479
|
+
* Production detached-turn lifetime. Applications project the completed native
|
|
480
|
+
* result before observation is closed, then publish/checkpoint their own state.
|
|
481
|
+
* @param {import("./openCodeProcessOwner.js").OpenCodeProcessOwner} processOwner
|
|
482
|
+
*/
|
|
483
|
+
export async function observeOpenCodeTurnCompletion(processOwner, target, conversationId, turn, {
|
|
484
|
+
events, signal, timeoutMs, finalResponse, projectResult = value => value, afterClose = ignoreObservationClose, ...waitOptions
|
|
479
485
|
} = {}) {
|
|
480
486
|
const turnAbort = turn.abortController;
|
|
481
487
|
let stopped = true;
|
|
@@ -494,7 +500,7 @@ export async function observeOpenCodeTurnCompletion(runtime, target, conversatio
|
|
|
494
500
|
if (turn.interrupted) return { interrupted: true };
|
|
495
501
|
if (!target.abortController.signal.aborted) {
|
|
496
502
|
try {
|
|
497
|
-
await
|
|
503
|
+
await processOwner.stopUnobservedSession(target, conversationId);
|
|
498
504
|
} catch (stopError) {
|
|
499
505
|
stopped = false;
|
|
500
506
|
throw Object.assign(new Error(`${failure.message} ${stopError.message}`, { cause: failure }), { cleanupFailed: true });
|
|
@@ -507,7 +513,7 @@ export async function observeOpenCodeTurnCompletion(runtime, target, conversatio
|
|
|
507
513
|
turn.active = !stopped;
|
|
508
514
|
turn.mayBeRunning = !stopped;
|
|
509
515
|
}
|
|
510
|
-
await afterClose
|
|
516
|
+
await afterClose({ stopped, outcome });
|
|
511
517
|
}
|
|
512
518
|
}
|
|
513
519
|
|
|
@@ -518,10 +524,10 @@ export async function runOpenCodeConversationTurn(client, conversationId, input
|
|
|
518
524
|
completeResult, close
|
|
519
525
|
} = {}) {
|
|
520
526
|
const { inputMessageId, readError,
|
|
521
|
-
createError = (reason, message, details) =>
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
527
|
+
createError = (reason, message, details) => new AppError(
|
|
528
|
+
["thread_mismatch", "fresh_thread_required", "unrelated_history"].includes(reason) ? 409 : 502,
|
|
529
|
+
message || (reason === "unreadable" ? "The exact OpenCode turn did not produce a readable result." : "OpenCode did not finish the turn."),
|
|
530
|
+
{ code: `assistant_opencode_turn_${reason}`, details })
|
|
525
531
|
} = input;
|
|
526
532
|
if (
|
|
527
533
|
(expectedThreadId && expectedThreadId !== conversationId) ||
|
|
@@ -538,10 +544,10 @@ export async function runOpenCodeConversationTurn(client, conversationId, input
|
|
|
538
544
|
let existingMessages;
|
|
539
545
|
if (requireFreshHistory) {
|
|
540
546
|
existingMessages = openCodeMessageRows(await readOpenCodeMessagesUntil(client, conversationId, rows => rows.some(
|
|
541
|
-
message => message?.type === "user" &&
|
|
547
|
+
message => message?.type === "user" && normalizeNullableText(message.id) !== inputMessageId
|
|
542
548
|
)));
|
|
543
549
|
const unrelatedUserMessage = existingMessages.find((message) => (
|
|
544
|
-
message?.type === "user" &&
|
|
550
|
+
message?.type === "user" && normalizeNullableText(message.id) !== inputMessageId
|
|
545
551
|
));
|
|
546
552
|
if (unrelatedUserMessage) {
|
|
547
553
|
throw createError(
|
|
@@ -594,7 +600,7 @@ export async function runOpenCodeConversationTurn(client, conversationId, input
|
|
|
594
600
|
}
|
|
595
601
|
throw createError(
|
|
596
602
|
"failed",
|
|
597
|
-
|
|
603
|
+
normalizeNullableText(error?.message),
|
|
598
604
|
{
|
|
599
605
|
inputAccepted,
|
|
600
606
|
threadId: conversationId
|
|
@@ -628,7 +634,7 @@ export async function runOpenCodeConversationTurn(client, conversationId, input
|
|
|
628
634
|
reconciled: !admitted,
|
|
629
635
|
text: result.text,
|
|
630
636
|
threadId: conversationId,
|
|
631
|
-
turnId: result.turnId ||
|
|
637
|
+
turnId: result.turnId || normalizeNullableText(admitted?.id)
|
|
632
638
|
};
|
|
633
639
|
if (completeResult) await completeResult(completion);
|
|
634
640
|
const proof = close ? await close() : undefined;
|