@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
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { serial } from "./conversationEvents.js";
|
|
2
|
+
|
|
3
|
+
/** @typedef {import("./conversationEntries.js").ConversationEntry} ConversationEntry */
|
|
4
|
+
|
|
5
|
+
// The newest messages cancelled in one open conversation, refused if sent again.
|
|
6
|
+
const MAX_CANCELLED_MESSAGES = 100;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Stopping work in an open conversation: stopping its turn, cancelling one message, retrying
|
|
10
|
+
* a failed save, and disposing of its entry.
|
|
11
|
+
* @param {object} dependencies
|
|
12
|
+
* @param {import("../types.js").ReentrantConversationStorage | null} dependencies.storage
|
|
13
|
+
* @param {import("./conversationEntries.js").ConversationEntries} dependencies.entries
|
|
14
|
+
* @param {import("./conversationEvents.js").ConversationEvents} dependencies.events
|
|
15
|
+
* @param {import("./conversationDelivery.js").ConversationDelivery} dependencies.delivery
|
|
16
|
+
*/
|
|
17
|
+
function createConversationStopping({ storage, entries, events, delivery }) {
|
|
18
|
+
/**
|
|
19
|
+
* @param {ConversationEntry} entry
|
|
20
|
+
* @param {object} [context]
|
|
21
|
+
* @param {object} [input]
|
|
22
|
+
* @param {string} [representation]
|
|
23
|
+
*/
|
|
24
|
+
async function stop(entry, context, input, representation) {
|
|
25
|
+
if (representation === "native") return entry.provider.cancel({ context, input, representation });
|
|
26
|
+
const active = entry.active;
|
|
27
|
+
if (!active && entry.executionFailure) {
|
|
28
|
+
await entry.provider.cancel();
|
|
29
|
+
await retrySave(entry);
|
|
30
|
+
if (entry.cleanupTurnId && !entry.conversation) await storage.write(entry.id, async transaction => {
|
|
31
|
+
const turn = await transaction.readTurn(entry.cleanupTurnId);
|
|
32
|
+
await transaction.updateTurnMetadata(entry.cleanupTurnId,
|
|
33
|
+
{ runtime: { ...turn.metadata.runtime, status: "cancelled", error: "Work stopped." } });
|
|
34
|
+
});
|
|
35
|
+
entry.executionFailure = null;
|
|
36
|
+
entry.cleanupTurnId = null;
|
|
37
|
+
entry.error = "Work stopped.";
|
|
38
|
+
return { stopped: true };
|
|
39
|
+
}
|
|
40
|
+
if (!active) {
|
|
41
|
+
if (!entry.conversation && !entry.driver.canonicalTranscript) return { stopped: false };
|
|
42
|
+
const metadata = { runtime: await entries.readState(entry) };
|
|
43
|
+
const result = await entry.provider.cancel({ configuration: metadata.runtime.configuration, context });
|
|
44
|
+
return { stopped: result?.interrupted === true || result?.operationOutcome === "interrupted" };
|
|
45
|
+
}
|
|
46
|
+
active.controller.abort(new Error("Work stopped."));
|
|
47
|
+
await active.done;
|
|
48
|
+
if (entry.executionFailure) throw entry.executionFailure;
|
|
49
|
+
return { stopped: true };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Cancel of one message in a runtime-owned conversation never waits for the conversation queue.
|
|
54
|
+
* The message is recorded as cancelled at once, so a send of it still queued, or arriving later,
|
|
55
|
+
* is refused as not sent; its own submission stops, ending any turn it started. Its unconfirmed
|
|
56
|
+
* request or pending native attempt is discarded afterwards, in queue order. A message already
|
|
57
|
+
* accepted stays history.
|
|
58
|
+
* @param {ConversationEntry} entry
|
|
59
|
+
* @param {object} context
|
|
60
|
+
* @param {string} messageId
|
|
61
|
+
* @param {string} representation
|
|
62
|
+
*/
|
|
63
|
+
async function cancelDelivery(entry, context, messageId, representation) {
|
|
64
|
+
entry.cancelledMessages.delete(messageId);
|
|
65
|
+
entry.cancelledMessages.add(messageId);
|
|
66
|
+
if (entry.cancelledMessages.size > MAX_CANCELLED_MESSAGES) entry.cancelledMessages.delete(entry.cancelledMessages.values().next().value);
|
|
67
|
+
const interrupted = Boolean(entry.active?.requests.has(messageId));
|
|
68
|
+
if (interrupted) await stop(entry, context, undefined, representation);
|
|
69
|
+
void serial(entry, async () => {
|
|
70
|
+
await events.access(context, entry.id, "cancel", entry);
|
|
71
|
+
if (entry.conversation) {
|
|
72
|
+
await delivery.nativeChangeover(entry, context, null, () => { throw new Error("Cancel never sends a message."); }).discard({ messageId });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
await storage.write(entry.id, async transaction => {
|
|
76
|
+
const metadata = await transaction.readMetadata();
|
|
77
|
+
if (metadata.runtime.request?.messageId !== messageId) return;
|
|
78
|
+
delete metadata.runtime.request;
|
|
79
|
+
await transaction.writeMetadata(metadata);
|
|
80
|
+
});
|
|
81
|
+
}).catch(() => {});
|
|
82
|
+
return { ok: true, cancelled: true, messageId, interrupted };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** @param {ConversationEntry} entry */
|
|
86
|
+
async function retrySave(entry) {
|
|
87
|
+
if (!entry.storageFailure || !entry.saveProgress) return { saved: false };
|
|
88
|
+
const event = await entry.saveProgress();
|
|
89
|
+
await events.emit(entry, event);
|
|
90
|
+
return { saved: true };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {ConversationEntry} entry
|
|
95
|
+
* @param {object} [context]
|
|
96
|
+
* @param {object} [options]
|
|
97
|
+
* @param {string} [representation]
|
|
98
|
+
*/
|
|
99
|
+
async function disposeEntry(entry, context, options, representation) {
|
|
100
|
+
if (entry.conversation?.commands) { entries.retire(entry); return; }
|
|
101
|
+
if (!entry.conversation) await stop(entry, context);
|
|
102
|
+
await retrySave(entry);
|
|
103
|
+
const result = await entry.provider.dispose(entry.conversation?.native?.scoped && representation === "native"
|
|
104
|
+
? { input: options, context } : representation === "native" ? options : undefined);
|
|
105
|
+
if (result?.ok === false) return result;
|
|
106
|
+
if (entry.conversation) await entry.active?.done;
|
|
107
|
+
entries.retire(entry);
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Object.freeze({
|
|
112
|
+
stop,
|
|
113
|
+
cancelDelivery,
|
|
114
|
+
retrySave,
|
|
115
|
+
disposeEntry
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** @typedef {ReturnType<typeof createConversationStopping>} ConversationStopping */
|
|
120
|
+
|
|
121
|
+
export { createConversationStopping };
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { conversationAttachmentManifest } from "../../shared/conversation/attachments.js";
|
|
2
|
+
import { createConversationAttachmentReader } from "./attachments.js";
|
|
3
|
+
import { conversationHistoryVersions, conversationNativeMessages, conversationRequestText } from "./continuity.js";
|
|
4
|
+
import { ignorePromptRejection, ignorePromptSending } from "./nativeCommandCallbacks.js";
|
|
5
|
+
import { deliveryFailure, failure } from "./conversationRefusals.js";
|
|
6
|
+
import { submittedMessage } from "./conversationReads.js";
|
|
7
|
+
|
|
8
|
+
/** @typedef {import("./conversationEntries.js").ConversationEntry} ConversationEntry */
|
|
9
|
+
/** @typedef {import("./conversationTurns.js").ActiveTurn} ActiveTurn */
|
|
10
|
+
/** @typedef {import("./conversationSending.js").ConversationRequest} ConversationRequest */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* How a running turn admits and delivers its requests: their admission and its publication, the
|
|
14
|
+
* attribution and progress every saved turn records, attachment reading and native dispatch.
|
|
15
|
+
* @param {object} dependencies
|
|
16
|
+
* @param {import("../types.js").ReentrantConversationStorage | null} dependencies.storage
|
|
17
|
+
* @param {import("../types.js").ConversationStreams} dependencies.streams
|
|
18
|
+
* @param {import("./conversationEvents.js").ConversationEvents} dependencies.events
|
|
19
|
+
* @param {import("./conversationReads.js").ConversationReads} dependencies.reads
|
|
20
|
+
* @param {import("./conversationDelivery.js").ConversationDelivery} dependencies.delivery
|
|
21
|
+
* @param {import("../types.js").ConversationAttachments} dependencies.attachments
|
|
22
|
+
* @param {boolean} dependencies.persistCommentary
|
|
23
|
+
* @param {number} dependencies.maximumContinuity
|
|
24
|
+
* @param {number} dependencies.maximumAttachmentBytes
|
|
25
|
+
*/
|
|
26
|
+
function createConversationTurnDelivery({ storage, streams, events, reads, delivery, attachments, persistCommentary, maximumContinuity,
|
|
27
|
+
maximumAttachmentBytes }) {
|
|
28
|
+
/**
|
|
29
|
+
* Every saved turn and pending request records its attribution identically.
|
|
30
|
+
* @param {ActiveTurn} active
|
|
31
|
+
* @param {ConversationRequest} request
|
|
32
|
+
* @param {object} [extra]
|
|
33
|
+
*/
|
|
34
|
+
const turnAttribution = (active, request, extra = {}) => ({ engine: active.engine, segmentId: active.segmentId, origin: request.input.origin,
|
|
35
|
+
...(request.completedEnvelope ? { completedEnvelope: true } : {}),
|
|
36
|
+
...(typeof request.nativeToolAttempt === "boolean" ? { nativeToolAttempt: request.nativeToolAttempt } : {}), ...extra });
|
|
37
|
+
/** @param {ConversationRequest} request */
|
|
38
|
+
const authoredRequestRecord = request => ({ ...request.input, at: request.at,
|
|
39
|
+
...(request.completedEnvelope ? { completedEnvelope: true, excludedMessageIds: request.excludedMessageIds } : {}),
|
|
40
|
+
...(typeof request.nativeToolAttempt === "boolean" ? { nativeToolAttempt: request.nativeToolAttempt } : {}) });
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Saves a request's progress on its turn, and returns the history versions it wrote.
|
|
44
|
+
* @param {ActiveTurn} active
|
|
45
|
+
* @param {object} transaction
|
|
46
|
+
* @param {ConversationRequest} request
|
|
47
|
+
* @param {string} status
|
|
48
|
+
* @param {string} [error]
|
|
49
|
+
* @param {object} [extra]
|
|
50
|
+
*/
|
|
51
|
+
async function writeProgress(active, transaction, request, status, error = "", extra = {}) {
|
|
52
|
+
if (request.tools) await transaction.updateTurnMetadata(request.turnId, { applicationTools: request.tools.records() });
|
|
53
|
+
if (!active.driver.canonicalTranscript) {
|
|
54
|
+
for (const message of request.progress.values()) {
|
|
55
|
+
if (!persistCommentary && message.role === "commentary") continue;
|
|
56
|
+
if (!await transaction.hasMessage(message.messageId)) await transaction.appendMessage(request.turnId, message);
|
|
57
|
+
}
|
|
58
|
+
if (request.answer?.text.trim()) await transaction.replaceAssistant(request.turnId, request.answer);
|
|
59
|
+
}
|
|
60
|
+
const saved = active.driver.canonicalTranscript ? await transaction.readTurn(request.turnId) : null;
|
|
61
|
+
await transaction.updateTurnMetadata(request.turnId, { runtime: { ...saved?.metadata?.runtime, status, ...turnAttribution(active, request, {
|
|
62
|
+
...(request.nativeTurnId ? { nativeTurnId: request.nativeTurnId } : {}),
|
|
63
|
+
...(request.goalMessageId ? { goalMessageId: request.goalMessageId } : {}),
|
|
64
|
+
...(request.continuedBy ? { continuedBy: request.continuedBy } : {}) }), ...extra, ...(error ? { error } : {}) } });
|
|
65
|
+
return conversationHistoryVersions([{ turnId: request.turnId,
|
|
66
|
+
messages: [...request.progress.values()].filter(message => !(message.turnId === request.turnId && message.status === "complete"))
|
|
67
|
+
.concat(request.answer?.text.trim() ? [request.answer] : []) }]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Admits a request into the running turn: its receipt is saved on its turn (or located in a bound
|
|
72
|
+
* conversation's transcript), and the request becomes the one the turn answers.
|
|
73
|
+
* @param {ConversationEntry} entry
|
|
74
|
+
* @param {ActiveTurn} active
|
|
75
|
+
* @param {ConversationRequest} request
|
|
76
|
+
* @param {Record<string, string> | undefined} seen The history versions an API request sends.
|
|
77
|
+
* @param {{ nativeDelivery?: boolean, conversationTurn?: { turnId: string } | null }} [options]
|
|
78
|
+
*/
|
|
79
|
+
async function admit(entry, active, request, seen, { nativeDelivery = false, conversationTurn = null } = {}) {
|
|
80
|
+
await events.access(request.context, entry.id, request.operation, entry);
|
|
81
|
+
request.deliveryTiming("admission-authorized");
|
|
82
|
+
if (!conversationTurn) active.controller.signal.throwIfAborted();
|
|
83
|
+
if (request.input.goal) request.goalMessageId = request.input.messageId;
|
|
84
|
+
else if (active.goalOwner) request.goalMessageId = active.goalOwner.input.messageId;
|
|
85
|
+
const nativeTurnId = active.nativeTurnId;
|
|
86
|
+
const previous = active.request;
|
|
87
|
+
if (entry.conversation) {
|
|
88
|
+
const saved = conversationTurn?.turnId ? await reads.readBoundTurn(entry, conversationTurn.turnId) : null;
|
|
89
|
+
request.deliveryTiming("turn-located");
|
|
90
|
+
if (submittedMessage(saved || {})?.messageId !== request.input.messageId) {
|
|
91
|
+
throw new Error("The native receipt does not belong to this authored request.");
|
|
92
|
+
}
|
|
93
|
+
request.turnId = saved.turnId;
|
|
94
|
+
} else request.turnId = await storage.write(entry.id, async transaction => {
|
|
95
|
+
const metadata = await transaction.readMetadata();
|
|
96
|
+
if (metadata.runtime.request?.messageId !== request.input.messageId) throw new Error("The pending conversation message changed before admission.");
|
|
97
|
+
if (previous !== request && !previous.finished) {
|
|
98
|
+
const versions = await writeProgress(active, transaction, previous, "running", "", { continuedBy: request.input.messageId });
|
|
99
|
+
if (active.engine === "api") Object.assign(metadata.runtime.seen, versions);
|
|
100
|
+
}
|
|
101
|
+
let id;
|
|
102
|
+
if (conversationTurn) {
|
|
103
|
+
const saved = await transaction.readTurn(conversationTurn.turnId);
|
|
104
|
+
if (submittedMessage(saved || {})?.messageId !== request.input.messageId || saved.metadata?.runtime?.segmentId !== active.segmentId) {
|
|
105
|
+
throw new Error("The native receipt does not belong to this authored request.");
|
|
106
|
+
}
|
|
107
|
+
if (request.completedEnvelope && saved.metadata.runtime.completedEnvelope !== true) {
|
|
108
|
+
throw new Error("The native receipt lost its completed-response marker.");
|
|
109
|
+
}
|
|
110
|
+
id = saved.turnId;
|
|
111
|
+
await transaction.updateTurnMetadata(id, { runtime: { ...saved.metadata.runtime,
|
|
112
|
+
...(request.goalMessageId ? { goalMessageId: request.goalMessageId } : {}),
|
|
113
|
+
...(nativeTurnId ? { nativeTurnId } : {}) } });
|
|
114
|
+
} else {
|
|
115
|
+
id = await transaction.nextTurnId();
|
|
116
|
+
await transaction.appendMessage(id, { role: request.input.origin === "application" ? "system" : "user", ...request.input, at: new Date().toISOString(),
|
|
117
|
+
turnMetadata: { runtime: { status: "running", ...turnAttribution(active, request, {
|
|
118
|
+
...(request.goalMessageId ? { goalMessageId: request.goalMessageId } : {}),
|
|
119
|
+
...(nativeTurnId ? { nativeTurnId } : {}) }) } } });
|
|
120
|
+
}
|
|
121
|
+
if (active.engine === "api") {
|
|
122
|
+
metadata.runtime.seen = { ...(seen || metadata.runtime.seen), ...conversationHistoryVersions([await transaction.readTurn(id)]) };
|
|
123
|
+
metadata.runtime.lastEngine = active.engine;
|
|
124
|
+
delete metadata.runtime.request;
|
|
125
|
+
}
|
|
126
|
+
await transaction.writeMetadata(metadata);
|
|
127
|
+
if (active.engine !== "api" && !nativeDelivery) await delivery.rememberNative(entry, transaction, [await transaction.readTurn(id)]);
|
|
128
|
+
return id;
|
|
129
|
+
});
|
|
130
|
+
if (previous !== request) {
|
|
131
|
+
previous.sealed = true;
|
|
132
|
+
previous.continuedBy = request.input.messageId;
|
|
133
|
+
if (!active.driver.canonicalTranscript) streams.clear(entry.id);
|
|
134
|
+
}
|
|
135
|
+
request.accepted = true;
|
|
136
|
+
if (!entry.conversation) request.deliveryTiming("receipt-persisted");
|
|
137
|
+
request.nativeTurnId = nativeTurnId;
|
|
138
|
+
if (request.input.goal) active.goalOwner = request;
|
|
139
|
+
active.request = request;
|
|
140
|
+
if (entry.conversation) entry.lastAcceptedRequest = request;
|
|
141
|
+
if (!nativeDelivery) await publishAdmission(entry, active, request);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolves a request's admission and tells subscribers, with the native items it already owns.
|
|
146
|
+
* @param {ConversationEntry} entry
|
|
147
|
+
* @param {ActiveTurn} active
|
|
148
|
+
* @param {ConversationRequest} request
|
|
149
|
+
* @param {unknown} [nativeResult]
|
|
150
|
+
*/
|
|
151
|
+
async function publishAdmission(entry, active, request, nativeResult) {
|
|
152
|
+
request.deliveryTiming("accepted");
|
|
153
|
+
const receipt = { status: "accepted", messageId: request.input.messageId, turnId: request.turnId, origin: request.input.origin };
|
|
154
|
+
request.admission.resolve(entry.conversation ? { receipt, nativeResult } : receipt);
|
|
155
|
+
request.admissionSettled = true;
|
|
156
|
+
await events.emit(entry, { type: "accepted", ...receipt });
|
|
157
|
+
if (active.driver.canonicalTranscript && !entry.conversation) {
|
|
158
|
+
// Native items can complete before common admission finishes. Their
|
|
159
|
+
// exact request already owns them; publish before tools join the
|
|
160
|
+
// existing delivery commit, without blocking the native event queue.
|
|
161
|
+
for (const [id, message] of request.progress) {
|
|
162
|
+
if (message.turnId !== request.turnId || message.status !== "complete") continue;
|
|
163
|
+
request.progress.delete(id);
|
|
164
|
+
if (request.sealed || entry.disposed || entry.active !== active ||
|
|
165
|
+
entry.bindingOwner !== active.bindingOwner || entry.segmentId !== active.segmentId) continue;
|
|
166
|
+
await events.emit(entry, { type: "message", ...message, streaming: streams.read(entry.id) },
|
|
167
|
+
() => !entry.disposed && !request.sealed && entry.active === active && entry.bindingOwner === active.bindingOwner &&
|
|
168
|
+
entry.segmentId === active.segmentId);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
request.admissionPublished = true;
|
|
172
|
+
await events.setPhase(entry, "working", request.turnId);
|
|
173
|
+
if ((entry.conversation || active.driver.canonicalTranscript) && entry.nativePhase && entry.nativePhase !== "working") await events.setPhase(entry, entry.nativePhase, request.turnId);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {ConversationEntry} entry
|
|
178
|
+
* @param {ActiveTurn} active
|
|
179
|
+
* @param {ConversationRequest} request
|
|
180
|
+
*/
|
|
181
|
+
function attachmentReader(entry, active, request) {
|
|
182
|
+
return createConversationAttachmentReader({ attachments, context: request.context, conversationId: entry.id,
|
|
183
|
+
signal: active.controller.signal, authorize: () => events.access(request.context, entry.id, request.operation, entry),
|
|
184
|
+
types: active.driver.attachmentTypes, maximumBytes: maximumAttachmentBytes });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The last checks before a request is dispatched to its engine.
|
|
189
|
+
* @param {ConversationEntry} entry
|
|
190
|
+
* @param {ActiveTurn} active
|
|
191
|
+
* @param {ConversationRequest} request
|
|
192
|
+
*/
|
|
193
|
+
async function beforeDispatch(entry, active, request) {
|
|
194
|
+
await events.access(request.context, entry.id, request.operation, entry);
|
|
195
|
+
active.controller.signal.throwIfAborted();
|
|
196
|
+
if (request.input.goal && request.context?.signal?.aborted) {
|
|
197
|
+
throw Object.assign(failure("The goal command was stopped before delivery.", "conversation_goal_stopped"), { delivery: "not-sent" });
|
|
198
|
+
}
|
|
199
|
+
if (active.finishing) throw Object.assign(failure("The native turn finished before this instruction was sent. Send it as a new message.", "conversation_steer_turn_finished", 409, { messageCode: "assistant.conversation.turnFinishedBeforeSend" }), { delivery: "not-sent" });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Delivers a request to its native engine through the changeover that catches the engine up,
|
|
204
|
+
* then admits it when the engine acknowledges it.
|
|
205
|
+
* @param {ConversationEntry} entry
|
|
206
|
+
* @param {ActiveTurn} active
|
|
207
|
+
* @param {ConversationRequest} request
|
|
208
|
+
* @param {(input: object) => Promise<unknown>} dispatch Runs the engine with the prepared input.
|
|
209
|
+
*/
|
|
210
|
+
async function deliverNative(entry, active, request, dispatch) {
|
|
211
|
+
// A bound conversation supplies its own native history below.
|
|
212
|
+
const history = entry.conversation ? [] : (await reads.readTranscript(entry)).filter(turn => !turn.metadata?.runtime?.supersededBy);
|
|
213
|
+
let completion;
|
|
214
|
+
let committed;
|
|
215
|
+
let rendered;
|
|
216
|
+
let threadId;
|
|
217
|
+
let attachmentManifest;
|
|
218
|
+
let nativeResult;
|
|
219
|
+
const dispatchPrepared = async input => {
|
|
220
|
+
rendered = input;
|
|
221
|
+
active.rejectionSaves.delete(request.input.messageId);
|
|
222
|
+
if (request.operation === "steer" && !request.steering) throw Object.assign(failure("There is no active native turn to steer. Send a new message.", "conversation_steer_turn_finished", 409, { messageCode: "assistant.conversation.noActiveTurnToSteer" }), { delivery: "not-sent" });
|
|
223
|
+
if ((input.contextText || "").length > maximumContinuity) throw new Error("Conversation updates exceed the configured history budget. Renew the context with a continuity briefing before continuing.");
|
|
224
|
+
if (request.input.goal && input.contextText) {
|
|
225
|
+
throw failure("Send a chat message to deliver the saved continuity briefing before starting a native terminal or goal.", "conversation_briefing_before_terminal", 409, { messageCode: "assistant.conversation.briefingBeforeTerminal" });
|
|
226
|
+
}
|
|
227
|
+
const readAttachments = attachmentReader(entry, active, request);
|
|
228
|
+
// The bound host has already resolved its original attachment records;
|
|
229
|
+
// the production preparation owner keeps their manifest/native mapping.
|
|
230
|
+
const boundInput = entry.conversation
|
|
231
|
+
? await entry.conversation.prepareInput(input, request.context, { deliveryTiming: request.deliveryTiming }) : null;
|
|
232
|
+
const files = boundInput ? { attachments: boundInput.displayAttachments || [], localFiles: [], content: [] }
|
|
233
|
+
: await readAttachments((input.attachmentIds || []).map(attachmentId => ({ attachmentId })));
|
|
234
|
+
const contextFiles = boundInput ? { localFiles: [], content: [] } : await readAttachments(input.contextAttachments || []);
|
|
235
|
+
request.deliveryTiming("input-prepared");
|
|
236
|
+
const localFiles = [...contextFiles.localFiles, ...files.localFiles];
|
|
237
|
+
if (request.input.goal && localFiles.length) {
|
|
238
|
+
throw failure("Native goals require extracted text or supported image bytes for attachments.", "conversation_goal_attachment_content_unsupported", 400, { messageCode: "assistant.conversation.goalAttachmentContentUnsupported" });
|
|
239
|
+
}
|
|
240
|
+
attachmentManifest = conversationAttachmentManifest(localFiles);
|
|
241
|
+
request.input.attachments = files.attachments;
|
|
242
|
+
if (!entry.conversation) await storage.write(entry.id, async transaction => {
|
|
243
|
+
const metadata = await transaction.readMetadata();
|
|
244
|
+
const pending = metadata.runtime.request;
|
|
245
|
+
if (pending && pending.messageId !== request.input.messageId && (pending.attempted || pending.inspectionOnly)) throw new Error("The pending conversation instruction changed.");
|
|
246
|
+
if (pending?.messageId === request.input.messageId && pending.attachmentManifest !== undefined &&
|
|
247
|
+
pending.attachmentManifest !== attachmentManifest) {
|
|
248
|
+
throw failure("Authorized attachment paths changed after this instruction was prepared. Restore the original files or send a new message.", "conversation_attachment_manifest_changed");
|
|
249
|
+
}
|
|
250
|
+
metadata.runtime.request = { ...(pending?.messageId === request.input.messageId ? pending
|
|
251
|
+
: { ...authoredRequestRecord(request), attempted: false }), attachmentManifest };
|
|
252
|
+
await transaction.writeMetadata(metadata);
|
|
253
|
+
});
|
|
254
|
+
const admitted = Promise.withResolvers();
|
|
255
|
+
committed = Promise.withResolvers();
|
|
256
|
+
committed.promise.catch(() => {});
|
|
257
|
+
request.deliveryCommitted = committed.promise;
|
|
258
|
+
request.deliveryTiming("provider-started");
|
|
259
|
+
completion = Promise.resolve().then(() => dispatch({
|
|
260
|
+
context: request.context,
|
|
261
|
+
input: { ...request.input, authoredInput: { ...request.input, at: request.at },
|
|
262
|
+
...(request.input.goal ? { signal: request.context?.signal } : {}),
|
|
263
|
+
...(boundInput ? { nativeMessage: boundInput } : {}),
|
|
264
|
+
text: input.message, contextText: input.contextText || "",
|
|
265
|
+
content: [...contextFiles.content, ...files.content], localFiles, attachmentManifest },
|
|
266
|
+
signal: active.controller.signal,
|
|
267
|
+
deliveryTiming: request.deliveryTiming,
|
|
268
|
+
onNativeReceipt: () => request.deliveryTiming("native-acknowledged"),
|
|
269
|
+
onReceiptPersisted: () => request.deliveryTiming("receipt-persisted"),
|
|
270
|
+
...(active.engine === "opencode" ? { deliveryCommitted: committed.promise } : {}),
|
|
271
|
+
async beforeDispatch(native) {
|
|
272
|
+
request.deliveryTiming("runtime-ready");
|
|
273
|
+
await beforeDispatch(entry, active, request);
|
|
274
|
+
threadId = native?.threadId;
|
|
275
|
+
if (!threadId) throw new Error("Native admission requires the saved conversation identity.");
|
|
276
|
+
await input.onPromptSending(entry.conversation ? native : { threadId, displayAttachments: files.attachments,
|
|
277
|
+
turnMetadata: { runtime: turnAttribution(active, request) } });
|
|
278
|
+
if (request.input.goal && request.context?.signal?.aborted) {
|
|
279
|
+
throw Object.assign(failure("The goal command was stopped before delivery.", "conversation_goal_stopped"), { delivery: "not-sent" });
|
|
280
|
+
}
|
|
281
|
+
request.deliveryTiming("dispatch-started");
|
|
282
|
+
},
|
|
283
|
+
async accept(native) {
|
|
284
|
+
if (entry.conversation) nativeResult = native.nativeResult;
|
|
285
|
+
if (native?.nativeTurnId) active.nativeTurnId = native.nativeTurnId;
|
|
286
|
+
request.nativeThreadId = threadId;
|
|
287
|
+
await admit(entry, active, request, undefined, { nativeDelivery: true, conversationTurn: native?.conversationTurn });
|
|
288
|
+
admitted.resolve(entry.conversation ? nativeResult.value
|
|
289
|
+
: { ok: true, delivered: true, messageId: request.input.messageId, threadId });
|
|
290
|
+
// Drivers may stream or continue a goal as soon as accept returns.
|
|
291
|
+
// The original delivery marker must be durable before that happens.
|
|
292
|
+
await committed.promise;
|
|
293
|
+
}
|
|
294
|
+
})).then(result => {
|
|
295
|
+
if (entry.conversation && !request.accepted && result) {
|
|
296
|
+
nativeResult = result;
|
|
297
|
+
admitted.resolve(result.value);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (!request.accepted) throw new Error("The engine did not acknowledge the message.");
|
|
301
|
+
}).catch(async error => {
|
|
302
|
+
if (!request.accepted && (error.delivery === "not-sent" || !entry.conversation && error.delivery !== "uncertain")) {
|
|
303
|
+
try { await input.onPromptRejected(); }
|
|
304
|
+
catch (saveError) {
|
|
305
|
+
active.rejectionSaves.set(request.input.messageId, input.onPromptRejected);
|
|
306
|
+
error = saveError;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
admitted.reject(error);
|
|
310
|
+
throw error;
|
|
311
|
+
});
|
|
312
|
+
completion.catch(() => {});
|
|
313
|
+
return admitted.promise;
|
|
314
|
+
};
|
|
315
|
+
// A native caller's own input may carry its prompt callbacks.
|
|
316
|
+
const { onPromptSending: originalPromptSending = ignorePromptSending,
|
|
317
|
+
onPromptRejected: originalPromptRejected = ignorePromptRejection } = request.originalInput || {};
|
|
318
|
+
try {
|
|
319
|
+
const input = {
|
|
320
|
+
...request.originalInput,
|
|
321
|
+
messageId: request.input.messageId,
|
|
322
|
+
message: request.originalInput ? request.originalInput.message ?? request.originalInput.text ?? "" : conversationRequestText(request.input),
|
|
323
|
+
displayMessage: request.input.text,
|
|
324
|
+
displayAttachments: request.originalInput?.displayAttachments || request.input.attachments,
|
|
325
|
+
attachmentIds: request.input.attachments.map(file => file.attachmentId),
|
|
326
|
+
async onPromptSending({ threadId, displayAttachments, turnMetadata }) {
|
|
327
|
+
if (entry.conversation) return originalPromptSending({ threadId, displayAttachments, turnMetadata });
|
|
328
|
+
await storage.write(entry.id, async transaction => {
|
|
329
|
+
const metadata = await transaction.readMetadata();
|
|
330
|
+
const pending = metadata.runtime.request;
|
|
331
|
+
if (pending && pending.messageId !== request.input.messageId && (pending.attempted || pending.inspectionOnly)) throw new Error("The pending conversation instruction changed.");
|
|
332
|
+
metadata.runtime.request = { ...authoredRequestRecord(request),
|
|
333
|
+
message: rendered.message, displayMessage: rendered.displayMessage,
|
|
334
|
+
displayAttachments, attachmentIds: rendered.attachmentIds,
|
|
335
|
+
attachmentManifest,
|
|
336
|
+
...(rendered.contextText !== undefined ? { contextText: rendered.contextText, contextAttachments: rendered.contextAttachments } : {}),
|
|
337
|
+
seen: pending?.seen || conversationHistoryVersions(history), attempted: true, threadId, turnMetadata,
|
|
338
|
+
...(request.operation === "steer" ? { steering: true } : {}) };
|
|
339
|
+
await transaction.writeMetadata(metadata);
|
|
340
|
+
});
|
|
341
|
+
},
|
|
342
|
+
async onPromptRejected() {
|
|
343
|
+
if (entry.conversation) return originalPromptRejected();
|
|
344
|
+
await storage.write(entry.id, async transaction => {
|
|
345
|
+
const metadata = await transaction.readMetadata();
|
|
346
|
+
if (metadata.runtime.request?.messageId === request.input.messageId) {
|
|
347
|
+
metadata.runtime.request.attempted = false;
|
|
348
|
+
await transaction.writeMetadata(metadata);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
// A native goal command calls the native sender directly. Chat
|
|
354
|
+
// context rendering and its delivery cursor must not change /goal input.
|
|
355
|
+
const delivered = entry.conversation && request.input.goal
|
|
356
|
+
? await dispatchPrepared(input)
|
|
357
|
+
: await delivery.nativeChangeover(entry, request.context, request, dispatchPrepared, active.controller.signal).send({ engineId: active.engine,
|
|
358
|
+
messages: entry.conversation ? await entry.conversation.transcript.history() : conversationNativeMessages(history),
|
|
359
|
+
...(entry.conversation ? { turnMetadata: { engineId: active.engine } } : {}),
|
|
360
|
+
...(request.completedEnvelope ? { completedEnvelope: true, excludedMessageIds: request.excludedMessageIds } : {}), input });
|
|
361
|
+
if (!delivered?.delivered) {
|
|
362
|
+
const error = deliveryFailure(delivered);
|
|
363
|
+
if (entry.conversation) {
|
|
364
|
+
request.admission.resolve({ nativeResult: nativeResult || { value: delivered }, error });
|
|
365
|
+
request.admissionSettled = true;
|
|
366
|
+
}
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
if (request.accepted) await publishAdmission(entry, active, request, nativeResult);
|
|
370
|
+
else {
|
|
371
|
+
const saved = await reads.findSubmittedTurn(entry, request.input.messageId);
|
|
372
|
+
if (!saved && entry.conversation) {
|
|
373
|
+
// A duplicate found in native history need not have an authored
|
|
374
|
+
// application row. Preserve its exact result without inventing a
|
|
375
|
+
// canonical receipt or changing native success into a failure.
|
|
376
|
+
request.admission.resolve({ nativeResult: nativeResult || { value: delivered },
|
|
377
|
+
error: failure("The native receipt has no stored authored message.", "conversation_receipt_unavailable") });
|
|
378
|
+
request.admissionSettled = request.finished = true;
|
|
379
|
+
committed?.resolve();
|
|
380
|
+
await completion;
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (!saved) throw new Error("The native receipt has no stored authored message.");
|
|
384
|
+
request.accepted = request.finished = true;
|
|
385
|
+
if (entry.conversation) entry.lastAcceptedRequest = request;
|
|
386
|
+
request.turnId = saved.turnId;
|
|
387
|
+
request.recovered = saved.metadata?.runtime;
|
|
388
|
+
const receipt = { status: "accepted", messageId: request.input.messageId, turnId: saved.turnId,
|
|
389
|
+
origin: request.input.origin, ...(delivered.duplicate ? { duplicate: true } : { recovered: true }) };
|
|
390
|
+
request.admission.resolve(entry.conversation ? { receipt, nativeResult: { value: delivered } } : receipt);
|
|
391
|
+
request.admissionSettled = true;
|
|
392
|
+
}
|
|
393
|
+
committed?.resolve();
|
|
394
|
+
await completion;
|
|
395
|
+
} catch (error) {
|
|
396
|
+
committed?.reject(error);
|
|
397
|
+
await completion?.catch(() => {});
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return Object.freeze({
|
|
403
|
+
writeProgress,
|
|
404
|
+
admit,
|
|
405
|
+
attachmentReader,
|
|
406
|
+
beforeDispatch,
|
|
407
|
+
deliverNative
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** @typedef {ReturnType<typeof createConversationTurnDelivery>} ConversationTurnDelivery */
|
|
412
|
+
|
|
413
|
+
export { createConversationTurnDelivery };
|