@jskit-ai/assistant-core 0.1.200 → 0.1.202
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/README.md +8 -1
- package/examples/conversation/package.json +5 -5
- package/fixtures/responsive-assistant/App.vue +4 -1
- package/package.json +99 -36
- package/src/client/conversation/AssistantConversationElement.vue +27 -4
- package/src/client/conversation/AssistantConversationStatus.vue +24 -0
- package/src/client/conversation/AssistantGoalControl.vue +8 -5
- package/src/client/conversation/AssistantTranscript.vue +10 -3
- package/src/client/conversation/index.js +2 -0
- package/src/client/conversation/messageDelivery.js +115 -32
- package/src/client/conversation/retainedConversation.js +32 -0
- package/src/client/conversation/useAssistantQuestions.js +76 -0
- package/src/client/lib/assistantApi.js +72 -2
- package/src/server/conversation/agentRun.js +222 -0
- package/src/server/conversation/attachments.js +93 -0
- package/src/server/conversation/claudeClient.js +221 -0
- package/src/server/conversation/claudeHistory.js +256 -0
- package/src/server/conversation/claudeProcess.js +327 -0
- package/src/server/conversation/claudeTools.js +42 -0
- package/src/server/conversation/claudeTurn.js +1082 -0
- package/src/server/conversation/codexConfiguration.js +1183 -0
- package/src/server/conversation/codexConversations.js +1082 -0
- package/src/server/conversation/codexDetachedTurn.js +332 -0
- package/src/server/conversation/codexEvents.js +194 -1
- package/src/server/conversation/codexGoalCommands.js +124 -0
- package/src/server/conversation/codexHelperExecution.js +306 -0
- package/src/server/conversation/codexHelperThreadLedger.js +804 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +395 -0
- package/src/server/conversation/codexHelperThreads.js +638 -0
- package/src/server/conversation/codexHistoryAdapter.js +369 -0
- package/src/server/conversation/codexMessageCommands.js +663 -0
- package/src/server/conversation/codexMessageDelivery.js +211 -0
- package/src/server/conversation/codexNativeHistoryExport.js +95 -0
- package/src/server/conversation/codexNotificationObserver.js +482 -0
- package/src/server/conversation/codexNotificationQueue.js +103 -0
- package/src/server/conversation/codexObservationControl.js +174 -0
- package/src/server/conversation/codexOutput.js +931 -0
- package/src/server/conversation/codexProcess.js +1559 -0
- package/src/server/conversation/codexProcessLeader.js +14 -0
- package/src/server/conversation/codexProvider.js +2710 -0
- package/src/server/conversation/codexProviderOwner.js +1058 -0
- package/src/server/conversation/codexProviderSelection.js +218 -0
- package/src/server/conversation/codexReasoning.js +314 -0
- package/src/server/conversation/codexRenewalCommands.js +290 -0
- package/src/server/conversation/codexResultSettlement.js +409 -0
- package/src/server/conversation/codexRunJournal.js +658 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +288 -0
- package/src/server/conversation/codexSessionCleanup.js +96 -0
- package/src/server/conversation/codexSessionReconciliation.js +284 -0
- package/src/server/conversation/codexThreadReadiness.js +397 -0
- package/src/server/conversation/codexThreadStatus.js +354 -0
- package/src/server/conversation/codexTurn.js +701 -255
- package/src/server/conversation/codexTurnControl.js +317 -0
- package/src/server/conversation/codexTurnRecovery.js +990 -0
- package/src/server/conversation/codexTurnState.js +549 -0
- package/src/server/conversation/commandHook.js +18 -0
- package/src/server/conversation/commandWrapper.js +37 -0
- package/src/server/conversation/configuration.js +43 -0
- package/src/server/conversation/continuity.js +289 -0
- package/src/server/conversation/fileStorage.js +55 -0
- package/src/server/conversation/hookBridge.js +19 -0
- package/src/server/conversation/index.js +13 -0
- package/src/server/conversation/localExecution.js +665 -0
- package/src/server/conversation/memoryStorage.js +7 -72
- package/src/server/conversation/nativeHistoryExport.js +90 -0
- package/src/server/conversation/nativeStorage.js +23 -0
- package/src/server/conversation/openCodeClient.js +209 -19
- package/src/server/conversation/openCodePlugin.js +84 -0
- package/src/server/conversation/openCodeProcess.js +476 -0
- package/src/server/conversation/openCodeRuntime.js +1578 -0
- package/src/server/conversation/openCodeTools.js +60 -0
- package/src/server/conversation/openCodeTurn.js +557 -0
- package/src/server/conversation/provider.js +30 -0
- package/src/server/conversation/providers/api.js +136 -0
- package/src/server/conversation/providers/claude.js +63 -0
- package/src/server/conversation/providers/claudeDriver.js +425 -0
- package/src/server/conversation/providers/codex.js +248 -0
- package/src/server/conversation/providers/codexDriver.js +1078 -0
- package/src/server/conversation/providers/opencode.js +49 -0
- package/src/server/conversation/providers/opencodeDriver.js +650 -0
- package/src/server/conversation/providers/scoped.js +312 -0
- package/src/server/conversation/runtime.js +2220 -0
- package/src/server/conversation/runtimeStateUpgrade.js +125 -0
- package/src/server/conversation/storage.js +184 -0
- package/src/server/conversation/streams.js +7 -2
- package/src/server/conversation/structuredOutput.js +240 -0
- package/src/server/conversation/systemPrompt.js +54 -0
- package/src/server/conversation/tools.js +84 -0
- package/src/server/conversation/transcript.js +5 -4
- package/src/server/hosts/claudeHistory.js +5 -0
- package/src/server/hosts/claudeProcess.js +11 -0
- package/src/server/hosts/claudeTurn.js +4 -0
- package/src/server/hosts/codexConfiguration.js +20 -0
- package/src/server/hosts/codexEvents.js +13 -0
- package/src/server/hosts/codexProcess.js +12 -0
- package/src/server/hosts/codexProvider.js +23 -0
- package/src/server/hosts/codexTurn.js +20 -0
- package/src/server/hosts/nativeHistory.js +3 -0
- package/src/server/hosts/openCodeClient.js +3 -0
- package/src/server/hosts/openCodeProcess.js +23 -0
- package/src/server/hosts/openCodeTurn.js +7 -0
- package/src/server/index.js +2 -0
- package/src/server/lib/aiConnectionClient.js +13 -6
- package/src/server/lib/assistantCompletion.js +190 -0
- package/src/server/lib/assistantToolLoop.js +314 -0
- package/src/server/lib/serviceToolCatalog.js +25 -10
- package/src/shared/assistantResponseText.js +29 -0
- package/src/shared/conversation/attachments.js +19 -0
- package/src/shared/conversation/index.js +3 -0
- package/src/shared/conversation/pagination.js +78 -0
- package/src/shared/conversation/patches.js +150 -0
- package/src/shared/conversation/questions.js +48 -0
- package/src/shared/conversation/streaming.js +7 -4
- package/src/shared/index.js +2 -0
- package/src/shared/nativeProviders.js +45 -0
- package/src/testing/conversationStorageContract.js +31 -1
- package/src/testing/nativeCodex.js +11 -0
- package/src/testing/nativeOpenCode.js +3 -0
- package/test/aiConnectionClient.test.js +2 -0
- package/test/apiConversationDriver.test.js +73 -0
- package/test/assistantApiSurfaceHeader.test.js +85 -0
- package/test/assistantBoundedToolLoop.test.js +285 -0
- package/test/assistantScroll.browser.test.js +16 -0
- package/test/claudeClient.test.js +189 -0
- package/test/claudeConversationSession.test.js +1173 -0
- package/test/claudeInstructionResume.test.js +82 -0
- package/test/claudeProcess.test.js +204 -0
- package/test/claudeTurn.test.js +253 -0
- package/test/codexConversationSession.test.js +2122 -0
- package/test/codexGoal.test.js +37 -0
- package/test/codexHelperDeletion.test.js +163 -0
- package/test/codexHelperThreadLedger.test.js +494 -0
- package/test/codexHistoryAdapter.test.js +541 -0
- package/test/codexInteractiveArguments.test.js +30 -0
- package/test/codexIsolation.test.js +756 -0
- package/test/codexModelCatalog.test.js +362 -0
- package/test/codexNativeHistoryExport.test.js +81 -0
- package/test/codexThreadSelection.test.js +136 -0
- package/test/codexTurn.test.js +2158 -0
- package/test/conversation.test.js +110 -0
- package/test/conversationAgentRun.test.js +326 -0
- package/test/conversationApiFacilities.test.js +90 -0
- package/test/conversationAttachments.test.js +238 -0
- package/test/conversationCommandWrapper.test.js +48 -0
- package/test/conversationConfiguration.test.js +174 -0
- package/test/conversationNativeStorage.test.js +25 -0
- package/test/conversationReplacement.test.js +172 -0
- package/test/conversationRuntime.test.js +241 -0
- package/test/conversationRuntimeStateUpgrade.test.js +144 -0
- package/test/conversationSession.test.js +1080 -0
- package/test/conversationStreaming.test.js +68 -0
- package/test/conversationSystemPrompt.test.js +84 -0
- package/test/conversationTools.test.js +416 -0
- package/test/fileConversationStorage.test.js +73 -0
- package/test/fixtures/codexHelperThreadLedger.js +39 -0
- package/test/localConversationExecution.test.js +90 -0
- package/test/messageDelivery.test.js +234 -1
- package/test/nativeConversationRetirement.test.js +215 -0
- package/test/openCodeClient.test.js +515 -0
- package/test/openCodeProcess.test.js +177 -0
- package/test/openCodeRuntime.test.js +322 -0
- package/test/openCodeTools.test.js +287 -0
- package/test/opencodeConversationSession.test.js +789 -0
- package/test/serviceToolCatalog.test.js +24 -0
|
@@ -1,26 +1,29 @@
|
|
|
1
|
-
import { reactive } from "vue";
|
|
1
|
+
import { reactive, toRaw, toValue, watch } from "vue";
|
|
2
2
|
|
|
3
3
|
const messageText = value => String(value || "").trim();
|
|
4
4
|
|
|
5
5
|
function turnMatchesOptimisticMessage(turn = {}, optimistic = {}) {
|
|
6
|
-
const
|
|
6
|
+
const authored = optimistic.origin === "application" ? turn.system : turn.user;
|
|
7
|
+
const canonicalMessageId = messageText(authored?.messageId);
|
|
7
8
|
const optimisticMessageId = messageText(optimistic.id);
|
|
8
9
|
if (canonicalMessageId && optimisticMessageId) {
|
|
9
10
|
return canonicalMessageId === optimisticMessageId;
|
|
10
11
|
}
|
|
11
|
-
if (messageText(
|
|
12
|
+
if (messageText(authored?.text) !== optimistic.text) {
|
|
12
13
|
return false;
|
|
13
14
|
}
|
|
14
|
-
const userAtMs = Date.parse(String(
|
|
15
|
+
const userAtMs = Date.parse(String(authored?.at || ""));
|
|
15
16
|
return Number.isFinite(userAtMs) && userAtMs >= optimistic.createdAtMs - 5000;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
function unmatchedOptimisticMessages(turns = [], optimisticMessages = []) {
|
|
19
|
+
function unmatchedOptimisticMessages(turns = [], optimisticMessages = [], { receiptsOnly = false } = {}) {
|
|
19
20
|
const conversationTurns = Array.isArray(turns) ? turns : [];
|
|
20
21
|
const matchedTurnIndexes = new Set();
|
|
21
22
|
return (Array.isArray(optimisticMessages) ? optimisticMessages : []).filter((message) => {
|
|
22
23
|
const turnIndex = conversationTurns.findIndex((turn, index) => (
|
|
23
|
-
!matchedTurnIndexes.has(index) && turnMatchesOptimisticMessage(turn, message)
|
|
24
|
+
!matchedTurnIndexes.has(index) && turnMatchesOptimisticMessage(turn, message) &&
|
|
25
|
+
(!receiptsOnly || message.status === "accepted" ||
|
|
26
|
+
(message.origin === "application" ? turn.system : turn.user)?.receipt !== false)
|
|
24
27
|
));
|
|
25
28
|
if (turnIndex < 0) {
|
|
26
29
|
return true;
|
|
@@ -31,9 +34,10 @@ function unmatchedOptimisticMessages(turns = [], optimisticMessages = []) {
|
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
// One delivery state per conversation. The caller owns transport and receipts;
|
|
34
|
-
// the shared assistant element renders
|
|
37
|
+
// the shared assistant element renders pending, failed and uncertain turns.
|
|
35
38
|
function createAssistantMessageDelivery({ deliver: defaultDeliver } = {}) {
|
|
36
39
|
const state = reactive({ messages: [], sending: false });
|
|
40
|
+
const acceptanceCallbacks = new WeakMap();
|
|
37
41
|
let deliveryTail = null;
|
|
38
42
|
let pendingSends = new Map();
|
|
39
43
|
|
|
@@ -46,35 +50,64 @@ function createAssistantMessageDelivery({ deliver: defaultDeliver } = {}) {
|
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
function reconcile(turns) {
|
|
49
|
-
|
|
53
|
+
const unmatched = unmatchedOptimisticMessages(turns, state.messages, { receiptsOnly: true });
|
|
54
|
+
const retained = new Set(unmatched.map(message => message.id));
|
|
55
|
+
for (const message of state.messages) {
|
|
56
|
+
if (!retained.has(message.id)) accept(message.id);
|
|
57
|
+
}
|
|
58
|
+
state.messages = unmatched;
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
function turns(savedTurns = []) {
|
|
53
62
|
return [
|
|
54
|
-
...savedTurns
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
text: message.text
|
|
64
|
-
}
|
|
65
|
-
}))
|
|
63
|
+
...savedTurns.map(turn => {
|
|
64
|
+
const message = state.messages.find(message => message.status !== "accepted" &&
|
|
65
|
+
turnMatchesOptimisticMessage(turn, message) &&
|
|
66
|
+
(message.origin === "application" ? turn.system : turn.user)?.receipt === false);
|
|
67
|
+
if (!message) return turn;
|
|
68
|
+
const pending = optimisticTurn(message);
|
|
69
|
+
return { ...turn, optimistic: pending.optimistic, ...(pending.system ? { system: pending.system } : {}) };
|
|
70
|
+
}),
|
|
71
|
+
...unmatchedOptimisticMessages(savedTurns, state.messages).map(optimisticTurn)
|
|
66
72
|
];
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
function optimisticTurn(message) {
|
|
76
|
+
const role = message.origin === "application" ? "system" : "user";
|
|
77
|
+
const authored = {
|
|
78
|
+
attachments: message.attachments,
|
|
79
|
+
at: message.createdAt,
|
|
80
|
+
messageId: message.id,
|
|
81
|
+
role,
|
|
82
|
+
text: message.text
|
|
83
|
+
};
|
|
84
|
+
const status = message.status === "uncertain" ? {
|
|
85
|
+
role: "system", text: "Message delivery is not confirmed.",
|
|
86
|
+
delivery: { messageId: message.id, error: message.error, checking: message.checking === true }
|
|
87
|
+
} : null;
|
|
88
|
+
return {
|
|
89
|
+
optimistic: { error: message.error, id: message.id, status: message.status },
|
|
90
|
+
turnId: message.id,
|
|
91
|
+
[role]: authored,
|
|
92
|
+
...(status ? { system: role === "system" ? { ...authored, ...status, text: `${authored.text}\n\n${status.text}` } : status } : {})
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
69
96
|
async function send(payload, {
|
|
70
97
|
messageId = crypto.randomUUID(),
|
|
71
98
|
deliver = defaultDeliver,
|
|
72
99
|
isCurrent = () => true,
|
|
100
|
+
receiptTurns = null,
|
|
101
|
+
onAccepted,
|
|
102
|
+
uncertainOnError = false,
|
|
73
103
|
queue = false
|
|
74
104
|
} = {}) {
|
|
75
|
-
if ((state.sending && !queue) || pendingSends.has(messageId) ||
|
|
105
|
+
if ((state.sending && !queue) || pendingSends.has(messageId) || find(messageId)?.status === "uncertain" ||
|
|
106
|
+
(!messageText(payload?.message) && !payload?.displayAttachments?.length)) return false;
|
|
76
107
|
if (typeof deliver !== "function") throw new TypeError("Message delivery requires a deliver function.");
|
|
77
108
|
const snapshot = JSON.parse(JSON.stringify(payload));
|
|
109
|
+
const previous = find(messageId);
|
|
110
|
+
const acknowledge = previous && acceptanceCallbacks.get(toRaw(previous)) || onAccepted;
|
|
78
111
|
const ownerPendingSends = pendingSends;
|
|
79
112
|
const current = () => pendingSends === ownerPendingSends && isCurrent();
|
|
80
113
|
const predecessor = deliveryTail;
|
|
@@ -90,27 +123,44 @@ function createAssistantMessageDelivery({ deliver: defaultDeliver } = {}) {
|
|
|
90
123
|
id: messageId,
|
|
91
124
|
payload: snapshot,
|
|
92
125
|
status: "pending",
|
|
93
|
-
text: String(snapshot.displayMessage || snapshot.message).trim()
|
|
126
|
+
text: String(snapshot.displayMessage || snapshot.message || "").trim()
|
|
94
127
|
};
|
|
128
|
+
if (typeof acknowledge === "function") acceptanceCallbacks.set(message, acknowledge);
|
|
95
129
|
state.messages = [...state.messages.filter((entry) => entry.id !== messageId), message];
|
|
96
130
|
state.sending = true;
|
|
131
|
+
let stopReceipt;
|
|
97
132
|
try {
|
|
98
133
|
if (predecessor) await predecessor;
|
|
99
134
|
if (!current()) return false;
|
|
100
|
-
|
|
135
|
+
if (uncertainOnError && state.messages.some(entry => entry.id !== messageId && entry.status === "uncertain")) {
|
|
136
|
+
fail(messageId, "Check the previous message's delivery before sending again.");
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
let receipt;
|
|
140
|
+
if (receiptTurns !== null) {
|
|
141
|
+
receipt = Promise.withResolvers();
|
|
142
|
+
settled.receipt = receipt;
|
|
143
|
+
stopReceipt = watch([current, () => toValue(receiptTurns)], ([isCurrent, turns]) => {
|
|
144
|
+
if (!isCurrent) receipt.resolve(false);
|
|
145
|
+
else if (turns?.some(turn => turn.user?.messageId === messageId && turn.user.receipt !== false)) receipt.resolve({ ok: true });
|
|
146
|
+
}, { immediate: true });
|
|
147
|
+
}
|
|
148
|
+
const submitted = deliver({ ...snapshot, messageId });
|
|
149
|
+
const response = await (receipt ? Promise.race([submitted, receipt.promise]) : submitted);
|
|
101
150
|
if (!current()) return false;
|
|
102
151
|
if (response === false || response?.ok === false) {
|
|
103
|
-
fail(messageId, response?.error || "Message could not be sent.");
|
|
152
|
+
fail(messageId, response?.error || "Message could not be sent.", response?.status === "uncertain" ? "uncertain" : "failed");
|
|
104
153
|
} else {
|
|
105
|
-
|
|
106
|
-
if (acceptedMessage) acceptedMessage.status = "accepted";
|
|
154
|
+
accept(messageId);
|
|
107
155
|
}
|
|
108
156
|
return response;
|
|
109
157
|
} catch (error) {
|
|
110
158
|
if (!current()) return false;
|
|
111
|
-
|
|
159
|
+
if (find(messageId)?.status === "accepted") return { ok: true };
|
|
160
|
+
fail(messageId, error, uncertainOnError || error?.status === "uncertain" ? "uncertain" : "failed");
|
|
112
161
|
throw error;
|
|
113
162
|
} finally {
|
|
163
|
+
stopReceipt?.();
|
|
114
164
|
ownerPendingSends.delete(messageId);
|
|
115
165
|
if (pendingSends === ownerPendingSends) {
|
|
116
166
|
state.sending = ownerPendingSends.size > 0;
|
|
@@ -120,14 +170,43 @@ function createAssistantMessageDelivery({ deliver: defaultDeliver } = {}) {
|
|
|
120
170
|
}
|
|
121
171
|
}
|
|
122
172
|
|
|
123
|
-
function fail(messageId, error) {
|
|
173
|
+
function fail(messageId, error, status = "failed") {
|
|
124
174
|
const message = find(messageId);
|
|
125
|
-
if (message) {
|
|
175
|
+
if (message && message.status !== "accepted") {
|
|
126
176
|
message.error = String(error?.message || error || "Message could not be sent.").trim();
|
|
127
|
-
message.status =
|
|
177
|
+
message.status = status;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function accept(messageId) {
|
|
182
|
+
const message = find(messageId);
|
|
183
|
+
if (message) { message.status = "accepted"; message.error = ""; }
|
|
184
|
+
pendingSends.get(messageId)?.receipt?.resolve({ ok: true });
|
|
185
|
+
if (message) {
|
|
186
|
+
const entry = toRaw(message);
|
|
187
|
+
const acknowledge = acceptanceCallbacks.get(entry);
|
|
188
|
+
acceptanceCallbacks.delete(entry);
|
|
189
|
+
acknowledge?.();
|
|
128
190
|
}
|
|
129
191
|
}
|
|
130
192
|
|
|
193
|
+
function restoreUncertain(request, savedTurns = []) {
|
|
194
|
+
const messageId = messageText(request?.messageId);
|
|
195
|
+
if (!messageId || savedTurns.some(turn => (turn.user || turn.system)?.messageId === messageId &&
|
|
196
|
+
(turn.user || turn.system).receipt !== false) ||
|
|
197
|
+
find(messageId)?.status === "accepted") return false;
|
|
198
|
+
if (!find(messageId)) {
|
|
199
|
+
const payload = JSON.parse(JSON.stringify({ message: request.text || "", displayAttachments: request.attachments || [] }));
|
|
200
|
+
const createdAt = request.at || new Date().toISOString();
|
|
201
|
+
state.messages = [...state.messages, { id: messageId, payload, text: payload.message, origin: request.origin,
|
|
202
|
+
attachments: payload.displayAttachments, createdAt, createdAtMs: Date.parse(createdAt),
|
|
203
|
+
status: "uncertain", error: "" }];
|
|
204
|
+
}
|
|
205
|
+
fail(messageId, request.error || "Delivery could not be confirmed.", "uncertain");
|
|
206
|
+
pendingSends.get(messageId)?.receipt?.resolve({ ok: false, status: "uncertain", error: request.error });
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
131
210
|
function cancel(messageId) {
|
|
132
211
|
if (find(messageId)?.status !== "failed") return false;
|
|
133
212
|
remove(messageId);
|
|
@@ -148,13 +227,17 @@ function createAssistantMessageDelivery({ deliver: defaultDeliver } = {}) {
|
|
|
148
227
|
|
|
149
228
|
function reset() {
|
|
150
229
|
deliveryTail = null;
|
|
151
|
-
for (const pending of pendingSends.values())
|
|
230
|
+
for (const pending of pendingSends.values()) {
|
|
231
|
+
pending.receipt?.resolve(false);
|
|
232
|
+
pending.resolve();
|
|
233
|
+
}
|
|
152
234
|
pendingSends = new Map();
|
|
153
235
|
state.messages = [];
|
|
154
236
|
state.sending = false;
|
|
155
237
|
}
|
|
156
238
|
|
|
157
|
-
return { cancel, edit, find, reconcile, remove, resend, reset, send, state, turns };
|
|
239
|
+
return { accept, cancel, edit, find, reconcile, remove, resend, reset, restoreUncertain, send, state, turns };
|
|
158
240
|
}
|
|
159
241
|
|
|
160
242
|
export { createAssistantMessageDelivery, unmatchedOptimisticMessages };
|
|
243
|
+
export { retainAssistantConversation } from "./retainedConversation.js";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { effectScope, shallowReactive } from "vue";
|
|
2
|
+
|
|
3
|
+
const applicationConversations = new WeakMap();
|
|
4
|
+
|
|
5
|
+
/** Views and voice retain the same readers and delivery state for an exact target. */
|
|
6
|
+
function retainAssistantConversation(app, key, createRuntime, reader = { active: true }) {
|
|
7
|
+
let conversations = applicationConversations.get(app);
|
|
8
|
+
if (!conversations) { conversations = new Map(); applicationConversations.set(app, conversations); }
|
|
9
|
+
let entry = conversations.get(key);
|
|
10
|
+
if (!entry) {
|
|
11
|
+
const scope = effectScope(true);
|
|
12
|
+
const readers = shallowReactive(new Map());
|
|
13
|
+
try {
|
|
14
|
+
const runtime = app.runWithContext(() => scope.run(() => createRuntime(readers)));
|
|
15
|
+
entry = { scope, readers, runtime };
|
|
16
|
+
runtime.retain = () => retainAssistantConversation(app, key, createRuntime);
|
|
17
|
+
conversations.set(key, entry);
|
|
18
|
+
} catch (error) { scope.stop(); throw error; }
|
|
19
|
+
}
|
|
20
|
+
const token = Symbol("conversation reader");
|
|
21
|
+
entry.readers.set(token, reader);
|
|
22
|
+
return {
|
|
23
|
+
runtime: entry.runtime,
|
|
24
|
+
release() {
|
|
25
|
+
if (!entry.readers.delete(token) || entry.readers.size) return;
|
|
26
|
+
entry.scope.stop();
|
|
27
|
+
conversations.delete(key);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { retainAssistantConversation };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { computed, ref, toValue, watch } from "vue";
|
|
2
|
+
import { numberedQuestionSubmissionText, parseNumberedQuestionPrompt } from "../../shared/conversation/numberedQuestionSugar.js";
|
|
3
|
+
import { parseAnswerChoicePrompt } from "../../shared/conversation/answerChoiceSugar.js";
|
|
4
|
+
|
|
5
|
+
/** Ordinary chat answer fields; the caller owns authorized message selection and delivery. */
|
|
6
|
+
function useAssistantQuestions({ message = "", extraChoice = null } = {}) {
|
|
7
|
+
const questionText = computed(() => String(toValue(message) || ""));
|
|
8
|
+
const answers = ref({});
|
|
9
|
+
const choice = ref("");
|
|
10
|
+
const dismissedText = ref("");
|
|
11
|
+
const submittedText = ref("");
|
|
12
|
+
const numberedInput = computed(() => parseNumberedQuestionPrompt(questionText.value));
|
|
13
|
+
const questions = computed(() => (
|
|
14
|
+
[dismissedText.value, submittedText.value].includes(questionText.value)
|
|
15
|
+
? []
|
|
16
|
+
: numberedInput.value.questions || []
|
|
17
|
+
));
|
|
18
|
+
const selectItems = computed(() => Object.fromEntries(
|
|
19
|
+
questions.value.map((question) => {
|
|
20
|
+
const choices = Array.isArray(question.choices) ? question.choices : [];
|
|
21
|
+
const extra = toValue(extraChoice);
|
|
22
|
+
const includesExtra = extra && choices.some((item) => (
|
|
23
|
+
String(item?.value || "").trim().toLowerCase() === String(extra.value || "").trim().toLowerCase()
|
|
24
|
+
));
|
|
25
|
+
return [question.name, !extra || includesExtra ? choices : [...choices, extra]];
|
|
26
|
+
})
|
|
27
|
+
));
|
|
28
|
+
const choices = computed(() => (
|
|
29
|
+
questions.value.length || submittedText.value === questionText.value
|
|
30
|
+
? []
|
|
31
|
+
: parseAnswerChoicePrompt(questionText.value).choices || []
|
|
32
|
+
));
|
|
33
|
+
const active = computed(() => Boolean(questions.value.length || choices.value.length));
|
|
34
|
+
|
|
35
|
+
function capture(draft = "") {
|
|
36
|
+
const additionalContext = String(draft || "").trim();
|
|
37
|
+
return {
|
|
38
|
+
message: questions.value.length
|
|
39
|
+
? [numberedQuestionSubmissionText(questions.value, answers.value), additionalContext].filter(Boolean).join("\n\n")
|
|
40
|
+
: choice.value || additionalContext,
|
|
41
|
+
questionText: active.value ? questionText.value : ""
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function markSubmitted(text = "") {
|
|
46
|
+
submittedText.value = text;
|
|
47
|
+
answers.value = {};
|
|
48
|
+
choice.value = "";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function dismiss() {
|
|
52
|
+
if (!questions.value.length) return false;
|
|
53
|
+
dismissedText.value = questionText.value;
|
|
54
|
+
answers.value = {};
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function reset() {
|
|
59
|
+
answers.value = {};
|
|
60
|
+
dismissedText.value = "";
|
|
61
|
+
submittedText.value = "";
|
|
62
|
+
choice.value = "";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
watch(questionText, (text) => {
|
|
66
|
+
answers.value = {};
|
|
67
|
+
if (text !== dismissedText.value) dismissedText.value = "";
|
|
68
|
+
if (text !== submittedText.value) submittedText.value = "";
|
|
69
|
+
choice.value = "";
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return { questions, selectItems, choices, answers, choice, active, capture, markSubmitted, dismiss, reset,
|
|
73
|
+
setAnswers(value) { answers.value = value; }, setChoice(value) { choice.value = value; } };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { useAssistantQuestions };
|
|
@@ -77,12 +77,82 @@ function resolveRequiredBasePath(resolveBasePath) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
function createAssistantApi({ request, requestStream, resolveBasePath, resolveSurfaceId = null } = {}) {
|
|
80
|
-
if (typeof request !== "function"
|
|
81
|
-
throw new Error("createAssistantApi requires request()
|
|
80
|
+
if (typeof request !== "function") {
|
|
81
|
+
throw new Error("createAssistantApi requires request().");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function requestConversation(conversationId, suffix, options) {
|
|
85
|
+
const basePath = resolveRequiredBasePath(resolveBasePath);
|
|
86
|
+
const encodedId = encodeURIComponent(String(conversationId || "").trim());
|
|
87
|
+
const headers = resolveAssistantRequestHeaders(resolveSurfaceId);
|
|
88
|
+
return request(`${basePath}/conversations/${encodedId}${suffix}`, {
|
|
89
|
+
...options,
|
|
90
|
+
...(headers ? { headers } : {})
|
|
91
|
+
});
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
return Object.freeze({
|
|
95
|
+
readConversation(conversationId, { signal, beforeTurnId, limit } = {}) {
|
|
96
|
+
const params = new URLSearchParams();
|
|
97
|
+
appendQueryParam(params, "beforeTurnId", beforeTurnId);
|
|
98
|
+
appendQueryParam(params, "limit", limit);
|
|
99
|
+
return requestConversation(conversationId, params.size ? `?${params}` : "", { method: "GET", signal });
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
sendConversationMessage(conversationId, payload, { signal } = {}) {
|
|
103
|
+
return requestConversation(conversationId, "/messages", {
|
|
104
|
+
method: "POST", signal,
|
|
105
|
+
body: {
|
|
106
|
+
messageId: payload.messageId,
|
|
107
|
+
text: payload.text,
|
|
108
|
+
...(Object.hasOwn(payload, "data") ? { data: payload.data } : {}),
|
|
109
|
+
...(Object.hasOwn(payload, "attachmentIds") ? { attachmentIds: payload.attachmentIds } : {}),
|
|
110
|
+
...(Object.hasOwn(payload, "steer") ? { steer: payload.steer } : {})
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
cancelConversation(conversationId, { signal } = {}) {
|
|
116
|
+
return requestConversation(conversationId, "/cancel", { method: "POST", signal });
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
inspectConversationDelivery(conversationId, messageId, { signal } = {}) {
|
|
120
|
+
const encodedMessageId = encodeURIComponent(String(messageId || "").trim());
|
|
121
|
+
return requestConversation(conversationId, `/deliveries/${encodedMessageId}/inspect`, { method: "POST", signal });
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
readConversationGoal(conversationId, { signal } = {}) {
|
|
125
|
+
return requestConversation(conversationId, "/goal", { method: "GET", signal });
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
updateConversationGoal(conversationId, payload, { signal } = {}) {
|
|
129
|
+
const body = {};
|
|
130
|
+
for (const key of ["action", "expectedSegmentId", "expectedGoalId", "messageId", "objective", "tokenBudget", "attachmentIds"]) {
|
|
131
|
+
if (Object.hasOwn(payload, key)) body[key] = payload[key];
|
|
132
|
+
}
|
|
133
|
+
return requestConversation(conversationId, "/goal", { method: "POST", signal, body });
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
configureConversation(conversationId, configuration, { signal } = {}) {
|
|
137
|
+
return requestConversation(conversationId, "/configuration", {
|
|
138
|
+
method: "PATCH", signal, body: { configuration }
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
selectConversation(conversationId, selection, { signal } = {}) {
|
|
143
|
+
return requestConversation(conversationId, "/selection", {
|
|
144
|
+
method: "POST", signal, body: { selection }
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
replaceConversation(conversationId, replacement, { signal } = {}) {
|
|
149
|
+
return requestConversation(conversationId, "/replacement", {
|
|
150
|
+
method: "POST", signal, body: { replacement }
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
|
|
85
154
|
async streamChat(payload, { signal, onEvent, onMalformedLine, rejectOnErrorEvent = true } = {}) {
|
|
155
|
+
if (typeof requestStream !== "function") throw new TypeError("streamChat requires requestStream().");
|
|
86
156
|
const basePath = resolveRequiredBasePath(resolveBasePath);
|
|
87
157
|
let streamEventError = null;
|
|
88
158
|
const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId);
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { isPlainObject, normalizeText } from "./normalize.js";
|
|
2
|
+
import { createReentrantConversationStorage } from "./storage.js";
|
|
3
|
+
import { normalizeCodexAppServerRunState, codexAppServerRunStateIsActive,
|
|
4
|
+
codexAppServerRunStateIsTerminal } from "./codexTurn.js";
|
|
5
|
+
|
|
6
|
+
const normalizeRunState = state => normalizeCodexAppServerRunState(normalizeText(state));
|
|
7
|
+
const runStateIsActive = state => codexAppServerRunStateIsActive(normalizeText(state));
|
|
8
|
+
const runStateIsTerminal = state => codexAppServerRunStateIsTerminal(normalizeText(state));
|
|
9
|
+
|
|
10
|
+
export function conversationAgentRunRecord(record, normalizedRunId, {
|
|
11
|
+
normalizeState = normalizeRunState,
|
|
12
|
+
isActive = runStateIsActive
|
|
13
|
+
} = {}) {
|
|
14
|
+
return isPlainObject(record)
|
|
15
|
+
? {
|
|
16
|
+
...record,
|
|
17
|
+
active: isActive(record.state),
|
|
18
|
+
events: Array.isArray(record.events) ? record.events.filter(isPlainObject) : [],
|
|
19
|
+
id: normalizedRunId,
|
|
20
|
+
state: normalizeState(record.state)
|
|
21
|
+
}
|
|
22
|
+
: null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function conversationAgentRunEvent(previous, normalizedRunId, {
|
|
26
|
+
event = {},
|
|
27
|
+
patch = {}
|
|
28
|
+
} = {}, {
|
|
29
|
+
now = () => new Date(),
|
|
30
|
+
normalizeState = normalizeRunState,
|
|
31
|
+
isTerminal = runStateIsTerminal
|
|
32
|
+
} = {}) {
|
|
33
|
+
const eventAt = normalizeText(event.at || patch.updatedAt) || now().toISOString();
|
|
34
|
+
const state = normalizeState(patch.state || event.state || previous.state);
|
|
35
|
+
const terminalState = isTerminal(state);
|
|
36
|
+
const eventRecord = {
|
|
37
|
+
...event,
|
|
38
|
+
at: eventAt,
|
|
39
|
+
kind: normalizeText(event.kind || state || "updated"),
|
|
40
|
+
message: normalizeText(event.message || patch.message),
|
|
41
|
+
state
|
|
42
|
+
};
|
|
43
|
+
const record = {
|
|
44
|
+
...previous,
|
|
45
|
+
...patch,
|
|
46
|
+
active: !terminalState,
|
|
47
|
+
events: [
|
|
48
|
+
...(Array.isArray(previous.events) ? previous.events : []),
|
|
49
|
+
eventRecord
|
|
50
|
+
],
|
|
51
|
+
finishedAt: terminalState
|
|
52
|
+
? normalizeText(patch.finishedAt || previous.finishedAt) || eventAt
|
|
53
|
+
: "",
|
|
54
|
+
id: normalizedRunId,
|
|
55
|
+
startedAt: normalizeText(previous.startedAt || patch.startedAt) || eventAt,
|
|
56
|
+
state,
|
|
57
|
+
updatedAt: eventAt
|
|
58
|
+
};
|
|
59
|
+
if (!terminalState && !Object.hasOwn(patch, "error")) {
|
|
60
|
+
record.error = "";
|
|
61
|
+
}
|
|
62
|
+
return record;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The original native run occupies its bound conversation segment. Reading a
|
|
66
|
+
* binding without a run never constructs historical ownership or writes state.
|
|
67
|
+
*/
|
|
68
|
+
export function createCodexConversationRunStore({ storage, scope, segmentId, isCurrent, clock = () => new Date() } = {}) {
|
|
69
|
+
if (typeof isCurrent !== "function") throw new TypeError("A native run store requires its provider ownership fence.");
|
|
70
|
+
storage = createReentrantConversationStorage(storage);
|
|
71
|
+
const runId = "codex_app_server";
|
|
72
|
+
|
|
73
|
+
function assertScope(sessionId, requestedRunId = runId) {
|
|
74
|
+
if (sessionId !== scope) throw new Error("The native run belongs to another conversation.");
|
|
75
|
+
if (normalizeText(requestedRunId) !== runId) throw new TypeError("This binding stores only its Codex app-server run.");
|
|
76
|
+
if (!isCurrent()) throw new Error("A retired provider cannot change a native binding.");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function currentMetadata(transaction) {
|
|
80
|
+
const metadata = await transaction.readMetadata();
|
|
81
|
+
if (!isCurrent() || metadata.runtime?.segmentId !== segmentId || metadata.runtime?.engine !== "codex") {
|
|
82
|
+
throw new Error("A retired native conversation cannot change the current binding.");
|
|
83
|
+
}
|
|
84
|
+
return metadata;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function readAgentRun(sessionId, requestedRunId) {
|
|
88
|
+
assertScope(sessionId, requestedRunId);
|
|
89
|
+
return storage.read(scope, async transaction => {
|
|
90
|
+
const metadata = await currentMetadata(transaction);
|
|
91
|
+
return conversationAgentRunRecord(metadata.runtime.binding?.codexAppServerRun, runId);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function mutateSession(sessionId, operation) {
|
|
96
|
+
assertScope(sessionId);
|
|
97
|
+
return storage.write(scope, async transaction => {
|
|
98
|
+
await currentMetadata(transaction);
|
|
99
|
+
return operation(transaction);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return Object.freeze({
|
|
104
|
+
readAgentRun,
|
|
105
|
+
mutateSession,
|
|
106
|
+
async getSession(sessionId) {
|
|
107
|
+
const run = await readAgentRun(sessionId, runId);
|
|
108
|
+
return { sessionId, agentRuns: run ? [run] : [] };
|
|
109
|
+
},
|
|
110
|
+
async writeAgentRunEvent(sessionId, requestedRunId, input = {}) {
|
|
111
|
+
assertScope(sessionId, requestedRunId);
|
|
112
|
+
return mutateSession(sessionId, async transaction => {
|
|
113
|
+
const previous = await readAgentRun(sessionId, runId) || { events: [], id: runId };
|
|
114
|
+
const record = conversationAgentRunEvent(previous, runId, input, { now: clock });
|
|
115
|
+
const metadata = await currentMetadata(transaction);
|
|
116
|
+
metadata.runtime.binding.codexAppServerRun = record;
|
|
117
|
+
await transaction.writeMetadata(metadata);
|
|
118
|
+
return record;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The native owner uses the existing transcript and transient streams through
|
|
125
|
+
* the same fenced transaction as its original run. Its private delivery read is
|
|
126
|
+
* a projection of the one authored request, never another stored journal.
|
|
127
|
+
*/
|
|
128
|
+
export function createCodexConversationStore({ storage, scope, segmentId, isCurrent, transcript, streams } = {}) {
|
|
129
|
+
const runStore = createCodexConversationRunStore({ storage, scope, segmentId, isCurrent });
|
|
130
|
+
const assertCurrent = sessionId => {
|
|
131
|
+
if (sessionId !== scope || !isCurrent()) throw new Error("A retired provider cannot change this conversation.");
|
|
132
|
+
};
|
|
133
|
+
const reader = name => (sessionId, ...args) => {
|
|
134
|
+
assertCurrent(sessionId);
|
|
135
|
+
return transcript[name](sessionId, ...args);
|
|
136
|
+
};
|
|
137
|
+
async function nativeOrigin(transaction, identity) {
|
|
138
|
+
if (!identity?.threadId || !identity.turnId) return undefined;
|
|
139
|
+
const { runtime } = await transaction.readMetadata();
|
|
140
|
+
const run = runtime.binding?.codexAppServerRun;
|
|
141
|
+
if (runtime.segmentId !== segmentId || runtime.binding.threadId !== identity.threadId ||
|
|
142
|
+
run?.providerThreadId !== identity.threadId || run.providerTurnId !== identity.turnId || !run.outerTurnId) return undefined;
|
|
143
|
+
for (const turnId of (await transaction.listTurnIds()).reverse()) {
|
|
144
|
+
const turn = await transaction.readTurn(turnId);
|
|
145
|
+
if (turn?.metadata?.runtime?.segmentId !== segmentId) continue;
|
|
146
|
+
if (turn.user?.messageId === run.outerTurnId) return "user";
|
|
147
|
+
if (turn.system?.messageId === run.outerTurnId && turn.system.origin === "application") return "application";
|
|
148
|
+
}
|
|
149
|
+
const request = runtime.request;
|
|
150
|
+
if (request?.messageId === run.outerTurnId && ["user", "application"].includes(request.origin)) return request.origin;
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
const writer = name => (sessionId, input) => runStore.mutateSession(sessionId, async transaction => {
|
|
154
|
+
const written = await transcript[name](sessionId, input);
|
|
155
|
+
if (!written || written.metadata?.runtime?.segmentId) return written;
|
|
156
|
+
const origin = await nativeOrigin(transaction, input.nativeIdentity);
|
|
157
|
+
await transaction.updateTurnMetadata(written.turnId, { runtime: {
|
|
158
|
+
status: "complete", engine: "codex", segmentId, ...(origin ? { origin } : {}),
|
|
159
|
+
...(input.nativeIdentity?.turnId ? { nativeTurnId: input.nativeIdentity.turnId } : {})
|
|
160
|
+
} });
|
|
161
|
+
return transaction.readTurn(written.turnId);
|
|
162
|
+
});
|
|
163
|
+
return Object.freeze({
|
|
164
|
+
...runStore,
|
|
165
|
+
readConversationLog: reader("readConversationLog"),
|
|
166
|
+
conversationMessageIdExists: reader("conversationMessageIdExists"),
|
|
167
|
+
writeConversationAssistantMessage: writer("writeConversationAssistantMessage"),
|
|
168
|
+
writeConversationCommentaryMessage: writer("writeConversationCommentaryMessage"),
|
|
169
|
+
writeConversationThinkingMessage: writer("writeConversationThinkingMessage"),
|
|
170
|
+
upsertConversationAssistantMessage: writer("upsertConversationAssistantMessage"),
|
|
171
|
+
async writeConversationUserMessage(sessionId, input = {}) {
|
|
172
|
+
return runStore.mutateSession(sessionId, async transaction => {
|
|
173
|
+
if (input.messageId && await transaction.hasMessage(input.messageId)) return null;
|
|
174
|
+
const metadata = await transaction.readMetadata();
|
|
175
|
+
const request = input.authoredRequest?.messageId === input.messageId ? input.authoredRequest
|
|
176
|
+
: metadata.runtime.request?.messageId === input.messageId ? metadata.runtime.request : null;
|
|
177
|
+
if (!request) {
|
|
178
|
+
const written = await transcript.writeConversationUserMessage(sessionId, input);
|
|
179
|
+
if (!written) return null;
|
|
180
|
+
await transaction.updateTurnMetadata(written.turnId, { runtime: {
|
|
181
|
+
status: "running", engine: "codex", segmentId, origin: "user",
|
|
182
|
+
...(input.nativeIdentity?.turnId ? { nativeTurnId: input.nativeIdentity.turnId } : {})
|
|
183
|
+
} });
|
|
184
|
+
return transaction.readTurn(written.turnId);
|
|
185
|
+
}
|
|
186
|
+
const turnId = await transaction.nextTurnId();
|
|
187
|
+
await transaction.appendMessage(turnId, {
|
|
188
|
+
role: request.origin === "application" ? "system" : "user",
|
|
189
|
+
messageId: request.messageId, text: request.text, origin: request.origin || "user",
|
|
190
|
+
attachments: request.attachments || input.attachments || [], at: request.at || new Date().toISOString(),
|
|
191
|
+
...(request.data !== undefined ? { data: request.data } : {}), ...(request.goal ? { goal: request.goal } : {}),
|
|
192
|
+
turnMetadata: { ...input.turnMetadata, runtime: { ...input.turnMetadata?.runtime,
|
|
193
|
+
status: "running", engine: "codex", segmentId, origin: request.origin || "user",
|
|
194
|
+
...(input.nativeIdentity?.turnId ? { nativeTurnId: input.nativeIdentity.turnId } : {}),
|
|
195
|
+
...(request.goal ? { goalMessageId: request.messageId } : {}) } }
|
|
196
|
+
});
|
|
197
|
+
return transaction.readTurn(turnId);
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
readMetadataValue(sessionId, key) {
|
|
201
|
+
assertCurrent(sessionId);
|
|
202
|
+
if (key !== "assistant_delivery") throw new Error("The native conversation requested unknown delivery metadata.");
|
|
203
|
+
return storage.read(sessionId, async transaction => {
|
|
204
|
+
const { runtime } = await transaction.readMetadata();
|
|
205
|
+
if (!isCurrent() || runtime.segmentId !== segmentId || runtime.engine !== "codex") {
|
|
206
|
+
throw new Error("A retired native conversation cannot read current delivery metadata.");
|
|
207
|
+
}
|
|
208
|
+
const request = runtime.request;
|
|
209
|
+
return JSON.stringify({ engines: { codex: { pending: request && { ...request,
|
|
210
|
+
displayMessage: request.displayMessage ?? request.text,
|
|
211
|
+
displayAttachments: request.displayAttachments ?? request.attachments } } } });
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
updateConversationStream(sessionId, input) {
|
|
215
|
+
return runStore.mutateSession(sessionId, async transaction => streams.update(sessionId, {
|
|
216
|
+
...input, origin: await nativeOrigin(transaction, input.nativeIdentity)
|
|
217
|
+
}));
|
|
218
|
+
},
|
|
219
|
+
completeConversationStreamMessage(sessionId, messageId) { assertCurrent(sessionId); return streams.complete(sessionId, messageId); },
|
|
220
|
+
clearConversationStream(sessionId) { assertCurrent(sessionId); return streams.clear(sessionId); }
|
|
221
|
+
});
|
|
222
|
+
}
|