@tangle-network/agent-app 0.43.25 → 0.43.26
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/dist/app-auth/index.d.ts +1 -1
- package/dist/app-auth/index.js +1 -1
- package/dist/assistant/index.d.ts +1 -0
- package/dist/assistant/index.js +2 -1
- package/dist/assistant/index.js.map +1 -1
- package/dist/chat-routes/index.d.ts +276 -0
- package/dist/chat-routes/index.js +564 -0
- package/dist/chat-routes/index.js.map +1 -0
- package/dist/chat-store/index.d.ts +3 -2
- package/dist/chat-store/index.js +7 -3
- package/dist/chat-store/index.js.map +1 -1
- package/dist/{chunk-4H77LX3V.js → chunk-5EQCITY3.js} +2 -20
- package/dist/chunk-5EQCITY3.js.map +1 -0
- package/dist/chunk-5SV5PSU7.js +80 -0
- package/dist/chunk-5SV5PSU7.js.map +1 -0
- package/dist/chunk-7LNGJDNA.js +1 -0
- package/dist/chunk-7LNGJDNA.js.map +1 -0
- package/dist/{chunk-77RLNLFP.js → chunk-ATRJULKZ.js} +41 -5
- package/dist/chunk-ATRJULKZ.js.map +1 -0
- package/dist/{chunk-PEPXQTJ3.js → chunk-FCQP75JT.js} +16 -5
- package/dist/chunk-FCQP75JT.js.map +1 -0
- package/dist/chunk-I2R2XT4M.js +62 -0
- package/dist/chunk-I2R2XT4M.js.map +1 -0
- package/dist/{chunk-AVBANQ67.js → chunk-TKVJE63N.js} +10 -1
- package/dist/{chunk-AVBANQ67.js.map → chunk-TKVJE63N.js.map} +1 -1
- package/dist/{chunk-U7DLCPJ6.js → chunk-Y4QHNQ75.js} +1 -1
- package/dist/core-7qIM7svy.d.ts +21 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +49 -44
- package/dist/interactions/index.js +2 -1
- package/dist/{parts-BeRnK54I.d.ts → parts-BcbitSNp.d.ts} +12 -21
- package/dist/platform/index.d.ts +1 -1
- package/dist/platform/index.js +3 -1
- package/dist/platform/index.js.map +1 -1
- package/dist/{sso-Df4wtL8D.d.ts → sso-CNOsARMJ.d.ts} +16 -1
- package/dist/stream/index.js +1 -1
- package/dist/web-react/index.d.ts +24 -4
- package/dist/web-react/index.js +5 -1
- package/dist/wire-BaUF66AS.d.ts +61 -0
- package/package.json +6 -1
- package/dist/chunk-4H77LX3V.js.map +0 -1
- package/dist/chunk-77RLNLFP.js.map +0 -1
- package/dist/chunk-PEPXQTJ3.js.map +0 -1
- /package/dist/{chunk-U7DLCPJ6.js.map → chunk-Y4QHNQ75.js.map} +0 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChatTurnInputError,
|
|
3
|
+
INLINE_PARTS_MAX_BYTES,
|
|
4
|
+
assertPromptPartsWithinCap,
|
|
5
|
+
chatTurnRequestInit,
|
|
6
|
+
parseChatTurnParts,
|
|
7
|
+
promptPartsByteSize
|
|
8
|
+
} from "../chunk-5SV5PSU7.js";
|
|
9
|
+
import {
|
|
10
|
+
createInteractionAnswerRoute
|
|
11
|
+
} from "../chunk-Y4QHNQ75.js";
|
|
12
|
+
import {
|
|
13
|
+
parseJsonObjectBody
|
|
14
|
+
} from "../chunk-HFC4BTWJ.js";
|
|
15
|
+
import {
|
|
16
|
+
isRenderableInteractionKind,
|
|
17
|
+
parseInteractionRequest
|
|
18
|
+
} from "../chunk-4TXDD6P2.js";
|
|
19
|
+
import {
|
|
20
|
+
toChatMessageParts
|
|
21
|
+
} from "../chunk-I2R2XT4M.js";
|
|
22
|
+
import {
|
|
23
|
+
asRecord,
|
|
24
|
+
asString,
|
|
25
|
+
coalesceDeltas,
|
|
26
|
+
createBufferedTurnTap,
|
|
27
|
+
finalizeAssistantParts,
|
|
28
|
+
getPartKey,
|
|
29
|
+
mergePersistedPart,
|
|
30
|
+
normalizeClientTurnId,
|
|
31
|
+
normalizePersistedPart,
|
|
32
|
+
normalizeToolEvent,
|
|
33
|
+
replayTurnEvents,
|
|
34
|
+
resolveChatTurn
|
|
35
|
+
} from "../chunk-ATRJULKZ.js";
|
|
36
|
+
|
|
37
|
+
// src/chat-routes/turn-routes.ts
|
|
38
|
+
import { deriveExecutionId, handleChatTurn } from "@tangle-network/agent-runtime";
|
|
39
|
+
function errorResponse(err) {
|
|
40
|
+
return Response.json({ code: err.code, error: err.message }, { status: err.status });
|
|
41
|
+
}
|
|
42
|
+
function validateTurnBody(body, maxInlinePartBytes) {
|
|
43
|
+
const threadId = typeof body.threadId === "string" ? body.threadId.trim() : "";
|
|
44
|
+
if (!threadId) throw new ChatTurnInputError("Missing threadId");
|
|
45
|
+
const rawContent = body.content ?? body.message ?? "";
|
|
46
|
+
if (typeof rawContent !== "string") throw new ChatTurnInputError("content must be a string");
|
|
47
|
+
const content = rawContent.trim();
|
|
48
|
+
const fileParts = parseChatTurnParts(body.parts);
|
|
49
|
+
if (!content && fileParts.length === 0) {
|
|
50
|
+
throw new ChatTurnInputError("Missing content (send text, parts, or both)");
|
|
51
|
+
}
|
|
52
|
+
assertPromptPartsWithinCap(fileParts, maxInlinePartBytes);
|
|
53
|
+
let turnId;
|
|
54
|
+
try {
|
|
55
|
+
turnId = normalizeClientTurnId(body.turnId);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
throw new ChatTurnInputError(err instanceof Error ? err.message : "Invalid turnId");
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
payload: { ...body, threadId, content },
|
|
61
|
+
content,
|
|
62
|
+
fileParts,
|
|
63
|
+
turnId
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function userPartsWithFiles(userParts, fileParts) {
|
|
67
|
+
return toChatMessageParts([...userParts, ...fileParts.map((part) => ({ ...part }))]);
|
|
68
|
+
}
|
|
69
|
+
function createChatTurnRoutes(options) {
|
|
70
|
+
const log = options.log ?? ((message, meta) => console.error(message, meta ?? ""));
|
|
71
|
+
async function turn(request, ctx) {
|
|
72
|
+
const [rawBody, badBody] = await parseJsonObjectBody(request);
|
|
73
|
+
if (badBody) return badBody;
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = validateTurnBody(rawBody, options.maxInlinePartBytes);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (err instanceof ChatTurnInputError) return errorResponse(err);
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
const { payload, content, fileParts, turnId } = parsed;
|
|
82
|
+
const auth = await options.authorize({ request, intent: "turn", body: payload });
|
|
83
|
+
if (!auth.ok) return auth.response;
|
|
84
|
+
const { tenantId, userId, context } = auth;
|
|
85
|
+
const existingMessages = (await options.store.listMessages(payload.threadId)).map((m) => ({
|
|
86
|
+
id: m.id,
|
|
87
|
+
role: m.role,
|
|
88
|
+
content: m.content,
|
|
89
|
+
parts: m.parts ?? null
|
|
90
|
+
}));
|
|
91
|
+
const chatTurn = resolveChatTurn({ existingMessages, userContent: content, turnId });
|
|
92
|
+
if (chatTurn.shouldInsertUserMessage) {
|
|
93
|
+
await options.store.appendMessage({
|
|
94
|
+
threadId: payload.threadId,
|
|
95
|
+
role: "user",
|
|
96
|
+
content,
|
|
97
|
+
parts: userPartsWithFiles(chatTurn.userParts, fileParts)
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const identity = {
|
|
101
|
+
tenantId,
|
|
102
|
+
sessionId: payload.threadId,
|
|
103
|
+
userId,
|
|
104
|
+
turnIndex: chatTurn.turnIndex
|
|
105
|
+
};
|
|
106
|
+
const executionId = deriveExecutionId({
|
|
107
|
+
projectId: options.projectId,
|
|
108
|
+
sessionId: payload.threadId,
|
|
109
|
+
turnIndex: chatTurn.turnIndex
|
|
110
|
+
});
|
|
111
|
+
const turnStreamId = crypto.randomUUID();
|
|
112
|
+
const prompt = fileParts.length === 0 ? content : content ? [{ type: "text", text: content }, ...fileParts] : [...fileParts];
|
|
113
|
+
const tap = createBufferedTurnTap({
|
|
114
|
+
store: options.turnStore,
|
|
115
|
+
turnId: turnStreamId,
|
|
116
|
+
scopeId: payload.threadId,
|
|
117
|
+
coalesce: options.coalesceTurnEvents ?? coalesceDeltas
|
|
118
|
+
});
|
|
119
|
+
const turnMarker = { type: "turn", turnId: turnStreamId };
|
|
120
|
+
await tap.onEvent(turnMarker);
|
|
121
|
+
let producer;
|
|
122
|
+
let runFailed = false;
|
|
123
|
+
const result = handleChatTurn({
|
|
124
|
+
identity,
|
|
125
|
+
waitUntil: ctx?.waitUntil,
|
|
126
|
+
log,
|
|
127
|
+
hooks: {
|
|
128
|
+
// The engine wants a synchronous producer; box resolution is async —
|
|
129
|
+
// defer it into the generator's first pull.
|
|
130
|
+
produce: () => ({
|
|
131
|
+
stream: (async function* () {
|
|
132
|
+
producer = await options.produce({
|
|
133
|
+
request,
|
|
134
|
+
body: payload,
|
|
135
|
+
identity,
|
|
136
|
+
context,
|
|
137
|
+
prompt,
|
|
138
|
+
executionId,
|
|
139
|
+
turnStreamId,
|
|
140
|
+
priorMessages: chatTurn.priorMessages
|
|
141
|
+
});
|
|
142
|
+
for await (const event of producer.stream) yield event;
|
|
143
|
+
})(),
|
|
144
|
+
finalText: () => producer?.finalText() ?? ""
|
|
145
|
+
}),
|
|
146
|
+
onEvent: async (event) => {
|
|
147
|
+
if (event.type === "session.run.failed" || event.type === "error") runFailed = true;
|
|
148
|
+
await tap.onEvent(event);
|
|
149
|
+
if (options.onEvent) await options.onEvent(event, context);
|
|
150
|
+
},
|
|
151
|
+
...options.transformFinalText ? { transformFinalText: options.transformFinalText } : {},
|
|
152
|
+
persistAssistantMessage: async ({ finalText }) => {
|
|
153
|
+
const parts = producer?.assistantParts ? toChatMessageParts(producer.assistantParts()) : void 0;
|
|
154
|
+
if (!finalText.trim() && (!parts || parts.length === 0)) return;
|
|
155
|
+
const usage = producer?.usage?.() ?? {};
|
|
156
|
+
await options.store.appendMessage({
|
|
157
|
+
threadId: payload.threadId,
|
|
158
|
+
role: "assistant",
|
|
159
|
+
content: finalText,
|
|
160
|
+
...parts && parts.length > 0 ? { parts } : {},
|
|
161
|
+
...producer?.model ? { model: producer.model } : {},
|
|
162
|
+
...usage.inputTokens !== void 0 ? { inputTokens: usage.inputTokens } : {},
|
|
163
|
+
...usage.outputTokens !== void 0 ? { outputTokens: usage.outputTokens } : {},
|
|
164
|
+
...usage.reasoningTokens !== void 0 ? { reasoningTokens: usage.reasoningTokens } : {},
|
|
165
|
+
...usage.cacheReadTokens !== void 0 ? { cacheReadTokens: usage.cacheReadTokens } : {},
|
|
166
|
+
...usage.cacheWriteTokens !== void 0 ? { cacheWriteTokens: usage.cacheWriteTokens } : {},
|
|
167
|
+
...usage.costUsd !== void 0 ? { costUsd: usage.costUsd } : {}
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
...options.onTurnComplete ? {
|
|
171
|
+
onTurnComplete: ({ identity: turnIdentity, finalText }) => options.onTurnComplete({ identity: turnIdentity, finalText, context })
|
|
172
|
+
} : {},
|
|
173
|
+
...options.traceFlush ? { traceFlush: () => options.traceFlush(context) } : {}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
const [clientBody, drainBody] = result.body.tee();
|
|
177
|
+
const drained = (async () => {
|
|
178
|
+
const reader = drainBody.getReader();
|
|
179
|
+
try {
|
|
180
|
+
for (; ; ) {
|
|
181
|
+
const { done } = await reader.read();
|
|
182
|
+
if (done) break;
|
|
183
|
+
}
|
|
184
|
+
await tap.done(runFailed ? "error" : "complete");
|
|
185
|
+
} catch (err) {
|
|
186
|
+
await tap.done("error");
|
|
187
|
+
log("[chat-routes] turn drain failed", {
|
|
188
|
+
turnId: turnStreamId,
|
|
189
|
+
error: err instanceof Error ? err.message : String(err)
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
})();
|
|
193
|
+
if (ctx?.waitUntil) ctx.waitUntil(drained);
|
|
194
|
+
else void drained.catch(() => {
|
|
195
|
+
});
|
|
196
|
+
const encoder = new TextEncoder();
|
|
197
|
+
const marker = new ReadableStream({
|
|
198
|
+
start(controller) {
|
|
199
|
+
controller.enqueue(encoder.encode(`${JSON.stringify(turnMarker)}
|
|
200
|
+
`));
|
|
201
|
+
controller.close();
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
const body = concatStreams([marker, clientBody]);
|
|
205
|
+
return new Response(body, {
|
|
206
|
+
headers: {
|
|
207
|
+
"Content-Type": result.contentType,
|
|
208
|
+
"Cache-Control": "no-cache"
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
async function replay(request, params) {
|
|
213
|
+
const turnId = params.turnId?.trim();
|
|
214
|
+
if (!turnId) return Response.json({ error: "Missing turnId" }, { status: 400 });
|
|
215
|
+
const auth = await options.authorize({ request, intent: "replay", turnId });
|
|
216
|
+
if (!auth.ok) return auth.response;
|
|
217
|
+
const fromSeqRaw = new URL(request.url).searchParams.get("fromSeq");
|
|
218
|
+
const fromSeq = fromSeqRaw ? Math.max(0, Math.trunc(Number(fromSeqRaw)) || 0) : 0;
|
|
219
|
+
const encoder = new TextEncoder();
|
|
220
|
+
const events = replayTurnEvents({
|
|
221
|
+
store: options.turnStore,
|
|
222
|
+
turnId,
|
|
223
|
+
fromSeq,
|
|
224
|
+
...options.replay?.pollMs !== void 0 ? { pollMs: options.replay.pollMs } : {},
|
|
225
|
+
...options.replay?.timeoutMs !== void 0 ? { timeoutMs: options.replay.timeoutMs } : {}
|
|
226
|
+
});
|
|
227
|
+
const body = new ReadableStream({
|
|
228
|
+
async pull(controller) {
|
|
229
|
+
const { done, value } = await events.next();
|
|
230
|
+
if (done) {
|
|
231
|
+
controller.close();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
controller.enqueue(encoder.encode(`${value.event}
|
|
235
|
+
`));
|
|
236
|
+
},
|
|
237
|
+
cancel() {
|
|
238
|
+
void events.return(void 0);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
return new Response(body, {
|
|
242
|
+
headers: {
|
|
243
|
+
"Content-Type": "application/x-ndjson",
|
|
244
|
+
"Cache-Control": "no-cache"
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
turn,
|
|
250
|
+
replay,
|
|
251
|
+
interactions: options.interactions ? createInteractionAnswerRoute(options.interactions) : null
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function concatStreams(streams) {
|
|
255
|
+
let index = 0;
|
|
256
|
+
let reader = null;
|
|
257
|
+
return new ReadableStream({
|
|
258
|
+
async pull(controller) {
|
|
259
|
+
for (; ; ) {
|
|
260
|
+
if (!reader) {
|
|
261
|
+
const next = streams[index++];
|
|
262
|
+
if (!next) {
|
|
263
|
+
controller.close();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
reader = next.getReader();
|
|
267
|
+
}
|
|
268
|
+
const { done, value } = await reader.read();
|
|
269
|
+
if (done) {
|
|
270
|
+
reader = null;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
controller.enqueue(value);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
async cancel(reason) {
|
|
278
|
+
await reader?.cancel(reason);
|
|
279
|
+
for (const stream of streams.slice(index)) await stream.cancel(reason);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/chat-routes/sandbox-producer.ts
|
|
285
|
+
function textDelta(tracker, key, part, rawDelta) {
|
|
286
|
+
const explicit = typeof rawDelta === "string" ? rawDelta : void 0;
|
|
287
|
+
const previous = tracker.seen.get(key) ?? "";
|
|
288
|
+
if (explicit !== void 0) {
|
|
289
|
+
tracker.seen.set(key, previous + explicit);
|
|
290
|
+
return explicit;
|
|
291
|
+
}
|
|
292
|
+
const snapshot = asString(part.text) ?? asString(part.content) ?? "";
|
|
293
|
+
if (!snapshot) return "";
|
|
294
|
+
if (snapshot.startsWith(previous)) {
|
|
295
|
+
tracker.seen.set(key, snapshot);
|
|
296
|
+
return snapshot.slice(previous.length);
|
|
297
|
+
}
|
|
298
|
+
tracker.seen.set(key, snapshot);
|
|
299
|
+
return snapshot;
|
|
300
|
+
}
|
|
301
|
+
function usageFromStepFinish(part, usage) {
|
|
302
|
+
const tokens = asRecord(part.tokens);
|
|
303
|
+
if (tokens) {
|
|
304
|
+
const cache = asRecord(tokens.cache);
|
|
305
|
+
const add = (current, value) => {
|
|
306
|
+
const n = Number(value);
|
|
307
|
+
if (!Number.isFinite(n)) return current;
|
|
308
|
+
return (current ?? 0) + n;
|
|
309
|
+
};
|
|
310
|
+
usage.inputTokens = add(usage.inputTokens, tokens.input);
|
|
311
|
+
usage.outputTokens = add(usage.outputTokens, tokens.output);
|
|
312
|
+
usage.reasoningTokens = add(usage.reasoningTokens, tokens.reasoning);
|
|
313
|
+
if (cache) {
|
|
314
|
+
usage.cacheReadTokens = add(usage.cacheReadTokens, cache.read);
|
|
315
|
+
usage.cacheWriteTokens = add(usage.cacheWriteTokens, cache.write);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const cost = Number(part.cost);
|
|
319
|
+
if (Number.isFinite(cost)) usage.costUsd = (usage.costUsd ?? 0) + cost;
|
|
320
|
+
}
|
|
321
|
+
function createSandboxChatProducer(options) {
|
|
322
|
+
const log = options.log ?? ((message, meta) => console.error(message, meta ?? ""));
|
|
323
|
+
const renderable = options.isRenderableInteraction ?? isRenderableInteractionKind;
|
|
324
|
+
let fullText = "";
|
|
325
|
+
const partOrder = [];
|
|
326
|
+
const partMap = /* @__PURE__ */ new Map();
|
|
327
|
+
const tracker = { seen: /* @__PURE__ */ new Map() };
|
|
328
|
+
const usage = {};
|
|
329
|
+
const announcedTools = /* @__PURE__ */ new Set();
|
|
330
|
+
const settledTools = /* @__PURE__ */ new Set();
|
|
331
|
+
let stepCounter = 0;
|
|
332
|
+
function recordPersistedPart(part, delta, keyOverride) {
|
|
333
|
+
const persisted = normalizePersistedPart(part);
|
|
334
|
+
if (!persisted) return;
|
|
335
|
+
const key = keyOverride ?? getPartKey(persisted);
|
|
336
|
+
if (!partMap.has(key)) partOrder.push(key);
|
|
337
|
+
partMap.set(key, mergePersistedPart(partMap.get(key), persisted, delta));
|
|
338
|
+
}
|
|
339
|
+
async function* stream() {
|
|
340
|
+
for await (const raw of options.events) {
|
|
341
|
+
const record = asRecord(raw);
|
|
342
|
+
if (!record || typeof record.type !== "string") continue;
|
|
343
|
+
const normalized = normalizeToolEvent({ type: record.type, data: asRecord(record.data) });
|
|
344
|
+
const event = normalized.type === "message.part.updated" ? normalized : record;
|
|
345
|
+
if (event.type === "message.part.updated") {
|
|
346
|
+
const part = asRecord(event.data?.part);
|
|
347
|
+
if (!part) continue;
|
|
348
|
+
const rawDelta = event.data?.delta;
|
|
349
|
+
const partType = String(part.type ?? "");
|
|
350
|
+
if (partType === "text" || partType === "reasoning") {
|
|
351
|
+
const key = getPartKey(part);
|
|
352
|
+
const delta = textDelta(tracker, key, part, rawDelta);
|
|
353
|
+
recordPersistedPart(part, delta || void 0);
|
|
354
|
+
if (delta) {
|
|
355
|
+
if (partType === "text") fullText += delta;
|
|
356
|
+
yield { type: partType, text: delta };
|
|
357
|
+
}
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (partType === "tool") {
|
|
361
|
+
recordPersistedPart(part, void 0);
|
|
362
|
+
const persisted = partMap.get(getPartKey(part));
|
|
363
|
+
const state = asRecord(persisted?.state);
|
|
364
|
+
const toolId = String(persisted?.id ?? "");
|
|
365
|
+
const toolName = String(persisted?.tool ?? "tool");
|
|
366
|
+
if (toolId && !announcedTools.has(toolId)) {
|
|
367
|
+
announcedTools.add(toolId);
|
|
368
|
+
yield {
|
|
369
|
+
type: "tool_call",
|
|
370
|
+
call: { toolCallId: toolId, toolName, args: asRecord(state?.input) ?? {} }
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
const status = String(state?.status ?? "");
|
|
374
|
+
if (toolId && (status === "completed" || status === "error") && !settledTools.has(toolId)) {
|
|
375
|
+
settledTools.add(toolId);
|
|
376
|
+
yield {
|
|
377
|
+
type: "tool_result",
|
|
378
|
+
toolCallId: toolId,
|
|
379
|
+
toolName,
|
|
380
|
+
outcome: {
|
|
381
|
+
ok: status === "completed",
|
|
382
|
+
...state?.output !== void 0 ? { result: state.output } : {},
|
|
383
|
+
...asString(state?.error) ? { message: asString(state?.error) } : {}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (partType === "step-finish") {
|
|
390
|
+
usageFromStepFinish(part, usage);
|
|
391
|
+
recordPersistedPart(part, void 0, `step-finish:#${stepCounter++}`);
|
|
392
|
+
const promptTokens = usage.inputTokens ?? 0;
|
|
393
|
+
const completionTokens = usage.outputTokens ?? 0;
|
|
394
|
+
if (promptTokens || completionTokens) {
|
|
395
|
+
yield { type: "usage", usage: { promptTokens, completionTokens } };
|
|
396
|
+
}
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (partType === "step-start") {
|
|
400
|
+
recordPersistedPart(part, void 0, `step-start:#${stepCounter}`);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
recordPersistedPart(part, void 0);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (event.type === "interaction") {
|
|
407
|
+
const parsed = parseInteractionRequest(asRecord(record.data));
|
|
408
|
+
if (!parsed.succeeded) {
|
|
409
|
+
log("[chat-routes] dropping malformed interaction event", { error: parsed.error });
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (renderable(parsed.value.kind)) {
|
|
413
|
+
yield event;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (options.declineInteraction) {
|
|
417
|
+
try {
|
|
418
|
+
await options.declineInteraction(parsed.value.id);
|
|
419
|
+
} catch (err) {
|
|
420
|
+
log("[chat-routes] failed to auto-decline interaction", {
|
|
421
|
+
id: parsed.value.id,
|
|
422
|
+
error: err instanceof Error ? err.message : String(err)
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
log("[chat-routes] non-renderable interaction with no declineInteraction wired", {
|
|
427
|
+
id: parsed.value.id,
|
|
428
|
+
kind: parsed.value.kind
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (event.type === "result") {
|
|
434
|
+
const finalText = asString(event.data?.finalText);
|
|
435
|
+
if (finalText) fullText = finalText;
|
|
436
|
+
const resultUsage = asRecord(event.data?.usage);
|
|
437
|
+
if (resultUsage) {
|
|
438
|
+
const input = Number(resultUsage.inputTokens);
|
|
439
|
+
const output = Number(resultUsage.outputTokens);
|
|
440
|
+
if (Number.isFinite(input)) usage.inputTokens = input;
|
|
441
|
+
if (Number.isFinite(output)) usage.outputTokens = output;
|
|
442
|
+
}
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
yield event;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
stream: stream(),
|
|
450
|
+
finalText: () => fullText,
|
|
451
|
+
assistantParts: () => finalizeAssistantParts(partOrder, partMap, fullText),
|
|
452
|
+
usage: () => usage,
|
|
453
|
+
...options.model ? { model: options.model } : {}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/chat-routes/upload.ts
|
|
458
|
+
var UPLOAD_INLINE_MAX_BYTES = 700 * 1024;
|
|
459
|
+
var UPLOAD_MAX_FILE_BYTES = 8 * 1024 * 1024;
|
|
460
|
+
function sanitizeUploadFilename(name) {
|
|
461
|
+
const base = name.split(/[\\/]/).pop() ?? "file";
|
|
462
|
+
const safe = base.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^\.+/, "_");
|
|
463
|
+
return (safe || "file").slice(0, 120);
|
|
464
|
+
}
|
|
465
|
+
var BASE64_CHUNK = 32768;
|
|
466
|
+
function bytesToBase64(bytes) {
|
|
467
|
+
let binary = "";
|
|
468
|
+
for (let offset = 0; offset < bytes.length; offset += BASE64_CHUNK) {
|
|
469
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_CHUNK));
|
|
470
|
+
}
|
|
471
|
+
return btoa(binary);
|
|
472
|
+
}
|
|
473
|
+
function uploadError(status, code, error) {
|
|
474
|
+
return Response.json({ code, error }, { status });
|
|
475
|
+
}
|
|
476
|
+
function createUploadRoute(options) {
|
|
477
|
+
const inlineMaxBytes = options.inlineMaxBytes ?? UPLOAD_INLINE_MAX_BYTES;
|
|
478
|
+
const maxFileBytes = options.maxFileBytes ?? UPLOAD_MAX_FILE_BYTES;
|
|
479
|
+
return async function upload(request) {
|
|
480
|
+
const auth = await options.authorize({ request });
|
|
481
|
+
if (!auth.ok) return auth.response;
|
|
482
|
+
const sink = auth.sink ?? null;
|
|
483
|
+
const uploadDir = (auth.uploadDir ?? options.uploadDir ?? "uploads").replace(/\/+$/, "");
|
|
484
|
+
let form;
|
|
485
|
+
try {
|
|
486
|
+
form = await request.formData();
|
|
487
|
+
} catch {
|
|
488
|
+
return uploadError(400, "INVALID_UPLOAD", "Expected a multipart/form-data body with file fields");
|
|
489
|
+
}
|
|
490
|
+
const files = [];
|
|
491
|
+
form.forEach((value) => {
|
|
492
|
+
if (value instanceof File) files.push(value);
|
|
493
|
+
});
|
|
494
|
+
if (files.length === 0) {
|
|
495
|
+
return uploadError(400, "INVALID_UPLOAD", "No files in the upload body");
|
|
496
|
+
}
|
|
497
|
+
const uploaded = [];
|
|
498
|
+
for (const file of files) {
|
|
499
|
+
const name = sanitizeUploadFilename(file.name);
|
|
500
|
+
const mediaType = file.type || "application/octet-stream";
|
|
501
|
+
const partType = mediaType.startsWith("image/") ? "image" : "file";
|
|
502
|
+
if (file.size > maxFileBytes) {
|
|
503
|
+
return uploadError(
|
|
504
|
+
413,
|
|
505
|
+
"FILE_TOO_LARGE",
|
|
506
|
+
`${name} is ${file.size}B, over the ${maxFileBytes}B per-file cap`
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
const id = crypto.randomUUID();
|
|
510
|
+
if (file.size <= inlineMaxBytes) {
|
|
511
|
+
const base642 = bytesToBase64(new Uint8Array(await file.arrayBuffer()));
|
|
512
|
+
uploaded.push({
|
|
513
|
+
id,
|
|
514
|
+
name,
|
|
515
|
+
size: file.size,
|
|
516
|
+
mediaType,
|
|
517
|
+
inline: true,
|
|
518
|
+
part: {
|
|
519
|
+
type: partType,
|
|
520
|
+
filename: name,
|
|
521
|
+
mediaType,
|
|
522
|
+
url: `data:${mediaType};base64,${base642}`
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (!sink) {
|
|
528
|
+
return uploadError(
|
|
529
|
+
413,
|
|
530
|
+
"SANDBOX_REQUIRED",
|
|
531
|
+
`${name} is ${file.size}B, over the ${inlineMaxBytes}B inline cap, and no sandbox is available to hold it`
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
const path = `${uploadDir}/${id}-${name}`;
|
|
535
|
+
const base64 = bytesToBase64(new Uint8Array(await file.arrayBuffer()));
|
|
536
|
+
await sink.write(path, base64, { encoding: "base64" });
|
|
537
|
+
uploaded.push({
|
|
538
|
+
id,
|
|
539
|
+
name,
|
|
540
|
+
size: file.size,
|
|
541
|
+
mediaType,
|
|
542
|
+
inline: false,
|
|
543
|
+
part: { type: partType, filename: name, mediaType, path }
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
return Response.json({ files: uploaded });
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
export {
|
|
550
|
+
ChatTurnInputError,
|
|
551
|
+
INLINE_PARTS_MAX_BYTES,
|
|
552
|
+
UPLOAD_INLINE_MAX_BYTES,
|
|
553
|
+
UPLOAD_MAX_FILE_BYTES,
|
|
554
|
+
assertPromptPartsWithinCap,
|
|
555
|
+
bytesToBase64,
|
|
556
|
+
chatTurnRequestInit,
|
|
557
|
+
createChatTurnRoutes,
|
|
558
|
+
createSandboxChatProducer,
|
|
559
|
+
createUploadRoute,
|
|
560
|
+
parseChatTurnParts,
|
|
561
|
+
promptPartsByteSize,
|
|
562
|
+
sanitizeUploadFilename
|
|
563
|
+
};
|
|
564
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/chat-routes/turn-routes.ts","../../src/chat-routes/sandbox-producer.ts","../../src/chat-routes/upload.ts"],"sourcesContent":["/**\n * `createChatTurnRoutes` — the assembled server chat vertical (issue #188\n * Phase 1). One factory composing the pieces every product re-wired by hand:\n *\n * body parse/validate → `/web` `parseJsonObjectBody` + `./wire`\n * turn identity → `/stream` `resolveChatTurn` + agent-runtime\n * `deriveExecutionId`\n * producer → injected seam (sandbox lane via\n * `createSandboxChatProducer`; router lane is the\n * product's own `ChatTurnProducer`)\n * turn engine → agent-runtime `handleChatTurn` (verbatim)\n * durability → `/stream` turn-buffer tap, wired BY DEFAULT\n * (tee + drain keeps the turn running after a\n * client drop; replay serves the buffered tail)\n * persistence → injected `/chat-store`-shaped store\n * (user row on send, assistant row on completion)\n * interactions answer → `/interactions` `createInteractionAnswerRoute`\n *\n * Handlers are web-standard `Request → Response` (Workers, Node 18+, Deno) —\n * no router import. Auth/access is one injected `authorize` seam, composable\n * with `/app-auth` guards but not coupled to them.\n */\n\nimport { deriveExecutionId, handleChatTurn } from '@tangle-network/agent-runtime'\nimport type { ChatTurnIdentity, ChatTurnProducer } from '@tangle-network/agent-runtime'\nimport { toChatMessageParts, type ChatMessagePart } from '../chat-store/parts'\nimport {\n createInteractionAnswerRoute,\n type InteractionAnswerRoute,\n type InteractionAnswerRouteOptions,\n} from '../interactions/route'\nimport {\n coalesceDeltas,\n createBufferedTurnTap,\n normalizeClientTurnId,\n replayTurnEvents,\n resolveChatTurn,\n type PersistedChatMessageForTurn,\n type TurnEventStore,\n} from '../stream/index'\nimport { parseJsonObjectBody } from '../web/index'\nimport {\n assertPromptPartsWithinCap,\n ChatTurnInputError,\n parseChatTurnParts,\n type ChatTurnFilePartInput,\n type ChatTurnPartInput,\n type ChatTurnRequestPayload,\n} from './wire'\n\n// ── seams ───────────────────────────────────────────────────────────────────\n\n/** Usage receipt persisted onto the assistant message (the flattened\n * `step-finish` shape `/chat-store`'s columns mirror). */\nexport interface ChatTurnUsage {\n inputTokens?: number\n outputTokens?: number\n reasoningTokens?: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n costUsd?: number\n}\n\n/** What the route persists — a structural subset of `/chat-store`'s\n * `ChatStore`, so `createChatStore(db, tables)` satisfies it directly and a\n * product with its own persistence adapts without importing drizzle. */\nexport interface ChatTurnMessageStore {\n listMessages(threadId: string): Promise<Array<{\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n parts?: ChatMessagePart[] | null\n }>>\n appendMessage(input: {\n threadId: string\n role: 'user' | 'assistant'\n content: string\n parts?: ChatMessagePart[]\n model?: string | null\n inputTokens?: number | null\n outputTokens?: number | null\n reasoningTokens?: number | null\n cacheReadTokens?: number | null\n cacheWriteTokens?: number | null\n costUsd?: number | null\n }): Promise<unknown>\n}\n\n/** `ChatTurnProducer` plus the persisted projection the assembly reads after\n * drain. `createSandboxChatProducer` returns this; a router-lane producer\n * may omit the optional members (finalText persists as a single text part). */\nexport interface ChatTurnRouteProducer extends ChatTurnProducer {\n assistantParts?(): Array<Record<string, unknown>>\n usage?(): ChatTurnUsage\n model?: string\n}\n\nexport type ChatTurnAuthorization<TContext> =\n | { ok: true; tenantId: string; userId: string; context: TContext }\n | { ok: false; response: Response }\n\nexport interface ChatTurnAuthorizeArgs {\n request: Request\n intent: 'turn' | 'replay'\n /** Parsed, validated POST body (turn intent only). */\n body?: ChatTurnRequestPayload\n /** The buffered turn id being replayed (replay intent only). */\n turnId?: string\n}\n\nexport interface ChatTurnProduceArgs<TContext> {\n request: Request\n body: ChatTurnRequestPayload\n identity: ChatTurnIdentity\n context: TContext\n /** The message to send: plain text, or parts when the client attached\n * files (a text part is prepended from `content` when present). */\n prompt: string | ChatTurnPartInput[]\n /** Stable id for cross-process reconnect (`deriveExecutionId`). */\n executionId: string\n /** The turn-buffer id announced to the client for replay. */\n turnStreamId: string\n priorMessages: PersistedChatMessageForTurn[]\n}\n\nexport interface CreateChatTurnRoutesOptions<TContext = void> {\n /** Names the product in `deriveExecutionId` so retries land on the same\n * substrate execution. */\n projectId: string\n /** Authenticate + authorize the caller for a turn or a replay. The only\n * product-supplied access step: session auth, thread/workspace access,\n * seat/balance gates, rate limits all live here. */\n authorize(args: ChatTurnAuthorizeArgs): Promise<ChatTurnAuthorization<TContext>>\n /** Thread/message persistence (`/chat-store`'s store or a product adapter). */\n store: ChatTurnMessageStore\n /** Turn-event buffer (`createD1TurnEventStore(env.DB)` in production,\n * `createMemoryTurnEventStore()` in tests). Wired by default — every turn\n * is buffered and replayable. */\n turnStore: TurnEventStore\n /** Build the turn's event stream. Sandbox lane: `streamSandboxPrompt(...)`\n * wrapped in `createSandboxChatProducer`. Router/openai-compat lane: the\n * product's own producer. May be async (box resolution). */\n produce(args: ChatTurnProduceArgs<TContext>): ChatTurnRouteProducer | Promise<ChatTurnRouteProducer>\n /** Pre-persist transform of the final text (e.g. `/redact`'s `redactPII`).\n * Live stream is never altered. */\n transformFinalText?(text: string): string | Promise<string>\n /** Post-processing after a successful turn (billing, titles, audit). Errors\n * are swallowed by the engine — they never fail a streamed turn. */\n onTurnComplete?(input: { identity: ChatTurnIdentity; finalText: string; context: TContext }): Promise<void>\n /** Per-event side channel (product broadcast). The turn-buffer tap is\n * already wired; this runs in addition. */\n onEvent?(event: { type: string; data?: Record<string, unknown> }, context: TContext): void | Promise<void>\n /** Trace flush handed to `waitUntil` (OTLP export). */\n traceFlush?(context: TContext): Promise<void>\n /** Compose the interaction-answer endpoints (`/interactions`). Omit when the\n * product has no sidecar ask channel. */\n interactions?: InteractionAnswerRouteOptions\n /** Byte budget for inline prompt parts. Default `INLINE_PARTS_MAX_BYTES`. */\n maxInlinePartBytes?: number\n /** Per-flush coalescer for the turn buffer. Default `coalesceDeltas` (this\n * assembly streams the client vocabulary's `{type:'text'|'reasoning',\n * text}` lines, which it merges). A producer streaming raw\n * `message.part.updated` events passes `coalesceChatStreamEvents`. */\n coalesceTurnEvents?: (events: unknown[]) => unknown[]\n replay?: { pollMs?: number; timeoutMs?: number }\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\nexport interface ChatTurnRoutes {\n /** POST — run one turn, streaming NDJSON. First line is\n * `{type:'turn', turnId}` (the replay handle); the rest is the engine's\n * event protocol. Pass the platform's `waitUntil` so the turn keeps\n * running (and buffering) after a client disconnect. */\n turn(request: Request, ctx?: { waitUntil?(p: Promise<unknown>): void }): Promise<Response>\n /** GET — replay a buffered turn from `?fromSeq=` (0 = everything), then\n * follow it live until it completes. */\n replay(request: Request, params: { turnId: string }): Promise<Response>\n /** list/answer endpoints from `/interactions`; null when not configured. */\n interactions: InteractionAnswerRoute | null\n}\n\n// ── body validation ────────────────────────────────────────────────────────\n\nfunction errorResponse(err: ChatTurnInputError): Response {\n return Response.json({ code: err.code, error: err.message }, { status: err.status })\n}\n\ninterface ParsedTurnBody {\n payload: ChatTurnRequestPayload\n content: string\n fileParts: ChatTurnFilePartInput[]\n turnId: string | undefined\n}\n\nfunction validateTurnBody(body: Record<string, unknown>, maxInlinePartBytes: number | undefined): ParsedTurnBody {\n const threadId = typeof body.threadId === 'string' ? body.threadId.trim() : ''\n if (!threadId) throw new ChatTurnInputError('Missing threadId')\n const rawContent = body.content ?? body.message ?? ''\n if (typeof rawContent !== 'string') throw new ChatTurnInputError('content must be a string')\n const content = rawContent.trim()\n const fileParts = parseChatTurnParts(body.parts)\n if (!content && fileParts.length === 0) {\n throw new ChatTurnInputError('Missing content (send text, parts, or both)')\n }\n assertPromptPartsWithinCap(fileParts, maxInlinePartBytes)\n let turnId: string | undefined\n try {\n turnId = normalizeClientTurnId(body.turnId)\n } catch (err) {\n throw new ChatTurnInputError(err instanceof Error ? err.message : 'Invalid turnId')\n }\n return {\n payload: { ...body, threadId, content } as ChatTurnRequestPayload,\n content,\n fileParts,\n turnId,\n }\n}\n\n/** File parts persist onto the user message verbatim — the wire shape is the\n * persisted `ChatFilePart`/`ChatImagePart` vocabulary already. The typed\n * projection is `/chat-store`'s (same boundary as the assistant hop). */\nfunction userPartsWithFiles(\n userParts: Array<Record<string, unknown>>,\n fileParts: ChatTurnFilePartInput[],\n): ChatMessagePart[] {\n return toChatMessageParts([...userParts, ...fileParts.map((part) => ({ ...part }))])\n}\n\n// ── the factory ────────────────────────────────────────────────────────────\n\nexport function createChatTurnRoutes<TContext = void>(\n options: CreateChatTurnRoutesOptions<TContext>,\n): ChatTurnRoutes {\n const log = options.log ?? ((message, meta) => console.error(message, meta ?? ''))\n\n async function turn(request: Request, ctx?: { waitUntil?(p: Promise<unknown>): void }): Promise<Response> {\n const [rawBody, badBody] = await parseJsonObjectBody(request)\n if (badBody) return badBody\n\n let parsed: ParsedTurnBody\n try {\n parsed = validateTurnBody(rawBody, options.maxInlinePartBytes)\n } catch (err) {\n if (err instanceof ChatTurnInputError) return errorResponse(err)\n throw err\n }\n const { payload, content, fileParts, turnId } = parsed\n\n const auth = await options.authorize({ request, intent: 'turn', body: payload })\n if (!auth.ok) return auth.response\n const { tenantId, userId, context } = auth\n\n // Turn identity: reuse the just-persisted user row on a retry (same\n // turnId or identical trailing content) instead of double-inserting.\n const existingMessages = (await options.store.listMessages(payload.threadId)).map((m) => ({\n id: m.id,\n role: m.role,\n content: m.content,\n parts: (m.parts ?? null) as PersistedChatMessageForTurn['parts'],\n }))\n const chatTurn = resolveChatTurn({ existingMessages, userContent: content, turnId })\n\n if (chatTurn.shouldInsertUserMessage) {\n await options.store.appendMessage({\n threadId: payload.threadId,\n role: 'user',\n content,\n parts: userPartsWithFiles(chatTurn.userParts, fileParts),\n })\n }\n\n const identity: ChatTurnIdentity = {\n tenantId,\n sessionId: payload.threadId,\n userId,\n turnIndex: chatTurn.turnIndex,\n }\n const executionId = deriveExecutionId({\n projectId: options.projectId,\n sessionId: payload.threadId,\n turnIndex: chatTurn.turnIndex,\n })\n const turnStreamId = crypto.randomUUID()\n\n const prompt: string | ChatTurnPartInput[] =\n fileParts.length === 0\n ? content\n : content\n ? [{ type: 'text', text: content }, ...fileParts]\n : [...fileParts]\n\n // Durability tap: every engine event buffers (coalesced) so a dropped\n // client replays the tail. Live delivery rides the Response body, not the\n // tap, so `write` is intentionally absent.\n const tap = createBufferedTurnTap({\n store: options.turnStore,\n turnId: turnStreamId,\n scopeId: payload.threadId,\n coalesce: options.coalesceTurnEvents ?? coalesceDeltas,\n })\n const turnMarker = { type: 'turn', turnId: turnStreamId }\n await tap.onEvent(turnMarker)\n\n let producer: ChatTurnRouteProducer | undefined\n let runFailed = false\n\n const result = handleChatTurn({\n identity,\n waitUntil: ctx?.waitUntil,\n log,\n hooks: {\n // The engine wants a synchronous producer; box resolution is async —\n // defer it into the generator's first pull.\n produce: () => ({\n stream: (async function* () {\n producer = await options.produce({\n request,\n body: payload,\n identity,\n context,\n prompt,\n executionId,\n turnStreamId,\n priorMessages: chatTurn.priorMessages,\n })\n for await (const event of producer.stream) yield event\n })(),\n finalText: () => producer?.finalText() ?? '',\n }),\n onEvent: async (event) => {\n if (event.type === 'session.run.failed' || event.type === 'error') runFailed = true\n await tap.onEvent(event)\n if (options.onEvent) await options.onEvent(event, context)\n },\n ...(options.transformFinalText ? { transformFinalText: options.transformFinalText } : {}),\n persistAssistantMessage: async ({ finalText }) => {\n // The typed boundary: stream-normalizer records → stored vocabulary\n // (validating projection owned by /chat-store — no cast here).\n const parts = producer?.assistantParts ? toChatMessageParts(producer.assistantParts()) : undefined\n if (!finalText.trim() && (!parts || parts.length === 0)) return\n const usage = producer?.usage?.() ?? {}\n await options.store.appendMessage({\n threadId: payload.threadId,\n role: 'assistant',\n content: finalText,\n ...(parts && parts.length > 0 ? { parts } : {}),\n ...(producer?.model ? { model: producer.model } : {}),\n ...(usage.inputTokens !== undefined ? { inputTokens: usage.inputTokens } : {}),\n ...(usage.outputTokens !== undefined ? { outputTokens: usage.outputTokens } : {}),\n ...(usage.reasoningTokens !== undefined ? { reasoningTokens: usage.reasoningTokens } : {}),\n ...(usage.cacheReadTokens !== undefined ? { cacheReadTokens: usage.cacheReadTokens } : {}),\n ...(usage.cacheWriteTokens !== undefined ? { cacheWriteTokens: usage.cacheWriteTokens } : {}),\n ...(usage.costUsd !== undefined ? { costUsd: usage.costUsd } : {}),\n })\n },\n ...(options.onTurnComplete\n ? {\n onTurnComplete: ({ identity: turnIdentity, finalText }: { identity: ChatTurnIdentity; finalText: string }) =>\n options.onTurnComplete!({ identity: turnIdentity, finalText, context }),\n }\n : {}),\n ...(options.traceFlush ? { traceFlush: () => options.traceFlush!(context) } : {}),\n },\n })\n\n // Tee: one branch to the live client, one drained under waitUntil so the\n // turn (and its buffering via onEvent) runs to completion after a client\n // drop — the engine body executes as it is pulled.\n const [clientBody, drainBody] = result.body.tee()\n const drained = (async () => {\n const reader = drainBody.getReader()\n try {\n for (;;) {\n const { done } = await reader.read()\n if (done) break\n }\n await tap.done(runFailed ? 'error' : 'complete')\n } catch (err) {\n await tap.done('error')\n log('[chat-routes] turn drain failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n })()\n if (ctx?.waitUntil) ctx.waitUntil(drained)\n else void drained.catch(() => {})\n\n // Announce the replay handle before the engine's first event.\n const encoder = new TextEncoder()\n const marker = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(encoder.encode(`${JSON.stringify(turnMarker)}\\n`))\n controller.close()\n },\n })\n const body = concatStreams([marker, clientBody])\n\n return new Response(body, {\n headers: {\n 'Content-Type': result.contentType,\n 'Cache-Control': 'no-cache',\n },\n })\n }\n\n async function replay(request: Request, params: { turnId: string }): Promise<Response> {\n const turnId = params.turnId?.trim()\n if (!turnId) return Response.json({ error: 'Missing turnId' }, { status: 400 })\n const auth = await options.authorize({ request, intent: 'replay', turnId })\n if (!auth.ok) return auth.response\n\n const fromSeqRaw = new URL(request.url).searchParams.get('fromSeq')\n const fromSeq = fromSeqRaw ? Math.max(0, Math.trunc(Number(fromSeqRaw)) || 0) : 0\n\n const encoder = new TextEncoder()\n const events = replayTurnEvents({\n store: options.turnStore,\n turnId,\n fromSeq,\n ...(options.replay?.pollMs !== undefined ? { pollMs: options.replay.pollMs } : {}),\n ...(options.replay?.timeoutMs !== undefined ? { timeoutMs: options.replay.timeoutMs } : {}),\n })\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n const { done, value } = await events.next()\n if (done) {\n controller.close()\n return\n }\n controller.enqueue(encoder.encode(`${value.event}\\n`))\n },\n cancel() {\n void events.return(undefined)\n },\n })\n return new Response(body, {\n headers: {\n 'Content-Type': 'application/x-ndjson',\n 'Cache-Control': 'no-cache',\n },\n })\n }\n\n return {\n turn,\n replay,\n interactions: options.interactions ? createInteractionAnswerRoute(options.interactions) : null,\n }\n}\n\n/** Sequential concat of byte streams (marker line, then the engine body). */\nfunction concatStreams(streams: ReadableStream<Uint8Array>[]): ReadableStream<Uint8Array> {\n let index = 0\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n for (;;) {\n if (!reader) {\n const next = streams[index++]\n if (!next) {\n controller.close()\n return\n }\n reader = next.getReader()\n }\n const { done, value } = await reader.read()\n if (done) {\n reader = null\n continue\n }\n controller.enqueue(value)\n return\n }\n },\n async cancel(reason) {\n await reader?.cancel(reason)\n for (const stream of streams.slice(index)) await stream.cancel(reason)\n },\n })\n}\n","/**\n * Sandbox lane: bridge a raw sandbox event stream (`streamSandboxPrompt`) into\n * the `ChatTurnProducer` shape agent-runtime's `handleChatTurn` consumes AND\n * the client vocabulary `/web-react`'s `dispatchChatStreamLine` already parses\n * (`text` / `reasoning` / `tool_call` / `tool_result` / `usage` /\n * `interaction`). Legal and tax each hand-rolled this mapping differently;\n * this is that middle, composed from `/stream`'s normalizers — no new loop\n * logic, no SDK import (the event source is an injected `AsyncIterable`).\n *\n * Alongside the live mapping it accumulates the PERSISTED projection — the\n * `message.parts` rows `/chat-store` stores — via `normalizePersistedPart` /\n * `mergePersistedPart` / `finalizeAssistantParts`, plus the usage receipt from\n * `step-finish` parts. `createChatTurnRoutes` reads both after drain.\n */\n\nimport {\n isRenderableInteractionKind,\n parseInteractionRequest,\n} from '../interactions/contract'\nimport {\n asRecord,\n asString,\n finalizeAssistantParts,\n getPartKey,\n mergePersistedPart,\n normalizePersistedPart,\n normalizeToolEvent,\n type JsonRecord,\n type StreamEvent,\n} from '../stream/index'\nimport type { ChatTurnRouteProducer, ChatTurnUsage } from './turn-routes'\n\nexport interface SandboxChatProducerOptions {\n /** The raw sandbox event stream (e.g. `streamSandboxPrompt(...)`). */\n events: AsyncIterable<unknown>\n /** Recorded on the persisted assistant message. */\n model?: string\n /** Which ask kinds the product renders a card for. Anything else is\n * auto-declined (see `declineInteraction`) so the run never hangs in the\n * broker waiting on a card no client will show. Default: question/plan. */\n isRenderableInteraction?: (kind: string) => boolean\n /** Resolve a non-renderable ask (wire `respondToSessionInteraction` with the\n * session's sidecar connection). Without it, non-renderable asks are only\n * logged — the run stays blocked until the broker times out. */\n declineInteraction?: (id: string) => Promise<void>\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\ninterface TextTracker {\n /** Full accumulated text per part key, to derive suffix deltas from\n * snapshot-only harness events. */\n seen: Map<string, string>\n}\n\n/** Delta to emit for one text/reasoning part update: prefer the harness's\n * explicit delta; otherwise diff the snapshot against what was already\n * emitted for that part (snapshot-only harnesses re-send the whole text). */\nfunction textDelta(tracker: TextTracker, key: string, part: JsonRecord, rawDelta: unknown): string {\n const explicit = typeof rawDelta === 'string' ? rawDelta : undefined\n const previous = tracker.seen.get(key) ?? ''\n if (explicit !== undefined) {\n tracker.seen.set(key, previous + explicit)\n return explicit\n }\n const snapshot = asString(part.text) ?? asString(part.content) ?? ''\n if (!snapshot) return ''\n if (snapshot.startsWith(previous)) {\n tracker.seen.set(key, snapshot)\n return snapshot.slice(previous.length)\n }\n // The snapshot replaced the text outright — emit it whole; the persisted\n // projection stays correct because finalText is authoritative at finalize.\n tracker.seen.set(key, snapshot)\n return snapshot\n}\n\nfunction usageFromStepFinish(part: JsonRecord, usage: ChatTurnUsage): void {\n const tokens = asRecord(part.tokens)\n if (tokens) {\n const cache = asRecord(tokens.cache)\n const add = (current: number | undefined, value: unknown): number | undefined => {\n const n = Number(value)\n if (!Number.isFinite(n)) return current\n return (current ?? 0) + n\n }\n usage.inputTokens = add(usage.inputTokens, tokens.input)\n usage.outputTokens = add(usage.outputTokens, tokens.output)\n usage.reasoningTokens = add(usage.reasoningTokens, tokens.reasoning)\n if (cache) {\n usage.cacheReadTokens = add(usage.cacheReadTokens, cache.read)\n usage.cacheWriteTokens = add(usage.cacheWriteTokens, cache.write)\n }\n }\n const cost = Number(part.cost)\n if (Number.isFinite(cost)) usage.costUsd = (usage.costUsd ?? 0) + cost\n}\n\nexport function createSandboxChatProducer(options: SandboxChatProducerOptions): ChatTurnRouteProducer {\n const log = options.log ?? ((message, meta) => console.error(message, meta ?? ''))\n const renderable = options.isRenderableInteraction ?? isRenderableInteractionKind\n\n let fullText = ''\n const partOrder: string[] = []\n const partMap = new Map<string, JsonRecord>()\n const tracker: TextTracker = { seen: new Map() }\n const usage: ChatTurnUsage = {}\n /** Tool ids already announced as `tool_call` / settled as `tool_result`. */\n const announcedTools = new Set<string>()\n const settledTools = new Set<string>()\n /** Id-less step boundaries: one occurrence per key, never merged. */\n let stepCounter = 0\n\n function recordPersistedPart(part: JsonRecord, delta: string | undefined, keyOverride?: string): void {\n const persisted = normalizePersistedPart(part)\n if (!persisted) return\n const key = keyOverride ?? getPartKey(persisted)\n if (!partMap.has(key)) partOrder.push(key)\n partMap.set(key, mergePersistedPart(partMap.get(key), persisted, delta))\n }\n\n async function* stream(): AsyncGenerator<StreamEvent, void, unknown> {\n for await (const raw of options.events) {\n const record = asRecord(raw)\n if (!record || typeof record.type !== 'string') continue\n // Fold bare tool_call/tool_result shapes into the canonical part event;\n // everything else keeps its original record (verbatim forwarding must\n // not strip fields outside `data`).\n const normalized = normalizeToolEvent({ type: record.type, data: asRecord(record.data) })\n const event = normalized.type === 'message.part.updated' ? normalized : (record as unknown as StreamEvent)\n\n if (event.type === 'message.part.updated') {\n const part = asRecord(event.data?.part)\n if (!part) continue\n const rawDelta = event.data?.delta\n const partType = String(part.type ?? '')\n\n if (partType === 'text' || partType === 'reasoning') {\n const key = getPartKey(part)\n const delta = textDelta(tracker, key, part, rawDelta)\n recordPersistedPart(part, delta || undefined)\n if (delta) {\n if (partType === 'text') fullText += delta\n yield { type: partType, text: delta } as StreamEvent & { text: string }\n }\n continue\n }\n\n if (partType === 'tool') {\n recordPersistedPart(part, undefined)\n const persisted = partMap.get(getPartKey(part))\n const state = asRecord(persisted?.state)\n const toolId = String(persisted?.id ?? '')\n const toolName = String(persisted?.tool ?? 'tool')\n if (toolId && !announcedTools.has(toolId)) {\n announcedTools.add(toolId)\n yield {\n type: 'tool_call',\n call: { toolCallId: toolId, toolName, args: asRecord(state?.input) ?? {} },\n } as StreamEvent\n }\n const status = String(state?.status ?? '')\n if (toolId && (status === 'completed' || status === 'error') && !settledTools.has(toolId)) {\n settledTools.add(toolId)\n yield {\n type: 'tool_result',\n toolCallId: toolId,\n toolName,\n outcome: {\n ok: status === 'completed',\n ...(state?.output !== undefined ? { result: state.output } : {}),\n ...(asString(state?.error) ? { message: asString(state?.error) } : {}),\n },\n } as StreamEvent\n }\n continue\n }\n\n if (partType === 'step-finish') {\n usageFromStepFinish(part, usage)\n // Persist the per-step receipt too (unique key per occurrence: the\n // parts have no id and two receipts must never merge into one).\n recordPersistedPart(part, undefined, `step-finish:#${stepCounter++}`)\n const promptTokens = usage.inputTokens ?? 0\n const completionTokens = usage.outputTokens ?? 0\n if (promptTokens || completionTokens) {\n yield { type: 'usage', usage: { promptTokens, completionTokens } } as StreamEvent\n }\n continue\n }\n\n if (partType === 'step-start') {\n recordPersistedPart(part, undefined, `step-start:#${stepCounter}`)\n continue\n }\n\n // Remaining storable kinds (file/image/subtask) have no live\n // vocabulary line; they persist so the transcript keeps them.\n recordPersistedPart(part, undefined)\n continue\n }\n\n if (event.type === 'interaction') {\n const parsed = parseInteractionRequest(asRecord(record.data))\n if (!parsed.succeeded) {\n log('[chat-routes] dropping malformed interaction event', { error: parsed.error })\n continue\n }\n if (renderable(parsed.value.kind)) {\n yield event\n continue\n }\n // Non-renderable ask: the run is blocked in the broker until someone\n // answers. Decline it so the turn proceeds instead of hanging.\n if (options.declineInteraction) {\n try {\n await options.declineInteraction(parsed.value.id)\n } catch (err) {\n log('[chat-routes] failed to auto-decline interaction', {\n id: parsed.value.id,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n } else {\n log('[chat-routes] non-renderable interaction with no declineInteraction wired', {\n id: parsed.value.id,\n kind: parsed.value.kind,\n })\n }\n continue\n }\n\n if (event.type === 'result') {\n const finalText = asString(event.data?.finalText)\n if (finalText) fullText = finalText\n const resultUsage = asRecord(event.data?.usage)\n if (resultUsage) {\n const input = Number(resultUsage.inputTokens)\n const output = Number(resultUsage.outputTokens)\n if (Number.isFinite(input)) usage.inputTokens = input\n if (Number.isFinite(output)) usage.outputTokens = output\n }\n continue\n }\n\n // Everything else (interaction.cancel, error, lifecycle) forwards\n // verbatim — the client parser ignores unknown types.\n yield event\n }\n }\n\n return {\n stream: stream(),\n finalText: () => fullText,\n assistantParts: () => finalizeAssistantParts(partOrder, partMap, fullText),\n usage: () => usage,\n ...(options.model ? { model: options.model } : {}),\n }\n}\n","/**\n * `createUploadRoute` — the multimodal middle. Accepts multipart file uploads\n * and returns `PromptInputPart`-shaped descriptors the client echoes back on\n * send (`ChatTurnRequestPayload.parts`):\n *\n * ≤ inlineMaxBytes (700 KiB default) → inline `data:` URI part — rides the\n * turn body directly, no sandbox round trip.\n * > inlineMaxBytes → written into the sandbox workspace (base64 through the\n * structural `write` seam — `box.fs` satisfies it) and referenced by\n * `path`. Mandatory two-step: the gateway caps request bodies at ~1 MiB,\n * so a large file can never ride the prompt POST.\n *\n * The sink is structural (no sandbox-SDK import); products pass `box.fs`.\n */\n\nimport type { ChatTurnFilePartInput } from './wire'\n\n/** 700 KiB: base64 inflates ~4/3, so an inline part stays comfortably under\n * the ~1 MiB gateway body cap alongside the JSON envelope. */\nexport const UPLOAD_INLINE_MAX_BYTES = 700 * 1024\n\n/** 8 MiB default ceiling per file — one base64 `write` call handles it. Raise\n * it only with a sink that can take the bigger single write. */\nexport const UPLOAD_MAX_FILE_BYTES = 8 * 1024 * 1024\n\n/** Structural match of the sandbox SDK's `box.fs` write surface (v0.10.5+:\n * `encoding: 'base64'` is the worker-safe binary path). */\nexport interface SandboxUploadSink {\n write(path: string, content: string, options?: { encoding?: 'utf8' | 'base64' }): Promise<unknown>\n}\n\nexport type UploadAuthorization =\n | {\n ok: true\n /** Where large files land. Absent/null: only inline uploads are\n * accepted and an over-inline-cap file is rejected with 413. */\n sink?: SandboxUploadSink | null\n /** Per-request override of the workspace directory large files go to. */\n uploadDir?: string\n }\n | { ok: false; response: Response }\n\nexport interface CreateUploadRouteOptions {\n /** Authenticate the caller and resolve the sandbox file sink (usually\n * `ensureWorkspaceSandbox(...)` → `box.fs`). */\n authorize(args: { request: Request }): Promise<UploadAuthorization>\n /** Inline-vs-sandbox threshold. Default {@link UPLOAD_INLINE_MAX_BYTES}. */\n inlineMaxBytes?: number\n /** Hard per-file cap. Default {@link UPLOAD_MAX_FILE_BYTES}. */\n maxFileBytes?: number\n /** Workspace directory for path-ref files. Default `'uploads'`. */\n uploadDir?: string\n}\n\n/** One uploaded file, ready for the composer chip and the turn body. */\nexport interface UploadedChatFile {\n id: string\n name: string\n size: number\n mediaType: string\n /** True when the part carries the bytes inline (`data:` URI). */\n inline: boolean\n /** Echo this back verbatim in `ChatTurnRequestPayload.parts`. */\n part: ChatTurnFilePartInput\n}\n\n/** Path-safe file name: basename only, conservative charset, length-capped. */\nexport function sanitizeUploadFilename(name: string): string {\n const base = name.split(/[\\\\/]/).pop() ?? 'file'\n const safe = base.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^\\.+/, '_')\n return (safe || 'file').slice(0, 120)\n}\n\nconst BASE64_CHUNK = 0x8000\n\nexport function bytesToBase64(bytes: Uint8Array): string {\n let binary = ''\n for (let offset = 0; offset < bytes.length; offset += BASE64_CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_CHUNK))\n }\n return btoa(binary)\n}\n\nfunction uploadError(status: number, code: string, error: string): Response {\n return Response.json({ code, error }, { status })\n}\n\nexport function createUploadRoute(options: CreateUploadRouteOptions): (request: Request) => Promise<Response> {\n const inlineMaxBytes = options.inlineMaxBytes ?? UPLOAD_INLINE_MAX_BYTES\n const maxFileBytes = options.maxFileBytes ?? UPLOAD_MAX_FILE_BYTES\n\n return async function upload(request: Request): Promise<Response> {\n const auth = await options.authorize({ request })\n if (!auth.ok) return auth.response\n const sink = auth.sink ?? null\n const uploadDir = (auth.uploadDir ?? options.uploadDir ?? 'uploads').replace(/\\/+$/, '')\n\n let form: FormData\n try {\n form = await request.formData()\n } catch {\n return uploadError(400, 'INVALID_UPLOAD', 'Expected a multipart/form-data body with file fields')\n }\n const files: File[] = []\n form.forEach((value) => {\n if (value instanceof File) files.push(value)\n })\n if (files.length === 0) {\n return uploadError(400, 'INVALID_UPLOAD', 'No files in the upload body')\n }\n\n const uploaded: UploadedChatFile[] = []\n for (const file of files) {\n const name = sanitizeUploadFilename(file.name)\n const mediaType = file.type || 'application/octet-stream'\n const partType: ChatTurnFilePartInput['type'] = mediaType.startsWith('image/') ? 'image' : 'file'\n\n if (file.size > maxFileBytes) {\n return uploadError(\n 413,\n 'FILE_TOO_LARGE',\n `${name} is ${file.size}B, over the ${maxFileBytes}B per-file cap`,\n )\n }\n\n const id = crypto.randomUUID()\n if (file.size <= inlineMaxBytes) {\n const base64 = bytesToBase64(new Uint8Array(await file.arrayBuffer()))\n uploaded.push({\n id,\n name,\n size: file.size,\n mediaType,\n inline: true,\n part: {\n type: partType,\n filename: name,\n mediaType,\n url: `data:${mediaType};base64,${base64}`,\n },\n })\n continue\n }\n\n if (!sink) {\n return uploadError(\n 413,\n 'SANDBOX_REQUIRED',\n `${name} is ${file.size}B, over the ${inlineMaxBytes}B inline cap, and no sandbox is available to hold it`,\n )\n }\n const path = `${uploadDir}/${id}-${name}`\n const base64 = bytesToBase64(new Uint8Array(await file.arrayBuffer()))\n await sink.write(path, base64, { encoding: 'base64' })\n uploaded.push({\n id,\n name,\n size: file.size,\n mediaType,\n inline: false,\n part: { type: partType, filename: name, mediaType, path },\n })\n }\n\n return Response.json({ files: uploaded })\n }\n}\n"],"mappings":"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","names":["base64"]}
|