@tangle-network/agent-app 0.6.0 → 0.7.0

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  dispatchAppTool,
3
3
  outcomeStatus
4
- } from "./chunk-JANT2G2E.js";
4
+ } from "./chunk-QAQBR6KQ.js";
5
5
 
6
6
  // src/tools/capability.ts
7
7
  async function createCapabilityToken(userId, opts) {
@@ -134,4 +134,4 @@ export {
134
134
  buildHttpMcpServer,
135
135
  buildAppToolMcpServer
136
136
  };
137
- //# sourceMappingURL=chunk-HZZD3ZYD.js.map
137
+ //# sourceMappingURL=chunk-OLCVUGGI.js.map
@@ -97,8 +97,15 @@ async function dispatchAppTool(toolName, rawArgs, ctx, opts) {
97
97
  }
98
98
  if (!title) return { ok: false, code: "missing_title", message: "title is required." };
99
99
  const description = rawArgs.description == null ? null : String(rawArgs.description);
100
- const r2 = await opts.handlers.submitProposal({ type, title, description }, ctx);
101
- const regulated = opts.taxonomy.regulatedTypes.includes(type);
100
+ let regulated = opts.taxonomy.regulatedTypes.includes(type);
101
+ if (opts.needsApproval) {
102
+ try {
103
+ regulated = await opts.needsApproval(type, { title, description }, ctx);
104
+ } catch {
105
+ regulated = true;
106
+ }
107
+ }
108
+ const r2 = await opts.handlers.submitProposal({ type, title, description, regulated }, ctx);
102
109
  const { proposalId, deduped, status, ...extra } = r2;
103
110
  const effectiveStatus = status ?? "queued_for_approval";
104
111
  opts.onProduced?.({
@@ -149,4 +156,4 @@ export {
149
156
  outcomeStatus,
150
157
  createAppToolRuntimeExecutor
151
158
  };
152
- //# sourceMappingURL=chunk-JANT2G2E.js.map
159
+ //# sourceMappingURL=chunk-QAQBR6KQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tools/errors.ts","../src/tools/openai.ts","../src/tools/dispatch.ts","../src/tools/runtime.ts"],"sourcesContent":["/** A correctable bad-input error a tool handler throws; the HTTP layer maps it\n * to a 4xx with the code, the runtime layer to a failed tool_result. So the\n * agent learns the call failed and can correct, instead of a silent success. */\nexport class ToolInputError extends Error {\n constructor(\n public code: string,\n message: string,\n public status = 400,\n ) {\n super(message)\n this.name = 'ToolInputError'\n }\n}\n","import type { AppToolTaxonomy } from './types'\n\n/** The four canonical app-tool names. Stable identifiers the model calls in\n * both the sandbox (MCP server name) and runtime (function-tool name) paths. */\nexport const APP_TOOL_NAMES = ['submit_proposal', 'schedule_followup', 'render_ui', 'add_citation'] as const\nexport type AppToolName = (typeof APP_TOOL_NAMES)[number]\n\nconst NAME_SET = new Set<string>(APP_TOOL_NAMES)\nexport function isAppToolName(name: string): name is AppToolName {\n return NAME_SET.has(name)\n}\n\n/** A minimal OpenAI Chat Completions function-tool shape — structurally\n * compatible with `@tangle-network/agent-runtime`'s `OpenAIChatTool` without\n * importing it (keeps this package runtime-free). */\nexport interface OpenAIFunctionTool {\n type: 'function'\n function: {\n name: string\n description: string\n parameters: Record<string, unknown>\n }\n}\n\n/**\n * Build the four app tools in OpenAI function-tool shape. `submit_proposal`'s\n * `type` enum is the product's {@link AppToolTaxonomy.proposalTypes}; the other\n * three are fixed. Pass the result to the agent-runtime backend's `tools`.\n */\nexport function buildAppToolOpenAITools(taxonomy: AppToolTaxonomy): OpenAIFunctionTool[] {\n return [\n {\n type: 'function',\n function: {\n name: 'submit_proposal',\n description:\n 'Route a regulated or state-changing action to a human for approval (a recommendation, contacting/soliciting a contact, outreach, a record/account change, scheduling). Queues it for a named certified human to approve before it executes.',\n parameters: {\n type: 'object',\n properties: {\n type: { type: 'string', enum: [...taxonomy.proposalTypes] },\n title: { type: 'string', description: 'Short label for the approval queue.' },\n description: { type: 'string', description: 'The full drafted message/recommendation, with sources.' },\n },\n required: ['type', 'title'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'schedule_followup',\n description: 'Register a dated cadence step (a reminder, chase, or check-in) on the follow-up calendar. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n dueDate: { type: 'string', description: 'ISO date YYYY-MM-DD.' },\n priority: { type: 'string', enum: ['low', 'medium', 'high'] },\n },\n required: ['title', 'dueDate'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'render_ui',\n description: 'Show a generated view live in the workspace. Validates the OpenUI JSON and persists the artifact. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n schema: { type: 'object', description: 'The OpenUI JSON object.' },\n },\n required: ['title', 'schema'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'add_citation',\n description: 'Anchor a grounding reference: the exact quote from a file backing a figure or claim. Verifies the quote appears in the file. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'The vault file path.' },\n quote: { type: 'string', description: 'The exact text from it.' },\n },\n required: ['path', 'quote'],\n },\n },\n },\n ]\n}\n","import { ToolInputError } from './errors'\nimport { isAppToolName } from './openai'\nimport type {\n AppToolContext,\n AppToolHandlers,\n AppToolOutcome,\n AppToolProducedEvent,\n AppToolTaxonomy,\n} from './types'\n\nexport interface DispatchOptions {\n handlers: AppToolHandlers\n taxonomy: AppToolTaxonomy\n /** Per-call approval policy. When provided it OVERRIDES the static\n * `taxonomy.regulatedTypes` membership check, so products can gate by\n * cost threshold, environment, or first-use instead of always/never.\n * Fail-closed: a predicate that throws counts as \"approval required\". */\n needsApproval?: (type: string, args: { title: string; description: string | null }, ctx: AppToolContext) => boolean | Promise<boolean>\n /** Called at the real side-effect site for proposals (proposal_created) and\n * generated views (artifact) so a consumer's completion oracle credits\n * persisted state. Omit when produced state isn't tracked. */\n onProduced?: (event: AppToolProducedEvent) => void\n}\n\n/**\n * The ONE place an app-tool call is validated, dispatched to the product's\n * handler, and turned into an {@link AppToolOutcome} + produced events. Shared\n * by the HTTP route layer and the agent-runtime executor so both paths apply\n * identical validation and identical side effects. A {@link ToolInputError}\n * (bad input the agent can correct) and any other throw both become\n * `{ ok: false }` — a tool call never silently \"succeeds\" without its effect.\n */\nexport async function dispatchAppTool(\n toolName: string,\n rawArgs: Record<string, unknown>,\n ctx: AppToolContext,\n opts: DispatchOptions,\n): Promise<AppToolOutcome> {\n try {\n if (!isAppToolName(toolName)) {\n return { ok: false, code: 'unknown_tool', message: `${toolName} is not an app tool.` }\n }\n\n if (toolName === 'submit_proposal') {\n const type = String(rawArgs.type ?? '').trim()\n const title = String(rawArgs.title ?? '').trim()\n if (!type || !opts.taxonomy.proposalTypes.includes(type)) {\n return { ok: false, code: 'invalid_type', message: `type must be one of: ${opts.taxonomy.proposalTypes.join(', ')}.` }\n }\n if (!title) return { ok: false, code: 'missing_title', message: 'title is required.' }\n const description = rawArgs.description == null ? null : String(rawArgs.description)\n // Approval policy runs BEFORE the handler so the decision can gate the\n // side effect itself, not merely re-label it afterwards.\n let regulated = opts.taxonomy.regulatedTypes.includes(type)\n if (opts.needsApproval) {\n try {\n regulated = await opts.needsApproval(type, { title, description }, ctx)\n } catch {\n regulated = true // fail-closed: a broken policy means approval required\n }\n }\n const r = await opts.handlers.submitProposal({ type, title, description, regulated }, ctx)\n // Pass the handler's result through: products with immediate-execute\n // proposal types return status 'executed' plus their own fields\n // (e.g. datasetId) — the model must see what actually happened, not a\n // hard-coded \"queued for approval\".\n const { proposalId, deduped, status, ...extra } = r\n const effectiveStatus = status ?? 'queued_for_approval'\n opts.onProduced?.({\n type: 'proposal_created',\n proposalId,\n title,\n status: effectiveStatus === 'executed' ? 'executed' : 'pending',\n })\n return { ok: true, result: { ...extra, status: effectiveStatus, proposalId, deduped, regulated } }\n }\n\n if (toolName === 'schedule_followup') {\n const r = await opts.handlers.scheduleFollowup(\n { title: String(rawArgs.title ?? ''), dueDate: String(rawArgs.dueDate ?? ''), priority: rawArgs.priority as string | undefined },\n ctx,\n )\n return { ok: true, result: { followupId: r.id, dueDate: r.dueDate, deduped: r.deduped } }\n }\n\n if (toolName === 'render_ui') {\n const r = await opts.handlers.renderUi({ title: String(rawArgs.title ?? ''), schema: rawArgs.schema }, ctx)\n opts.onProduced?.({ type: 'artifact', path: r.path, content: r.content })\n return { ok: true, result: { path: r.path } }\n }\n\n // add_citation\n const r = await opts.handlers.addCitation(\n { path: String(rawArgs.path ?? ''), quote: String(rawArgs.quote ?? ''), label: rawArgs.label as string | undefined },\n ctx,\n )\n return { ok: true, result: { citationId: r.citationId, path: r.path } }\n } catch (err) {\n if (err instanceof ToolInputError) return { ok: false, code: err.code, message: err.message, status: err.status }\n return { ok: false, code: 'app_tool_error', message: err instanceof Error ? err.message : String(err), status: 500 }\n }\n}\n\n/** HTTP status for a failed outcome — the handler's `ToolInputError.status`\n * when present, else 400 for a validation reject. */\nexport function outcomeStatus(outcome: Extract<AppToolOutcome, { ok: false }>): number {\n return outcome.status ?? 400\n}\n","import { dispatchAppTool, type DispatchOptions } from './dispatch'\nimport type { AppToolContext, AppToolOutcome } from './types'\n\n/** Executes an app-tool call the model emits on the agent-runtime chat path.\n * Plug into `runChatThroughRuntime({ appToolExecutor })` (or any loop that\n * dispatches function tool_calls). */\nexport type AppToolRuntimeExecutor = (call: {\n toolName: string\n args: Record<string, unknown>\n}) => Promise<AppToolOutcome>\n\nexport interface RuntimeExecutorOptions extends DispatchOptions {\n /** The trusted per-turn context — supplied directly (not from headers), since\n * the runtime path has no HTTP request. */\n ctx: AppToolContext\n}\n\n/**\n * Build the runtime executor for one turn. The agent-runtime backend must also\n * advertise the tools (`buildAppToolOpenAITools(taxonomy)` on the backend's\n * `tools`) for the model to call them; this executor fulfils each call against\n * the product's handlers and emits produced events via `opts.onProduced`.\n */\nexport function createAppToolRuntimeExecutor(opts: RuntimeExecutorOptions): AppToolRuntimeExecutor {\n return ({ toolName, args }) => dispatchAppTool(toolName, args, opts.ctx, opts)\n}\n"],"mappings":"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","names":["r"]}
@@ -225,6 +225,150 @@ function countUserMessages(messages) {
225
225
  return messages.filter((message) => message.role === "user").length;
226
226
  }
227
227
 
228
+ // src/stream/turn-buffer.ts
229
+ function deltaTypeOf(ev) {
230
+ const e = ev;
231
+ if (!e || typeof e !== "object") return null;
232
+ const inner = e.kind === "event" ? e.event : e;
233
+ if (!inner || typeof inner !== "object") return null;
234
+ if ((inner.type === "text" || inner.type === "reasoning") && typeof inner.text === "string") {
235
+ return inner.type;
236
+ }
237
+ return null;
238
+ }
239
+ function coalesceDeltas(events) {
240
+ const out = [];
241
+ for (const ev of events) {
242
+ const type = deltaTypeOf(ev);
243
+ const prev = out[out.length - 1];
244
+ if (type && prev && deltaTypeOf(prev) === type) {
245
+ const read = (x) => x.kind === "event" ? x.event : x;
246
+ const merged = JSON.parse(JSON.stringify(prev));
247
+ read(merged).text = String(read(prev).text) + String(read(ev).text);
248
+ out[out.length - 1] = merged;
249
+ continue;
250
+ }
251
+ out.push(ev);
252
+ }
253
+ return out;
254
+ }
255
+ async function pumpBufferedTurn(opts) {
256
+ const flushIntervalMs = opts.flushIntervalMs ?? 400;
257
+ let seq = 0;
258
+ let clientGone = false;
259
+ let pending = [];
260
+ let lastFlush = Date.now();
261
+ async function flush() {
262
+ if (pending.length === 0) return;
263
+ const batch = coalesceDeltas(pending);
264
+ pending = [];
265
+ const rows = batch.map((ev) => ({ seq: ++seq, event: JSON.stringify(ev) }));
266
+ await opts.store.append(opts.turnId, rows);
267
+ lastFlush = Date.now();
268
+ }
269
+ await opts.store.setStatus(opts.turnId, "running");
270
+ try {
271
+ for await (const ev of opts.source) {
272
+ pending.push(ev);
273
+ if (!clientGone && opts.write) {
274
+ try {
275
+ await opts.write(JSON.stringify(ev));
276
+ } catch {
277
+ clientGone = true;
278
+ }
279
+ }
280
+ if (Date.now() - lastFlush >= flushIntervalMs) await flush();
281
+ }
282
+ await flush();
283
+ await opts.store.setStatus(opts.turnId, "complete");
284
+ } catch (err) {
285
+ await flush().catch(() => {
286
+ });
287
+ await opts.store.setStatus(opts.turnId, "error").catch(() => {
288
+ });
289
+ throw err;
290
+ }
291
+ }
292
+ async function* replayTurnEvents(opts) {
293
+ const pollMs = opts.pollMs ?? 500;
294
+ const timeoutMs = opts.timeoutMs ?? 12e4;
295
+ let cursor = opts.fromSeq ?? 0;
296
+ const deadline = Date.now() + timeoutMs;
297
+ for (; ; ) {
298
+ const batch = await opts.store.read(opts.turnId, cursor);
299
+ for (const row of batch) {
300
+ cursor = Math.max(cursor, row.seq);
301
+ yield row;
302
+ }
303
+ const status = await opts.store.getStatus(opts.turnId);
304
+ if (status !== "running") {
305
+ yield { seq: -1, event: JSON.stringify({ type: "turn_status", status: status ?? "unknown" }) };
306
+ return;
307
+ }
308
+ if (Date.now() >= deadline) {
309
+ yield { seq: -1, event: JSON.stringify({ type: "turn_status", status: "timeout" }) };
310
+ return;
311
+ }
312
+ await new Promise((r) => setTimeout(r, pollMs));
313
+ }
314
+ }
315
+ var TURN_EVENTS_MIGRATION_SQL = `
316
+ CREATE TABLE IF NOT EXISTS turn_events (
317
+ turnId TEXT NOT NULL,
318
+ seq INTEGER NOT NULL,
319
+ event TEXT NOT NULL,
320
+ PRIMARY KEY (turnId, seq)
321
+ );
322
+ CREATE TABLE IF NOT EXISTS turn_status (
323
+ turnId TEXT PRIMARY KEY,
324
+ status TEXT NOT NULL,
325
+ updatedAt TEXT NOT NULL
326
+ );
327
+ `;
328
+ function createD1TurnEventStore(db) {
329
+ return {
330
+ async append(turnId, events) {
331
+ if (!events.length) return;
332
+ const placeholders = events.map(() => "(?, ?, ?)").join(", ");
333
+ const values = events.flatMap((e) => [turnId, e.seq, e.event]);
334
+ await db.prepare(`INSERT OR IGNORE INTO turn_events (turnId, seq, event) VALUES ${placeholders}`).bind(...values).run();
335
+ },
336
+ async read(turnId, fromSeq) {
337
+ const { results } = await db.prepare("SELECT seq, event FROM turn_events WHERE turnId = ? AND seq > ? ORDER BY seq ASC").bind(turnId, fromSeq).all();
338
+ return results;
339
+ },
340
+ async setStatus(turnId, status) {
341
+ await db.prepare(
342
+ "INSERT INTO turn_status (turnId, status, updatedAt) VALUES (?, ?, ?) ON CONFLICT(turnId) DO UPDATE SET status = excluded.status, updatedAt = excluded.updatedAt"
343
+ ).bind(turnId, status, (/* @__PURE__ */ new Date()).toISOString()).run();
344
+ },
345
+ async getStatus(turnId) {
346
+ const row = await db.prepare("SELECT status FROM turn_status WHERE turnId = ?").bind(turnId).first();
347
+ return row?.status ?? null;
348
+ }
349
+ };
350
+ }
351
+ function createMemoryTurnEventStore() {
352
+ const events = /* @__PURE__ */ new Map();
353
+ const status = /* @__PURE__ */ new Map();
354
+ return {
355
+ async append(turnId, rows) {
356
+ const list = events.get(turnId) ?? [];
357
+ list.push(...rows);
358
+ events.set(turnId, list);
359
+ },
360
+ async read(turnId, fromSeq) {
361
+ return (events.get(turnId) ?? []).filter((e) => e.seq > fromSeq);
362
+ },
363
+ async setStatus(turnId, s) {
364
+ status.set(turnId, s);
365
+ },
366
+ async getStatus(turnId) {
367
+ return status.get(turnId) ?? null;
368
+ }
369
+ };
370
+ }
371
+
228
372
  export {
229
373
  asRecord,
230
374
  asString,
@@ -240,6 +384,12 @@ export {
240
384
  normalizeClientTurnId,
241
385
  buildUserTextParts,
242
386
  messageHasTurnId,
243
- resolveChatTurn
387
+ resolveChatTurn,
388
+ coalesceDeltas,
389
+ pumpBufferedTurn,
390
+ replayTurnEvents,
391
+ TURN_EVENTS_MIGRATION_SQL,
392
+ createD1TurnEventStore,
393
+ createMemoryTurnEventStore
244
394
  };
245
- //# sourceMappingURL=chunk-GMFPCCQZ.js.map
395
+ //# sourceMappingURL=chunk-SDOT7RNB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stream/stream-normalizer.ts","../src/stream/turn-identity.ts","../src/stream/turn-buffer.ts"],"sourcesContent":["export type JsonRecord = Record<string, unknown>\n\nexport interface StreamEvent {\n type: string\n data?: JsonRecord\n}\n\nexport function asRecord(value: unknown): JsonRecord | undefined {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? value as JsonRecord\n : undefined\n}\n\nexport function asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nexport function resolveToolId(part: JsonRecord): string {\n return String(\n part.id ??\n part.callID ??\n part.callId ??\n part.toolUseId ??\n part.toolCallId ??\n part.tool ??\n part.name ??\n `tool-${Date.now()}`,\n )\n}\n\nexport function resolveToolName(part: JsonRecord): string {\n return String(part.tool ?? part.name ?? 'tool')\n}\n\nexport function normalizeTime(value: unknown): JsonRecord | undefined {\n const record = asRecord(value)\n if (!record) return undefined\n\n const start = Number(record.start ?? record.startedAt ?? record.started_at)\n const end = Number(record.end ?? record.completedAt ?? record.completed_at)\n if (!Number.isFinite(start) && !Number.isFinite(end)) return undefined\n\n return {\n start: Number.isFinite(start) ? start : undefined,\n end: Number.isFinite(end) ? end : undefined,\n }\n}\n\nexport function normalizeToolEvent(event: StreamEvent): StreamEvent {\n if (event.type === 'tool_call' || event.type === 'tool.call') {\n const data = event.data ?? {}\n return {\n type: 'message.part.updated',\n data: {\n part: {\n type: 'tool',\n id: data.id ?? data.callId ?? data.callID ?? data.name,\n tool: data.name ?? data.tool ?? 'tool',\n input: data.arguments ?? data.input,\n status: 'running',\n },\n },\n }\n }\n\n if (event.type === 'tool_result' || event.type === 'tool.result') {\n const data = event.data ?? {}\n const error = asString(data.error)\n return {\n type: 'message.part.updated',\n data: {\n part: {\n type: 'tool',\n id: data.id ?? data.callId ?? data.callID ?? data.name,\n tool: data.name ?? data.tool ?? 'tool',\n output: data.output,\n error,\n status: error ? 'error' : 'completed',\n },\n },\n }\n }\n\n return event\n}\n\nexport function normalizePersistedPart(rawPart: JsonRecord): JsonRecord | null {\n const type = String(rawPart.type ?? '')\n\n if (type === 'text') {\n return {\n type: 'text',\n text: asString(rawPart.text) ?? asString(rawPart.content) ?? '',\n }\n }\n\n if (type === 'reasoning') {\n return {\n type: 'reasoning',\n text: asString(rawPart.text) ?? asString(rawPart.content) ?? '',\n time: normalizeTime(rawPart.time),\n }\n }\n\n if (type === 'tool') {\n const state = asRecord(rawPart.state)\n const output = state?.output ?? rawPart.output\n const error = asString(state?.error ?? rawPart.error)\n const status =\n state?.status === 'completed' || rawPart.status === 'completed'\n ? 'completed'\n : state?.status === 'error' || rawPart.status === 'error' || error\n ? 'error'\n : output !== undefined\n ? 'completed'\n : 'running'\n\n return {\n type: 'tool',\n id: resolveToolId(rawPart),\n tool: resolveToolName(rawPart),\n callID:\n rawPart.callID != null || rawPart.callId != null\n ? String(rawPart.callID ?? rawPart.callId)\n : undefined,\n state: {\n status,\n input: state?.input ?? rawPart.input,\n output,\n error,\n metadata: asRecord(state?.metadata) ?? asRecord(rawPart.metadata),\n time: normalizeTime(state?.time ?? rawPart.time),\n },\n }\n }\n\n return null\n}\n\nexport function getPartKey(part: JsonRecord): string {\n const type = String(part.type ?? 'unknown')\n if (type === 'tool') {\n return `tool:${resolveToolId(part)}`\n }\n\n if (type === 'reasoning') {\n return `reasoning:${String(part.id ?? part.partId ?? part.index ?? 'current')}`\n }\n\n return `text:${String(part.id ?? part.partId ?? part.index ?? 'current')}`\n}\n\nexport function mergePersistedPart(existing: JsonRecord | undefined, incoming: JsonRecord, delta?: string): JsonRecord {\n const type = String(incoming.type ?? '')\n if (!existing) {\n if (type === 'text' && delta) {\n return { type: 'text', text: delta }\n }\n return incoming\n }\n\n if (type === 'text' && String(existing.type ?? '') === 'text') {\n return {\n ...existing,\n ...incoming,\n text: delta ? `${String(existing.text ?? '')}${delta}` : String(incoming.text ?? ''),\n }\n }\n\n if (type === 'reasoning' && String(existing.type ?? '') === 'reasoning') {\n const existingText = String(existing.text ?? '')\n const incomingText = String(incoming.text ?? '')\n return {\n ...existing,\n ...incoming,\n text: delta && incomingText === existingText ? `${existingText}${delta}` : incomingText || existingText,\n time: incoming.time ?? existing.time,\n }\n }\n\n if (type === 'tool' && String(existing.type ?? '') === 'tool') {\n return {\n ...existing,\n ...incoming,\n state: {\n ...(asRecord(existing.state) ?? {}),\n ...(asRecord(incoming.state) ?? {}),\n time: asRecord(incoming.state)?.time ?? asRecord(existing.state)?.time,\n },\n }\n }\n\n return incoming\n}\n\nexport function finalizeAssistantParts(\n partOrder: string[],\n partMap: Map<string, JsonRecord>,\n finalText: string,\n): JsonRecord[] {\n const parts = partOrder\n .map((key) => partMap.get(key))\n .filter((part): part is JsonRecord => Boolean(part))\n\n if (!parts.some((part) => String(part.type ?? '') === 'text')) {\n if (finalText.trim()) {\n parts.push({ type: 'text', text: finalText })\n }\n return parts\n }\n\n return parts.map((part) => {\n if (String(part.type ?? '') !== 'text') return part\n return {\n ...part,\n text: finalText || String(part.text ?? ''),\n }\n })\n}\n\nexport function encodeEvent(encoder: TextEncoder, event: StreamEvent): Uint8Array {\n return encoder.encode(`${JSON.stringify(event)}\\n`)\n}\n","import type { JsonRecord } from './stream-normalizer'\n\nexport interface PersistedChatMessageForTurn {\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n parts: Array<Record<string, unknown>> | null\n}\n\nexport interface ResolvedChatTurn {\n turnIndex: number\n shouldInsertUserMessage: boolean\n priorMessages: PersistedChatMessageForTurn[]\n userParts: JsonRecord[]\n}\n\nexport function normalizeClientTurnId(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'string') throw new Error('turnId must be a string')\n const trimmed = value.trim()\n if (!trimmed) throw new Error('turnId must not be blank')\n if (trimmed.length > 160) throw new Error('turnId is too long')\n if (!/^[A-Za-z0-9:_-]+$/.test(trimmed)) {\n throw new Error('turnId contains unsupported characters')\n }\n return trimmed\n}\n\nexport function buildUserTextParts(text: string, turnId: string | undefined): JsonRecord[] {\n const part: JsonRecord = { type: 'text', text }\n if (turnId) part.turnId = turnId\n return [part]\n}\n\nexport function messageHasTurnId(message: PersistedChatMessageForTurn, turnId: string): boolean {\n for (const part of message.parts ?? []) {\n if (part && typeof part === 'object' && String(part.turnId ?? '') === turnId) {\n return true\n }\n }\n return false\n}\n\nexport function resolveChatTurn(input: {\n existingMessages: PersistedChatMessageForTurn[]\n userContent: string\n turnId?: string\n}): ResolvedChatTurn {\n const { existingMessages, userContent, turnId } = input\n const reusableIndex = findReusableUserMessageIndex(existingMessages, userContent, turnId)\n if (reusableIndex >= 0) {\n return {\n turnIndex: countUserMessages(existingMessages.slice(0, reusableIndex)),\n shouldInsertUserMessage: false,\n priorMessages: existingMessages.slice(0, reusableIndex),\n userParts: buildUserTextParts(userContent, turnId),\n }\n }\n\n return {\n turnIndex: countUserMessages(existingMessages),\n shouldInsertUserMessage: true,\n priorMessages: existingMessages,\n userParts: buildUserTextParts(userContent, turnId),\n }\n}\n\nfunction findReusableUserMessageIndex(\n messages: PersistedChatMessageForTurn[],\n userContent: string,\n turnId: string | undefined,\n): number {\n if (turnId) {\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n const message = messages[index]\n if (message?.role === 'user' && messageHasTurnId(message, turnId)) return index\n }\n }\n\n const latest = messages.at(-1)\n if (latest?.role === 'user' && latest.content === userContent) {\n return messages.length - 1\n }\n\n return -1\n}\n\nfunction countUserMessages(messages: PersistedChatMessageForTurn[]): number {\n return messages.filter((message) => message.role === 'user').length\n}\n","/**\n * Resumable chat turns — the router-path answer to \"streams resume on\n * disconnect\" (issue #27). A turn's loop events are teed into a store as they\n * stream; the turn keeps running under `ctx.waitUntil` when the client drops;\n * a reconnecting client replays the buffered tail by sequence number and\n * keeps following until the turn completes.\n *\n * POST /chat/stream → pumpBufferedTurn(...) + live NDJSON\n * GET /chat/stream/:turnId → replayTurnEvents({ fromSeq }) → NDJSON\n *\n * Storage is a structural seam ({@link TurnEventStore}); a D1 implementation\n * ships here because that's what Cloudflare products have (KV is unsuitable:\n * eventually consistent cross-isolate). Per-token deltas would mean hundreds\n * of rows per turn, so consecutive text/reasoning deltas are coalesced within\n * a flush window before they are persisted — replay yields slightly chunkier\n * deltas with identical concatenation.\n */\n\nexport type TurnStatus = 'running' | 'complete' | 'error'\n\nexport interface BufferedTurnEvent {\n seq: number\n /** The serialized event line (JSON string, no trailing newline). */\n event: string\n}\n\nexport interface TurnEventStore {\n append(turnId: string, events: BufferedTurnEvent[]): Promise<void>\n read(turnId: string, fromSeq: number): Promise<BufferedTurnEvent[]>\n setStatus(turnId: string, status: TurnStatus): Promise<void>\n getStatus(turnId: string): Promise<TurnStatus | null>\n}\n\n// ── coalescing ────────────────────────────────────────────────────────────\n\ntype AnyRecord = Record<string, unknown>\n\nfunction deltaTypeOf(ev: unknown): 'text' | 'reasoning' | null {\n const e = ev as AnyRecord | null\n if (!e || typeof e !== 'object') return null\n const inner = (e.kind === 'event' ? (e.event as AnyRecord | undefined) : e) as AnyRecord | undefined\n if (!inner || typeof inner !== 'object') return null\n if ((inner.type === 'text' || inner.type === 'reasoning') && typeof inner.text === 'string') {\n return inner.type\n }\n return null\n}\n\n/** Merge consecutive text/reasoning deltas of the same type into one event.\n * Concatenation-preserving: replaying the coalesced stream produces the same\n * accumulated text as the original. */\nexport function coalesceDeltas(events: unknown[]): unknown[] {\n const out: unknown[] = []\n for (const ev of events) {\n const type = deltaTypeOf(ev)\n const prev = out[out.length - 1]\n if (type && prev && deltaTypeOf(prev) === type) {\n const read = (x: unknown): AnyRecord =>\n ((x as AnyRecord).kind === 'event' ? (x as AnyRecord).event : x) as AnyRecord\n const merged = JSON.parse(JSON.stringify(prev)) as AnyRecord\n read(merged).text = String(read(prev).text) + String(read(ev).text)\n out[out.length - 1] = merged\n continue\n }\n out.push(ev)\n }\n return out\n}\n\n// ── pump (producer side) ──────────────────────────────────────────────────\n\nexport interface PumpBufferedTurnOptions {\n source: AsyncIterable<unknown>\n store: TurnEventStore\n turnId: string\n /** Deliver one serialized line (with seq) to the live client. Throwing here\n * (client disconnected) does NOT stop the turn — events keep buffering. */\n write?: (line: string) => Promise<void> | void\n /** Flush buffered events to the store at most this often. Default 400ms. */\n flushIntervalMs?: number\n}\n\n/**\n * Drive a turn to completion regardless of the live client: every source\n * event is sequence-numbered, delivered to `write` (best-effort), and flushed\n * to the store in coalesced batches. Returns a promise that resolves when the\n * turn finishes — hand it to `ctx.waitUntil` so a disconnect can't kill the\n * turn. Never rejects on client-write failure; a source error marks the turn\n * status 'error' (after flushing what was produced) and rethrows.\n */\nexport async function pumpBufferedTurn(opts: PumpBufferedTurnOptions): Promise<void> {\n const flushIntervalMs = opts.flushIntervalMs ?? 400\n let seq = 0\n let clientGone = false\n let pending: unknown[] = []\n let lastFlush = Date.now()\n\n async function flush(): Promise<void> {\n if (pending.length === 0) return\n const batch = coalesceDeltas(pending)\n pending = []\n const rows = batch.map((ev) => ({ seq: ++seq, event: JSON.stringify(ev) }))\n await opts.store.append(opts.turnId, rows)\n lastFlush = Date.now()\n }\n\n await opts.store.setStatus(opts.turnId, 'running')\n try {\n for await (const ev of opts.source) {\n pending.push(ev)\n if (!clientGone && opts.write) {\n try {\n // Live delivery carries a provisional ordering hint, not the\n // persisted seq (coalescing changes seq assignment); clients resume\n // with the seqs from replay, or 0 for \"everything\".\n await opts.write(JSON.stringify(ev))\n } catch {\n clientGone = true\n }\n }\n if (Date.now() - lastFlush >= flushIntervalMs) await flush()\n }\n await flush()\n await opts.store.setStatus(opts.turnId, 'complete')\n } catch (err) {\n await flush().catch(() => {})\n await opts.store.setStatus(opts.turnId, 'error').catch(() => {})\n throw err\n }\n}\n\n// ── replay (consumer side) ────────────────────────────────────────────────\n\nexport interface ReplayTurnEventsOptions {\n store: TurnEventStore\n turnId: string\n /** Replay strictly after this sequence number (0 = from the beginning). */\n fromSeq?: number\n /** Poll cadence while the turn is still running. Default 500ms. */\n pollMs?: number\n /** Give up following a 'running' turn after this long. Default 120s. */\n timeoutMs?: number\n}\n\n/**\n * Yield buffered events after `fromSeq`, then keep polling while the turn is\n * still 'running' until it completes, errors, or times out. Terminates with a\n * final `{seq: -1, event: '{\"type\":\"turn_status\",...}'}` marker so clients\n * know why the replay ended.\n */\nexport async function* replayTurnEvents(opts: ReplayTurnEventsOptions): AsyncGenerator<BufferedTurnEvent> {\n const pollMs = opts.pollMs ?? 500\n const timeoutMs = opts.timeoutMs ?? 120_000\n let cursor = opts.fromSeq ?? 0\n const deadline = Date.now() + timeoutMs\n\n for (;;) {\n const batch = await opts.store.read(opts.turnId, cursor)\n for (const row of batch) {\n cursor = Math.max(cursor, row.seq)\n yield row\n }\n const status = await opts.store.getStatus(opts.turnId)\n if (status !== 'running') {\n yield { seq: -1, event: JSON.stringify({ type: 'turn_status', status: status ?? 'unknown' }) }\n return\n }\n if (Date.now() >= deadline) {\n yield { seq: -1, event: JSON.stringify({ type: 'turn_status', status: 'timeout' }) }\n return\n }\n await new Promise((r) => setTimeout(r, pollMs))\n }\n}\n\n// ── D1 store ──────────────────────────────────────────────────────────────\n\n/** Minimal structural D1 contract (Cloudflare `D1Database` satisfies it). */\nexport interface D1LikeForTurns {\n prepare(sql: string): {\n bind(...values: unknown[]): {\n run(): Promise<unknown>\n all<T = Record<string, unknown>>(): Promise<{ results: T[] }>\n first<T = Record<string, unknown>>(): Promise<T | null>\n }\n }\n}\n\n/** Schema for the D1 store — append to the product's migrations. */\nexport const TURN_EVENTS_MIGRATION_SQL = `\nCREATE TABLE IF NOT EXISTS turn_events (\n turnId TEXT NOT NULL,\n seq INTEGER NOT NULL,\n event TEXT NOT NULL,\n PRIMARY KEY (turnId, seq)\n);\nCREATE TABLE IF NOT EXISTS turn_status (\n turnId TEXT PRIMARY KEY,\n status TEXT NOT NULL,\n updatedAt TEXT NOT NULL\n);\n`\n\nexport function createD1TurnEventStore(db: D1LikeForTurns): TurnEventStore {\n return {\n async append(turnId, events) {\n if (!events.length) return\n // One multi-row insert per flush window keeps write volume bounded.\n const placeholders = events.map(() => '(?, ?, ?)').join(', ')\n const values = events.flatMap((e) => [turnId, e.seq, e.event])\n await db.prepare(`INSERT OR IGNORE INTO turn_events (turnId, seq, event) VALUES ${placeholders}`).bind(...values).run()\n },\n async read(turnId, fromSeq) {\n const { results } = await db\n .prepare('SELECT seq, event FROM turn_events WHERE turnId = ? AND seq > ? ORDER BY seq ASC')\n .bind(turnId, fromSeq)\n .all<{ seq: number; event: string }>()\n return results\n },\n async setStatus(turnId, status) {\n await db\n .prepare(\n 'INSERT INTO turn_status (turnId, status, updatedAt) VALUES (?, ?, ?) ON CONFLICT(turnId) DO UPDATE SET status = excluded.status, updatedAt = excluded.updatedAt',\n )\n .bind(turnId, status, new Date().toISOString())\n .run()\n },\n async getStatus(turnId) {\n const row = await db.prepare('SELECT status FROM turn_status WHERE turnId = ?').bind(turnId).first<{ status: TurnStatus }>()\n return row?.status ?? null\n },\n }\n}\n\n/** In-memory store for tests and keyless local dev. */\nexport function createMemoryTurnEventStore(): TurnEventStore {\n const events = new Map<string, BufferedTurnEvent[]>()\n const status = new Map<string, TurnStatus>()\n return {\n async append(turnId, rows) {\n const list = events.get(turnId) ?? []\n list.push(...rows)\n events.set(turnId, list)\n },\n async read(turnId, fromSeq) {\n return (events.get(turnId) ?? []).filter((e) => e.seq > fromSeq)\n },\n async setStatus(turnId, s) {\n status.set(turnId, s)\n },\n async getStatus(turnId) {\n return status.get(turnId) ?? null\n },\n }\n}\n"],"mappings":";AAOO,SAAS,SAAS,OAAwC;AAC/D,SAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAC7D,QACA;AACN;AAEO,SAAS,SAAS,OAAoC;AAC3D,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AACjE;AAEO,SAAS,cAAc,MAA0B;AACtD,SAAO;AAAA,IACL,KAAK,MACH,KAAK,UACL,KAAK,UACL,KAAK,aACL,KAAK,cACL,KAAK,QACL,KAAK,QACL,QAAQ,KAAK,IAAI,CAAC;AAAA,EACtB;AACF;AAEO,SAAS,gBAAgB,MAA0B;AACxD,SAAO,OAAO,KAAK,QAAQ,KAAK,QAAQ,MAAM;AAChD;AAEO,SAAS,cAAc,OAAwC;AACpE,QAAM,SAAS,SAAS,KAAK;AAC7B,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,QAAQ,OAAO,OAAO,SAAS,OAAO,aAAa,OAAO,UAAU;AAC1E,QAAM,MAAM,OAAO,OAAO,OAAO,OAAO,eAAe,OAAO,YAAY;AAC1E,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,CAAC,OAAO,SAAS,GAAG,EAAG,QAAO;AAE7D,SAAO;AAAA,IACL,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,IACxC,KAAK,OAAO,SAAS,GAAG,IAAI,MAAM;AAAA,EACpC;AACF;AAEO,SAAS,mBAAmB,OAAiC;AAClE,MAAI,MAAM,SAAS,eAAe,MAAM,SAAS,aAAa;AAC5D,UAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,MAAM;AAAA,UACJ,MAAM;AAAA,UACN,IAAI,KAAK,MAAM,KAAK,UAAU,KAAK,UAAU,KAAK;AAAA,UAClD,MAAM,KAAK,QAAQ,KAAK,QAAQ;AAAA,UAChC,OAAO,KAAK,aAAa,KAAK;AAAA,UAC9B,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,MAAM,SAAS,iBAAiB,MAAM,SAAS,eAAe;AAChE,UAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,UAAM,QAAQ,SAAS,KAAK,KAAK;AACjC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,MAAM;AAAA,UACJ,MAAM;AAAA,UACN,IAAI,KAAK,MAAM,KAAK,UAAU,KAAK,UAAU,KAAK;AAAA,UAClD,MAAM,KAAK,QAAQ,KAAK,QAAQ;AAAA,UAChC,QAAQ,KAAK;AAAA,UACb;AAAA,UACA,QAAQ,QAAQ,UAAU;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,uBAAuB,SAAwC;AAC7E,QAAM,OAAO,OAAO,QAAQ,QAAQ,EAAE;AAEtC,MAAI,SAAS,QAAQ;AACnB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM,SAAS,QAAQ,IAAI,KAAK,SAAS,QAAQ,OAAO,KAAK;AAAA,IAC/D;AAAA,EACF;AAEA,MAAI,SAAS,aAAa;AACxB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM,SAAS,QAAQ,IAAI,KAAK,SAAS,QAAQ,OAAO,KAAK;AAAA,MAC7D,MAAM,cAAc,QAAQ,IAAI;AAAA,IAClC;AAAA,EACF;AAEA,MAAI,SAAS,QAAQ;AACnB,UAAM,QAAQ,SAAS,QAAQ,KAAK;AACpC,UAAM,SAAS,OAAO,UAAU,QAAQ;AACxC,UAAM,QAAQ,SAAS,OAAO,SAAS,QAAQ,KAAK;AACpD,UAAM,SACJ,OAAO,WAAW,eAAe,QAAQ,WAAW,cAChD,cACA,OAAO,WAAW,WAAW,QAAQ,WAAW,WAAW,QACzD,UACA,WAAW,SACT,cACA;AAEV,WAAO;AAAA,MACL,MAAM;AAAA,MACN,IAAI,cAAc,OAAO;AAAA,MACzB,MAAM,gBAAgB,OAAO;AAAA,MAC7B,QACE,QAAQ,UAAU,QAAQ,QAAQ,UAAU,OACxC,OAAO,QAAQ,UAAU,QAAQ,MAAM,IACvC;AAAA,MACN,OAAO;AAAA,QACL;AAAA,QACA,OAAO,OAAO,SAAS,QAAQ;AAAA,QAC/B;AAAA,QACA;AAAA,QACA,UAAU,SAAS,OAAO,QAAQ,KAAK,SAAS,QAAQ,QAAQ;AAAA,QAChE,MAAM,cAAc,OAAO,QAAQ,QAAQ,IAAI;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,WAAW,MAA0B;AACnD,QAAM,OAAO,OAAO,KAAK,QAAQ,SAAS;AAC1C,MAAI,SAAS,QAAQ;AACnB,WAAO,QAAQ,cAAc,IAAI,CAAC;AAAA,EACpC;AAEA,MAAI,SAAS,aAAa;AACxB,WAAO,aAAa,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,SAAS,SAAS,CAAC;AAAA,EAC/E;AAEA,SAAO,QAAQ,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,SAAS,SAAS,CAAC;AAC1E;AAEO,SAAS,mBAAmB,UAAkC,UAAsB,OAA4B;AACrH,QAAM,OAAO,OAAO,SAAS,QAAQ,EAAE;AACvC,MAAI,CAAC,UAAU;AACb,QAAI,SAAS,UAAU,OAAO;AAC5B,aAAO,EAAE,MAAM,QAAQ,MAAM,MAAM;AAAA,IACrC;AACA,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,UAAU,OAAO,SAAS,QAAQ,EAAE,MAAM,QAAQ;AAC7D,WAAO;AAAA,MACL,GAAG;AAAA,MACH,GAAG;AAAA,MACH,MAAM,QAAQ,GAAG,OAAO,SAAS,QAAQ,EAAE,CAAC,GAAG,KAAK,KAAK,OAAO,SAAS,QAAQ,EAAE;AAAA,IACrF;AAAA,EACF;AAEA,MAAI,SAAS,eAAe,OAAO,SAAS,QAAQ,EAAE,MAAM,aAAa;AACvE,UAAM,eAAe,OAAO,SAAS,QAAQ,EAAE;AAC/C,UAAM,eAAe,OAAO,SAAS,QAAQ,EAAE;AAC/C,WAAO;AAAA,MACL,GAAG;AAAA,MACH,GAAG;AAAA,MACH,MAAM,SAAS,iBAAiB,eAAe,GAAG,YAAY,GAAG,KAAK,KAAK,gBAAgB;AAAA,MAC3F,MAAM,SAAS,QAAQ,SAAS;AAAA,IAClC;AAAA,EACF;AAEA,MAAI,SAAS,UAAU,OAAO,SAAS,QAAQ,EAAE,MAAM,QAAQ;AAC7D,WAAO;AAAA,MACL,GAAG;AAAA,MACH,GAAG;AAAA,MACH,OAAO;AAAA,QACL,GAAI,SAAS,SAAS,KAAK,KAAK,CAAC;AAAA,QACjC,GAAI,SAAS,SAAS,KAAK,KAAK,CAAC;AAAA,QACjC,MAAM,SAAS,SAAS,KAAK,GAAG,QAAQ,SAAS,SAAS,KAAK,GAAG;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,uBACd,WACA,SACA,WACc;AACd,QAAM,QAAQ,UACX,IAAI,CAAC,QAAQ,QAAQ,IAAI,GAAG,CAAC,EAC7B,OAAO,CAAC,SAA6B,QAAQ,IAAI,CAAC;AAErD,MAAI,CAAC,MAAM,KAAK,CAAC,SAAS,OAAO,KAAK,QAAQ,EAAE,MAAM,MAAM,GAAG;AAC7D,QAAI,UAAU,KAAK,GAAG;AACpB,YAAM,KAAK,EAAE,MAAM,QAAQ,MAAM,UAAU,CAAC;AAAA,IAC9C;AACA,WAAO;AAAA,EACT;AAEA,SAAO,MAAM,IAAI,CAAC,SAAS;AACzB,QAAI,OAAO,KAAK,QAAQ,EAAE,MAAM,OAAQ,QAAO;AAC/C,WAAO;AAAA,MACL,GAAG;AAAA,MACH,MAAM,aAAa,OAAO,KAAK,QAAQ,EAAE;AAAA,IAC3C;AAAA,EACF,CAAC;AACH;AAEO,SAAS,YAAY,SAAsB,OAAgC;AAChF,SAAO,QAAQ,OAAO,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,CAAI;AACpD;;;AC9MO,SAAS,sBAAsB,OAAoC;AACxE,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,OAAM,IAAI,MAAM,yBAAyB;AACxE,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,0BAA0B;AACxD,MAAI,QAAQ,SAAS,IAAK,OAAM,IAAI,MAAM,oBAAoB;AAC9D,MAAI,CAAC,oBAAoB,KAAK,OAAO,GAAG;AACtC,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACA,SAAO;AACT;AAEO,SAAS,mBAAmB,MAAc,QAA0C;AACzF,QAAM,OAAmB,EAAE,MAAM,QAAQ,KAAK;AAC9C,MAAI,OAAQ,MAAK,SAAS;AAC1B,SAAO,CAAC,IAAI;AACd;AAEO,SAAS,iBAAiB,SAAsC,QAAyB;AAC9F,aAAW,QAAQ,QAAQ,SAAS,CAAC,GAAG;AACtC,QAAI,QAAQ,OAAO,SAAS,YAAY,OAAO,KAAK,UAAU,EAAE,MAAM,QAAQ;AAC5E,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,gBAAgB,OAIX;AACnB,QAAM,EAAE,kBAAkB,aAAa,OAAO,IAAI;AAClD,QAAM,gBAAgB,6BAA6B,kBAAkB,aAAa,MAAM;AACxF,MAAI,iBAAiB,GAAG;AACtB,WAAO;AAAA,MACL,WAAW,kBAAkB,iBAAiB,MAAM,GAAG,aAAa,CAAC;AAAA,MACrE,yBAAyB;AAAA,MACzB,eAAe,iBAAiB,MAAM,GAAG,aAAa;AAAA,MACtD,WAAW,mBAAmB,aAAa,MAAM;AAAA,IACnD;AAAA,EACF;AAEA,SAAO;AAAA,IACL,WAAW,kBAAkB,gBAAgB;AAAA,IAC7C,yBAAyB;AAAA,IACzB,eAAe;AAAA,IACf,WAAW,mBAAmB,aAAa,MAAM;AAAA,EACnD;AACF;AAEA,SAAS,6BACP,UACA,aACA,QACQ;AACR,MAAI,QAAQ;AACV,aAAS,QAAQ,SAAS,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG;AAC5D,YAAM,UAAU,SAAS,KAAK;AAC9B,UAAI,SAAS,SAAS,UAAU,iBAAiB,SAAS,MAAM,EAAG,QAAO;AAAA,IAC5E;AAAA,EACF;AAEA,QAAM,SAAS,SAAS,GAAG,EAAE;AAC7B,MAAI,QAAQ,SAAS,UAAU,OAAO,YAAY,aAAa;AAC7D,WAAO,SAAS,SAAS;AAAA,EAC3B;AAEA,SAAO;AACT;AAEA,SAAS,kBAAkB,UAAiD;AAC1E,SAAO,SAAS,OAAO,CAAC,YAAY,QAAQ,SAAS,MAAM,EAAE;AAC/D;;;ACpDA,SAAS,YAAY,IAA0C;AAC7D,QAAM,IAAI;AACV,MAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,QAAM,QAAS,EAAE,SAAS,UAAW,EAAE,QAAkC;AACzE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,OAAK,MAAM,SAAS,UAAU,MAAM,SAAS,gBAAgB,OAAO,MAAM,SAAS,UAAU;AAC3F,WAAO,MAAM;AAAA,EACf;AACA,SAAO;AACT;AAKO,SAAS,eAAe,QAA8B;AAC3D,QAAM,MAAiB,CAAC;AACxB,aAAW,MAAM,QAAQ;AACvB,UAAM,OAAO,YAAY,EAAE;AAC3B,UAAM,OAAO,IAAI,IAAI,SAAS,CAAC;AAC/B,QAAI,QAAQ,QAAQ,YAAY,IAAI,MAAM,MAAM;AAC9C,YAAM,OAAO,CAAC,MACV,EAAgB,SAAS,UAAW,EAAgB,QAAQ;AAChE,YAAM,SAAS,KAAK,MAAM,KAAK,UAAU,IAAI,CAAC;AAC9C,WAAK,MAAM,EAAE,OAAO,OAAO,KAAK,IAAI,EAAE,IAAI,IAAI,OAAO,KAAK,EAAE,EAAE,IAAI;AAClE,UAAI,IAAI,SAAS,CAAC,IAAI;AACtB;AAAA,IACF;AACA,QAAI,KAAK,EAAE;AAAA,EACb;AACA,SAAO;AACT;AAuBA,eAAsB,iBAAiB,MAA8C;AACnF,QAAM,kBAAkB,KAAK,mBAAmB;AAChD,MAAI,MAAM;AACV,MAAI,aAAa;AACjB,MAAI,UAAqB,CAAC;AAC1B,MAAI,YAAY,KAAK,IAAI;AAEzB,iBAAe,QAAuB;AACpC,QAAI,QAAQ,WAAW,EAAG;AAC1B,UAAM,QAAQ,eAAe,OAAO;AACpC,cAAU,CAAC;AACX,UAAM,OAAO,MAAM,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,OAAO,KAAK,UAAU,EAAE,EAAE,EAAE;AAC1E,UAAM,KAAK,MAAM,OAAO,KAAK,QAAQ,IAAI;AACzC,gBAAY,KAAK,IAAI;AAAA,EACvB;AAEA,QAAM,KAAK,MAAM,UAAU,KAAK,QAAQ,SAAS;AACjD,MAAI;AACF,qBAAiB,MAAM,KAAK,QAAQ;AAClC,cAAQ,KAAK,EAAE;AACf,UAAI,CAAC,cAAc,KAAK,OAAO;AAC7B,YAAI;AAIF,gBAAM,KAAK,MAAM,KAAK,UAAU,EAAE,CAAC;AAAA,QACrC,QAAQ;AACN,uBAAa;AAAA,QACf;AAAA,MACF;AACA,UAAI,KAAK,IAAI,IAAI,aAAa,gBAAiB,OAAM,MAAM;AAAA,IAC7D;AACA,UAAM,MAAM;AACZ,UAAM,KAAK,MAAM,UAAU,KAAK,QAAQ,UAAU;AAAA,EACpD,SAAS,KAAK;AACZ,UAAM,MAAM,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC5B,UAAM,KAAK,MAAM,UAAU,KAAK,QAAQ,OAAO,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC/D,UAAM;AAAA,EACR;AACF;AAqBA,gBAAuB,iBAAiB,MAAkE;AACxG,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,YAAY,KAAK,aAAa;AACpC,MAAI,SAAS,KAAK,WAAW;AAC7B,QAAM,WAAW,KAAK,IAAI,IAAI;AAE9B,aAAS;AACP,UAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,KAAK,QAAQ,MAAM;AACvD,eAAW,OAAO,OAAO;AACvB,eAAS,KAAK,IAAI,QAAQ,IAAI,GAAG;AACjC,YAAM;AAAA,IACR;AACA,UAAM,SAAS,MAAM,KAAK,MAAM,UAAU,KAAK,MAAM;AACrD,QAAI,WAAW,WAAW;AACxB,YAAM,EAAE,KAAK,IAAI,OAAO,KAAK,UAAU,EAAE,MAAM,eAAe,QAAQ,UAAU,UAAU,CAAC,EAAE;AAC7F;AAAA,IACF;AACA,QAAI,KAAK,IAAI,KAAK,UAAU;AAC1B,YAAM,EAAE,KAAK,IAAI,OAAO,KAAK,UAAU,EAAE,MAAM,eAAe,QAAQ,UAAU,CAAC,EAAE;AACnF;AAAA,IACF;AACA,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC;AAAA,EAChD;AACF;AAgBO,IAAM,4BAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAclC,SAAS,uBAAuB,IAAoC;AACzE,SAAO;AAAA,IACL,MAAM,OAAO,QAAQ,QAAQ;AAC3B,UAAI,CAAC,OAAO,OAAQ;AAEpB,YAAM,eAAe,OAAO,IAAI,MAAM,WAAW,EAAE,KAAK,IAAI;AAC5D,YAAM,SAAS,OAAO,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC;AAC7D,YAAM,GAAG,QAAQ,iEAAiE,YAAY,EAAE,EAAE,KAAK,GAAG,MAAM,EAAE,IAAI;AAAA,IACxH;AAAA,IACA,MAAM,KAAK,QAAQ,SAAS;AAC1B,YAAM,EAAE,QAAQ,IAAI,MAAM,GACvB,QAAQ,kFAAkF,EAC1F,KAAK,QAAQ,OAAO,EACpB,IAAoC;AACvC,aAAO;AAAA,IACT;AAAA,IACA,MAAM,UAAU,QAAQ,QAAQ;AAC9B,YAAM,GACH;AAAA,QACC;AAAA,MACF,EACC,KAAK,QAAQ,SAAQ,oBAAI,KAAK,GAAE,YAAY,CAAC,EAC7C,IAAI;AAAA,IACT;AAAA,IACA,MAAM,UAAU,QAAQ;AACtB,YAAM,MAAM,MAAM,GAAG,QAAQ,iDAAiD,EAAE,KAAK,MAAM,EAAE,MAA8B;AAC3H,aAAO,KAAK,UAAU;AAAA,IACxB;AAAA,EACF;AACF;AAGO,SAAS,6BAA6C;AAC3D,QAAM,SAAS,oBAAI,IAAiC;AACpD,QAAM,SAAS,oBAAI,IAAwB;AAC3C,SAAO;AAAA,IACL,MAAM,OAAO,QAAQ,MAAM;AACzB,YAAM,OAAO,OAAO,IAAI,MAAM,KAAK,CAAC;AACpC,WAAK,KAAK,GAAG,IAAI;AACjB,aAAO,IAAI,QAAQ,IAAI;AAAA,IACzB;AAAA,IACA,MAAM,KAAK,QAAQ,SAAS;AAC1B,cAAQ,OAAO,IAAI,MAAM,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,OAAO;AAAA,IACjE;AAAA,IACA,MAAM,UAAU,QAAQ,GAAG;AACzB,aAAO,IAAI,QAAQ,CAAC;AAAA,IACtB;AAAA,IACA,MAAM,UAAU,QAAQ;AACtB,aAAO,OAAO,IAAI,MAAM,KAAK;AAAA,IAC/B;AAAA,EACF;AACF;","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  buildAppToolOpenAITools,
3
3
  createAppToolRuntimeExecutor,
4
4
  isAppToolName
5
- } from "./chunk-JANT2G2E.js";
5
+ } from "./chunk-QAQBR6KQ.js";
6
6
 
7
7
  // src/runtime/model-catalog.ts
8
8
  var PROVIDER_TIER = [
@@ -423,4 +423,4 @@ export {
423
423
  runAppToolLoop,
424
424
  streamAppToolLoop
425
425
  };
426
- //# sourceMappingURL=chunk-EO4IGDQD.js.map
426
+ //# sourceMappingURL=chunk-TH2AOJJM.js.map
@@ -1,6 +1,6 @@
1
1
  import { CompletionRequirement, RuntimeEventLike } from '@tangle-network/agent-eval';
2
2
  export { CompletionRequirement, CompletionVerdict, CorrectnessChecker, ProducedState, RuntimeEventLike, SatisfiedBy, TaskGold, createLlmCorrectnessChecker, extractProducedState, verifyCompletion, weightedComposite } from '@tangle-network/agent-eval';
3
- import { e as AppToolProducedEvent } from '../types-CTOaTNtU.js';
3
+ import { e as AppToolProducedEvent } from '../types-By4B3K37.js';
4
4
 
5
5
  /**
6
6
  * Eval — the app-shell BRIDGE to `@tangle-network/agent-eval`, not a reimpl.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { APP_TOOL_NAMES, AppToolMcpServer, AppToolName, AppToolRuntimeExecutor, AuthenticateOptions, BuildHttpMcpServerOptions, BuildMcpServerOptions, CapabilityTokenOptions, DEFAULT_APP_TOOL_PATHS, DEFAULT_HEADER_NAMES, DispatchOptions, HandleToolRequestOptions, OpenAIFunctionTool, RuntimeExecutorOptions, ToolAuthResult, ToolHeaderNames, ToolInputError, authenticateToolRequest, buildAppToolMcpServer, buildAppToolOpenAITools, buildHttpMcpServer, createAppToolRuntimeExecutor, createCapabilityToken, dispatchAppTool, handleAppToolRequest, isAppToolName, outcomeStatus, readToolArgs, verifyCapabilityToken } from './tools/index.js';
2
- export { A as AddCitationArgs, a as AddCitationResult, b as AppToolContext, c as AppToolHandlers, d as AppToolOutcome, e as AppToolProducedEvent, f as AppToolTaxonomy, R as RenderUiArgs, g as RenderUiResult, S as ScheduleFollowupArgs, h as ScheduleFollowupResult, i as SubmitProposalArgs, j as SubmitProposalResult } from './types-CTOaTNtU.js';
2
+ export { A as AddCitationArgs, a as AddCitationResult, b as AppToolContext, c as AppToolHandlers, d as AppToolOutcome, e as AppToolProducedEvent, f as AppToolTaxonomy, R as RenderUiArgs, g as RenderUiResult, S as ScheduleFollowupArgs, h as ScheduleFollowupResult, i as SubmitProposalArgs, j as SubmitProposalResult } from './types-By4B3K37.js';
3
3
  export { BuildDelegationOptions, DELEGATION_MCP_SERVER_KEY, DELEGATION_TOOLS, DelegationMcpServer, buildDelegationMcpServer, delegationMcpForConfig } from './delegation/index.js';
4
4
  export { BrokerToken, BrokerTokenMinter, BrokerTokenProvider, BrokerTokenProviderOptions, ConsentUrlInput, buildConsentUrl, createBrokerTokenProvider } from './tangle/index.js';
5
5
  export { AgentRuntime, AgentRuntimeModelConfig, AgentTurnOptions, AppToolLoopOptions, CreateAgentRuntimeOptions, LoopAssistantToolCall, LoopEvent, LoopMessage, LoopToolCall, OpenAICompatStreamTurnOptions, OpenAIStreamChunk, StreamAppToolLoopOptions, StreamLoopYield, ToolLoopResult, createAgentRuntime, createOpenAICompatStreamTurn, runAppToolLoop, streamAppToolLoop, toLoopEvents } from './runtime/index.js';
@@ -11,7 +11,7 @@ export { AgentAppConfig, AgentDelegationConfig, AgentIdentityConfig, AgentIntegr
11
11
  export { D1Like, D1PreparedLike, DrizzleColumnLike, DrizzleSqliteCoreLike, PRESET_MIGRATION_SQL, PRESET_TABLES, PresetBillingOptions, PresetKnowledgeAccessorOptions, PresetToolHandlerOptions, VaultKv, createD1KnowledgeStateAccessor, createPresetDrizzleSchema, createPresetFieldCrypto, createPresetToolHandlers, createPresetWorkspaceKeyManager, createPresetWorkspaceKeyStore } from './preset-cloudflare/index.js';
12
12
  export { KeyCrypto, KeyProvisioner, PlanLimit, PlatformBalanceInfo, PlatformBalanceManager, PlatformBalanceManagerOptions, PlatformBillingClient, PlatformIdentity, PlatformProductUsage, SharedBillingState, TcloudKeyClient, WorkspaceKeyManager, WorkspaceKeyManagerOptions, WorkspaceKeyRecord, WorkspaceKeyStore, WorkspaceModelKeyUsage, createPlatformBalanceManager, createTcloudKeyProvisioner, createWorkspaceKeyManager } from './billing/index.js';
13
13
  export { DeriveKeyOptions, createFieldCrypto, decodeHexKey, decryptAesGcm, decryptBytes, decryptWithKey, deriveKey, encryptAesGcm, encryptBytes, encryptWithKey } from './crypto/index.js';
14
- export { JsonRecord, PersistedChatMessageForTurn, ResolvedChatTurn, StreamEvent, asRecord, asString, buildUserTextParts, encodeEvent, finalizeAssistantParts, getPartKey, mergePersistedPart, messageHasTurnId, normalizeClientTurnId, normalizePersistedPart, normalizeTime, normalizeToolEvent, resolveChatTurn, resolveToolId, resolveToolName } from './stream/index.js';
14
+ export { BufferedTurnEvent, D1LikeForTurns, JsonRecord, PersistedChatMessageForTurn, PumpBufferedTurnOptions, ReplayTurnEventsOptions, ResolvedChatTurn, StreamEvent, TURN_EVENTS_MIGRATION_SQL, TurnEventStore, TurnStatus, asRecord, asString, buildUserTextParts, coalesceDeltas, createD1TurnEventStore, createMemoryTurnEventStore, encodeEvent, finalizeAssistantParts, getPartKey, mergePersistedPart, messageHasTurnId, normalizeClientTurnId, normalizePersistedPart, normalizeTime, normalizeToolEvent, pumpBufferedTurn, replayTurnEvents, resolveChatTurn, resolveToolId, resolveToolName } from './stream/index.js';
15
15
  export { HubExecClient, HubExecClientOptions, HubExecErrorCode, HubExecResult, HubInvokeDeps, HubInvokeInput, HubInvokeOutcome, ParsedIntegrationAction, invokeIntegrationHub, resolveIntegrationAction } from './integrations/index.js';
16
16
  export { CookieOptions, JsonObject, KvLike, RateLimitResult, RequestContext, SecurityHeaderOptions, addSecurityHeaders, checkRateLimit, clearCookieHeader, extractRequestContext, parseJsonObjectBody, readCookieValue, requireString, serializeCookie } from './web/index.js';
17
17
  export { BuildRedactedDocumentOptions, DEFAULT_REDACTION_PATTERNS, RedactForIngestionOptions, RedactedDocSegment, RedactedDocument, RedactionPattern, RedactionSpan, RevealResult, RevealSpanOptions, buildRedactedDocument, detectSpans, maskSpans, redactForIngestion, revealSpan } from './redact/index.js';
package/dist/index.js CHANGED
@@ -59,9 +59,13 @@ import {
59
59
  encryptWithKey
60
60
  } from "./chunk-TA5Q4I2K.js";
61
61
  import {
62
+ TURN_EVENTS_MIGRATION_SQL,
62
63
  asRecord,
63
64
  asString,
64
65
  buildUserTextParts,
66
+ coalesceDeltas,
67
+ createD1TurnEventStore,
68
+ createMemoryTurnEventStore,
65
69
  encodeEvent,
66
70
  finalizeAssistantParts,
67
71
  getPartKey,
@@ -71,10 +75,12 @@ import {
71
75
  normalizePersistedPart,
72
76
  normalizeTime,
73
77
  normalizeToolEvent,
78
+ pumpBufferedTurn,
79
+ replayTurnEvents,
74
80
  resolveChatTurn,
75
81
  resolveToolId,
76
82
  resolveToolName
77
- } from "./chunk-GMFPCCQZ.js";
83
+ } from "./chunk-SDOT7RNB.js";
78
84
  import {
79
85
  HubExecClient,
80
86
  invokeIntegrationHub,
@@ -90,7 +96,7 @@ import {
90
96
  handleAppToolRequest,
91
97
  readToolArgs,
92
98
  verifyCapabilityToken
93
- } from "./chunk-HZZD3ZYD.js";
99
+ } from "./chunk-OLCVUGGI.js";
94
100
  import {
95
101
  DELEGATION_MCP_SERVER_KEY,
96
102
  DELEGATION_TOOLS,
@@ -111,7 +117,7 @@ import {
111
117
  runAppToolLoop,
112
118
  streamAppToolLoop,
113
119
  toLoopEvents
114
- } from "./chunk-EO4IGDQD.js";
120
+ } from "./chunk-TH2AOJJM.js";
115
121
  import {
116
122
  DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
117
123
  DEFAULT_TANGLE_ROUTER_BASE_URL,
@@ -133,7 +139,7 @@ import {
133
139
  dispatchAppTool,
134
140
  isAppToolName,
135
141
  outcomeStatus
136
- } from "./chunk-JANT2G2E.js";
142
+ } from "./chunk-QAQBR6KQ.js";
137
143
  import {
138
144
  createLlmCorrectnessChecker,
139
145
  createTokenRecallChecker,
@@ -160,6 +166,7 @@ export {
160
166
  KNOWN_HARNESSES,
161
167
  PRESET_MIGRATION_SQL,
162
168
  PRESET_TABLES,
169
+ TURN_EVENTS_MIGRATION_SQL,
163
170
  TangleExecutionKeyError,
164
171
  ToolInputError,
165
172
  __resetCatalogCache,
@@ -179,15 +186,18 @@ export {
179
186
  buildUserTextParts,
180
187
  checkRateLimit,
181
188
  clearCookieHeader,
189
+ coalesceDeltas,
182
190
  coerceHarness,
183
191
  createAgentRuntime,
184
192
  createAppToolRuntimeExecutor,
185
193
  createBrokerTokenProvider,
186
194
  createCapabilityToken,
187
195
  createD1KnowledgeStateAccessor,
196
+ createD1TurnEventStore,
188
197
  createFieldCrypto,
189
198
  createKnowledgeLoop,
190
199
  createLlmCorrectnessChecker,
200
+ createMemoryTurnEventStore,
191
201
  createOpenAICompatStreamTurn,
192
202
  createPlatformBalanceManager,
193
203
  createPresetDrizzleSchema,
@@ -236,9 +246,11 @@ export {
236
246
  outcomeStatus,
237
247
  parseJsonObjectBody,
238
248
  producedFromToolEvents,
249
+ pumpBufferedTurn,
239
250
  readCookieValue,
240
251
  readToolArgs,
241
252
  redactForIngestion,
253
+ replayTurnEvents,
242
254
  requireString,
243
255
  resolveChatTurn,
244
256
  resolveIntegrationAction,
@@ -1,6 +1,6 @@
1
1
  import { KeyProvisioner, KeyCrypto, WorkspaceKeyManager, WorkspaceKeyStore } from '../billing/index.js';
2
2
  import { KnowledgeStateAccessor } from '../knowledge/index.js';
3
- import { c as AppToolHandlers } from '../types-CTOaTNtU.js';
3
+ import { c as AppToolHandlers } from '../types-By4B3K37.js';
4
4
  import { KvLike } from '../web/index.js';
5
5
  import '@tangle-network/agent-eval';
6
6
 
@@ -1,6 +1,6 @@
1
1
  export { C as CatalogModel, M as ModelCatalog, R as RouterModel, _ as __resetCatalogCache, b as buildCatalog, f as fetchModelCatalog, n as normalizeModelId } from '../model-catalog-BEAEVDaa.js';
2
2
  export { C as CreateTangleRouterModelConfigOptions, D as DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR, a as DEFAULT_TANGLE_ROUTER_BASE_URL, R as ResolveModelOptions, b as ResolveUserTangleExecutionKeyForUserOptions, c as ResolveUserTangleExecutionKeyOptions, d as ResolvedTangleExecutionKey, T as TangleBillingEnforcementOptions, e as TangleExecutionEnvironment, f as TangleExecutionKeyError, g as TangleExecutionKeyErrorCode, h as TangleExecutionKeyHttpError, i as TangleExecutionKeySource, j as TangleModelConfig, k as createTangleRouterModelConfig, l as isTangleBillingEnforcementDisabled, m as isTangleExecutionKeyError, r as resolveTangleExecutionEnvironment, n as resolveTangleModelConfig, o as resolveUserTangleExecutionKey, p as resolveUserTangleExecutionKeyForUser, t as tangleExecutionKeyHttpError } from '../model-CKzniMMr.js';
3
- import { b as AppToolContext, e as AppToolProducedEvent, f as AppToolTaxonomy, c as AppToolHandlers, d as AppToolOutcome } from '../types-CTOaTNtU.js';
3
+ import { b as AppToolContext, e as AppToolProducedEvent, f as AppToolTaxonomy, c as AppToolHandlers, d as AppToolOutcome } from '../types-By4B3K37.js';
4
4
 
5
5
  /**
6
6
  * OpenAI-compatible stream → `LoopEvent` adapter, for NON-sandbox copilots.
@@ -8,7 +8,7 @@ import {
8
8
  runAppToolLoop,
9
9
  streamAppToolLoop,
10
10
  toLoopEvents
11
- } from "../chunk-EO4IGDQD.js";
11
+ } from "../chunk-TH2AOJJM.js";
12
12
  import {
13
13
  DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
14
14
  DEFAULT_TANGLE_ROUTER_BASE_URL,
@@ -22,7 +22,7 @@ import {
22
22
  resolveUserTangleExecutionKeyForUser,
23
23
  tangleExecutionKeyHttpError
24
24
  } from "../chunk-EHPK7GKR.js";
25
- import "../chunk-JANT2G2E.js";
25
+ import "../chunk-QAQBR6KQ.js";
26
26
  export {
27
27
  DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
28
28
  DEFAULT_TANGLE_ROUTER_BASE_URL,
@@ -36,4 +36,91 @@ declare function resolveChatTurn(input: {
36
36
  turnId?: string;
37
37
  }): ResolvedChatTurn;
38
38
 
39
- export { type JsonRecord, type PersistedChatMessageForTurn, type ResolvedChatTurn, type StreamEvent, asRecord, asString, buildUserTextParts, encodeEvent, finalizeAssistantParts, getPartKey, mergePersistedPart, messageHasTurnId, normalizeClientTurnId, normalizePersistedPart, normalizeTime, normalizeToolEvent, resolveChatTurn, resolveToolId, resolveToolName };
39
+ /**
40
+ * Resumable chat turns — the router-path answer to "streams resume on
41
+ * disconnect" (issue #27). A turn's loop events are teed into a store as they
42
+ * stream; the turn keeps running under `ctx.waitUntil` when the client drops;
43
+ * a reconnecting client replays the buffered tail by sequence number and
44
+ * keeps following until the turn completes.
45
+ *
46
+ * POST /chat/stream → pumpBufferedTurn(...) + live NDJSON
47
+ * GET /chat/stream/:turnId → replayTurnEvents({ fromSeq }) → NDJSON
48
+ *
49
+ * Storage is a structural seam ({@link TurnEventStore}); a D1 implementation
50
+ * ships here because that's what Cloudflare products have (KV is unsuitable:
51
+ * eventually consistent cross-isolate). Per-token deltas would mean hundreds
52
+ * of rows per turn, so consecutive text/reasoning deltas are coalesced within
53
+ * a flush window before they are persisted — replay yields slightly chunkier
54
+ * deltas with identical concatenation.
55
+ */
56
+ type TurnStatus = 'running' | 'complete' | 'error';
57
+ interface BufferedTurnEvent {
58
+ seq: number;
59
+ /** The serialized event line (JSON string, no trailing newline). */
60
+ event: string;
61
+ }
62
+ interface TurnEventStore {
63
+ append(turnId: string, events: BufferedTurnEvent[]): Promise<void>;
64
+ read(turnId: string, fromSeq: number): Promise<BufferedTurnEvent[]>;
65
+ setStatus(turnId: string, status: TurnStatus): Promise<void>;
66
+ getStatus(turnId: string): Promise<TurnStatus | null>;
67
+ }
68
+ /** Merge consecutive text/reasoning deltas of the same type into one event.
69
+ * Concatenation-preserving: replaying the coalesced stream produces the same
70
+ * accumulated text as the original. */
71
+ declare function coalesceDeltas(events: unknown[]): unknown[];
72
+ interface PumpBufferedTurnOptions {
73
+ source: AsyncIterable<unknown>;
74
+ store: TurnEventStore;
75
+ turnId: string;
76
+ /** Deliver one serialized line (with seq) to the live client. Throwing here
77
+ * (client disconnected) does NOT stop the turn — events keep buffering. */
78
+ write?: (line: string) => Promise<void> | void;
79
+ /** Flush buffered events to the store at most this often. Default 400ms. */
80
+ flushIntervalMs?: number;
81
+ }
82
+ /**
83
+ * Drive a turn to completion regardless of the live client: every source
84
+ * event is sequence-numbered, delivered to `write` (best-effort), and flushed
85
+ * to the store in coalesced batches. Returns a promise that resolves when the
86
+ * turn finishes — hand it to `ctx.waitUntil` so a disconnect can't kill the
87
+ * turn. Never rejects on client-write failure; a source error marks the turn
88
+ * status 'error' (after flushing what was produced) and rethrows.
89
+ */
90
+ declare function pumpBufferedTurn(opts: PumpBufferedTurnOptions): Promise<void>;
91
+ interface ReplayTurnEventsOptions {
92
+ store: TurnEventStore;
93
+ turnId: string;
94
+ /** Replay strictly after this sequence number (0 = from the beginning). */
95
+ fromSeq?: number;
96
+ /** Poll cadence while the turn is still running. Default 500ms. */
97
+ pollMs?: number;
98
+ /** Give up following a 'running' turn after this long. Default 120s. */
99
+ timeoutMs?: number;
100
+ }
101
+ /**
102
+ * Yield buffered events after `fromSeq`, then keep polling while the turn is
103
+ * still 'running' until it completes, errors, or times out. Terminates with a
104
+ * final `{seq: -1, event: '{"type":"turn_status",...}'}` marker so clients
105
+ * know why the replay ended.
106
+ */
107
+ declare function replayTurnEvents(opts: ReplayTurnEventsOptions): AsyncGenerator<BufferedTurnEvent>;
108
+ /** Minimal structural D1 contract (Cloudflare `D1Database` satisfies it). */
109
+ interface D1LikeForTurns {
110
+ prepare(sql: string): {
111
+ bind(...values: unknown[]): {
112
+ run(): Promise<unknown>;
113
+ all<T = Record<string, unknown>>(): Promise<{
114
+ results: T[];
115
+ }>;
116
+ first<T = Record<string, unknown>>(): Promise<T | null>;
117
+ };
118
+ };
119
+ }
120
+ /** Schema for the D1 store — append to the product's migrations. */
121
+ declare const TURN_EVENTS_MIGRATION_SQL = "\nCREATE TABLE IF NOT EXISTS turn_events (\n turnId TEXT NOT NULL,\n seq INTEGER NOT NULL,\n event TEXT NOT NULL,\n PRIMARY KEY (turnId, seq)\n);\nCREATE TABLE IF NOT EXISTS turn_status (\n turnId TEXT PRIMARY KEY,\n status TEXT NOT NULL,\n updatedAt TEXT NOT NULL\n);\n";
122
+ declare function createD1TurnEventStore(db: D1LikeForTurns): TurnEventStore;
123
+ /** In-memory store for tests and keyless local dev. */
124
+ declare function createMemoryTurnEventStore(): TurnEventStore;
125
+
126
+ export { type BufferedTurnEvent, type D1LikeForTurns, type JsonRecord, type PersistedChatMessageForTurn, type PumpBufferedTurnOptions, type ReplayTurnEventsOptions, type ResolvedChatTurn, type StreamEvent, TURN_EVENTS_MIGRATION_SQL, type TurnEventStore, type TurnStatus, asRecord, asString, buildUserTextParts, coalesceDeltas, createD1TurnEventStore, createMemoryTurnEventStore, encodeEvent, finalizeAssistantParts, getPartKey, mergePersistedPart, messageHasTurnId, normalizeClientTurnId, normalizePersistedPart, normalizeTime, normalizeToolEvent, pumpBufferedTurn, replayTurnEvents, resolveChatTurn, resolveToolId, resolveToolName };
@@ -1,7 +1,11 @@
1
1
  import {
2
+ TURN_EVENTS_MIGRATION_SQL,
2
3
  asRecord,
3
4
  asString,
4
5
  buildUserTextParts,
6
+ coalesceDeltas,
7
+ createD1TurnEventStore,
8
+ createMemoryTurnEventStore,
5
9
  encodeEvent,
6
10
  finalizeAssistantParts,
7
11
  getPartKey,
@@ -11,14 +15,20 @@ import {
11
15
  normalizePersistedPart,
12
16
  normalizeTime,
13
17
  normalizeToolEvent,
18
+ pumpBufferedTurn,
19
+ replayTurnEvents,
14
20
  resolveChatTurn,
15
21
  resolveToolId,
16
22
  resolveToolName
17
- } from "../chunk-GMFPCCQZ.js";
23
+ } from "../chunk-SDOT7RNB.js";
18
24
  export {
25
+ TURN_EVENTS_MIGRATION_SQL,
19
26
  asRecord,
20
27
  asString,
21
28
  buildUserTextParts,
29
+ coalesceDeltas,
30
+ createD1TurnEventStore,
31
+ createMemoryTurnEventStore,
22
32
  encodeEvent,
23
33
  finalizeAssistantParts,
24
34
  getPartKey,
@@ -28,6 +38,8 @@ export {
28
38
  normalizePersistedPart,
29
39
  normalizeTime,
30
40
  normalizeToolEvent,
41
+ pumpBufferedTurn,
42
+ replayTurnEvents,
31
43
  resolveChatTurn,
32
44
  resolveToolId,
33
45
  resolveToolName
@@ -1,5 +1,5 @@
1
- import { b as AppToolContext, f as AppToolTaxonomy, c as AppToolHandlers, e as AppToolProducedEvent, d as AppToolOutcome } from '../types-CTOaTNtU.js';
2
- export { A as AddCitationArgs, a as AddCitationResult, R as RenderUiArgs, g as RenderUiResult, S as ScheduleFollowupArgs, h as ScheduleFollowupResult, i as SubmitProposalArgs, j as SubmitProposalResult } from '../types-CTOaTNtU.js';
1
+ import { b as AppToolContext, f as AppToolTaxonomy, c as AppToolHandlers, e as AppToolProducedEvent, d as AppToolOutcome } from '../types-By4B3K37.js';
2
+ export { A as AddCitationArgs, a as AddCitationResult, R as RenderUiArgs, g as RenderUiResult, S as ScheduleFollowupArgs, h as ScheduleFollowupResult, i as SubmitProposalArgs, j as SubmitProposalResult } from '../types-By4B3K37.js';
3
3
 
4
4
  /** A correctable bad-input error a tool handler throws; the HTTP layer maps it
5
5
  * to a 4xx with the code, the runtime layer to a failed tool_result. So the
@@ -102,6 +102,14 @@ declare function buildAppToolOpenAITools(taxonomy: AppToolTaxonomy): OpenAIFunct
102
102
  interface DispatchOptions {
103
103
  handlers: AppToolHandlers;
104
104
  taxonomy: AppToolTaxonomy;
105
+ /** Per-call approval policy. When provided it OVERRIDES the static
106
+ * `taxonomy.regulatedTypes` membership check, so products can gate by
107
+ * cost threshold, environment, or first-use instead of always/never.
108
+ * Fail-closed: a predicate that throws counts as "approval required". */
109
+ needsApproval?: (type: string, args: {
110
+ title: string;
111
+ description: string | null;
112
+ }, ctx: AppToolContext) => boolean | Promise<boolean>;
105
113
  /** Called at the real side-effect site for proposals (proposal_created) and
106
114
  * generated views (artifact) so a consumer's completion oracle credits
107
115
  * persisted state. Omit when produced state isn't tracked. */
@@ -8,7 +8,7 @@ import {
8
8
  handleAppToolRequest,
9
9
  readToolArgs,
10
10
  verifyCapabilityToken
11
- } from "../chunk-HZZD3ZYD.js";
11
+ } from "../chunk-OLCVUGGI.js";
12
12
  import {
13
13
  APP_TOOL_NAMES,
14
14
  ToolInputError,
@@ -17,7 +17,7 @@ import {
17
17
  dispatchAppTool,
18
18
  isAppToolName,
19
19
  outcomeStatus
20
- } from "../chunk-JANT2G2E.js";
20
+ } from "../chunk-QAQBR6KQ.js";
21
21
  export {
22
22
  APP_TOOL_NAMES,
23
23
  DEFAULT_APP_TOOL_PATHS,
@@ -41,6 +41,10 @@ interface SubmitProposalArgs {
41
41
  type: string;
42
42
  title: string;
43
43
  description?: string | null;
44
+ /** Stamped by dispatch from the approval policy (needsApproval predicate or
45
+ * taxonomy.regulatedTypes). Handlers MUST queue (never auto-execute) when
46
+ * true. Products don't set this; dispatch owns it — fail-closed. */
47
+ regulated?: boolean;
44
48
  }
45
49
  interface SubmitProposalResult {
46
50
  proposalId: string;
@@ -37,11 +37,46 @@ interface EffortPickerProps {
37
37
  /** Reasoning-effort selector pill, styled to match {@link ModelPicker}. Show
38
38
  * it only when the selected model `supportsReasoning`. */
39
39
  declare function EffortPicker({ value, onChange }: EffortPickerProps): react.JSX.Element;
40
+ /** One step of a retained tool run (e.g. a sandbox command + its output). */
41
+ interface ToolRunStep {
42
+ at: string;
43
+ label: string;
44
+ detail?: string;
45
+ status?: 'ok' | 'error';
46
+ }
47
+ /** A retained tool run keyed by the parent message's toolCallId. The product
48
+ * persists these server-side (fail-closed: only ids its own loop created)
49
+ * and serves them to the drill-in panel. */
50
+ interface ToolRunRecord {
51
+ toolCallId: string;
52
+ toolName: string;
53
+ title: string;
54
+ status: 'running' | 'complete' | 'error';
55
+ steps: ToolRunStep[];
56
+ }
57
+ interface RunDrillInProps {
58
+ run: ToolRunRecord;
59
+ onClose: () => void;
60
+ }
61
+ /**
62
+ * Readonly side panel showing a retained tool run's transcript — the
63
+ * "drill into what the sandbox actually did" view. Follow-ups happen in the
64
+ * main chat, never here.
65
+ */
66
+ declare function RunDrillIn({ run, onClose }: RunDrillInProps): react.JSX.Element;
40
67
  interface ChatToolCallInfo {
41
68
  id: string;
42
69
  name: string;
43
70
  status: 'running' | 'done' | 'error';
71
+ /** The tool outcome (`{ok, result}` shape). When `result.status` is
72
+ * 'queued_for_approval' the chip renders the approval state. */
73
+ result?: unknown;
44
74
  }
75
+ /** Extract `{proposalId, status}` from a tool outcome when it is a proposal
76
+ * awaiting human approval; null otherwise. */
77
+ declare function pendingApprovalOf(call: ChatToolCallInfo): {
78
+ proposalId: string;
79
+ } | null;
45
80
  interface ChatUiMessage extends ChatMessageMetrics {
46
81
  id: string;
47
82
  role: 'user' | 'assistant' | 'system';
@@ -61,6 +96,15 @@ interface ChatMessagesProps {
61
96
  agentLabel?: string;
62
97
  /** Render the trailing "agent is thinking" row. */
63
98
  loading?: boolean;
99
+ /** Approve/Reject handlers for proposals awaiting approval. When omitted the
100
+ * chip still shows "awaiting approval" but without action buttons. */
101
+ approval?: ProposalApprovalHandlers;
102
+ /** Make tool chips clickable (e.g. open a {@link RunDrillIn} panel). */
103
+ onToolCallClick?: (call: ChatToolCallInfo, message: ChatUiMessage) => void;
104
+ }
105
+ interface ProposalApprovalHandlers {
106
+ onApprove: (proposalId: string, toolCallId: string) => void | Promise<void>;
107
+ onReject: (proposalId: string, toolCallId: string) => void | Promise<void>;
64
108
  }
65
109
  /**
66
110
  * The message thread: one centered column; user messages are right-aligned
@@ -68,6 +112,6 @@ interface ChatMessagesProps {
68
112
  * model id, tokens/sec, and cost, plus a collapsible thinking section and
69
113
  * tool-call chips.
70
114
  */
71
- declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, userLabel, agentLabel, loading, }: ChatMessagesProps): react.JSX.Element;
115
+ declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, userLabel, agentLabel, loading, approval, onToolCallClick, }: ChatMessagesProps): react.JSX.Element;
72
116
 
73
- export { type ChatMessageMetrics, ChatMessages, type ChatMessagesProps, type ChatToolCallInfo, type ChatUiMessage, EffortPicker, type EffortPickerProps, ModelPicker, type ModelPickerProps, formatModelCost, formatTokensPerSecond };
117
+ export { type ChatMessageMetrics, ChatMessages, type ChatMessagesProps, type ChatToolCallInfo, type ChatUiMessage, EffortPicker, type EffortPickerProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, RunDrillIn, type RunDrillInProps, type ToolRunRecord, type ToolRunStep, formatModelCost, formatTokensPerSecond, pendingApprovalOf };
@@ -199,19 +199,105 @@ function EffortPicker({ value, onChange }) {
199
199
  )) })
200
200
  ] });
