@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.
- package/dist/{chunk-HZZD3ZYD.js → chunk-OLCVUGGI.js} +2 -2
- package/dist/{chunk-JANT2G2E.js → chunk-QAQBR6KQ.js} +10 -3
- package/dist/chunk-QAQBR6KQ.js.map +1 -0
- package/dist/{chunk-GMFPCCQZ.js → chunk-SDOT7RNB.js} +152 -2
- package/dist/chunk-SDOT7RNB.js.map +1 -0
- package/dist/{chunk-EO4IGDQD.js → chunk-TH2AOJJM.js} +2 -2
- package/dist/eval/index.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +16 -4
- package/dist/preset-cloudflare/index.d.ts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +2 -2
- package/dist/stream/index.d.ts +88 -1
- package/dist/stream/index.js +13 -1
- package/dist/tools/index.d.ts +10 -2
- package/dist/tools/index.js +2 -2
- package/dist/{types-CTOaTNtU.d.ts → types-By4B3K37.d.ts} +4 -0
- package/dist/web-react/index.d.ts +46 -2
- package/dist/web-react/index.js +113 -16
- package/dist/web-react/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-GMFPCCQZ.js.map +0 -1
- package/dist/chunk-JANT2G2E.js.map +0 -1
- /package/dist/{chunk-HZZD3ZYD.js.map → chunk-OLCVUGGI.js.map} +0 -0
- /package/dist/{chunk-EO4IGDQD.js.map → chunk-TH2AOJJM.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dispatchAppTool,
|
|
3
3
|
outcomeStatus
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
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
|
-
|
|
101
|
-
|
|
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-
|
|
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-
|
|
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":"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","names":[]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
buildAppToolOpenAITools,
|
|
3
3
|
createAppToolRuntimeExecutor,
|
|
4
4
|
isAppToolName
|
|
5
|
-
} from "./chunk-
|
|
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-
|
|
426
|
+
//# sourceMappingURL=chunk-TH2AOJJM.js.map
|
package/dist/eval/index.d.ts
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -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-
|
|
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.
|
package/dist/runtime/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
runAppToolLoop,
|
|
9
9
|
streamAppToolLoop,
|
|
10
10
|
toLoopEvents
|
|
11
|
-
} from "../chunk-
|
|
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-
|
|
25
|
+
import "../chunk-QAQBR6KQ.js";
|
|
26
26
|
export {
|
|
27
27
|
DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
|
|
28
28
|
DEFAULT_TANGLE_ROUTER_BASE_URL,
|
package/dist/stream/index.d.ts
CHANGED
|
@@ -36,4 +36,91 @@ declare function resolveChatTurn(input: {
|
|
|
36
36
|
turnId?: string;
|
|
37
37
|
}): ResolvedChatTurn;
|
|
38
38
|
|
|
39
|
-
|
|
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 };
|
package/dist/stream/index.js
CHANGED
|
@@ -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-
|
|
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
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as AppToolContext, f as AppToolTaxonomy, c as AppToolHandlers, e as AppToolProducedEvent, d as AppToolOutcome } from '../types-
|
|
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-
|
|
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. */
|
package/dist/tools/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
handleAppToolRequest,
|
|
9
9
|
readToolArgs,
|
|
10
10
|
verifyCapabilityToken
|
|
11
|
-
} from "../chunk-
|
|
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-
|
|
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 };
|
package/dist/web-react/index.js
CHANGED
|
@@ -199,19 +199,105 @@ function EffortPicker({ value, onChange }) {
|
|
|
199
199
|
)) })
|
|
200
200
|
] });
|
|
201
201
|
}
|
|
202
|
-
function
|
|
203
|
-
return /* @__PURE__ */
|
|
204
|
-
"div",
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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(
|
|
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":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-app",
|
|
3
|
-
"version": "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"]}
|
|
File without changes
|
|
File without changes
|