201
201
  }
202
- function ToolChips({ toolCalls }) {
203
- return /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-col gap-1", children: toolCalls.map((tc) => /* @__PURE__ */ jsxs(
204
- "div",
205
- {
206
- className: `inline-flex w-fit items-center gap-2 rounded-md px-2.5 py-1 text-xs ${tc.status === "running" ? "bg-yellow-500/10 text-yellow-700" : tc.status === "error" ? "bg-red-500/10 text-red-700" : "bg-green-500/10 text-green-700"}`,
207
- children: [
208
- /* @__PURE__ */ jsx("span", { className: "font-mono opacity-70", children: tc.status === "running" ? "\u26A1" : tc.status === "error" ? "\u2717" : "\u2713" }),
209
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: tc.name }),
210
- /* @__PURE__ */ jsx("span", { className: "opacity-60", children: tc.status === "running" ? "running\u2026" : tc.status === "error" ? "failed" : "done" })
211
- ]
212
- },
213
- tc.id
214
- )) });
202
+ function RunDrillIn({ run, onClose }) {
203
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-y-0 right-0 z-50 flex w-[480px] max-w-full flex-col border-l border-border bg-card shadow-xl", children: [
204
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-border px-4 py-3", children: [
205
+ /* @__PURE__ */ jsx(
206
+ "span",
207
+ {
208
+ className: `h-2 w-2 shrink-0 rounded-full ${run.status === "running" ? "bg-yellow-500" : run.status === "error" ? "bg-red-500" : "bg-green-500"}`
209
+ }
210
+ ),
211
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
212
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold", children: run.title }),
213
+ /* @__PURE__ */ jsx("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: run.toolName })
214
+ ] }),
215
+ /* @__PURE__ */ jsx(
216
+ "button",
217
+ {
218
+ type: "button",
219
+ onClick: onClose,
220
+ "aria-label": "Close",
221
+ className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground",
222
+ children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) })
223
+ }
224
+ )
225
+ ] }),
226
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
227
+ run.steps.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No steps recorded yet." }),
228
+ run.steps.map((step, i) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border/60 bg-background", children: [
229
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5", children: [
230
+ /* @__PURE__ */ jsx("span", { className: `font-mono text-[11px] ${step.status === "error" ? "text-red-600" : "text-muted-foreground"}`, children: step.status === "error" ? "\u2717" : "$" }),
231
+ /* @__PURE__ */ jsx("code", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: step.label }),
232
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] text-muted-foreground/60", children: new Date(step.at).toLocaleTimeString() })
233
+ ] }),
234
+ step.detail && /* @__PURE__ */ jsx("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap px-3 py-2 font-mono text-[11px] leading-relaxed text-muted-foreground", children: step.detail })
235
+ ] }, i))
236
+ ] }),
237
+ /* @__PURE__ */ jsx("p", { className: "border-t border-border px-4 py-2 text-[11px] text-muted-foreground/60", children: "Readonly drill-in. Follow up in the main chat." })
238
+ ] });
239
+ }
240
+ function pendingApprovalOf(call) {
241
+ const outcome = call.result;
242
+ if (!outcome?.ok || outcome.result?.status !== "queued_for_approval" || !outcome.result.proposalId) return null;
243
+ return { proposalId: outcome.result.proposalId };
244
+ }
245
+ function ToolChips({
246
+ toolCalls,
247
+ approval,
248
+ onClick
249
+ }) {
250
+ return /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-col gap-1", children: toolCalls.map((tc) => {
251
+ const pending = tc.status === "done" ? pendingApprovalOf(tc) : null;
252
+ if (pending) {
253
+ return /* @__PURE__ */ jsxs(
254
+ "div",
255
+ {
256
+ className: "inline-flex w-fit items-center gap-2 rounded-md bg-amber-500/10 px-2.5 py-1 text-xs text-amber-700",
257
+ children: [
258
+ /* @__PURE__ */ jsx("span", { className: "font-mono opacity-70", children: "\u23F8" }),
259
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: tc.name }),
260
+ /* @__PURE__ */ jsx("span", { className: "opacity-60", children: "awaiting approval" }),
261
+ approval && /* @__PURE__ */ jsxs("span", { className: "ml-1 inline-flex items-center gap-1", children: [
262
+ /* @__PURE__ */ jsx(
263
+ "button",
264
+ {
265
+ type: "button",
266
+ onClick: () => approval.onApprove(pending.proposalId, tc.id),
267
+ className: "rounded bg-green-600/90 px-2 py-0.5 text-[11px] font-semibold text-white transition hover:bg-green-600",
268
+ children: "Approve"
269
+ }
270
+ ),
271
+ /* @__PURE__ */ jsx(
272
+ "button",
273
+ {
274
+ type: "button",
275
+ onClick: () => approval.onReject(pending.proposalId, tc.id),
276
+ className: "rounded border border-border bg-card px-2 py-0.5 text-[11px] font-medium text-foreground transition hover:bg-accent/30",
277
+ children: "Reject"
278
+ }
279
+ )
280
+ ] })
281
+ ]
282
+ },
283
+ tc.id
284
+ );
285
+ }
286
+ const Tag = onClick ? "button" : "div";
287
+ return /* @__PURE__ */ jsxs(
288
+ Tag,
289
+ {
290
+ ...onClick ? { type: "button", onClick: () => onClick(tc) } : {},
291
+ className: `inline-flex w-fit items-center gap-2 rounded-md px-2.5 py-1 text-xs ${tc.status === "running" ? "bg-yellow-500/10 text-yellow-700" : tc.status === "error" ? "bg-red-500/10 text-red-700" : "bg-green-500/10 text-green-700"} ${onClick ? "cursor-pointer transition hover:ring-1 hover:ring-border" : ""}`,
292
+ children: [
293
+ /* @__PURE__ */ jsx("span", { className: "font-mono opacity-70", children: tc.status === "running" ? "\u26A1" : tc.status === "error" ? "\u2717" : "\u2713" }),
294
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: tc.name }),
295
+ /* @__PURE__ */ jsx("span", { className: "opacity-60", children: tc.status === "running" ? "running\u2026" : tc.status === "error" ? "failed" : "done" })
296
+ ]
297
+ },
298
+ tc.id
299
+ );
300
+ }) });
215
301
  }
216
302
  function ChatMessages({
217
303
  messages,
@@ -220,7 +306,9 @@ function ChatMessages({
220
306
  renderExtras,
221
307
  userLabel = "User",
222
308
  agentLabel = "Agent",
223
- loading
309
+ loading,
310
+ approval,
311
+ onToolCallClick
224
312
  }) {
225
313
  const renderBody = renderMarkdown ?? ((content) => /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: content }));
226
314
  const lastIsUser = messages[messages.length - 1]?.role === "user";
@@ -241,7 +329,14 @@ function ChatMessages({
241
329
  /* @__PURE__ */ jsx("div", { className: "mt-2 whitespace-pre-wrap text-sm text-muted-foreground/80", children: msg.reasoning })
242
330
  ] }),
243
331
  /* @__PURE__ */ jsx("div", { className: "text-base leading-[1.75]", children: renderBody(msg.content) }),
244
- msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx(ToolChips, { toolCalls: msg.toolCalls }),
332
+ msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx(
333
+ ToolChips,
334
+ {
335
+ toolCalls: msg.toolCalls,
336
+ approval,
337
+ onClick: onToolCallClick ? (tc) => onToolCallClick(tc, msg) : void 0
338
+ }
339
+ ),
245
340
  renderExtras?.(msg)
246
341
  ] }, msg.id)
247
342
  ),
@@ -258,7 +353,9 @@ export {
258
353
  ChatMessages,
259
354
  EffortPicker,
260
355
  ModelPicker,
356
+ RunDrillIn,
261
357
  formatModelCost,
262
- formatTokensPerSecond
358
+ formatTokensPerSecond,
359
+ pendingApprovalOf
263
360
  };
264
361
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/web-react/index.tsx"],"sourcesContent":["/**\n * `@tangle-network/agent-app/web-react` — the shared chat-shell components\n * every agent app's web UI hand-rolls: a model picker over the runtime's\n * model catalogue, a reasoning-effort selector, and a message thread with\n * User/Agent identity, per-message model + cost + tokens/sec metrics, tool\n * chips, and a collapsible thinking section.\n *\n * Works for BOTH chat shapes: router-backed copilots (LoopEvents from\n * `runtime/openai-stream`) and sandbox-backed chats — the thread renders\n * `ChatUiMessage`s; how they're produced is the app's business.\n *\n * Styling contract: Tailwind classes against the shared design tokens\n * (`bg-card`, `border-border`, `text-muted-foreground`, `bg-primary`, …) that\n * Tangle app shells define. No icon library — the few glyphs are inline SVGs.\n * Markdown and provider logos are injected (`renderMarkdown`,\n * `renderProviderBadge`) so this package stays dependency-free beyond React.\n */\n\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport type { CatalogModel } from '../runtime/model-catalog'\n\n// ── shared glyphs (no icon-library dependency) ────────────────────────────\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n )\n}\n\nfunction SearchGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n )\n}\n\nfunction SparkleGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1\" />\n </svg>\n )\n}\n\n/** Close an absolutely-positioned popover on outside mousedown. */\nfunction useClickOutside(onOutside: () => void) {\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n function handler(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) onOutside()\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n })\n return ref\n}\n\n// ── metrics helpers ───────────────────────────────────────────────────────\n\nexport interface ChatMessageMetrics {\n modelUsed?: string\n promptTokens?: number\n completionTokens?: number\n durationMs?: number\n}\n\n/** \"$0.0042\" from token counts × catalogue per-token pricing; null when unknown. */\nexport function formatModelCost(msg: ChatMessageMetrics, models: CatalogModel[]): string | null {\n if (msg.promptTokens == null && msg.completionTokens == null) return null\n const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing\n if (!pricing) return null\n const cost =\n (msg.promptTokens ?? 0) * Number(pricing.prompt ?? 0) +\n (msg.completionTokens ?? 0) * Number(pricing.completion ?? 0)\n if (!isFinite(cost) || cost <= 0) return null\n return cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`\n}\n\n/** \"38 tok/s\" from completion tokens over first-token→end duration; null when unknown. */\nexport function formatTokensPerSecond(msg: ChatMessageMetrics): string | null {\n if (msg.completionTokens == null || !msg.durationMs) return null\n return `${Math.round(msg.completionTokens / (msg.durationMs / 1000))} tok/s`\n}\n\n// ── ModelPicker ───────────────────────────────────────────────────────────\n\nexport interface ModelPickerProps {\n value: string\n onChange: (id: string) => void\n /** Catalogue models — from `GET`ing the app's catalogue route (see\n * `runtime/model-catalog`), plus any product-specific entries appended. */\n models: CatalogModel[]\n loading?: boolean\n /** Render a provider logo/badge; default is a generic sparkle. */\n renderProviderBadge?: (provider: string) => ReactNode\n /** Section label for `featured` models. */\n recommendedLabel?: string\n}\n\nfunction formatPrice(p?: string): string | undefined {\n if (!p) return undefined\n const n = Number(p)\n if (isNaN(n) || n === 0) return undefined\n const perM = n * 1_000_000\n return perM >= 1 ? `$${perM.toFixed(0)}/M` : `$${perM.toFixed(2)}/M`\n}\n\nfunction formatContext(len?: number): string | undefined {\n if (!len) return undefined\n if (len >= 1_000_000) return `${(len / 1_000_000).toFixed(1)}M ctx`\n if (len >= 1_000) return `${Math.round(len / 1_000)}K ctx`\n return `${len} ctx`\n}\n\nfunction SectionHeader({ children }: { children: ReactNode }) {\n return (\n <div className=\"px-3 pb-1 pt-3 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/70\">\n {children}\n </div>\n )\n}\n\nfunction ModelRow({\n model,\n selected,\n onSelect,\n renderProviderBadge,\n}: {\n model: CatalogModel\n selected: boolean\n onSelect: () => void\n renderProviderBadge?: (provider: string) => ReactNode\n}) {\n const price = formatPrice(model.pricing?.prompt)\n const ctx = formatContext(model.contextLength)\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n className={`flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition ${\n selected ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {renderProviderBadge ? renderProviderBadge(model.provider) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"truncate\">{model.name}</span>\n {!model.supportsTools && (\n <span className=\"shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\">\n no tools\n </span>\n )}\n <span className=\"ml-auto flex shrink-0 items-center gap-2 text-[11px] text-muted-foreground\">\n {ctx && <span>{ctx}</span>}\n {price && <span>{price}</span>}\n </span>\n </button>\n )\n}\n\n/**\n * Searchable model picker pill + popover: a featured/recommended section\n * first, then per-provider groups in catalogue order (the server already\n * sorts providers by tier).\n */\nexport function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel = 'Recommended' }: ModelPickerProps) {\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const containerRef = useClickOutside(() => setOpen(false))\n const inputRef = useRef<HTMLInputElement>(null)\n\n useEffect(() => {\n if (open) inputRef.current?.focus()\n }, [open])\n\n const selected = models.find((m) => m.id === value)\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (!q) return null\n return models.filter(\n (m) =>\n m.id.toLowerCase().includes(q) ||\n m.name.toLowerCase().includes(q) ||\n (m.description?.toLowerCase() ?? '').includes(q) ||\n m.provider.toLowerCase().includes(q),\n )\n }, [models, query])\n\n const sections = useMemo(() => {\n const recommended = models.filter((m) => m.featured)\n const byProvider: Array<{ provider: string; items: CatalogModel[] }> = []\n for (const m of models) {\n if (m.featured) continue\n const last = byProvider[byProvider.length - 1]\n if (last && last.provider === m.provider) last.items.push(m)\n else byProvider.push({ provider: m.provider, items: [m] })\n }\n return { recommended, byProvider }\n }, [models])\n\n const select = (id: string) => {\n onChange(id)\n setOpen(false)\n setQuery('')\n }\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n {selected && renderProviderBadge ? renderProviderBadge(selected.provider) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"max-w-[160px] truncate\">{selected?.name ?? value}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-[420px] overflow-hidden rounded-xl border border-border bg-card shadow-lg\">\n <div className=\"border-b border-border px-3 py-2\">\n <div className=\"flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2\">\n <SearchGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search models...\"\n className=\"flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n </div>\n </div>\n <div className=\"max-h-[400px] overflow-y-auto p-1 pb-2\">\n {loading && <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">Loading models...</div>}\n {!loading && filtered && (\n <>\n {filtered.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">No models match your search</div>\n )}\n {filtered.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {!loading && !filtered && (\n <>\n {sections.recommended.length > 0 && (\n <>\n <SectionHeader>{recommendedLabel}</SectionHeader>\n {sections.recommended.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {sections.byProvider.map((g) => (\n <div key={g.provider}>\n <SectionHeader>{g.provider}</SectionHeader>\n {g.items.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </div>\n ))}\n </>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\n// ── EffortPicker ──────────────────────────────────────────────────────────\n\nconst EFFORT_LEVELS = [\n { id: 'off', label: 'Off' },\n { id: 'low', label: 'Low' },\n { id: 'medium', label: 'Medium' },\n { id: 'high', label: 'High' },\n] as const\n\nexport interface EffortPickerProps {\n value: string\n onChange: (id: string) => void\n}\n\n/** Reasoning-effort selector pill, styled to match {@link ModelPicker}. Show\n * it only when the selected model `supportsReasoning`. */\nexport function EffortPicker({ value, onChange }: EffortPickerProps) {\n const [open, setOpen] = useState(false)\n const containerRef = useClickOutside(() => setOpen(false))\n const selected = EFFORT_LEVELS.find((l) => l.id === value) ?? EFFORT_LEVELS[2]\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n title=\"Reasoning effort\"\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <span>{selected.label}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-36 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-lg\">\n {EFFORT_LEVELS.map((l) => (\n <button\n key={l.id}\n type=\"button\"\n onClick={() => {\n onChange(l.id)\n setOpen(false)\n }}\n className={`flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${\n l.id === value ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {l.label}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n// ── ChatMessages ──────────────────────────────────────────────────────────\n\nexport interface ChatToolCallInfo {\n id: string\n name: string\n status: 'running' | 'done' | 'error'\n}\n\nexport interface ChatUiMessage extends ChatMessageMetrics {\n id: string\n role: 'user' | 'assistant' | 'system'\n content: string\n reasoning?: string\n toolCalls?: ChatToolCallInfo[]\n}\n\nexport interface ChatMessagesProps {\n messages: ChatUiMessage[]\n /** Catalogue models, for per-message cost from pricing. Pass [] to skip cost. */\n models?: CatalogModel[]\n /** Markdown renderer for assistant content; default renders pre-wrapped text. */\n renderMarkdown?: (content: string) => ReactNode\n /** Extra per-message content (artifacts, custom panels) appended after the body. */\n renderExtras?: (message: ChatUiMessage) => ReactNode\n userLabel?: string\n agentLabel?: string\n /** Render the trailing \"agent is thinking\" row. */\n loading?: boolean\n}\n\nfunction ToolChips({ toolCalls }: { toolCalls: ChatToolCallInfo[] }) {\n return (\n <div className=\"mt-2 flex flex-col gap-1\">\n {toolCalls.map((tc) => (\n <div\n key={tc.id}\n className={`inline-flex w-fit items-center gap-2 rounded-md px-2.5 py-1 text-xs ${\n tc.status === 'running'\n ? 'bg-yellow-500/10 text-yellow-700'\n : tc.status === 'error'\n ? 'bg-red-500/10 text-red-700'\n : 'bg-green-500/10 text-green-700'\n }`}\n >\n <span className=\"font-mono opacity-70\">{tc.status === 'running' ? '⚡' : tc.status === 'error' ? '✗' : '✓'}</span>\n <span className=\"font-medium\">{tc.name}</span>\n <span className=\"opacity-60\">{tc.status === 'running' ? 'running…' : tc.status === 'error' ? 'failed' : 'done'}</span>\n </div>\n ))}\n </div>\n )\n}\n\n/**\n * The message thread: one centered column; user messages are right-aligned\n * bubbles with a User label; agent messages carry an Agent meta line with\n * model id, tokens/sec, and cost, plus a collapsible thinking section and\n * tool-call chips.\n */\nexport function ChatMessages({\n messages,\n models = [],\n renderMarkdown,\n renderExtras,\n userLabel = 'User',\n agentLabel = 'Agent',\n loading,\n}: ChatMessagesProps) {\n const renderBody = renderMarkdown ?? ((content: string) => <p className=\"whitespace-pre-wrap\">{content}</p>)\n const lastIsUser = messages[messages.length - 1]?.role === 'user'\n return (\n <>\n {messages.map((msg) =>\n msg.role === 'user' ? (\n <div key={msg.id} className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"ml-auto w-fit max-w-[85%]\">\n <p className=\"mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">\n {userLabel}\n </p>\n <div className=\"rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed\">\n <p className=\"whitespace-pre-wrap\">{msg.content}</p>\n </div>\n </div>\n </div>\n ) : (\n <div key={msg.id} className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground/60\">\n <span className=\"font-semibold uppercase\">{agentLabel}</span>\n {msg.modelUsed && <span className=\"font-mono normal-case\">{msg.modelUsed}</span>}\n {formatTokensPerSecond(msg) && <span>{formatTokensPerSecond(msg)}</span>}\n {formatModelCost(msg, models) && <span>{formatModelCost(msg, models)}</span>}\n </div>\n {msg.reasoning && (\n <details className=\"mb-2 rounded-md border border-border/40 bg-muted/30 px-3 py-2\">\n <summary className=\"cursor-pointer select-none text-xs font-medium text-muted-foreground\">Thinking…</summary>\n <div className=\"mt-2 whitespace-pre-wrap text-sm text-muted-foreground/80\">{msg.reasoning}</div>\n </details>\n )}\n <div className=\"text-base leading-[1.75]\">{renderBody(msg.content)}</div>\n {msg.toolCalls && msg.toolCalls.length > 0 && <ToolChips toolCalls={msg.toolCalls} />}\n {renderExtras?.(msg)}\n </div>\n ),\n )}\n {loading && lastIsUser && (\n <div className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <p className=\"mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">{agentLabel}</p>\n <div className=\"flex items-center gap-2 text-base text-muted-foreground\">\n <svg className=\"h-4 w-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden>\n <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" strokeLinecap=\"round\" />\n </svg>\n Thinking...\n </div>\n </div>\n )}\n </>\n )\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/web-react/index.tsx"],"sourcesContent":["/**\n * `@tangle-network/agent-app/web-react` — the shared chat-shell components\n * every agent app's web UI hand-rolls: a model picker over the runtime's\n * model catalogue, a reasoning-effort selector, and a message thread with\n * User/Agent identity, per-message model + cost + tokens/sec metrics, tool\n * chips, and a collapsible thinking section.\n *\n * Works for BOTH chat shapes: router-backed copilots (LoopEvents from\n * `runtime/openai-stream`) and sandbox-backed chats — the thread renders\n * `ChatUiMessage`s; how they're produced is the app's business.\n *\n * Styling contract: Tailwind classes against the shared design tokens\n * (`bg-card`, `border-border`, `text-muted-foreground`, `bg-primary`, …) that\n * Tangle app shells define. No icon library — the few glyphs are inline SVGs.\n * Markdown and provider logos are injected (`renderMarkdown`,\n * `renderProviderBadge`) so this package stays dependency-free beyond React.\n */\n\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport type { CatalogModel } from '../runtime/model-catalog'\n\n// ── shared glyphs (no icon-library dependency) ────────────────────────────\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n )\n}\n\nfunction SearchGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n )\n}\n\nfunction SparkleGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1\" />\n </svg>\n )\n}\n\n/** Close an absolutely-positioned popover on outside mousedown. */\nfunction useClickOutside(onOutside: () => void) {\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n function handler(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) onOutside()\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n })\n return ref\n}\n\n// ── metrics helpers ───────────────────────────────────────────────────────\n\nexport interface ChatMessageMetrics {\n modelUsed?: string\n promptTokens?: number\n completionTokens?: number\n durationMs?: number\n}\n\n/** \"$0.0042\" from token counts × catalogue per-token pricing; null when unknown. */\nexport function formatModelCost(msg: ChatMessageMetrics, models: CatalogModel[]): string | null {\n if (msg.promptTokens == null && msg.completionTokens == null) return null\n const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing\n if (!pricing) return null\n const cost =\n (msg.promptTokens ?? 0) * Number(pricing.prompt ?? 0) +\n (msg.completionTokens ?? 0) * Number(pricing.completion ?? 0)\n if (!isFinite(cost) || cost <= 0) return null\n return cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`\n}\n\n/** \"38 tok/s\" from completion tokens over first-token→end duration; null when unknown. */\nexport function formatTokensPerSecond(msg: ChatMessageMetrics): string | null {\n if (msg.completionTokens == null || !msg.durationMs) return null\n return `${Math.round(msg.completionTokens / (msg.durationMs / 1000))} tok/s`\n}\n\n// ── ModelPicker ───────────────────────────────────────────────────────────\n\nexport interface ModelPickerProps {\n value: string\n onChange: (id: string) => void\n /** Catalogue models — from `GET`ing the app's catalogue route (see\n * `runtime/model-catalog`), plus any product-specific entries appended. */\n models: CatalogModel[]\n loading?: boolean\n /** Render a provider logo/badge; default is a generic sparkle. */\n renderProviderBadge?: (provider: string) => ReactNode\n /** Section label for `featured` models. */\n recommendedLabel?: string\n}\n\nfunction formatPrice(p?: string): string | undefined {\n if (!p) return undefined\n const n = Number(p)\n if (isNaN(n) || n === 0) return undefined\n const perM = n * 1_000_000\n return perM >= 1 ? `$${perM.toFixed(0)}/M` : `$${perM.toFixed(2)}/M`\n}\n\nfunction formatContext(len?: number): string | undefined {\n if (!len) return undefined\n if (len >= 1_000_000) return `${(len / 1_000_000).toFixed(1)}M ctx`\n if (len >= 1_000) return `${Math.round(len / 1_000)}K ctx`\n return `${len} ctx`\n}\n\nfunction SectionHeader({ children }: { children: ReactNode }) {\n return (\n <div className=\"px-3 pb-1 pt-3 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/70\">\n {children}\n </div>\n )\n}\n\nfunction ModelRow({\n model,\n selected,\n onSelect,\n renderProviderBadge,\n}: {\n model: CatalogModel\n selected: boolean\n onSelect: () => void\n renderProviderBadge?: (provider: string) => ReactNode\n}) {\n const price = formatPrice(model.pricing?.prompt)\n const ctx = formatContext(model.contextLength)\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n className={`flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition ${\n selected ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {renderProviderBadge ? renderProviderBadge(model.provider) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"truncate\">{model.name}</span>\n {!model.supportsTools && (\n <span className=\"shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\">\n no tools\n </span>\n )}\n <span className=\"ml-auto flex shrink-0 items-center gap-2 text-[11px] text-muted-foreground\">\n {ctx && <span>{ctx}</span>}\n {price && <span>{price}</span>}\n </span>\n </button>\n )\n}\n\n/**\n * Searchable model picker pill + popover: a featured/recommended section\n * first, then per-provider groups in catalogue order (the server already\n * sorts providers by tier).\n */\nexport function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel = 'Recommended' }: ModelPickerProps) {\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const containerRef = useClickOutside(() => setOpen(false))\n const inputRef = useRef<HTMLInputElement>(null)\n\n useEffect(() => {\n if (open) inputRef.current?.focus()\n }, [open])\n\n const selected = models.find((m) => m.id === value)\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (!q) return null\n return models.filter(\n (m) =>\n m.id.toLowerCase().includes(q) ||\n m.name.toLowerCase().includes(q) ||\n (m.description?.toLowerCase() ?? '').includes(q) ||\n m.provider.toLowerCase().includes(q),\n )\n }, [models, query])\n\n const sections = useMemo(() => {\n const recommended = models.filter((m) => m.featured)\n const byProvider: Array<{ provider: string; items: CatalogModel[] }> = []\n for (const m of models) {\n if (m.featured) continue\n const last = byProvider[byProvider.length - 1]\n if (last && last.provider === m.provider) last.items.push(m)\n else byProvider.push({ provider: m.provider, items: [m] })\n }\n return { recommended, byProvider }\n }, [models])\n\n const select = (id: string) => {\n onChange(id)\n setOpen(false)\n setQuery('')\n }\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n {selected && renderProviderBadge ? renderProviderBadge(selected.provider) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"max-w-[160px] truncate\">{selected?.name ?? value}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-[420px] overflow-hidden rounded-xl border border-border bg-card shadow-lg\">\n <div className=\"border-b border-border px-3 py-2\">\n <div className=\"flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2\">\n <SearchGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search models...\"\n className=\"flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n </div>\n </div>\n <div className=\"max-h-[400px] overflow-y-auto p-1 pb-2\">\n {loading && <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">Loading models...</div>}\n {!loading && filtered && (\n <>\n {filtered.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">No models match your search</div>\n )}\n {filtered.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {!loading && !filtered && (\n <>\n {sections.recommended.length > 0 && (\n <>\n <SectionHeader>{recommendedLabel}</SectionHeader>\n {sections.recommended.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {sections.byProvider.map((g) => (\n <div key={g.provider}>\n <SectionHeader>{g.provider}</SectionHeader>\n {g.items.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </div>\n ))}\n </>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\n// ── EffortPicker ──────────────────────────────────────────────────────────\n\nconst EFFORT_LEVELS = [\n { id: 'off', label: 'Off' },\n { id: 'low', label: 'Low' },\n { id: 'medium', label: 'Medium' },\n { id: 'high', label: 'High' },\n] as const\n\nexport interface EffortPickerProps {\n value: string\n onChange: (id: string) => void\n}\n\n/** Reasoning-effort selector pill, styled to match {@link ModelPicker}. Show\n * it only when the selected model `supportsReasoning`. */\nexport function EffortPicker({ value, onChange }: EffortPickerProps) {\n const [open, setOpen] = useState(false)\n const containerRef = useClickOutside(() => setOpen(false))\n const selected = EFFORT_LEVELS.find((l) => l.id === value) ?? EFFORT_LEVELS[2]\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n title=\"Reasoning effort\"\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <span>{selected.label}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-36 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-lg\">\n {EFFORT_LEVELS.map((l) => (\n <button\n key={l.id}\n type=\"button\"\n onClick={() => {\n onChange(l.id)\n setOpen(false)\n }}\n className={`flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${\n l.id === value ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {l.label}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n// ── Tool run drill-in (retained runs) ─────────────────────────────────────\n\n/** One step of a retained tool run (e.g. a sandbox command + its output). */\nexport interface ToolRunStep {\n at: string\n label: string\n detail?: string\n status?: 'ok' | 'error'\n}\n\n/** A retained tool run keyed by the parent message's toolCallId. The product\n * persists these server-side (fail-closed: only ids its own loop created)\n * and serves them to the drill-in panel. */\nexport interface ToolRunRecord {\n toolCallId: string\n toolName: string\n title: string\n status: 'running' | 'complete' | 'error'\n steps: ToolRunStep[]\n}\n\nexport interface RunDrillInProps {\n run: ToolRunRecord\n onClose: () => void\n}\n\n/**\n * Readonly side panel showing a retained tool run's transcript — the\n * \"drill into what the sandbox actually did\" view. Follow-ups happen in the\n * main chat, never here.\n */\nexport function RunDrillIn({ run, onClose }: RunDrillInProps) {\n return (\n <div className=\"fixed inset-y-0 right-0 z-50 flex w-[480px] max-w-full flex-col border-l border-border bg-card shadow-xl\">\n <div className=\"flex items-center gap-2 border-b border-border px-4 py-3\">\n <span\n className={`h-2 w-2 shrink-0 rounded-full ${\n run.status === 'running' ? 'bg-yellow-500' : run.status === 'error' ? 'bg-red-500' : 'bg-green-500'\n }`}\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold\">{run.title}</p>\n <p className=\"truncate font-mono text-[11px] text-muted-foreground\">{run.toolName}</p>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close\"\n className=\"rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground\"\n >\n <svg className=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div className=\"flex-1 space-y-3 overflow-y-auto p-4\">\n {run.steps.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">No steps recorded yet.</p>\n )}\n {run.steps.map((step, i) => (\n <div key={i} className=\"rounded-lg border border-border/60 bg-background\">\n <div className=\"flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5\">\n <span className={`font-mono text-[11px] ${step.status === 'error' ? 'text-red-600' : 'text-muted-foreground'}`}>\n {step.status === 'error' ? '✗' : '$'}\n </span>\n <code className=\"min-w-0 flex-1 truncate font-mono text-xs\">{step.label}</code>\n <span className=\"shrink-0 text-[10px] text-muted-foreground/60\">\n {new Date(step.at).toLocaleTimeString()}\n </span>\n </div>\n {step.detail && (\n <pre className=\"max-h-48 overflow-auto whitespace-pre-wrap px-3 py-2 font-mono text-[11px] leading-relaxed text-muted-foreground\">\n {step.detail}\n </pre>\n )}\n </div>\n ))}\n </div>\n <p className=\"border-t border-border px-4 py-2 text-[11px] text-muted-foreground/60\">\n Readonly drill-in. Follow up in the main chat.\n </p>\n </div>\n )\n}\n\n// ── ChatMessages ──────────────────────────────────────────────────────────\n\nexport interface ChatToolCallInfo {\n id: string\n name: string\n status: 'running' | 'done' | 'error'\n /** The tool outcome (`{ok, result}` shape). When `result.status` is\n * 'queued_for_approval' the chip renders the approval state. */\n result?: unknown\n}\n\n/** Extract `{proposalId, status}` from a tool outcome when it is a proposal\n * awaiting human approval; null otherwise. */\nexport function pendingApprovalOf(call: ChatToolCallInfo): { proposalId: string } | null {\n const outcome = call.result as { ok?: boolean; result?: { status?: string; proposalId?: string } } | undefined\n if (!outcome?.ok || outcome.result?.status !== 'queued_for_approval' || !outcome.result.proposalId) return null\n return { proposalId: outcome.result.proposalId }\n}\n\nexport interface ChatUiMessage extends ChatMessageMetrics {\n id: string\n role: 'user' | 'assistant' | 'system'\n content: string\n reasoning?: string\n toolCalls?: ChatToolCallInfo[]\n}\n\nexport interface ChatMessagesProps {\n messages: ChatUiMessage[]\n /** Catalogue models, for per-message cost from pricing. Pass [] to skip cost. */\n models?: CatalogModel[]\n /** Markdown renderer for assistant content; default renders pre-wrapped text. */\n renderMarkdown?: (content: string) => ReactNode\n /** Extra per-message content (artifacts, custom panels) appended after the body. */\n renderExtras?: (message: ChatUiMessage) => ReactNode\n userLabel?: string\n agentLabel?: string\n /** Render the trailing \"agent is thinking\" row. */\n loading?: boolean\n /** Approve/Reject handlers for proposals awaiting approval. When omitted the\n * chip still shows \"awaiting approval\" but without action buttons. */\n approval?: ProposalApprovalHandlers\n /** Make tool chips clickable (e.g. open a {@link RunDrillIn} panel). */\n onToolCallClick?: (call: ChatToolCallInfo, message: ChatUiMessage) => void\n}\n\nexport interface ProposalApprovalHandlers {\n onApprove: (proposalId: string, toolCallId: string) => void | Promise<void>\n onReject: (proposalId: string, toolCallId: string) => void | Promise<void>\n}\n\nfunction ToolChips({\n toolCalls,\n approval,\n onClick,\n}: {\n toolCalls: ChatToolCallInfo[]\n approval?: ProposalApprovalHandlers\n onClick?: (call: ChatToolCallInfo) => void\n}) {\n return (\n <div className=\"mt-2 flex flex-col gap-1\">\n {toolCalls.map((tc) => {\n const pending = tc.status === 'done' ? pendingApprovalOf(tc) : null\n if (pending) {\n return (\n <div\n key={tc.id}\n className=\"inline-flex w-fit items-center gap-2 rounded-md bg-amber-500/10 px-2.5 py-1 text-xs text-amber-700\"\n >\n <span className=\"font-mono opacity-70\">⏸</span>\n <span className=\"font-medium\">{tc.name}</span>\n <span className=\"opacity-60\">awaiting approval</span>\n {approval && (\n <span className=\"ml-1 inline-flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => approval.onApprove(pending.proposalId, tc.id)}\n className=\"rounded bg-green-600/90 px-2 py-0.5 text-[11px] font-semibold text-white transition hover:bg-green-600\"\n >\n Approve\n </button>\n <button\n type=\"button\"\n onClick={() => approval.onReject(pending.proposalId, tc.id)}\n className=\"rounded border border-border bg-card px-2 py-0.5 text-[11px] font-medium text-foreground transition hover:bg-accent/30\"\n >\n Reject\n </button>\n </span>\n )}\n </div>\n )\n }\n const Tag = onClick ? 'button' : 'div'\n return (\n <Tag\n key={tc.id}\n {...(onClick ? { type: 'button' as const, onClick: () => onClick(tc) } : {})}\n className={`inline-flex w-fit items-center gap-2 rounded-md px-2.5 py-1 text-xs ${\n tc.status === 'running'\n ? 'bg-yellow-500/10 text-yellow-700'\n : tc.status === 'error'\n ? 'bg-red-500/10 text-red-700'\n : 'bg-green-500/10 text-green-700'\n } ${onClick ? 'cursor-pointer transition hover:ring-1 hover:ring-border' : ''}`}\n >\n <span className=\"font-mono opacity-70\">{tc.status === 'running' ? '⚡' : tc.status === 'error' ? '✗' : '✓'}</span>\n <span className=\"font-medium\">{tc.name}</span>\n <span className=\"opacity-60\">{tc.status === 'running' ? 'running…' : tc.status === 'error' ? 'failed' : 'done'}</span>\n </Tag>\n )\n })}\n </div>\n )\n}\n\n/**\n * The message thread: one centered column; user messages are right-aligned\n * bubbles with a User label; agent messages carry an Agent meta line with\n * model id, tokens/sec, and cost, plus a collapsible thinking section and\n * tool-call chips.\n */\nexport function ChatMessages({\n messages,\n models = [],\n renderMarkdown,\n renderExtras,\n userLabel = 'User',\n agentLabel = 'Agent',\n loading,\n approval,\n onToolCallClick,\n}: ChatMessagesProps) {\n const renderBody = renderMarkdown ?? ((content: string) => <p className=\"whitespace-pre-wrap\">{content}</p>)\n const lastIsUser = messages[messages.length - 1]?.role === 'user'\n return (\n <>\n {messages.map((msg) =>\n msg.role === 'user' ? (\n <div key={msg.id} className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"ml-auto w-fit max-w-[85%]\">\n <p className=\"mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">\n {userLabel}\n </p>\n <div className=\"rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed\">\n <p className=\"whitespace-pre-wrap\">{msg.content}</p>\n </div>\n </div>\n </div>\n ) : (\n <div key={msg.id} className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground/60\">\n <span className=\"font-semibold uppercase\">{agentLabel}</span>\n {msg.modelUsed && <span className=\"font-mono normal-case\">{msg.modelUsed}</span>}\n {formatTokensPerSecond(msg) && <span>{formatTokensPerSecond(msg)}</span>}\n {formatModelCost(msg, models) && <span>{formatModelCost(msg, models)}</span>}\n </div>\n {msg.reasoning && (\n <details className=\"mb-2 rounded-md border border-border/40 bg-muted/30 px-3 py-2\">\n <summary className=\"cursor-pointer select-none text-xs font-medium text-muted-foreground\">Thinking…</summary>\n <div className=\"mt-2 whitespace-pre-wrap text-sm text-muted-foreground/80\">{msg.reasoning}</div>\n </details>\n )}\n <div className=\"text-base leading-[1.75]\">{renderBody(msg.content)}</div>\n {msg.toolCalls && msg.toolCalls.length > 0 && (\n <ToolChips\n toolCalls={msg.toolCalls}\n approval={approval}\n onClick={onToolCallClick ? (tc) => onToolCallClick(tc, msg) : undefined}\n />\n )}\n {renderExtras?.(msg)}\n </div>\n ),\n )}\n {loading && lastIsUser && (\n <div className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <p className=\"mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">{agentLabel}</p>\n <div className=\"flex items-center gap-2 text-base text-muted-foreground\">\n <svg className=\"h-4 w-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden>\n <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" strokeLinecap=\"round\" />\n </svg>\n Thinking...\n </div>\n </div>\n )}\n </>\n )\n}\n"],"mappings":";AAkBA,SAAS,WAAW,SAAS,QAAQ,gBAAgC;AAQ/D,SAqNQ,UArNR,KAOF,YAPE;AAHN,SAAS,YAAY,EAAE,UAAU,GAA2B;AAC1D,SACE,oBAAC,SAAI,WAAsB,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,gBAAe,SAAQ,eAAW,MACvJ,8BAAC,UAAK,GAAE,gBAAe,GACzB;AAEJ;AAEA,SAAS,YAAY,EAAE,UAAU,GAA2B;AAC1D,SACE,qBAAC,SAAI,WAAsB,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,gBAAe,SAAQ,eAAW,MACvJ;AAAA,wBAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,KAAI;AAAA,IAC9B,oBAAC,UAAK,GAAE,kBAAiB;AAAA,KAC3B;AAEJ;AAEA,SAAS,aAAa,EAAE,UAAU,GAA2B;AAC3D,SACE,oBAAC,SAAI,WAAsB,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,gBAAe,SAAQ,eAAW,MACvJ,8BAAC,UAAK,GAAE,iGAAgG,GAC1G;AAEJ;AAGA,SAAS,gBAAgB,WAAuB;AAC9C,QAAM,MAAM,OAAuB,IAAI;AACvC,YAAU,MAAM;AACd,aAAS,QAAQ,GAAe;AAC9B,UAAI,IAAI,WAAW,CAAC,IAAI,QAAQ,SAAS,EAAE,MAAc,EAAG,WAAU;AAAA,IACxE;AACA,aAAS,iBAAiB,aAAa,OAAO;AAC9C,WAAO,MAAM,SAAS,oBAAoB,aAAa,OAAO;AAAA,EAChE,CAAC;AACD,SAAO;AACT;AAYO,SAAS,gBAAgB,KAAyB,QAAuC;AAC9F,MAAI,IAAI,gBAAgB,QAAQ,IAAI,oBAAoB,KAAM,QAAO;AACrE,QAAM,UAAU,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,IAAI,SAAS,GAAG;AAC5D,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,QACH,IAAI,gBAAgB,KAAK,OAAO,QAAQ,UAAU,CAAC,KACnD,IAAI,oBAAoB,KAAK,OAAO,QAAQ,cAAc,CAAC;AAC9D,MAAI,CAAC,SAAS,IAAI,KAAK,QAAQ,EAAG,QAAO;AACzC,SAAO,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,KAAK,IAAI,KAAK,QAAQ,CAAC,CAAC;AAClE;AAGO,SAAS,sBAAsB,KAAwC;AAC5E,MAAI,IAAI,oBAAoB,QAAQ,CAAC,IAAI,WAAY,QAAO;AAC5D,SAAO,GAAG,KAAK,MAAM,IAAI,oBAAoB,IAAI,aAAa,IAAK,CAAC;AACtE;AAiBA,SAAS,YAAY,GAAgC;AACnD,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,IAAI,OAAO,CAAC;AAClB,MAAI,MAAM,CAAC,KAAK,MAAM,EAAG,QAAO;AAChC,QAAM,OAAO,IAAI;AACjB,SAAO,QAAQ,IAAI,IAAI,KAAK,QAAQ,CAAC,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC;AAClE;AAEA,SAAS,cAAc,KAAkC;AACvD,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,OAAO,IAAW,QAAO,IAAI,MAAM,KAAW,QAAQ,CAAC,CAAC;AAC5D,MAAI,OAAO,IAAO,QAAO,GAAG,KAAK,MAAM,MAAM,GAAK,CAAC;AACnD,SAAO,GAAG,GAAG;AACf;AAEA,SAAS,cAAc,EAAE,SAAS,GAA4B;AAC5D,SACE,oBAAC,SAAI,WAAU,6FACZ,UACH;AAEJ;AAEA,SAAS,SAAS;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,QAAQ,YAAY,MAAM,SAAS,MAAM;AAC/C,QAAM,MAAM,cAAc,MAAM,aAAa;AAC7C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAS;AAAA,MACT,WAAW,oFACT,WAAW,8BAA8B,oBAC3C;AAAA,MAEC;AAAA,8BAAsB,oBAAoB,MAAM,QAAQ,IAAI,oBAAC,gBAAa,WAAU,qCAAoC;AAAA,QACzH,oBAAC,UAAK,WAAU,YAAY,gBAAM,MAAK;AAAA,QACtC,CAAC,MAAM,iBACN,oBAAC,UAAK,WAAU,4FAA2F,sBAE3G;AAAA,QAEF,qBAAC,UAAK,WAAU,8EACb;AAAA,iBAAO,oBAAC,UAAM,eAAI;AAAA,UAClB,SAAS,oBAAC,UAAM,iBAAM;AAAA,WACzB;AAAA;AAAA;AAAA,EACF;AAEJ;AAOO,SAAS,YAAY,EAAE,OAAO,UAAU,QAAQ,SAAS,qBAAqB,mBAAmB,cAAc,GAAqB;AACzI,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,eAAe,gBAAgB,MAAM,QAAQ,KAAK,CAAC;AACzD,QAAM,WAAW,OAAyB,IAAI;AAE9C,YAAU,MAAM;AACd,QAAI,KAAM,UAAS,SAAS,MAAM;AAAA,EACpC,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,WAAW,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK;AAElD,QAAM,WAAW,QAAQ,MAAM;AAC7B,UAAM,IAAI,MAAM,KAAK,EAAE,YAAY;AACnC,QAAI,CAAC,EAAG,QAAO;AACf,WAAO,OAAO;AAAA,MACZ,CAAC,MACC,EAAE,GAAG,YAAY,EAAE,SAAS,CAAC,KAC7B,EAAE,KAAK,YAAY,EAAE,SAAS,CAAC,MAC9B,EAAE,aAAa,YAAY,KAAK,IAAI,SAAS,CAAC,KAC/C,EAAE,SAAS,YAAY,EAAE,SAAS,CAAC;AAAA,IACvC;AAAA,EACF,GAAG,CAAC,QAAQ,KAAK,CAAC;AAElB,QAAM,WAAW,QAAQ,MAAM;AAC7B,UAAM,cAAc,OAAO,OAAO,CAAC,MAAM,EAAE,QAAQ;AACnD,UAAM,aAAiE,CAAC;AACxE,eAAW,KAAK,QAAQ;AACtB,UAAI,EAAE,SAAU;AAChB,YAAM,OAAO,WAAW,WAAW,SAAS,CAAC;AAC7C,UAAI,QAAQ,KAAK,aAAa,EAAE,SAAU,MAAK,MAAM,KAAK,CAAC;AAAA,UACtD,YAAW,KAAK,EAAE,UAAU,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE,CAAC;AAAA,IAC3D;AACA,WAAO,EAAE,aAAa,WAAW;AAAA,EACnC,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,SAAS,CAAC,OAAe;AAC7B,aAAS,EAAE;AACX,YAAQ,KAAK;AACb,aAAS,EAAE;AAAA,EACb;AAEA,SACE,qBAAC,SAAI,KAAK,cAAc,WAAU,wBAChC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;AAAA,QAChC,WAAU;AAAA,QAET;AAAA,sBAAY,sBAAsB,oBAAoB,SAAS,QAAQ,IAAI,oBAAC,gBAAa,WAAU,qCAAoC;AAAA,UACxI,oBAAC,UAAK,WAAU,0BAA0B,oBAAU,QAAQ,OAAM;AAAA,UAClE,oBAAC,eAAY,WAAU,qCAAoC;AAAA;AAAA;AAAA,IAC7D;AAAA,IAEC,QACC,qBAAC,SAAI,WAAU,qHACb;AAAA,0BAAC,SAAI,WAAU,oCACb,+BAAC,SAAI,WAAU,mFACb;AAAA,4BAAC,eAAY,WAAU,qCAAoC;AAAA,QAC3D;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,MAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,YACxC,aAAY;AAAA,YACZ,WAAU;AAAA;AAAA,QACZ;AAAA,SACF,GACF;AAAA,MACA,qBAAC,SAAI,WAAU,0CACZ;AAAA,mBAAW,oBAAC,SAAI,WAAU,uDAAsD,+BAAiB;AAAA,QACjG,CAAC,WAAW,YACX,iCACG;AAAA,mBAAS,WAAW,KACnB,oBAAC,SAAI,WAAU,uDAAsD,yCAA2B;AAAA,UAEjG,SAAS,IAAI,CAAC,MACb,oBAAC,YAAoB,OAAO,GAAG,UAAU,EAAE,OAAO,OAAO,UAAU,MAAM,OAAO,EAAE,EAAE,GAAG,uBAAxE,EAAE,EAAgH,CAClI;AAAA,WACH;AAAA,QAED,CAAC,WAAW,CAAC,YACZ,iCACG;AAAA,mBAAS,YAAY,SAAS,KAC7B,iCACE;AAAA,gCAAC,iBAAe,4BAAiB;AAAA,YAChC,SAAS,YAAY,IAAI,CAAC,MACzB,oBAAC,YAAoB,OAAO,GAAG,UAAU,EAAE,OAAO,OAAO,UAAU,MAAM,OAAO,EAAE,EAAE,GAAG,uBAAxE,EAAE,EAAgH,CAClI;AAAA,aACH;AAAA,UAED,SAAS,WAAW,IAAI,CAAC,MACxB,qBAAC,SACC;AAAA,gCAAC,iBAAe,YAAE,UAAS;AAAA,YAC1B,EAAE,MAAM,IAAI,CAAC,MACZ,oBAAC,YAAoB,OAAO,GAAG,UAAU,EAAE,OAAO,OAAO,UAAU,MAAM,OAAO,EAAE,EAAE,GAAG,uBAAxE,EAAE,EAAgH,CAClI;AAAA,eAJO,EAAE,QAKZ,CACD;AAAA,WACH;AAAA,SAEJ;AAAA,OACF;AAAA,KAEJ;AAEJ;AAIA,IAAM,gBAAgB;AAAA,EACpB,EAAE,IAAI,OAAO,OAAO,MAAM;AAAA,EAC1B,EAAE,IAAI,OAAO,OAAO,MAAM;AAAA,EAC1B,EAAE,IAAI,UAAU,OAAO,SAAS;AAAA,EAChC,EAAE,IAAI,QAAQ,OAAO,OAAO;AAC9B;AASO,SAAS,aAAa,EAAE,OAAO,SAAS,GAAsB;AACnE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,eAAe,gBAAgB,MAAM,QAAQ,KAAK,CAAC;AACzD,QAAM,WAAW,cAAc,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,KAAK,cAAc,CAAC;AAE7E,SACE,qBAAC,SAAI,KAAK,cAAc,WAAU,wBAChC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;AAAA,QAChC,OAAM;AAAA,QACN,WAAU;AAAA,QAEV;AAAA,8BAAC,gBAAa,WAAU,qCAAoC;AAAA,UAC5D,oBAAC,UAAM,mBAAS,OAAM;AAAA,UACtB,oBAAC,eAAY,WAAU,qCAAoC;AAAA;AAAA;AAAA,IAC7D;AAAA,IACC,QACC,oBAAC,SAAI,WAAU,oHACZ,wBAAc,IAAI,CAAC,MAClB;AAAA,MAAC;AAAA;AAAA,QAEC,MAAK;AAAA,QACL,SAAS,MAAM;AACb,mBAAS,EAAE,EAAE;AACb,kBAAQ,KAAK;AAAA,QACf;AAAA,QACA,WAAW,8EACT,EAAE,OAAO,QAAQ,8BAA8B,oBACjD;AAAA,QAEC,YAAE;AAAA;AAAA,MAVE,EAAE;AAAA,IAWT,CACD,GACH;AAAA,KAEJ;AAEJ;AAiCO,SAAS,WAAW,EAAE,KAAK,QAAQ,GAAoB;AAC5D,SACE,qBAAC,SAAI,WAAU,4GACb;AAAA,yBAAC,SAAI,WAAU,4DACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,iCACT,IAAI,WAAW,YAAY,kBAAkB,IAAI,WAAW,UAAU,eAAe,cACvF;AAAA;AAAA,MACF;AAAA,MACA,qBAAC,SAAI,WAAU,kBACb;AAAA,4BAAC,OAAE,WAAU,kCAAkC,cAAI,OAAM;AAAA,QACzD,oBAAC,OAAE,WAAU,wDAAwD,cAAI,UAAS;AAAA,SACpF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,cAAW;AAAA,UACX,WAAU;AAAA,UAEV,8BAAC,SAAI,WAAU,WAAU,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,eAAW,MAC9H,8BAAC,UAAK,GAAE,wBAAuB,GACjC;AAAA;AAAA,MACF;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,wCACZ;AAAA,UAAI,MAAM,WAAW,KACpB,oBAAC,OAAE,WAAU,iCAAgC,oCAAsB;AAAA,MAEpE,IAAI,MAAM,IAAI,CAAC,MAAM,MACpB,qBAAC,SAAY,WAAU,oDACrB;AAAA,6BAAC,SAAI,WAAU,mEACb;AAAA,8BAAC,UAAK,WAAW,yBAAyB,KAAK,WAAW,UAAU,iBAAiB,uBAAuB,IACzG,eAAK,WAAW,UAAU,WAAM,KACnC;AAAA,UACA,oBAAC,UAAK,WAAU,6CAA6C,eAAK,OAAM;AAAA,UACxE,oBAAC,UAAK,WAAU,iDACb,cAAI,KAAK,KAAK,EAAE,EAAE,mBAAmB,GACxC;AAAA,WACF;AAAA,QACC,KAAK,UACJ,oBAAC,SAAI,WAAU,oHACZ,eAAK,QACR;AAAA,WAbM,CAeV,CACD;AAAA,OACH;AAAA,IACA,oBAAC,OAAE,WAAU,yEAAwE,4DAErF;AAAA,KACF;AAEJ;AAeO,SAAS,kBAAkB,MAAuD;AACvF,QAAM,UAAU,KAAK;AACrB,MAAI,CAAC,SAAS,MAAM,QAAQ,QAAQ,WAAW,yBAAyB,CAAC,QAAQ,OAAO,WAAY,QAAO;AAC3G,SAAO,EAAE,YAAY,QAAQ,OAAO,WAAW;AACjD;AAkCA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE,oBAAC,SAAI,WAAU,4BACZ,oBAAU,IAAI,CAAC,OAAO;AACrB,UAAM,UAAU,GAAG,WAAW,SAAS,kBAAkB,EAAE,IAAI;AAC/D,QAAI,SAAS;AACX,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UAEV;AAAA,gCAAC,UAAK,WAAU,wBAAuB,oBAAC;AAAA,YACxC,oBAAC,UAAK,WAAU,eAAe,aAAG,MAAK;AAAA,YACvC,oBAAC,UAAK,WAAU,cAAa,+BAAiB;AAAA,YAC7C,YACC,qBAAC,UAAK,WAAU,uCACd;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAS,MAAM,SAAS,UAAU,QAAQ,YAAY,GAAG,EAAE;AAAA,kBAC3D,WAAU;AAAA,kBACX;AAAA;AAAA,cAED;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAS,MAAM,SAAS,SAAS,QAAQ,YAAY,GAAG,EAAE;AAAA,kBAC1D,WAAU;AAAA,kBACX;AAAA;AAAA,cAED;AAAA,eACF;AAAA;AAAA;AAAA,QAtBG,GAAG;AAAA,MAwBV;AAAA,IAEJ;AACA,UAAM,MAAM,UAAU,WAAW;AACjC,WACE;AAAA,MAAC;AAAA;AAAA,QAEE,GAAI,UAAU,EAAE,MAAM,UAAmB,SAAS,MAAM,QAAQ,EAAE,EAAE,IAAI,CAAC;AAAA,QAC1E,WAAW,uEACT,GAAG,WAAW,YACV,qCACA,GAAG,WAAW,UACZ,+BACA,gCACR,IAAI,UAAU,6DAA6D,EAAE;AAAA,QAE7E;AAAA,8BAAC,UAAK,WAAU,wBAAwB,aAAG,WAAW,YAAY,WAAM,GAAG,WAAW,UAAU,WAAM,UAAI;AAAA,UAC1G,oBAAC,UAAK,WAAU,eAAe,aAAG,MAAK;AAAA,UACvC,oBAAC,UAAK,WAAU,cAAc,aAAG,WAAW,YAAY,kBAAa,GAAG,WAAW,UAAU,WAAW,QAAO;AAAA;AAAA;AAAA,MAZ1G,GAAG;AAAA,IAaV;AAAA,EAEJ,CAAC,GACH;AAEJ;AAQO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,SAAS,CAAC;AAAA,EACV;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,aAAa,mBAAmB,CAAC,YAAoB,oBAAC,OAAE,WAAU,uBAAuB,mBAAQ;AACvG,QAAM,aAAa,SAAS,SAAS,SAAS,CAAC,GAAG,SAAS;AAC3D,SACE,iCACG;AAAA,aAAS;AAAA,MAAI,CAAC,QACb,IAAI,SAAS,SACX,oBAAC,SAAiB,WAAU,sCAC1B,+BAAC,SAAI,WAAU,6BACb;AAAA,4BAAC,OAAE,WAAU,8FACV,qBACH;AAAA,QACA,oBAAC,SAAI,WAAU,iFACb,8BAAC,OAAE,WAAU,uBAAuB,cAAI,SAAQ,GAClD;AAAA,SACF,KARQ,IAAI,EASd,IAEA,qBAAC,SAAiB,WAAU,sCAC1B;AAAA,6BAAC,SAAI,WAAU,qFACb;AAAA,8BAAC,UAAK,WAAU,2BAA2B,sBAAW;AAAA,UACrD,IAAI,aAAa,oBAAC,UAAK,WAAU,yBAAyB,cAAI,WAAU;AAAA,UACxE,sBAAsB,GAAG,KAAK,oBAAC,UAAM,gCAAsB,GAAG,GAAE;AAAA,UAChE,gBAAgB,KAAK,MAAM,KAAK,oBAAC,UAAM,0BAAgB,KAAK,MAAM,GAAE;AAAA,WACvE;AAAA,QACC,IAAI,aACH,qBAAC,aAAQ,WAAU,iEACjB;AAAA,8BAAC,aAAQ,WAAU,wEAAuE,4BAAS;AAAA,UACnG,oBAAC,SAAI,WAAU,6DAA6D,cAAI,WAAU;AAAA,WAC5F;AAAA,QAEF,oBAAC,SAAI,WAAU,4BAA4B,qBAAW,IAAI,OAAO,GAAE;AAAA,QAClE,IAAI,aAAa,IAAI,UAAU,SAAS,KACvC;AAAA,UAAC;AAAA;AAAA,YACC,WAAW,IAAI;AAAA,YACf;AAAA,YACA,SAAS,kBAAkB,CAAC,OAAO,gBAAgB,IAAI,GAAG,IAAI;AAAA;AAAA,QAChE;AAAA,QAED,eAAe,GAAG;AAAA,WArBX,IAAI,EAsBd;AAAA,IAEJ;AAAA,IACC,WAAW,cACV,qBAAC,SAAI,WAAU,sCACb;AAAA,0BAAC,OAAE,WAAU,mFAAmF,sBAAW;AAAA,MAC3G,qBAAC,SAAI,WAAU,2DACb;AAAA,4BAAC,SAAI,WAAU,wBAAuB,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAW,MACrH,8BAAC,UAAK,GAAE,+BAA8B,eAAc,SAAQ,GAC9D;AAAA,QAAM;AAAA,SAER;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "packageManager": "pnpm@10.33.4",
5
5
  "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent\u2192app tool side channel, integration-hub client, per-workspace billing, and crypto \u2014 composed over the Tangle agent substrate through typed seams.",
6
6
  "keywords": [
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/stream/stream-normalizer.ts","../src/stream/turn-identity.ts"],"sourcesContent":["export type JsonRecord = Record<string, unknown>\n\nexport interface StreamEvent {\n type: string\n data?: JsonRecord\n}\n\nexport function asRecord(value: unknown): JsonRecord | undefined {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? value as JsonRecord\n : undefined\n}\n\nexport function asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nexport function resolveToolId(part: JsonRecord): string {\n return String(\n part.id ??\n part.callID ??\n part.callId ??\n part.toolUseId ??\n part.toolCallId ??\n part.tool ??\n part.name ??\n `tool-${Date.now()}`,\n )\n}\n\nexport function resolveToolName(part: JsonRecord): string {\n return String(part.tool ?? part.name ?? 'tool')\n}\n\nexport function normalizeTime(value: unknown): JsonRecord | undefined {\n const record = asRecord(value)\n if (!record) return undefined\n\n const start = Number(record.start ?? record.startedAt ?? record.started_at)\n const end = Number(record.end ?? record.completedAt ?? record.completed_at)\n if (!Number.isFinite(start) && !Number.isFinite(end)) return undefined\n\n return {\n start: Number.isFinite(start) ? start : undefined,\n end: Number.isFinite(end) ? end : undefined,\n }\n}\n\nexport function normalizeToolEvent(event: StreamEvent): StreamEvent {\n if (event.type === 'tool_call' || event.type === 'tool.call') {\n const data = event.data ?? {}\n return {\n type: 'message.part.updated',\n data: {\n part: {\n type: 'tool',\n id: data.id ?? data.callId ?? data.callID ?? data.name,\n tool: data.name ?? data.tool ?? 'tool',\n input: data.arguments ?? data.input,\n status: 'running',\n },\n },\n }\n }\n\n if (event.type === 'tool_result' || event.type === 'tool.result') {\n const data = event.data ?? {}\n const error = asString(data.error)\n return {\n type: 'message.part.updated',\n data: {\n part: {\n type: 'tool',\n id: data.id ?? data.callId ?? data.callID ?? data.name,\n tool: data.name ?? data.tool ?? 'tool',\n output: data.output,\n error,\n status: error ? 'error' : 'completed',\n },\n },\n }\n }\n\n return event\n}\n\nexport function normalizePersistedPart(rawPart: JsonRecord): JsonRecord | null {\n const type = String(rawPart.type ?? '')\n\n if (type === 'text') {\n return {\n type: 'text',\n text: asString(rawPart.text) ?? asString(rawPart.content) ?? '',\n }\n }\n\n if (type === 'reasoning') {\n return {\n type: 'reasoning',\n text: asString(rawPart.text) ?? asString(rawPart.content) ?? '',\n time: normalizeTime(rawPart.time),\n }\n }\n\n if (type === 'tool') {\n const state = asRecord(rawPart.state)\n const output = state?.output ?? rawPart.output\n const error = asString(state?.error ?? rawPart.error)\n const status =\n state?.status === 'completed' || rawPart.status === 'completed'\n ? 'completed'\n : state?.status === 'error' || rawPart.status === 'error' || error\n ? 'error'\n : output !== undefined\n ? 'completed'\n : 'running'\n\n return {\n type: 'tool',\n id: resolveToolId(rawPart),\n tool: resolveToolName(rawPart),\n callID:\n rawPart.callID != null || rawPart.callId != null\n ? String(rawPart.callID ?? rawPart.callId)\n : undefined,\n state: {\n status,\n input: state?.input ?? rawPart.input,\n output,\n error,\n metadata: asRecord(state?.metadata) ?? asRecord(rawPart.metadata),\n time: normalizeTime(state?.time ?? rawPart.time),\n },\n }\n }\n\n return null\n}\n\nexport function getPartKey(part: JsonRecord): string {\n const type = String(part.type ?? 'unknown')\n if (type === 'tool') {\n return `tool:${resolveToolId(part)}`\n }\n\n if (type === 'reasoning') {\n return `reasoning:${String(part.id ?? part.partId ?? part.index ?? 'current')}`\n }\n\n return `text:${String(part.id ?? part.partId ?? part.index ?? 'current')}`\n}\n\nexport function mergePersistedPart(existing: JsonRecord | undefined, incoming: JsonRecord, delta?: string): JsonRecord {\n const type = String(incoming.type ?? '')\n if (!existing) {\n if (type === 'text' && delta) {\n return { type: 'text', text: delta }\n }\n return incoming\n }\n\n if (type === 'text' && String(existing.type ?? '') === 'text') {\n return {\n ...existing,\n ...incoming,\n text: delta ? `${String(existing.text ?? '')}${delta}` : String(incoming.text ?? ''),\n }\n }\n\n if (type === 'reasoning' && String(existing.type ?? '') === 'reasoning') {\n const existingText = String(existing.text ?? '')\n const incomingText = String(incoming.text ?? '')\n return {\n ...existing,\n ...incoming,\n text: delta && incomingText === existingText ? `${existingText}${delta}` : incomingText || existingText,\n time: incoming.time ?? existing.time,\n }\n }\n\n if (type === 'tool' && String(existing.type ?? '') === 'tool') {\n return {\n ...existing,\n ...incoming,\n state: {\n ...(asRecord(existing.state) ?? {}),\n ...(asRecord(incoming.state) ?? {}),\n time: asRecord(incoming.state)?.time ?? asRecord(existing.state)?.time,\n },\n }\n }\n\n return incoming\n}\n\nexport function finalizeAssistantParts(\n partOrder: string[],\n partMap: Map<string, JsonRecord>,\n finalText: string,\n): JsonRecord[] {\n const parts = partOrder\n .map((key) => partMap.get(key))\n .filter((part): part is JsonRecord => Boolean(part))\n\n if (!parts.some((part) => String(part.type ?? '') === 'text')) {\n if (finalText.trim()) {\n parts.push({ type: 'text', text: finalText })\n }\n return parts\n }\n\n return parts.map((part) => {\n if (String(part.type ?? '') !== 'text') return part\n return {\n ...part,\n text: finalText || String(part.text ?? ''),\n }\n })\n}\n\nexport function encodeEvent(encoder: TextEncoder, event: StreamEvent): Uint8Array {\n return encoder.encode(`${JSON.stringify(event)}\\n`)\n}\n","import type { JsonRecord } from './stream-normalizer'\n\nexport interface PersistedChatMessageForTurn {\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n parts: Array<Record<string, unknown>> | null\n}\n\nexport interface ResolvedChatTurn {\n turnIndex: number\n shouldInsertUserMessage: boolean\n priorMessages: PersistedChatMessageForTurn[]\n userParts: JsonRecord[]\n}\n\nexport function normalizeClientTurnId(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'string') throw new Error('turnId must be a string')\n const trimmed = value.trim()\n if (!trimmed) throw new Error('turnId must not be blank')\n if (trimmed.length > 160) throw new Error('turnId is too long')\n if (!/^[A-Za-z0-9:_-]+$/.test(trimmed)) {\n throw new Error('turnId contains unsupported characters')\n }\n return trimmed\n}\n\nexport function buildUserTextParts(text: string, turnId: string | undefined): JsonRecord[] {\n const part: JsonRecord = { type: 'text', text }\n if (turnId) part.turnId = turnId\n return [part]\n}\n\nexport function messageHasTurnId(message: PersistedChatMessageForTurn, turnId: string): boolean {\n for (const part of message.parts ?? []) {\n if (part && typeof part === 'object' && String(part.turnId ?? '') === turnId) {\n return true\n }\n }\n return false\n}\n\nexport function resolveChatTurn(input: {\n existingMessages: PersistedChatMessageForTurn[]\n userContent: string\n turnId?: string\n}): ResolvedChatTurn {\n const { existingMessages, userContent, turnId } = input\n const reusableIndex = findReusableUserMessageIndex(existingMessages, userContent, turnId)\n if (reusableIndex >= 0) {\n return {\n turnIndex: countUserMessages(existingMessages.slice(0, reusableIndex)),\n shouldInsertUserMessage: false,\n priorMessages: existingMessages.slice(0, reusableIndex),\n userParts: buildUserTextParts(userContent, turnId),\n }\n }\n\n return {\n turnIndex: countUserMessages(existingMessages),\n shouldInsertUserMessage: true,\n priorMessages: existingMessages,\n userParts: buildUserTextParts(userContent, turnId),\n }\n}\n\nfunction findReusableUserMessageIndex(\n messages: PersistedChatMessageForTurn[],\n userContent: string,\n turnId: string | undefined,\n): number {\n if (turnId) {\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n const message = messages[index]\n if (message?.role === 'user' && messageHasTurnId(message, turnId)) return index\n }\n }\n\n const latest = messages.at(-1)\n if (latest?.role === 'user' && latest.content === userContent) {\n return messages.length - 1\n }\n\n return -1\n}\n\nfunction countUserMessages(messages: PersistedChatMessageForTurn[]): number {\n return messages.filter((message) => message.role === 'user').length\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tools/errors.ts","../src/tools/openai.ts","../src/tools/dispatch.ts","../src/tools/runtime.ts"],"sourcesContent":["/** A correctable bad-input error a tool handler throws; the HTTP layer maps it\n * to a 4xx with the code, the runtime layer to a failed tool_result. So the\n * agent learns the call failed and can correct, instead of a silent success. */\nexport class ToolInputError extends Error {\n constructor(\n public code: string,\n message: string,\n public status = 400,\n ) {\n super(message)\n this.name = 'ToolInputError'\n }\n}\n","import type { AppToolTaxonomy } from './types'\n\n/** The four canonical app-tool names. Stable identifiers the model calls in\n * both the sandbox (MCP server name) and runtime (function-tool name) paths. */\nexport const APP_TOOL_NAMES = ['submit_proposal', 'schedule_followup', 'render_ui', 'add_citation'] as const\nexport type AppToolName = (typeof APP_TOOL_NAMES)[number]\n\nconst NAME_SET = new Set<string>(APP_TOOL_NAMES)\nexport function isAppToolName(name: string): name is AppToolName {\n return NAME_SET.has(name)\n}\n\n/** A minimal OpenAI Chat Completions function-tool shape — structurally\n * compatible with `@tangle-network/agent-runtime`'s `OpenAIChatTool` without\n * importing it (keeps this package runtime-free). */\nexport interface OpenAIFunctionTool {\n type: 'function'\n function: {\n name: string\n description: string\n parameters: Record<string, unknown>\n }\n}\n\n/**\n * Build the four app tools in OpenAI function-tool shape. `submit_proposal`'s\n * `type` enum is the product's {@link AppToolTaxonomy.proposalTypes}; the other\n * three are fixed. Pass the result to the agent-runtime backend's `tools`.\n */\nexport function buildAppToolOpenAITools(taxonomy: AppToolTaxonomy): OpenAIFunctionTool[] {\n return [\n {\n type: 'function',\n function: {\n name: 'submit_proposal',\n description:\n 'Route a regulated or state-changing action to a human for approval (a recommendation, contacting/soliciting a contact, outreach, a record/account change, scheduling). Queues it for a named certified human to approve before it executes.',\n parameters: {\n type: 'object',\n properties: {\n type: { type: 'string', enum: [...taxonomy.proposalTypes] },\n title: { type: 'string', description: 'Short label for the approval queue.' },\n description: { type: 'string', description: 'The full drafted message/recommendation, with sources.' },\n },\n required: ['type', 'title'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'schedule_followup',\n description: 'Register a dated cadence step (a reminder, chase, or check-in) on the follow-up calendar. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n dueDate: { type: 'string', description: 'ISO date YYYY-MM-DD.' },\n priority: { type: 'string', enum: ['low', 'medium', 'high'] },\n },\n required: ['title', 'dueDate'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'render_ui',\n description: 'Show a generated view live in the workspace. Validates the OpenUI JSON and persists the artifact. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n schema: { type: 'object', description: 'The OpenUI JSON object.' },\n },\n required: ['title', 'schema'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'add_citation',\n description: 'Anchor a grounding reference: the exact quote from a file backing a figure or claim. Verifies the quote appears in the file. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'The vault file path.' },\n quote: { type: 'string', description: 'The exact text from it.' },\n },\n required: ['path', 'quote'],\n },\n },\n },\n ]\n}\n","import { ToolInputError } from './errors'\nimport { isAppToolName } from './openai'\nimport type {\n AppToolContext,\n AppToolHandlers,\n AppToolOutcome,\n AppToolProducedEvent,\n AppToolTaxonomy,\n} from './types'\n\nexport interface DispatchOptions {\n handlers: AppToolHandlers\n taxonomy: AppToolTaxonomy\n /** Called at the real side-effect site for proposals (proposal_created) and\n * generated views (artifact) so a consumer's completion oracle credits\n * persisted state. Omit when produced state isn't tracked. */\n onProduced?: (event: AppToolProducedEvent) => void\n}\n\n/**\n * The ONE place an app-tool call is validated, dispatched to the product's\n * handler, and turned into an {@link AppToolOutcome} + produced events. Shared\n * by the HTTP route layer and the agent-runtime executor so both paths apply\n * identical validation and identical side effects. A {@link ToolInputError}\n * (bad input the agent can correct) and any other throw both become\n * `{ ok: false }` — a tool call never silently \"succeeds\" without its effect.\n */\nexport async function dispatchAppTool(\n toolName: string,\n rawArgs: Record<string, unknown>,\n ctx: AppToolContext,\n opts: DispatchOptions,\n): Promise<AppToolOutcome> {\n try {\n if (!isAppToolName(toolName)) {\n return { ok: false, code: 'unknown_tool', message: `${toolName} is not an app tool.` }\n }\n\n if (toolName === 'submit_proposal') {\n const type = String(rawArgs.type ?? '').trim()\n const title = String(rawArgs.title ?? '').trim()\n if (!type || !opts.taxonomy.proposalTypes.includes(type)) {\n return { ok: false, code: 'invalid_type', message: `type must be one of: ${opts.taxonomy.proposalTypes.join(', ')}.` }\n }\n if (!title) return { ok: false, code: 'missing_title', message: 'title is required.' }\n const description = rawArgs.description == null ? null : String(rawArgs.description)\n const r = await opts.handlers.submitProposal({ type, title, description }, ctx)\n const regulated = opts.taxonomy.regulatedTypes.includes(type)\n // Pass the handler's result through: products with immediate-execute\n // proposal types return status 'executed' plus their own fields\n // (e.g. datasetId) — the model must see what actually happened, not a\n // hard-coded \"queued for approval\".\n const { proposalId, deduped, status, ...extra } = r\n const effectiveStatus = status ?? 'queued_for_approval'\n opts.onProduced?.({\n type: 'proposal_created',\n proposalId,\n title,\n status: effectiveStatus === 'executed' ? 'executed' : 'pending',\n })\n return { ok: true, result: { ...extra, status: effectiveStatus, proposalId, deduped, regulated } }\n }\n\n if (toolName === 'schedule_followup') {\n const r = await opts.handlers.scheduleFollowup(\n { title: String(rawArgs.title ?? ''), dueDate: String(rawArgs.dueDate ?? ''), priority: rawArgs.priority as string | undefined },\n ctx,\n )\n return { ok: true, result: { followupId: r.id, dueDate: r.dueDate, deduped: r.deduped } }\n }\n\n if (toolName === 'render_ui') {\n const r = await opts.handlers.renderUi({ title: String(rawArgs.title ?? ''), schema: rawArgs.schema }, ctx)\n opts.onProduced?.({ type: 'artifact', path: r.path, content: r.content })\n return { ok: true, result: { path: r.path } }\n }\n\n // add_citation\n const r = await opts.handlers.addCitation(\n { path: String(rawArgs.path ?? ''), quote: String(rawArgs.quote ?? ''), label: rawArgs.label as string | undefined },\n ctx,\n )\n return { ok: true, result: { citationId: r.citationId, path: r.path } }\n } catch (err) {\n if (err instanceof ToolInputError) return { ok: false, code: err.code, message: err.message, status: err.status }\n return { ok: false, code: 'app_tool_error', message: err instanceof Error ? err.message : String(err), status: 500 }\n }\n}\n\n/** HTTP status for a failed outcome — the handler's `ToolInputError.status`\n * when present, else 400 for a validation reject. */\nexport function outcomeStatus(outcome: Extract<AppToolOutcome, { ok: false }>): number {\n return outcome.status ?? 400\n}\n","import { dispatchAppTool, type DispatchOptions } from './dispatch'\nimport type { AppToolContext, AppToolOutcome } from './types'\n\n/** Executes an app-tool call the model emits on the agent-runtime chat path.\n * Plug into `runChatThroughRuntime({ appToolExecutor })` (or any loop that\n * dispatches function tool_calls). */\nexport type AppToolRuntimeExecutor = (call: {\n toolName: string\n args: Record<string, unknown>\n}) => Promise<AppToolOutcome>\n\nexport interface RuntimeExecutorOptions extends DispatchOptions {\n /** The trusted per-turn context — supplied directly (not from headers), since\n * the runtime path has no HTTP request. */\n ctx: AppToolContext\n}\n\n/**\n * Build the runtime executor for one turn. The agent-runtime backend must also\n * advertise the tools (`buildAppToolOpenAITools(taxonomy)` on the backend's\n * `tools`) for the model to call them; this executor fulfils each call against\n * the product's handlers and emits produced events via `opts.onProduced`.\n */\nexport function createAppToolRuntimeExecutor(opts: RuntimeExecutorOptions): AppToolRuntimeExecutor {\n return ({ toolName, args }) => dispatchAppTool(toolName, args, opts.ctx, opts)\n}\n"],"mappings":"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","names":["r"]}