@tangle-network/agent-app 0.14.0 → 0.15.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.
@@ -2,7 +2,7 @@
2
2
  import { verifyCompletion, extractProducedState, weightedComposite, createLlmCorrectnessChecker } from "@tangle-network/agent-eval";
3
3
  function producedFromToolEvents(events) {
4
4
  return events.map(
5
- (e) => e.type === "proposal_created" ? { type: "proposal_created", proposalId: e.proposalId, title: e.title, status: e.status } : { type: "artifact", artifactId: `vault:${e.path}`, name: e.path, uri: `vault://${e.path}`, mimeType: "text/markdown", content: e.content }
5
+ (e) => e.type === "proposal_created" ? { type: "proposal_created", proposalId: e.proposalId, title: e.title, status: e.status, content: e.content } : { type: "artifact", artifactId: `vault:${e.path}`, name: e.path, uri: `vault://${e.path}`, mimeType: "text/markdown", content: e.content }
6
6
  );
7
7
  }
8
8
  var STOPWORDS = /* @__PURE__ */ new Set(["the", "a", "an", "and", "or", "for", "to", "of", "in", "on", "with", "review", "update", "new", "proposed"]);
@@ -29,4 +29,4 @@ export {
29
29
  weightedComposite,
30
30
  createLlmCorrectnessChecker
31
31
  };
32
- //# sourceMappingURL=chunk-4NXVI7PW.js.map
32
+ //# sourceMappingURL=chunk-FA4XR66Y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/eval/index.ts"],"sourcesContent":["/**\n * Eval — the app-shell BRIDGE to `@tangle-network/agent-eval`, not a reimpl.\n *\n * The completion/scoring ENGINE lives in agent-eval (a peer dependency):\n * `verifyCompletion`, `extractProducedState`, `weightedComposite`,\n * `createLlmCorrectnessChecker`, and the `CompletionRequirement` / `TaskGold` /\n * `ProducedState` types — all re-exported here so a consumer has one import\n * root. This module adds only what agent-eval doesn't have and what is\n * app-shell-specific:\n *\n * 1. {@link producedFromToolEvents} — the bridge: turn the structured app-tool\n * side channel's `AppToolProducedEvent`s (from a tool runtime executor's\n * `onProduced`) into the `RuntimeEventLike`s agent-eval's\n * `extractProducedState` consumes. This is the one piece that knows about\n * the app-tool channel, so it belongs here, not in the engine.\n * 2. {@link createTokenRecallChecker} — a deterministic, no-LLM\n * `CorrectnessChecker` (agent-eval ships only the LLM one). For apps/tests\n * that gate completion without a judge call.\n *\n * Full campaigns (persona simulation, traces, scorecards, held-out gates) are\n * agent-eval's `runEvalCampaign` / `AgentDriver` / `BenchmarkRunner` — use them\n * directly; this module composes with them.\n */\nimport type { RuntimeEventLike, CompletionRequirement } from '@tangle-network/agent-eval'\nimport type { AppToolProducedEvent } from '../tools/types'\n\n// Re-export the engine so consumers import completion + scoring from one place.\nexport { verifyCompletion, extractProducedState, weightedComposite, createLlmCorrectnessChecker } from '@tangle-network/agent-eval'\nexport type {\n CompletionRequirement,\n TaskGold,\n ProducedState,\n SatisfiedBy,\n CompletionVerdict,\n CorrectnessChecker,\n RuntimeEventLike,\n} from '@tangle-network/agent-eval'\n\n/**\n * Bridge the app-tool side channel's produced events into the runtime-event\n * shape agent-eval's `extractProducedState` reads. Pipe it:\n * `verifyCompletion(taskGold, extractProducedState(producedFromToolEvents(events)), checker)`\n */\nexport function producedFromToolEvents(events: readonly AppToolProducedEvent[]): RuntimeEventLike[] {\n return events.map((e) =>\n e.type === 'proposal_created'\n ? { type: 'proposal_created', proposalId: e.proposalId, title: e.title, status: e.status, content: e.content }\n : { type: 'artifact', artifactId: `vault:${e.path}`, name: e.path, uri: `vault://${e.path}`, mimeType: 'text/markdown', content: e.content },\n )\n}\n\nconst STOPWORDS = new Set(['the', 'a', 'an', 'and', 'or', 'for', 'to', 'of', 'in', 'on', 'with', 'review', 'update', 'new', 'proposed'])\n\n/**\n * A deterministic `CorrectnessChecker` (agent-eval exports only\n * `createLlmCorrectnessChecker`). A produced item fulfils a requirement when\n * its content is substantive and recalls ≥ `minRecall` of the requirement\n * title's significant tokens. No network — the default gate for apps/tests\n * without an LLM judge. Pass to `verifyCompletion` as the checker.\n */\nexport function createTokenRecallChecker(opts: { minRecall?: number; minContentLength?: number } = {}): (\n requirement: CompletionRequirement,\n content: string,\n) => Promise<{ correct: boolean; reason: string }> {\n const minRecall = opts.minRecall ?? 0.5\n const minLen = opts.minContentLength ?? 120\n return async (requirement, content) => {\n const body = content.trim()\n if (body.length < minLen) return { correct: false, reason: `content too thin (${body.length} chars) to be the deliverable` }\n const tokens = requirement.title.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2 && !STOPWORDS.has(t))\n if (tokens.length === 0) return { correct: true, reason: 'requirement title has no significant tokens — structural match accepted' }\n const lower = body.toLowerCase()\n const hits = tokens.filter((t) => lower.includes(t)).length\n const recall = hits / tokens.length\n return recall >= minRecall\n ? { correct: true, reason: `content recalls ${hits}/${tokens.length} requirement tokens` }\n : { correct: false, reason: `content recalls only ${hits}/${tokens.length} requirement tokens` }\n }\n}\n"],"mappings":";AA2BA,SAAS,kBAAkB,sBAAsB,mBAAmB,mCAAmC;AAgBhG,SAAS,uBAAuB,QAA6D;AAClG,SAAO,OAAO;AAAA,IAAI,CAAC,MACjB,EAAE,SAAS,qBACP,EAAE,MAAM,oBAAoB,YAAY,EAAE,YAAY,OAAO,EAAE,OAAO,QAAQ,EAAE,QAAQ,SAAS,EAAE,QAAQ,IAC3G,EAAE,MAAM,YAAY,YAAY,SAAS,EAAE,IAAI,IAAI,MAAM,EAAE,MAAM,KAAK,WAAW,EAAE,IAAI,IAAI,UAAU,iBAAiB,SAAS,EAAE,QAAQ;AAAA,EAC/I;AACF;AAEA,IAAM,YAAY,oBAAI,IAAI,CAAC,OAAO,KAAK,MAAM,OAAO,MAAM,OAAO,MAAM,MAAM,MAAM,MAAM,QAAQ,UAAU,UAAU,OAAO,UAAU,CAAC;AAShI,SAAS,yBAAyB,OAA0D,CAAC,GAGjD;AACjD,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,SAAS,KAAK,oBAAoB;AACxC,SAAO,OAAO,aAAa,YAAY;AACrC,UAAM,OAAO,QAAQ,KAAK;AAC1B,QAAI,KAAK,SAAS,OAAQ,QAAO,EAAE,SAAS,OAAO,QAAQ,qBAAqB,KAAK,MAAM,gCAAgC;AAC3H,UAAM,SAAS,YAAY,MAAM,YAAY,EAAE,MAAM,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;AAClH,QAAI,OAAO,WAAW,EAAG,QAAO,EAAE,SAAS,MAAM,QAAQ,+EAA0E;AACnI,UAAM,QAAQ,KAAK,YAAY;AAC/B,UAAM,OAAO,OAAO,OAAO,CAAC,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE;AACrD,UAAM,SAAS,OAAO,OAAO;AAC7B,WAAO,UAAU,YACb,EAAE,SAAS,MAAM,QAAQ,mBAAmB,IAAI,IAAI,OAAO,MAAM,sBAAsB,IACvF,EAAE,SAAS,OAAO,QAAQ,wBAAwB,IAAI,IAAI,OAAO,MAAM,sBAAsB;AAAA,EACnG;AACF;","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  buildAppToolOpenAITools,
3
3
  createAppToolRuntimeExecutor,
4
4
  isAppToolName
5
- } from "./chunk-QAQBR6KQ.js";
5
+ } from "./chunk-JZZ6AWF4.js";
6
6
 
7
7
  // src/runtime/model-catalog.ts
8
8
  var PROVIDER_TIER = [
@@ -282,18 +282,20 @@ function createAgentRuntime(opts) {
282
282
  };
283
283
  return {
284
284
  run(userMessage, turn) {
285
- return runAppToolLoop({
285
+ return runToolLoop({
286
286
  systemPrompt: turn.systemPrompt ?? opts.systemPrompt,
287
287
  userMessage,
288
288
  priorMessages: turn.priorMessages,
289
- streamTurn,
289
+ // The awaitable loop consumes only text + tool_call; the app's UI-only
290
+ // reasoning/usage events ride the substrate's `other` channel.
291
+ streamTurn: narrowToToolLoopEvents(streamTurn),
290
292
  executeToolCall: buildExecutor(turn),
291
293
  isExecutableTool,
292
294
  maxToolTurns: opts.maxToolTurns
293
295
  });
294
296
  },
295
297
  stream(userMessage, turn) {
296
- return streamAppToolLoop({
298
+ return streamToolLoop({
297
299
  systemPrompt: turn.systemPrompt ?? opts.systemPrompt,
298
300
  userMessage,
299
301
  priorMessages: turn.priorMessages,
@@ -307,6 +309,15 @@ function createAgentRuntime(opts) {
307
309
  }
308
310
  };
309
311
  }
312
+ function narrowToToolLoopEvents(streamTurn) {
313
+ return (messages) => (async function* () {
314
+ for await (const ev of streamTurn(messages)) {
315
+ if (ev.type === "text") yield { type: "text", text: ev.text };
316
+ else if (ev.type === "tool_call") yield { type: "tool_call", call: ev.call };
317
+ else yield { type: "other", event: ev };
318
+ }
319
+ })();
320
+ }
310
321
 
311
322
  // src/runtime/surface-profile.ts
312
323
  function defineSurfaceKind(opts) {
@@ -403,168 +414,10 @@ function assertSurfaceOverlay(overlay, label) {
403
414
  }
404
415
 
405
416
  // src/runtime/index.ts
406
- function toolCallId(call) {
407
- return call.toolCallId ?? `call_${call.toolName}`;
408
- }
409
- function assistantToolCallMessage(turnText, pending) {
410
- return {
411
- role: "assistant",
412
- content: turnText.trim() || null,
413
- tool_calls: pending.map((call) => ({
414
- id: toolCallId(call),
415
- type: "function",
416
- function: { name: call.toolName, arguments: JSON.stringify(call.args) }
417
- }))
418
- };
419
- }
420
- function toolResultMessage(call, content) {
421
- return { role: "tool", tool_call_id: toolCallId(call), content };
422
- }
423
- var RUNAWAY_BACKSTOP_TURNS = 200;
424
- var STUCK_LOOP_THRESHOLD = 3;
425
- function canonicalCallHash(call) {
426
- const sortedArgs = Object.fromEntries(
427
- Object.entries(call.args).sort(([a], [b]) => a.localeCompare(b))
428
- );
429
- return `${call.toolName}:${JSON.stringify(sortedArgs)}`;
430
- }
431
- function defaultRender(label, outcome) {
432
- if (outcome.ok) return `${label} \u2192 ok: ${JSON.stringify(outcome.result)}`;
433
- return `${label} \u2192 failed (${outcome.code}): ${outcome.message}`;
434
- }
435
- async function runAppToolLoop(opts) {
436
- const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS;
437
- const render = opts.renderResult ?? defaultRender;
438
- const labelFor = opts.labelFor ?? ((c) => c.toolName);
439
- const messages = [
440
- { role: "system", content: opts.systemPrompt },
441
- ...opts.priorMessages ?? [],
442
- { role: "user", content: opts.userMessage }
443
- ];
444
- const toolResults = [];
445
- let finalText = "";
446
- let turns = 0;
447
- let accumulatedCostUsd = 0;
448
- let lastCallHash = null;
449
- let consecutiveCount = 0;
450
- for (let toolTurn = 0; ; toolTurn++) {
451
- turns++;
452
- if (opts.deadlineMs !== void 0 && Date.now() >= opts.deadlineMs) {
453
- return { finalText, toolResults, turns, stopReason: "deadline", cappedOut: true };
454
- }
455
- let turnText = "";
456
- const pending = [];
457
- for await (const ev of opts.streamTurn([...messages])) {
458
- if (ev.type === "text") {
459
- turnText += ev.text;
460
- finalText += ev.text;
461
- } else if (ev.type === "tool_call" && opts.isExecutableTool(ev.call.toolName)) {
462
- pending.push(ev.call);
463
- }
464
- }
465
- if (pending.length === 0) break;
466
- if (toolTurn >= backstop) {
467
- return { finalText, toolResults, turns, stopReason: "backstop", cappedOut: true };
468
- }
469
- messages.push(assistantToolCallMessage(turnText, pending));
470
- for (const call of pending) {
471
- const callHash = canonicalCallHash(call);
472
- if (callHash === lastCallHash) {
473
- consecutiveCount++;
474
- } else {
475
- lastCallHash = callHash;
476
- consecutiveCount = 1;
477
- }
478
- if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {
479
- return { finalText, toolResults, turns, stopReason: "stuck-loop", cappedOut: true };
480
- }
481
- let outcome;
482
- try {
483
- outcome = await opts.executeToolCall(call);
484
- } catch (err) {
485
- outcome = { ok: false, code: "executor_error", message: err instanceof Error ? err.message : String(err) };
486
- }
487
- if (opts.maxCostUsd !== void 0 && opts.costOf !== void 0) {
488
- accumulatedCostUsd += opts.costOf(call, outcome);
489
- if (accumulatedCostUsd >= opts.maxCostUsd) {
490
- const label2 = labelFor(call);
491
- toolResults.push({ call, label: label2, outcome });
492
- messages.push(toolResultMessage(call, render(label2, outcome)));
493
- return { finalText, toolResults, turns, stopReason: "budget", cappedOut: true };
494
- }
495
- }
496
- const label = labelFor(call);
497
- toolResults.push({ call, label, outcome });
498
- messages.push(toolResultMessage(call, render(label, outcome)));
499
- }
500
- }
501
- return { finalText, toolResults, turns, stopReason: "completed", cappedOut: false };
502
- }
503
- async function* streamAppToolLoop(opts) {
504
- const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS;
505
- const render = opts.renderResult ?? defaultRender;
506
- const labelFor = opts.labelFor ?? ((c) => c.toolName);
507
- const messages = [
508
- { role: "system", content: opts.systemPrompt },
509
- ...opts.priorMessages ?? [],
510
- { role: "user", content: opts.userMessage }
511
- ];
512
- let accumulatedCostUsd = 0;
513
- let lastCallHash = null;
514
- let consecutiveCount = 0;
515
- for (let toolTurn = 0; ; toolTurn++) {
516
- if (opts.deadlineMs !== void 0 && Date.now() >= opts.deadlineMs) {
517
- yield { kind: "capped", pending: 0, stopReason: "deadline" };
518
- return;
519
- }
520
- let turnText = "";
521
- const pending = [];
522
- for await (const event of opts.streamTurn([...messages])) {
523
- yield { kind: "event", event };
524
- turnText += opts.extractText(event);
525
- const call = opts.extractToolCall(event);
526
- if (call && opts.isExecutableTool(call.toolName)) pending.push(call);
527
- }
528
- if (pending.length === 0) return;
529
- if (toolTurn >= backstop) {
530
- yield { kind: "capped", pending: pending.length, stopReason: "backstop" };
531
- return;
532
- }
533
- messages.push(assistantToolCallMessage(turnText, pending));
534
- for (const call of pending) {
535
- const callHash = canonicalCallHash(call);
536
- if (callHash === lastCallHash) {
537
- consecutiveCount++;
538
- } else {
539
- lastCallHash = callHash;
540
- consecutiveCount = 1;
541
- }
542
- if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {
543
- yield { kind: "capped", pending: pending.length, stopReason: "stuck-loop" };
544
- return;
545
- }
546
- let outcome;
547
- try {
548
- outcome = await opts.executeToolCall(call);
549
- } catch (err) {
550
- outcome = { ok: false, code: "executor_error", message: err instanceof Error ? err.message : String(err) };
551
- }
552
- if (opts.maxCostUsd !== void 0 && opts.costOf !== void 0) {
553
- accumulatedCostUsd += opts.costOf(call, outcome);
554
- if (accumulatedCostUsd >= opts.maxCostUsd) {
555
- const label2 = labelFor(call);
556
- yield { kind: "tool_result", toolName: call.toolName, toolCallId: call.toolCallId, label: label2, outcome };
557
- messages.push(toolResultMessage(call, render(label2, outcome)));
558
- yield { kind: "capped", pending: pending.length, stopReason: "budget" };
559
- return;
560
- }
561
- }
562
- const label = labelFor(call);
563
- yield { kind: "tool_result", toolName: call.toolName, toolCallId: call.toolCallId, label, outcome };
564
- messages.push(toolResultMessage(call, render(label, outcome)));
565
- }
566
- }
567
- }
417
+ import {
418
+ runToolLoop,
419
+ streamToolLoop
420
+ } from "@tangle-network/agent-runtime";
568
421
 
569
422
  export {
570
423
  normalizeModelId,
@@ -577,7 +430,7 @@ export {
577
430
  defineSurfaceKind,
578
431
  createSurfaceRegistry,
579
432
  mergeSurfaceOverlay,
580
- runAppToolLoop,
581
- streamAppToolLoop
433
+ runToolLoop,
434
+ streamToolLoop
582
435
  };
583
- //# sourceMappingURL=chunk-ETX4O4BB.js.map
436
+ //# sourceMappingURL=chunk-HD4NFA4U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime/model-catalog.ts","../src/runtime/openai-stream.ts","../src/runtime/agent.ts","../src/runtime/surface-profile.ts","../src/runtime/index.ts"],"sourcesContent":["/**\n * Model catalogue — computed live from the Tangle Router, never hand-curated.\n * Lifted from tuner-agent so every agent app's model picker shares one\n * filter/dedupe/rank/feature pipeline instead of re-deriving it.\n *\n * The router's /models endpoint returns every routeable model (~200), which is\n * unusable as a picker list: it mixes chat models with TTS/embedding/realtime\n * endpoints, dated snapshots alias their parents, and provider-prefixed ids\n * duplicate canonical ones. This module turns that into a product catalogue:\n *\n * filter (chat-capable, routeable) → dedupe (snapshot/prefix/:free aliases)\n * → rank (provider tier, family, version) → feature (best model per family)\n * → default (env override or first featured)\n *\n * Freshness is automatic: everything is derived from the live router response,\n * so new models surface as soon as the router lists them. The only static\n * knowledge here is slow-moving: provider display order and family name\n * patterns (e.g. \"claude-sonnet-*\", \"gpt-N\"). A new Sonnet or GPT release\n * outranks its predecessor by version comparison with zero code change; only\n * a brand-new *family name* (rare) needs a one-line rule addition.\n */\n\nexport interface RouterModel {\n id: string\n name?: string\n description?: string\n _provider?: string\n pricing?: { prompt?: string | null; completion?: string | null }\n context_length?: number\n architecture?: {\n modality?: string\n input_modalities?: string[]\n output_modalities?: string[]\n }\n supported_parameters?: string[]\n routeability?: {\n status?: string\n routeable?: boolean\n provider?: string\n }\n}\n\nexport interface CatalogModel {\n id: string\n name: string\n provider: string\n description?: string\n contextLength?: number\n pricing?: { prompt?: string; completion?: string }\n supportsTools: boolean\n supportsReasoning: boolean\n featured: boolean\n}\n\nexport interface ModelCatalog {\n defaultModelId: string | null\n fetchedAt: string\n models: CatalogModel[]\n}\n\n/** Display order. Unlisted providers sort after these, alphabetically. */\nconst PROVIDER_TIER: string[] = [\n 'anthropic',\n 'openai',\n 'google',\n 'xai',\n 'deepseek',\n 'moonshotai',\n 'moonshot',\n 'zai',\n 'z-ai',\n 'mistral',\n 'groq',\n 'nvidia',\n 'cohere',\n 'cerebras',\n]\n\n/** Non-chat endpoints that pollute the router list (matched on normalized id). */\nconst EXCLUDED_ID = /(embedding|tts|transcribe|whisper|audio|realtime|image|lyria|sora|dall-e|moderation|content-safety|search-preview|search-api|deep-research)/\n\n/**\n * Featured families, in display order. Each rule surfaces the highest-version\n * routeable model whose normalized id matches. Patterns anchor on the family\n * name and stop before specialty suffixes (codex, nano, lite, …) so the\n * mainline model wins.\n */\nconst FEATURED_RULES: Array<{ providers: string[]; match: RegExp }> = [\n { providers: ['anthropic'], match: /^claude-sonnet-[\\d-]+$/ },\n { providers: ['anthropic'], match: /^claude-opus-[\\d-]+$/ },\n { providers: ['anthropic'], match: /^claude-haiku-[\\d-]+$/ },\n { providers: ['openai'], match: /^gpt-\\d+(\\.\\d+)?$/ },\n { providers: ['openai'], match: /^gpt-\\d+(\\.\\d+)?-mini$/ },\n { providers: ['google'], match: /^gemini-[\\d.]+-pro(-preview)?$/ },\n { providers: ['google'], match: /^gemini-[\\d.]+-flash(-preview)?$/ },\n { providers: ['xai'], match: /^grok-[\\d.]+$/ },\n { providers: ['deepseek'], match: /^deepseek-(chat|v[\\d.]+(-\\w+)?)$/ },\n { providers: ['moonshotai', 'moonshot'], match: /^kimi-k[\\d.]+$/ },\n { providers: ['zai', 'z-ai'], match: /^glm-[\\d.]+$/ },\n { providers: ['mistral'], match: /^mistral-(large|medium)-?[\\d.-]*$/ },\n]\n\n/** Families known to support tool calls even when router metadata omits it\n * (dated snapshots often lack the supported_parameters of their parent). */\nconst TOOL_CAPABLE_FAMILY = /^(claude|gpt-[45]|gpt-oss|o[134]|gemini|grok|deepseek|glm|kimi|mistral|ministral|magistral|command|nemotron|llama)/\n\n/** Strip provider prefix, :free suffix, and trailing date stamps. */\nexport function normalizeModelId(id: string): string {\n let tail = id.split('/').pop() ?? id\n tail = tail.replace(/:free$/, '')\n tail = tail.replace(/-\\d{8}$/, '')\n tail = tail.replace(/-\\d{4}-\\d{2}-\\d{2}$/, '')\n return tail\n}\n\n/** All numeric groups in a normalized id, for version comparison. */\nfunction versionOf(normId: string): number[] {\n return (normId.match(/\\d+/g) ?? []).map(Number)\n}\n\nfunction compareVersions(a: number[], b: number[]): number {\n const len = Math.max(a.length, b.length)\n for (let i = 0; i < len; i++) {\n const d = (a[i] ?? -1) - (b[i] ?? -1)\n if (d !== 0) return d\n }\n return 0\n}\n\n/** Lower = preferred representative for an alias group. */\nfunction aliasPenalty(id: string): number {\n let p = 0\n if (id.includes('/')) p += 4\n if (/-\\d{8}$|-\\d{4}-\\d{2}-\\d{2}$/.test(id.replace(/:free$/, ''))) p += 2\n if (id.endsWith(':free')) p += 1\n return p\n}\n\nfunction providerRank(provider: string): number {\n const i = PROVIDER_TIER.indexOf(provider)\n return i === -1 ? PROVIDER_TIER.length : i\n}\n\nfunction isChatModel(m: RouterModel): boolean {\n const arch = m.architecture\n if (!arch?.input_modalities || !arch?.output_modalities) return true\n return arch.input_modalities.includes('text') && arch.output_modalities.includes('text')\n}\n\nfunction isRouteable(m: RouterModel): boolean {\n return m.routeability?.routeable !== false && m.routeability?.status !== 'unavailable'\n}\n\nfunction familyOf(normId: string): string {\n return normId.replace(/[\\d.]+/g, '').replace(/-+/g, '-').replace(/-$/, '')\n}\n\n/**\n * Pure catalogue pipeline. `preferredDefault` (typically the MODEL_NAME env\n * var) wins when it survives filtering; otherwise the first featured model.\n */\nexport function buildCatalog(raw: RouterModel[], opts?: { preferredDefault?: string }): ModelCatalog {\n // Filter to chat-capable, routeable, non-specialty models\n const candidates = raw.filter(\n (m) => m.id && isRouteable(m) && isChatModel(m) && !EXCLUDED_ID.test(normalizeModelId(m.id)),\n )\n\n // Dedupe alias groups (dated snapshots, provider prefixes, :free variants).\n // Within a group, merge metadata so the representative keeps the richest\n // supported_parameters claim (snapshots often omit what the parent lists).\n const groups = new Map<string, RouterModel[]>()\n for (const m of candidates) {\n const key = `${m._provider ?? ''}::${normalizeModelId(m.id)}`\n const g = groups.get(key)\n if (g) g.push(m)\n else groups.set(key, [m])\n }\n\n const reps: Array<{ model: RouterModel; normId: string; mergedParams: Set<string> }> = []\n for (const group of groups.values()) {\n group.sort((a, b) => aliasPenalty(a.id) - aliasPenalty(b.id) || a.id.length - b.id.length)\n const rep = group[0]!\n const mergedParams = new Set<string>(group.flatMap((m) => m.supported_parameters ?? []))\n reps.push({ model: rep, normId: normalizeModelId(rep.id), mergedParams })\n }\n\n // Featured: best version per family rule, in rule order\n const featuredIds: string[] = []\n for (const rule of FEATURED_RULES) {\n const matches = reps.filter(\n (r) =>\n rule.providers.includes(r.model._provider ?? '') &&\n rule.match.test(r.normId) &&\n !featuredIds.includes(r.model.id),\n )\n if (!matches.length) continue\n matches.sort(\n (a, b) =>\n compareVersions(versionOf(b.normId), versionOf(a.normId)) ||\n Number(a.normId.includes('preview')) - Number(b.normId.includes('preview')) ||\n a.model.id.length - b.model.id.length,\n )\n featuredIds.push(matches[0]!.model.id)\n }\n\n const toCatalogModel = (r: (typeof reps)[number]): CatalogModel => {\n const m = r.model\n const provider = m._provider ?? 'unknown'\n return {\n id: m.id,\n name: m.name ?? m.id,\n provider,\n description: m.description ? m.description.slice(0, 160) : undefined,\n contextLength: m.context_length,\n pricing:\n m.pricing?.prompt || m.pricing?.completion\n ? { prompt: m.pricing.prompt ?? undefined, completion: m.pricing.completion ?? undefined }\n : undefined,\n supportsTools: r.mergedParams.has('tools') || TOOL_CAPABLE_FAMILY.test(r.normId),\n supportsReasoning: r.mergedParams.has('reasoning') || r.mergedParams.has('include_reasoning'),\n featured: featuredIds.includes(m.id),\n }\n }\n\n // Sort: featured first (rule order), then provider tier → family → version desc\n const featured = featuredIds\n .map((id) => reps.find((r) => r.model.id === id)!)\n .map(toCatalogModel)\n const rest = reps\n .filter((r) => !featuredIds.includes(r.model.id))\n .sort((a, b) => {\n const pa = providerRank(a.model._provider ?? '')\n const pb = providerRank(b.model._provider ?? '')\n if (pa !== pb) return pa - pb\n const fa = familyOf(a.normId)\n const fb = familyOf(b.normId)\n if (fa !== fb) return fa.localeCompare(fb)\n return compareVersions(versionOf(b.normId), versionOf(a.normId)) || a.model.id.localeCompare(b.model.id)\n })\n .map(toCatalogModel)\n\n const models = [...featured, ...rest]\n\n const preferred = opts?.preferredDefault\n const defaultModelId =\n (preferred && models.find((m) => m.id === preferred || normalizeModelId(m.id) === normalizeModelId(preferred))?.id) ||\n featured.find((m) => m.supportsTools)?.id ||\n models[0]?.id ||\n null\n\n return { defaultModelId, fetchedAt: new Date().toISOString(), models }\n}\n\n// ── Cached fetch ─────────────────────────────────────────────────────────\n\nconst CATALOG_TTL_MS = 5 * 60 * 1000\n\nlet _cache: { catalog: ModelCatalog; at: number } | null = null\n\n/**\n * Fetch the router model list and build the catalogue, with an in-isolate\n * cache (TTL 5 min). On router failure a stale catalogue is served rather\n * than erroring the picker.\n */\nexport async function fetchModelCatalog(cfg: {\n baseUrl: string\n apiKey: string\n preferredDefault?: string\n}): Promise<ModelCatalog> {\n if (_cache && Date.now() - _cache.at < CATALOG_TTL_MS) {\n return _cache.catalog\n }\n try {\n const res = await fetch(`${cfg.baseUrl}/models`, {\n headers: { Authorization: `Bearer ${cfg.apiKey}` },\n })\n if (!res.ok) throw new Error(`Router /models returned ${res.status}`)\n const data = (await res.json()) as { data?: RouterModel[] }\n const catalog = buildCatalog(data.data ?? [], { preferredDefault: cfg.preferredDefault })\n _cache = { catalog, at: Date.now() }\n return catalog\n } catch (err) {\n if (_cache) return _cache.catalog\n throw err\n }\n}\n\n/** Test-only: clear the catalogue cache. */\nexport function __resetCatalogCache(): void {\n _cache = null\n}\n","/**\n * OpenAI-compatible stream → `LoopEvent` adapter, for NON-sandbox copilots.\n *\n * `streamAppToolLoop` takes a `streamTurn` seam that yields `LoopEvent`s. A\n * sandboxed agent produces those from its container; a browser/edge copilot\n * instead calls a model directly. The Tangle Router, the tcloud SDK, and most\n * providers all speak the OpenAI Chat Completions streaming shape — so the ONE\n * reusable piece is assembling that stream (content deltas + FRAGMENTED\n * tool-call deltas) into `LoopEvent`s. That assembly is the boilerplate every\n * copilot would re-write (and get wrong — OpenAI streams tool-call arguments in\n * pieces across chunks).\n *\n * This does NOT implement an HTTP client beyond a minimal `fetch` + SSE reader\n * (browser/edge/Node-safe, zero deps). For richer transport use the tcloud SDK\n * or the Vercel AI SDK and pipe their stream through {@link toLoopEvents}.\n */\nimport type { LoopEvent, LoopMessage, LoopToolCall } from './index'\n\n/** Minimal OpenAI Chat Completions streaming chunk (structural — no `openai` dep). */\nexport interface OpenAIStreamChunk {\n choices?: Array<{\n delta?: {\n content?: string | null\n /** Reasoning deltas — DeepSeek/router use `reasoning_content`; some proxies use `thinking`. */\n reasoning_content?: string | null\n thinking?: string | null\n tool_calls?: Array<{\n index: number\n id?: string\n function?: { name?: string; arguments?: string }\n }>\n }\n finish_reason?: string | null\n }>\n /** Final-chunk token accounting (requires `stream_options.include_usage`). */\n usage?: {\n prompt_tokens?: number\n completion_tokens?: number\n } | null\n}\n\ninterface PartialToolCall {\n id?: string\n name: string\n args: string\n}\n\n/**\n * Map an OpenAI-compat streaming chunk iterator to `LoopEvent`s: each content\n * delta → a `text` event; tool-call deltas are accumulated by index across\n * chunks and emitted as one complete `tool_call` event when the stream finishes\n * (arguments JSON-parsed; an empty/garbled args string yields `{}` rather than\n * throwing). Works for the Tangle Router, tcloud, or any OpenAI-compat source.\n */\nexport async function* toLoopEvents(chunks: AsyncIterable<OpenAIStreamChunk>): AsyncIterable<LoopEvent> {\n const calls = new Map<number, PartialToolCall>()\n for await (const chunk of chunks) {\n // Usage rides the final chunk, which has an empty choices array — handle\n // it before the choice guard.\n if (chunk.usage?.prompt_tokens != null || chunk.usage?.completion_tokens != null) {\n yield {\n type: 'usage',\n usage: {\n promptTokens: chunk.usage.prompt_tokens ?? 0,\n completionTokens: chunk.usage.completion_tokens ?? 0,\n },\n }\n }\n const choice = chunk.choices?.[0]\n if (!choice) continue\n const content = choice.delta?.content\n if (content) yield { type: 'text', text: content }\n const reasoning = choice.delta?.reasoning_content ?? choice.delta?.thinking\n if (reasoning) yield { type: 'reasoning', text: reasoning }\n for (const tc of choice.delta?.tool_calls ?? []) {\n const cur = calls.get(tc.index) ?? { name: '', args: '' }\n if (tc.id) cur.id = tc.id\n if (tc.function?.name) cur.name += tc.function.name\n if (tc.function?.arguments) cur.args += tc.function.arguments\n calls.set(tc.index, cur)\n }\n }\n for (const [, c] of [...calls.entries()].sort((a, b) => a[0] - b[0])) {\n if (!c.name) continue\n yield { type: 'tool_call', call: { toolCallId: c.id, toolName: c.name, args: safeParse(c.args) } satisfies LoopToolCall }\n }\n}\n\nfunction safeParse(s: string): Record<string, unknown> {\n if (!s.trim()) return {}\n try {\n const v = JSON.parse(s)\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : {}\n } catch {\n return {}\n }\n}\n\nexport interface OpenAICompatStreamTurnOptions {\n /** OpenAI-compat base URL (e.g. the Tangle Router `https://router.tangle.tools/v1`). */\n baseUrl: string\n apiKey: string\n model: string\n /** OpenAI tool definitions — pass `buildAppToolOpenAITools(taxonomy)` so the\n * model can call the app tools. Omit for a tool-free copilot. */\n tools?: unknown[]\n temperature?: number\n fetchImpl?: typeof fetch\n /** Extra body fields (e.g. `max_tokens`). */\n extraBody?: Record<string, unknown>\n}\n\n/**\n * Build a `streamTurn` that calls an OpenAI-compatible `/chat/completions`\n * endpoint (Tangle Router / tcloud / any compat provider) with `stream: true`\n * and yields `LoopEvent`s via {@link toLoopEvents}. Browser/edge/Node-safe —\n * just `fetch` + an SSE reader. Drop straight into `streamAppToolLoop`:\n *\n * const cfg = resolveTangleModelConfig() // or { baseUrl, apiKey, model }\n * streamAppToolLoop({ streamTurn: createOpenAICompatStreamTurn({ ...cfg, tools }), executeToolCall, ... })\n */\nexport function createOpenAICompatStreamTurn(\n opts: OpenAICompatStreamTurnOptions,\n): (messages: LoopMessage[]) => AsyncIterable<LoopEvent> {\n const base = opts.baseUrl.replace(/\\/+$/, '')\n const doFetch = opts.fetchImpl ?? fetch\n return (messages) =>\n toLoopEvents(\n streamChatCompletions(doFetch, `${base}/chat/completions`, opts.apiKey, {\n model: opts.model,\n messages,\n stream: true,\n stream_options: { include_usage: true },\n ...(opts.tools && opts.tools.length > 0 ? { tools: opts.tools } : {}),\n ...(opts.temperature != null ? { temperature: opts.temperature } : {}),\n ...opts.extraBody,\n }),\n )\n}\n\n/** Stream + parse an OpenAI-compat SSE response into chunks. Tolerates `data:`\n * framing, multi-line buffers, and the terminal `[DONE]`. */\nasync function* streamChatCompletions(\n doFetch: typeof fetch,\n url: string,\n apiKey: string,\n body: Record<string, unknown>,\n): AsyncIterable<OpenAIStreamChunk> {\n const res = await doFetch(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', Accept: 'text/event-stream' },\n body: JSON.stringify(body),\n })\n if (!res.ok || !res.body) {\n const text = res.body ? await res.text().catch(() => '') : ''\n throw new Error(`OpenAI-compat stream failed (HTTP ${res.status})${text ? `: ${text.slice(0, 200)}` : ''}`)\n }\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('data:')) continue\n const data = trimmed.slice(5).trim()\n if (data === '[DONE]') return\n try {\n yield JSON.parse(data) as OpenAIStreamChunk\n } catch {\n /* skip a partial/garbled SSE frame */\n }\n }\n }\n}\n","/**\n * `createAgentRuntime` — the in-process agent core, assembled.\n *\n * The bricks to run an agent turn WITHOUT a sandbox already exist in this\n * package, but a consumer must hand-wire five of them every time: resolve the\n * model config, build the OpenAI tool schemas from the taxonomy, build a\n * `streamTurn` over the model endpoint, build an `executeToolCall` over the\n * product's handlers, and drive `runAppToolLoop` / `streamAppToolLoop` with an\n * `isExecutableTool` predicate. That boilerplate is identical across every\n * sandbox-free surface (an edge/browser copilot, an eval harness, a Node CLI),\n * and getting it subtly wrong — e.g. NOT advertising the tools, so the model\n * never emits a `tool_call` and no side effect ever fires — is exactly the\n * failure that makes a tool-driven agent score zero off-sandbox.\n *\n * This factory bundles those five into one object configured for ONE agent:\n *\n * const runtime = createAgentRuntime({ model, taxonomy, handlers, systemPrompt })\n * const result = await runtime.run(userMessage, { ctx }) // awaitable\n * for await (const y of runtime.stream(userMessage, { ctx })) {…} // streaming\n *\n * The model is advertised the app tools (so it CAN call them); each call is\n * dispatched against the product's `handlers` (so the side effect is real); the\n * `onProduced` hook fires at the real side-effect site (so an eval/UI credits a\n * persisted proposal or artifact). Substrate-free: no `@tangle-network/sandbox`,\n * no Durable Object, no `@tangle-network/agent-runtime` import. The SAME core\n * the Cloudflare Worker runs, runnable anywhere a `fetch` to an OpenAI-compatible\n * endpoint works.\n *\n * Domain stays out: the proposal taxonomy, the handlers, and the system prompt\n * are all injected — the factory knows nothing about insurance, law, tax, etc.\n */\nimport {\n type AppToolHandlers,\n type AppToolContext,\n type AppToolOutcome,\n type AppToolProducedEvent,\n type AppToolTaxonomy,\n} from '../tools/types'\nimport { buildAppToolOpenAITools, isAppToolName } from '../tools/openai'\nimport { createAppToolRuntimeExecutor } from '../tools/runtime'\nimport {\n runAppToolLoop,\n streamAppToolLoop,\n type LoopEvent,\n type LoopMessage,\n type LoopToolCall,\n type StreamLoopYield,\n type ToolLoopEvent,\n type ToolLoopResult,\n} from './index'\nimport { createOpenAICompatStreamTurn } from './openai-stream'\n\n/** OpenAI-compatible model endpoint (Tangle Router / tcloud / any compat\n * provider). Build from {@link resolveTangleModelConfig} or pass literals. */\nexport interface AgentRuntimeModelConfig {\n baseUrl: string\n apiKey: string\n model: string\n temperature?: number\n fetchImpl?: typeof fetch\n /** Extra request-body fields (e.g. `max_tokens`, a `reasoning` block). */\n extraBody?: Record<string, unknown>\n}\n\nexport interface CreateAgentRuntimeOptions {\n /** The model endpoint the turns stream from. */\n model: AgentRuntimeModelConfig\n /** The product's proposal taxonomy — advertises `submit_proposal`'s `type`\n * enum to the model and labels the regulated subset on the result. */\n taxonomy: AppToolTaxonomy\n /** Domain handlers persisting each tool to the product's store/vault. */\n handlers: AppToolHandlers\n /** Default agent identity / system prompt. A turn may override it. */\n systemPrompt: string\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow.\n * For per-workflow limits use `deadlineMs` or `maxCostUsd` on the loop options. */\n maxToolTurns?: number\n /** Extra OpenAI tool definitions advertised ALONGSIDE the four app tools\n * (e.g. `integration_invoke`). Pair with {@link executeOtherTool}. */\n extraTools?: unknown[]\n /** Execute a tool that is NOT one of the four app tools (e.g. an integration\n * action). Only consulted for names {@link isOtherExecutableTool} accepts. */\n executeOtherTool?: (call: LoopToolCall, ctx: AppToolContext) => Promise<AppToolOutcome>\n /** Which non-app tool names are executable here. Required if {@link executeOtherTool} is set. */\n isOtherExecutableTool?: (toolName: string) => boolean\n}\n\nexport interface AgentTurnOptions {\n /** The trusted per-turn context (who/where the turn runs as). */\n ctx: AppToolContext\n /** Prior conversation turns, in order. */\n priorMessages?: Array<{ role: string; content: string }>\n /** Override the factory's default system prompt for this turn. */\n systemPrompt?: string\n /** Fires at the real side-effect site for each produced proposal/artifact. */\n onProduced?: (event: AppToolProducedEvent) => void\n}\n\nexport interface AgentRuntime {\n /** Run the bounded tool loop to completion; resolve with final text + every\n * executed tool outcome. */\n run(userMessage: string, turn: AgentTurnOptions): Promise<ToolLoopResult>\n /** Stream the bounded tool loop: yields each raw model event and each executed\n * tool result as it happens (for SSE re-emission + telemetry). */\n stream(userMessage: string, turn: AgentTurnOptions): AsyncGenerator<StreamLoopYield<LoopEvent>, void, unknown>\n}\n\n/**\n * Create an in-process agent runtime for one agent. See the module doc for the\n * full rationale; the short version: it advertises the app tools to the model,\n * dispatches each emitted call against `handlers`, and drives the bounded loop —\n * the whole agent core, sandbox-free.\n */\nexport function createAgentRuntime(opts: CreateAgentRuntimeOptions): AgentRuntime {\n if (opts.executeOtherTool && !opts.isOtherExecutableTool) {\n throw new Error('createAgentRuntime: isOtherExecutableTool is required when executeOtherTool is set')\n }\n\n // Tool schemas + the streamTurn are stable across turns — build once. The\n // model MUST be advertised the tools or it never emits a tool_call (the exact\n // failure that scores a tool-driven agent zero off-sandbox).\n const tools = [...buildAppToolOpenAITools(opts.taxonomy), ...(opts.extraTools ?? [])]\n const m = opts.model\n const streamTurn = createOpenAICompatStreamTurn({\n baseUrl: m.baseUrl,\n apiKey: m.apiKey,\n model: m.model,\n tools,\n temperature: m.temperature,\n fetchImpl: m.fetchImpl,\n extraBody: m.extraBody,\n })\n\n const isExecutableTool = (name: string): boolean =>\n isAppToolName(name) || (opts.isOtherExecutableTool?.(name) ?? false)\n\n const buildExecutor = (turn: AgentTurnOptions) => {\n const appExecutor = createAppToolRuntimeExecutor({\n handlers: opts.handlers,\n taxonomy: opts.taxonomy,\n ctx: turn.ctx,\n onProduced: turn.onProduced,\n })\n return async (call: LoopToolCall): Promise<AppToolOutcome> => {\n if (isAppToolName(call.toolName)) return appExecutor({ toolName: call.toolName, args: call.args })\n if (opts.executeOtherTool && opts.isOtherExecutableTool?.(call.toolName)) {\n return opts.executeOtherTool(call, turn.ctx)\n }\n return { ok: false, code: 'unknown_tool', message: `No executor for tool: ${call.toolName}` }\n }\n }\n\n return {\n run(userMessage, turn) {\n return runAppToolLoop({\n systemPrompt: turn.systemPrompt ?? opts.systemPrompt,\n userMessage,\n priorMessages: turn.priorMessages,\n // The awaitable loop consumes only text + tool_call; the app's UI-only\n // reasoning/usage events ride the substrate's `other` channel.\n streamTurn: narrowToToolLoopEvents(streamTurn),\n executeToolCall: buildExecutor(turn),\n isExecutableTool,\n maxToolTurns: opts.maxToolTurns,\n })\n },\n stream(userMessage, turn) {\n return streamAppToolLoop<LoopEvent>({\n systemPrompt: turn.systemPrompt ?? opts.systemPrompt,\n userMessage,\n priorMessages: turn.priorMessages,\n streamTurn,\n extractText: (ev) => (ev.type === 'text' ? ev.text : ''),\n extractToolCall: (ev) => (ev.type === 'tool_call' ? ev.call : null),\n isExecutableTool,\n executeToolCall: buildExecutor(turn),\n maxToolTurns: opts.maxToolTurns,\n })\n },\n }\n}\n\n/**\n * Adapt the app's rich {@link LoopEvent} stream to the substrate awaitable\n * loop's `ToolLoopEvent` contract. The loop reads only `text` (accumulated into\n * the answer) and `tool_call` (dispatched); the app's UI-only `reasoning` /\n * `usage` events have no awaitable meaning, so they collapse onto the\n * substrate's `other` channel and are ignored by the loop.\n */\nfunction narrowToToolLoopEvents(\n streamTurn: (messages: LoopMessage[]) => AsyncIterable<LoopEvent>,\n): (messages: LoopMessage[]) => AsyncIterable<ToolLoopEvent> {\n return (messages) =>\n (async function* () {\n for await (const ev of streamTurn(messages)) {\n if (ev.type === 'text') yield { type: 'text', text: ev.text }\n else if (ev.type === 'tool_call') yield { type: 'tool_call', call: ev.call }\n else yield { type: 'other', event: ev }\n }\n })()\n}\n","/**\n * Surface-scoped profile overlay — the seam letting any product page (a\n * sequence editor, a brief composer, a dataset view) add MCP servers, a prompt\n * addendum, and permission tightening to the workspace agent profile for turns\n * initiated FROM that surface, without the chat orchestrator knowing any\n * surface's specifics. The orchestrator resolves `(kind, ctx)` through a\n * registry the REQUEST HANDLER constructs per request (construction is a Map\n * build — cheap) and merges the result into the base profile it was about to\n * send to the sandbox. Per-request construction is the trust mechanism, not an\n * optimization target: each `build()` closes over server-trusted request state\n * (env bindings, secrets, the AUTHENTICATED user/workspace), which on Workers\n * exists only per request — a startup-built registry would force identity\n * through the untrusted client `ctx`.\n *\n * SECURITY INVARIANT: the surface `kind` and the ids inside `ctx` arrive on\n * the client request and are pure ROUTING data — never trusted content, never\n * identity. Identity comes from the closure (see above). The registered\n * `build()` runs server-side only: it validates the routing ids against the\n * product's access control, then mints its own URLs and capability tokens from\n * server configuration (`buildHttpMcpServer` + `createCapabilityToken` in\n * ../tools). A client can therefore never inject an arbitrary MCP url, header,\n * or token into the agent profile: the overlay's `mcp` values are typed as\n * {@link SurfaceMcpServer} (= the server-built `AppToolMcpServer` entry shape),\n * and only build() constructs them.\n */\n\nimport type { AppToolMcpServer } from '../tools/mcp'\n\n/** Sandbox permission posture values, ranked deny > ask > allow for merging. */\nexport type SurfacePermissionValue = 'allow' | 'ask' | 'deny'\n\n/** The only MCP entry shape an overlay may carry: the server-built bridge\n * entry from ../tools/mcp (transport, url, headers, and capability token all\n * assembled server-side). The alias exists so overlay authors reach for the\n * builders in ../tools rather than hand-rolling `{ url: ctx.url }` shapes\n * that would let request data become a dialable endpoint. */\nexport type SurfaceMcpServer = AppToolMcpServer\n\n/** What one surface contributes to the agent profile for a single turn. */\nexport interface SurfaceOverlay {\n /** MCP servers to mount for this turn, keyed by tool-routing name. Names\n * must not collide with the base profile's — see {@link mergeSurfaceOverlay}. */\n mcp?: Record<string, SurfaceMcpServer>\n /** Appended to the base system-prompt addendum with a blank-line separator. */\n promptAddendum?: string\n /** Per-key posture the surface wants for its turns. Merging is monotone\n * fail-closed: the stricter of base/overlay wins, so a surface can tighten\n * the workspace posture but never relax it. */\n permissions?: Record<string, SurfacePermissionValue>\n}\n\n/**\n * One registered surface kind. `TCtx` is the shape build() expects — a CLAIM\n * about the client payload, not a guarantee: the registry hands build() the\n * request's `ctx` unvalidated, so build() must treat every field as an\n * untrusted id (resolve it through access control that throws on a bad or\n * foreign id) before minting anything from it.\n */\nexport interface SurfaceKindDefinition<TCtx> {\n kind: string\n build: (ctx: TCtx) => SurfaceOverlay | Promise<SurfaceOverlay>\n}\n\n/** The variance-erased form a registry accepts (`build` is contravariant in\n * `TCtx`, so every concrete definition is assignable to this). */\nexport type AnySurfaceKind = SurfaceKindDefinition<never>\n\n/**\n * Declare one surface kind. The `kind` string is the client-visible routing\n * key (e.g. `'sequences'`); `build` is the server-side factory that turns a\n * validated ctx into the overlay for one turn.\n */\nexport function defineSurfaceKind<TCtx>(opts: {\n kind: string\n build: (ctx: TCtx) => SurfaceOverlay | Promise<SurfaceOverlay>\n}): SurfaceKindDefinition<TCtx> {\n if (typeof opts.kind !== 'string' || opts.kind.length === 0 || /\\s/.test(opts.kind)) {\n throw new Error(`surface kind must be a non-empty string without whitespace (got ${JSON.stringify(opts.kind)})`)\n }\n if (typeof opts.build !== 'function') {\n throw new Error(`surface kind '${opts.kind}' requires a build function`)\n }\n return { kind: opts.kind, build: opts.build }\n}\n\nexport interface SurfaceRegistry {\n /** Build the overlay for one turn. Throws on an unknown kind — an unknown\n * surface is a routing bug (client and server registries drifted), and\n * silently returning an empty overlay would strip the surface's tools from\n * the turn with no signal anywhere. Build errors propagate unwrapped. */\n resolve(kind: string, ctx: unknown): Promise<SurfaceOverlay>\n}\n\n/**\n * Assemble the product's surface registry from its registered kinds. Duplicate\n * kinds throw at construction: two builders behind one routing key would make\n * the mounted toolset depend on registration order.\n */\nexport function createSurfaceRegistry(kinds: readonly AnySurfaceKind[]): SurfaceRegistry {\n const byKind = new Map<string, AnySurfaceKind>()\n for (const definition of kinds) {\n if (byKind.has(definition.kind)) {\n throw new Error(`duplicate surface kind '${definition.kind}' — each kind must be registered exactly once`)\n }\n byKind.set(definition.kind, definition)\n }\n\n return {\n async resolve(kind, ctx) {\n const definition = byKind.get(kind)\n if (!definition) {\n const known = [...byKind.keys()].join(', ') || '(none)'\n throw new Error(\n `unknown surface kind '${kind}' — registered kinds: ${known}. ` +\n 'An unknown surface is a routing bug: register the kind via defineSurfaceKind before clients can reference it.',\n )\n }\n // The trust boundary where static ctx typing ends: the request payload is\n // handed to build() as-is, and build() validates it (see SurfaceKindDefinition).\n const overlay = await definition.build(ctx as never)\n assertSurfaceOverlay(overlay, `surface kind '${kind}'`)\n return overlay\n },\n }\n}\n\n/** Base-profile slice the merge reads/writes. Real callers pass their full\n * profile object; every field outside this slice passes through untouched. */\nexport interface SurfaceMergeBase {\n mcp?: Record<string, unknown>\n systemPromptAddendum?: string\n permissions?: Record<string, SurfacePermissionValue>\n}\n\nconst PERMISSION_SEVERITY: Record<SurfacePermissionValue, number> = { allow: 0, ask: 1, deny: 2 }\n\n/**\n * Merge one surface overlay into a base profile, returning a new object\n * (the base is never mutated; untouched nested records are shared by\n * reference).\n *\n * - `mcp`: overlay servers are added under their own names. A name already\n * present on the base THROWS — a collision is two servers claiming one\n * routing name, and renaming either silently would corrupt tool routing for\n * whichever caller expected the original binding.\n * - `systemPromptAddendum`: the overlay's `promptAddendum` appends after a\n * blank-line separator (no separator when the base has no addendum).\n * - `permissions`: per key the STRICTER value wins (deny > ask > allow). A\n * surface can tighten the base posture for its turns; a base 'deny' survives\n * any overlay.\n */\nexport function mergeSurfaceOverlay<TBase extends SurfaceMergeBase>(\n base: TBase,\n overlay: SurfaceOverlay,\n): TBase & SurfaceMergeBase {\n assertSurfaceOverlay(overlay, 'surface overlay')\n const merged: SurfaceMergeBase = { ...base }\n\n if (overlay.mcp && Object.keys(overlay.mcp).length > 0) {\n const baseMcp = base.mcp ?? {}\n const collisions = Object.keys(overlay.mcp).filter((name) => name in baseMcp)\n if (collisions.length > 0) {\n throw new Error(\n `surface overlay MCP name collision: ${collisions.map((n) => `'${n}'`).join(', ')} already exist on the base profile. ` +\n 'Two servers cannot claim one name — give the surface server a distinct name.',\n )\n }\n merged.mcp = { ...baseMcp, ...overlay.mcp }\n }\n\n if (overlay.promptAddendum !== undefined) {\n merged.systemPromptAddendum = base.systemPromptAddendum\n ? `${base.systemPromptAddendum}\\n\\n${overlay.promptAddendum}`\n : overlay.promptAddendum\n }\n\n if (overlay.permissions && Object.keys(overlay.permissions).length > 0) {\n const permissions: Record<string, SurfacePermissionValue> = { ...(base.permissions ?? {}) }\n for (const [key, value] of Object.entries(overlay.permissions)) {\n const existing = permissions[key]\n permissions[key] =\n existing === undefined || PERMISSION_SEVERITY[value] > PERMISSION_SEVERITY[existing] ? value : existing\n }\n merged.permissions = permissions\n }\n\n // merged began as a shallow copy of base; only the three slice fields were\n // replaced, so the intersection type is the true shape.\n return merged as TBase & SurfaceMergeBase\n}\n\n/** Reject overlays a build() (or hand-rolled caller) malformed, with the exact\n * field named: a relative MCP url, a blank addendum, or an off-vocabulary\n * permission would otherwise surface only as an opaque sandbox failure. */\nfunction assertSurfaceOverlay(overlay: SurfaceOverlay, label: string): void {\n if (overlay.promptAddendum !== undefined) {\n if (typeof overlay.promptAddendum !== 'string' || overlay.promptAddendum.trim().length === 0) {\n throw new Error(`${label}: promptAddendum must be a non-blank string when provided`)\n }\n }\n if (overlay.mcp !== undefined) {\n for (const [name, server] of Object.entries(overlay.mcp)) {\n if (name.trim().length === 0) throw new Error(`${label}: MCP server names must be non-empty`)\n if (server.transport !== 'http') {\n throw new Error(`${label}: MCP server '${name}' must use transport 'http' (got ${JSON.stringify((server as { transport?: unknown }).transport)})`)\n }\n let parsed: URL\n try {\n parsed = new URL(server.url)\n } catch {\n throw new Error(`${label}: MCP server '${name}' url must be an absolute URL (got ${JSON.stringify(server.url)})`)\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`${label}: MCP server '${name}' url must be http(s) (got ${JSON.stringify(server.url)})`)\n }\n }\n }\n if (overlay.permissions !== undefined) {\n for (const [key, value] of Object.entries(overlay.permissions)) {\n if (!(value in PERMISSION_SEVERITY)) {\n throw new Error(`${label}: permission '${key}' must be 'allow' | 'ask' | 'deny' (got ${JSON.stringify(value)})`)\n }\n }\n }\n}\n","export * from './model-catalog'\nexport * from './model'\nexport * from './openai-stream'\nexport * from './agent'\nexport * from './surface-profile'\n/**\n * The bounded agent tool-loop — owned by `@tangle-network/agent-runtime`.\n *\n * A model turn may emit tool calls (integration-hub actions, the app tools from\n * `../tools`, delegation). The loop streams a turn, collects the executable tool\n * calls, dispatches each, appends the results to history in OpenAI\n * function-calling shape, and re-runs so the model reads them — bounded by\n * `maxToolTurns`, a wall-clock `deadlineMs`, and a `maxCostUsd` budget.\n *\n * The history shape is the OpenAI function-calling contract: the assistant turn\n * that emitted tool calls is preserved as an `assistant` message carrying its\n * `tool_calls` array, and each result is its own `{ role: 'tool', tool_call_id,\n * content }` message keyed to the call. A strict model (Claude, and any\n * OpenAI-compatible provider that validates tool history) needs this to read its\n * own tool use back; folding results into a `user` message makes such models\n * re-issue the same call in a loop.\n *\n * The loop is substrate-owned (`runToolLoop` / `streamToolLoop`); the app\n * supplies `streamTurn` (wrapping its model endpoint) and `executeToolCall`\n * (routing to its integration + app-tool executors). The app-facing names below\n * are 1:1 aliases of the canonical symbols, kept so this package's consumers and\n * the in-package `createAgentRuntime` read against a single, stable vocabulary.\n */\nexport {\n runToolLoop as runAppToolLoop,\n streamToolLoop as streamAppToolLoop,\n} from '@tangle-network/agent-runtime'\nexport type {\n ToolLoopCall as LoopToolCall,\n ToolLoopAssistantToolCall as LoopAssistantToolCall,\n ToolLoopMessage as LoopMessage,\n ToolLoopEvent,\n ToolLoopStopReason,\n ToolLoopResult,\n RunToolLoopOptions as AppToolLoopOptions,\n StreamToolLoopOptions as StreamAppToolLoopOptions,\n StreamToolLoopYield as StreamLoopYield,\n} from '@tangle-network/agent-runtime'\n\n/**\n * Events the app's OpenAI-compat stream adapter ({@link toLoopEvents}) yields.\n *\n * This is the app's own `Raw` event type for the streaming loop — the canonical\n * `streamToolLoop<Raw>` is generic over it. It widens the substrate's\n * tool-loop event with `reasoning` (DeepSeek/router `reasoning_content` /\n * `thinking` deltas, rendered as thinking sections) and `usage` (per-message\n * token accounting) — neither belongs in the substrate's loop contract, so they\n * stay here. The adapter maps each into the `streamTurn` seam; `text` and\n * `tool_call` drive the loop, `reasoning` / `usage` pass through to the UI.\n */\nexport type LoopEvent =\n | { type: 'text'; text: string }\n | { type: 'reasoning'; text: string }\n | { type: 'tool_call'; call: import('@tangle-network/agent-runtime').ToolLoopCall }\n | { type: 'usage'; usage: { promptTokens: number; completionTokens: number } }\n | { type: 'other'; event: unknown }\n"],"mappings":"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","names":[]}
@@ -112,7 +112,8 @@ async function dispatchAppTool(toolName, rawArgs, ctx, opts) {
112
112
  type: "proposal_created",
113
113
  proposalId,
114
114
  title,
115
- status: effectiveStatus === "executed" ? "executed" : "pending"
115
+ status: effectiveStatus === "executed" ? "executed" : "pending",
116
+ content: description ?? void 0
116
117
  });
117
118
  return { ok: true, result: { ...extra, status: effectiveStatus, proposalId, deduped, regulated } };
118
119
  }
@@ -156,4 +157,4 @@ export {
156
157
  outcomeStatus,
157
158
  createAppToolRuntimeExecutor
158
159
  };
159
- //# sourceMappingURL=chunk-QAQBR6KQ.js.map
160
+ //# sourceMappingURL=chunk-JZZ6AWF4.js.map
@@ -1 +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"]}
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 content: description ?? undefined,\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"]}
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  dispatchAppTool,
6
6
  outcomeStatus
7
- } from "./chunk-QAQBR6KQ.js";
7
+ } from "./chunk-JZZ6AWF4.js";
8
8
 
9
9
  // src/tools/capability.ts
10
10
  async function createCapabilityToken(userId, opts) {
@@ -145,4 +145,4 @@ export {
145
145
  restrictTaxonomy,
146
146
  handleAppToolRequest
147
147
  };
148
- //# sourceMappingURL=chunk-SSX2A6XX.js.map
148
+ //# sourceMappingURL=chunk-MH6AVXQ7.js.map
@@ -5,8 +5,8 @@ export { A as AddElementOperation, a as AddPageOperation, b as ApplyDataOperatio
5
5
  import { d as SceneStore, N as NewSceneDecision, a as SceneDocumentRecord } from '../store-CUStmtdH.js';
6
6
  export { S as SceneDecision, b as SceneExportFormat, c as SceneExportRecord, e as SceneStoreScope } from '../store-CUStmtdH.js';
7
7
  import { c as McpToolDefinition } from '../mcp-rpc-DLw_r9PQ.js';
8
- import { f as ToolHeaderNames, a as AppToolMcpServer } from '../mcp-CIupfjxV.js';
9
- import { b as AppToolContext } from '../types-By4B3K37.js';
8
+ import { f as ToolHeaderNames, a as AppToolMcpServer } from '../mcp-eZCmkgCF.js';
9
+ import { b as AppToolContext } from '../types-2rOJo8Hc.js';
10
10
 
11
11
  /**
12
12
  * Pre-write validation for scene operations. Every rule runs against a
@@ -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-By4B3K37.js';
3
+ import { e as AppToolProducedEvent } from '../types-2rOJo8Hc.js';
4
4
 
5
5
  /**
6
6
  * Eval — the app-shell BRIDGE to `@tangle-network/agent-eval`, not a reimpl.
@@ -5,7 +5,7 @@ import {
5
5
  producedFromToolEvents,
6
6
  verifyCompletion,
7
7
  weightedComposite
8
- } from "../chunk-4NXVI7PW.js";
8
+ } from "../chunk-FA4XR66Y.js";
9
9
  export {
10
10
  createLlmCorrectnessChecker,
11
11
  createTokenRecallChecker,
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  export { AppToolRuntimeExecutor, CapabilityTokenOptions, DispatchOptions, ExpiringCapabilityTokenOptions, HandleToolRequestOptions, ResolveToolCapabilitiesOptions, ResolvedToolCapabilities, RuntimeExecutorOptions, ToolCapability, ToolInputError, createAppToolRuntimeExecutor, createCapabilityToken, createExpiringCapabilityToken, dispatchAppTool, handleAppToolRequest, outcomeStatus, resolveToolCapabilities, restrictTaxonomy, verifyCapabilityToken, verifyExpiringCapabilityToken } from './tools/index.js';
2
- export { A as APP_TOOL_NAMES, a as AppToolMcpServer, b as AppToolName, c as AuthenticateOptions, B as BuildHttpMcpServerOptions, d as BuildMcpServerOptions, D as DEFAULT_APP_TOOL_PATHS, e as DEFAULT_HEADER_NAMES, O as OpenAIFunctionTool, T as ToolAuthResult, f as ToolHeaderNames, g as authenticateToolRequest, h as buildAppToolMcpServer, i as buildAppToolOpenAITools, j as buildHttpMcpServer, k as isAppToolName, r as readToolArgs } from './mcp-CIupfjxV.js';
2
+ export { A as APP_TOOL_NAMES, a as AppToolMcpServer, b as AppToolName, c as AuthenticateOptions, B as BuildHttpMcpServerOptions, d as BuildMcpServerOptions, D as DEFAULT_APP_TOOL_PATHS, e as DEFAULT_HEADER_NAMES, O as OpenAIFunctionTool, T as ToolAuthResult, f as ToolHeaderNames, g as authenticateToolRequest, h as buildAppToolMcpServer, i as buildAppToolOpenAITools, j as buildHttpMcpServer, k as isAppToolName, r as readToolArgs } from './mcp-eZCmkgCF.js';
3
3
  export { C as CreateMcpToolHandlerOptions, M as MCP_PROTOCOL_VERSIONS, a as McpProtocolVersion, b as McpServerInfo, c as McpToolDefinition, d as createMcpToolHandler } from './mcp-rpc-DLw_r9PQ.js';
4
- 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';
4
+ 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-2rOJo8Hc.js';
5
5
  export { BuildDelegationOptions, DELEGATION_MCP_SERVER_KEY, DELEGATION_TOOLS, DelegationMcpServer, buildDelegationMcpServer, delegationMcpForConfig } from './delegation/index.js';
6
6
  export { BrokerToken, BrokerTokenMinter, BrokerTokenProvider, BrokerTokenProviderOptions, ConsentUrlInput, buildConsentUrl, createBrokerTokenProvider } from './tangle/index.js';
7
- export { AgentRuntime, AgentRuntimeModelConfig, AgentTurnOptions, AnySurfaceKind, AppToolLoopOptions, CreateAgentRuntimeOptions, LoopAssistantToolCall, LoopEvent, LoopMessage, LoopToolCall, OpenAICompatStreamTurnOptions, OpenAIStreamChunk, StreamAppToolLoopOptions, StreamLoopYield, SurfaceKindDefinition, SurfaceMcpServer, SurfaceMergeBase, SurfaceOverlay, SurfacePermissionValue, SurfaceRegistry, ToolLoopResult, ToolLoopStopReason, createAgentRuntime, createOpenAICompatStreamTurn, createSurfaceRegistry, defineSurfaceKind, mergeSurfaceOverlay, runAppToolLoop, streamAppToolLoop, toLoopEvents } from './runtime/index.js';
7
+ export { AgentRuntime, AgentRuntimeModelConfig, AgentTurnOptions, AnySurfaceKind, CreateAgentRuntimeOptions, LoopEvent, OpenAICompatStreamTurnOptions, OpenAIStreamChunk, SurfaceKindDefinition, SurfaceMcpServer, SurfaceMergeBase, SurfaceOverlay, SurfacePermissionValue, SurfaceRegistry, createAgentRuntime, createOpenAICompatStreamTurn, createSurfaceRegistry, defineSurfaceKind, mergeSurfaceOverlay, toLoopEvents } from './runtime/index.js';
8
8
  export { createTokenRecallChecker, producedFromToolEvents } from './eval/index.js';
9
9
  export { KnowledgeRequirementSpec, KnowledgeSignal, KnowledgeStateAccessor, SatisfiedByRule, buildKnowledgeRequirements, deriveSignals } from './knowledge/index.js';
10
10
  export { CreateKnowledgeLoopDeps, KnowledgeCandidate, KnowledgeDecider, KnowledgeDeciderInput, KnowledgeDecision, KnowledgeGateVerdict, KnowledgeLoop, KnowledgeLoopDriver, createKnowledgeLoop, createReviewerDecider, reviewCandidate } from './knowledge-loop/index.js';
@@ -28,6 +28,7 @@ export { B as Bounds, E as EllipseElement, G as GroupElement, I as ImageElement,
28
28
  export { A as AddElementOperation, a as AddPageOperation, b as ApplyDataOperation, B as BindSlotOperation, C as CHANNEL_PRESETS, c as ChannelPreset, d as ChannelPresetId, e as ChannelScaleResult, D as DeleteElementOperation, f as DeletePageOperation, g as DuplicatePageOperation, E as EXPORT_PRESETS, h as ExportCropRect, i as ExportFormat, j as ExportPreset, G as GroupElementsOperation, R as ReorderElementOperation, k as ReorderPageOperation, S as SCENE_OPERATION_TYPES, l as SIZE_PRESETS, m as SceneAttrsPatch, n as SceneOperation, o as SceneOperationType, p as ScenePlan, q as SetAttrsOperation, r as SetDocumentTitleOperation, s as SetPageGuidesOperation, t as SetPagePropsOperation, u as SizePreset, U as UngroupElementOperation, v as bleedAwareExportBounds, w as bleedAwareExportRect, x as findPreset, y as matchPreset, z as requireChannelPreset, F as scaleForPreset, H as scalePageForChannelPreset } from './export-presets-Dl5Aa5xj.js';
29
29
  export { N as NewSceneDecision, S as SceneDecision, a as SceneDocumentRecord, b as SceneExportFormat, c as SceneExportRecord, d as SceneStore, e as SceneStoreScope } from './store-CUStmtdH.js';
30
30
  export { ApplySceneOptions, BuildDesignCanvasMcpServerEntryOptions, CANVAS_ELEMENT_KINDS, CANVAS_MCP_TOOLS, CANVAS_MCP_TOOL_NAMES, CreateDesignCanvasMcpHandlerOptions, DEFAULT_DESIGN_CANVAS_MCP_DESCRIPTION, DesignCanvasMcpServerInfo, DesignCanvasMcpToolEnv, InstantiateOptions, SceneApplyResult, SlotFillKind, TemplateSlot, applyBindingsToDocument, applySceneOperation, applySceneOperations, buildDesignCanvasMcpServerEntry, createDesignCanvasMcpHandler, findCanvasMcpTool, instantiateTemplate, listTemplateSlots, storeApplyScenePlan, validateBindings, validateSceneOperation, validateSceneOperations, validateSlotValue } from './design-canvas/index.js';
31
+ export { RunToolLoopOptions as AppToolLoopOptions, ToolLoopAssistantToolCall as LoopAssistantToolCall, ToolLoopMessage as LoopMessage, ToolLoopCall as LoopToolCall, StreamToolLoopOptions as StreamAppToolLoopOptions, StreamToolLoopYield as StreamLoopYield, ToolLoopEvent, ToolLoopResult, ToolLoopStopReason, runToolLoop as runAppToolLoop, streamToolLoop as streamAppToolLoop } from '@tangle-network/agent-runtime';
31
32
  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';
32
33
  export { CompletionRequirement, CompletionVerdict, CorrectnessChecker, ProducedState, RuntimeEventLike, SatisfiedBy, TaskGold, createLlmCorrectnessChecker, extractProducedState, verifyCompletion, weightedComposite } from '@tangle-network/agent-eval';
33
34
  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';
package/dist/index.js CHANGED
@@ -249,7 +249,7 @@ import {
249
249
  restrictTaxonomy,
250
250
  verifyCapabilityToken,
251
251
  verifyExpiringCapabilityToken
252
- } from "./chunk-SSX2A6XX.js";
252
+ } from "./chunk-MH6AVXQ7.js";
253
253
  import {
254
254
  DEFAULT_APP_TOOL_PATHS,
255
255
  DEFAULT_HEADER_NAMES,
@@ -280,10 +280,10 @@ import {
280
280
  fetchModelCatalog,
281
281
  mergeSurfaceOverlay,
282
282
  normalizeModelId,
283
- runAppToolLoop,
284
- streamAppToolLoop,
283
+ runToolLoop,
284
+ streamToolLoop,
285
285
  toLoopEvents
286
- } from "./chunk-ETX4O4BB.js";
286
+ } from "./chunk-HD4NFA4U.js";
287
287
  import {
288
288
  DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
289
289
  DEFAULT_TANGLE_ROUTER_BASE_URL,
@@ -305,7 +305,7 @@ import {
305
305
  dispatchAppTool,
306
306
  isAppToolName,
307
307
  outcomeStatus
308
- } from "./chunk-QAQBR6KQ.js";
308
+ } from "./chunk-JZZ6AWF4.js";
309
309
  import {
310
310
  createLlmCorrectnessChecker,
311
311
  createTokenRecallChecker,
@@ -313,7 +313,7 @@ import {
313
313
  producedFromToolEvents,
314
314
  verifyCompletion,
315
315
  weightedComposite
316
- } from "./chunk-4NXVI7PW.js";
316
+ } from "./chunk-FA4XR66Y.js";
317
317
  import {
318
318
  buildKnowledgeRequirements,
319
319
  deriveSignals
@@ -545,7 +545,7 @@ export {
545
545
  restrictTaxonomy,
546
546
  revealSpan,
547
547
  reviewCandidate,
548
- runAppToolLoop,
548
+ runToolLoop as runAppToolLoop,
549
549
  safeParseAssetSpec,
550
550
  scaleForPreset,
551
551
  scalePageForChannelPreset,
@@ -556,7 +556,7 @@ export {
556
556
  stepAgentActivity,
557
557
  stepGateProposalId,
558
558
  storeApplyScenePlan,
559
- streamAppToolLoop,
559
+ streamToolLoop as streamAppToolLoop,
560
560
  summarize,
561
561
  tangleExecutionKeyHttpError,
562
562
  timedEventsFromLines,
@@ -1,4 +1,4 @@
1
- import { f as AppToolTaxonomy, b as AppToolContext } from './types-By4B3K37.js';
1
+ import { f as AppToolTaxonomy, b as AppToolContext } from './types-2rOJo8Hc.js';
2
2
 
3
3
  /** The four canonical app-tool names. Stable identifiers the model calls in
4
4
  * both the sandbox (MCP server name) and runtime (function-tool name) paths. */
@@ -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-By4B3K37.js';
3
+ import { c as AppToolHandlers } from '../types-2rOJo8Hc.js';
4
4
  import { KvLike } from '../web/index.js';
5
5
  import '@tangle-network/agent-eval';
6
6
 
@@ -1,7 +1,10 @@
1
+ import * as _tangle_network_agent_runtime from '@tangle-network/agent-runtime';
2
+ import { ToolLoopMessage, ToolLoopResult, StreamToolLoopYield, ToolLoopCall } from '@tangle-network/agent-runtime';
3
+ export { RunToolLoopOptions as AppToolLoopOptions, ToolLoopAssistantToolCall as LoopAssistantToolCall, ToolLoopMessage as LoopMessage, ToolLoopCall as LoopToolCall, StreamToolLoopOptions as StreamAppToolLoopOptions, StreamToolLoopYield as StreamLoopYield, ToolLoopEvent, ToolLoopResult, ToolLoopStopReason, runToolLoop as runAppToolLoop, streamToolLoop as streamAppToolLoop } from '@tangle-network/agent-runtime';
1
4
  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
5
  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-By4B3K37.js';
4
- import { a as AppToolMcpServer } from '../mcp-CIupfjxV.js';
6
+ import { b as AppToolContext, e as AppToolProducedEvent, f as AppToolTaxonomy, c as AppToolHandlers, d as AppToolOutcome } from '../types-2rOJo8Hc.js';
7
+ import { a as AppToolMcpServer } from '../mcp-eZCmkgCF.js';
5
8
 
6
9
  /**
7
10
  * OpenAI-compatible stream → `LoopEvent` adapter, for NON-sandbox copilots.
@@ -75,7 +78,7 @@ interface OpenAICompatStreamTurnOptions {
75
78
  * const cfg = resolveTangleModelConfig() // or { baseUrl, apiKey, model }
76
79
  * streamAppToolLoop({ streamTurn: createOpenAICompatStreamTurn({ ...cfg, tools }), executeToolCall, ... })
77
80
  */
78
- declare function createOpenAICompatStreamTurn(opts: OpenAICompatStreamTurnOptions): (messages: LoopMessage[]) => AsyncIterable<LoopEvent>;
81
+ declare function createOpenAICompatStreamTurn(opts: OpenAICompatStreamTurnOptions): (messages: ToolLoopMessage[]) => AsyncIterable<LoopEvent>;
79
82
 
80
83
  /**
81
84
  * `createAgentRuntime` — the in-process agent core, assembled.
@@ -138,7 +141,7 @@ interface CreateAgentRuntimeOptions {
138
141
  extraTools?: unknown[];
139
142
  /** Execute a tool that is NOT one of the four app tools (e.g. an integration
140
143
  * action). Only consulted for names {@link isOtherExecutableTool} accepts. */
141
- executeOtherTool?: (call: LoopToolCall, ctx: AppToolContext) => Promise<AppToolOutcome>;
144
+ executeOtherTool?: (call: ToolLoopCall, ctx: AppToolContext) => Promise<AppToolOutcome>;
142
145
  /** Which non-app tool names are executable here. Required if {@link executeOtherTool} is set. */
143
146
  isOtherExecutableTool?: (toolName: string) => boolean;
144
147
  }
@@ -161,7 +164,7 @@ interface AgentRuntime {
161
164
  run(userMessage: string, turn: AgentTurnOptions): Promise<ToolLoopResult>;
162
165
  /** Stream the bounded tool loop: yields each raw model event and each executed
163
166
  * tool result as it happens (for SSE re-emission + telemetry). */
164
- stream(userMessage: string, turn: AgentTurnOptions): AsyncGenerator<StreamLoopYield<LoopEvent>, void, unknown>;
167
+ stream(userMessage: string, turn: AgentTurnOptions): AsyncGenerator<StreamToolLoopYield<LoopEvent>, void, unknown>;
165
168
  }
166
169
  /**
167
170
  * Create an in-process agent runtime for one agent. See the module doc for the
@@ -277,46 +280,17 @@ interface SurfaceMergeBase {
277
280
  */
278
281
  declare function mergeSurfaceOverlay<TBase extends SurfaceMergeBase>(base: TBase, overlay: SurfaceOverlay): TBase & SurfaceMergeBase;
279
282
 
280
- interface LoopToolCall {
281
- toolCallId?: string;
282
- toolName: string;
283
- args: Record<string, unknown>;
284
- }
285
- /** One OpenAI-shaped tool-call entry on an assistant message. */
286
- interface LoopAssistantToolCall {
287
- id: string;
288
- type: 'function';
289
- function: {
290
- name: string;
291
- arguments: string;
292
- };
293
- }
294
283
  /**
295
- * A message in the running conversation the loop sends to `streamTurn`.
296
- *
297
- * The base `{ role, content }` covers `system` / `user` / plain `assistant`
298
- * turns. Two optional fields carry the OpenAI function-calling contract so the
299
- * model reads its own tool use back correctly instead of re-issuing it:
284
+ * Events the app's OpenAI-compat stream adapter ({@link toLoopEvents}) yields.
300
285
  *
301
- * - an assistant turn that emitted tool calls carries `tool_calls`, and its
302
- * `content` is `null` when the turn was tool-only;
303
- * - each tool result is its own `{ role: 'tool', tool_call_id, content }`
304
- * message keyed to the call that produced it.
305
- *
306
- * Widening is additive: a `streamTurn` that reads only `role` + `content` still
307
- * works; one that forwards the whole message to an OpenAI-compatible endpoint
308
- * now gets correct tool history. */
309
- interface LoopMessage {
310
- role: string;
311
- content: string | null;
312
- tool_calls?: LoopAssistantToolCall[];
313
- tool_call_id?: string;
314
- }
315
- /** Events a turn stream yields. `text` accumulates into the final answer;
316
- * `tool_call` is collected for dispatch; `reasoning` and `usage` pass through
317
- * for UIs that render thinking sections and per-message token/cost metrics.
318
- * Extra event types pass through untouched (the caller re-emits them to its
319
- * own UI stream). */
286
+ * This is the app's own `Raw` event type for the streaming loop — the canonical
287
+ * `streamToolLoop<Raw>` is generic over it. It widens the substrate's
288
+ * tool-loop event with `reasoning` (DeepSeek/router `reasoning_content` /
289
+ * `thinking` deltas, rendered as thinking sections) and `usage` (per-message
290
+ * token accounting) — neither belongs in the substrate's loop contract, so they
291
+ * stay here. The adapter maps each into the `streamTurn` seam; `text` and
292
+ * `tool_call` drive the loop, `reasoning` / `usage` pass through to the UI.
293
+ */
320
294
  type LoopEvent = {
321
295
  type: 'text';
322
296
  text: string;
@@ -325,7 +299,7 @@ type LoopEvent = {
325
299
  text: string;
326
300
  } | {
327
301
  type: 'tool_call';
328
- call: LoopToolCall;
302
+ call: _tangle_network_agent_runtime.ToolLoopCall;
329
303
  } | {
330
304
  type: 'usage';
331
305
  usage: {
@@ -336,121 +310,5 @@ type LoopEvent = {
336
310
  type: 'other';
337
311
  event: unknown;
338
312
  };
339
- /** Why the loop stopped. `completed` = model finished naturally; `stuck-loop` =
340
- * ≥3 consecutive identical tool calls (same tool + args); `backstop` = hit the
341
- * runaway-backstop cap (200 by default); `deadline` = wall-clock deadlineMs
342
- * exceeded; `budget` = maxCostUsd exhausted. Non-`completed` stops are infra /
343
- * resource outcomes — eval scoring must distinguish them from capability failure. */
344
- type ToolLoopStopReason = 'completed' | 'stuck-loop' | 'backstop' | 'deadline' | 'budget';
345
- interface ToolLoopResult {
346
- /** The model's final text across the loop. */
347
- finalText: string;
348
- /** Every tool call executed, with its outcome, in order. */
349
- toolResults: Array<{
350
- call: LoopToolCall;
351
- label: string;
352
- outcome: AppToolOutcome;
353
- }>;
354
- /** Number of model turns run (1 + tool-driven re-runs). */
355
- turns: number;
356
- /** Why the loop stopped. */
357
- stopReason: ToolLoopStopReason;
358
- /** @deprecated Use `stopReason !== 'completed'` instead. */
359
- cappedOut: boolean;
360
- }
361
- interface AppToolLoopOptions {
362
- systemPrompt: string;
363
- userMessage: string;
364
- priorMessages?: Array<{
365
- role: string;
366
- content: string;
367
- }>;
368
- /** Stream one model turn over the running message list. The app wraps its
369
- * backend here. Messages follow {@link LoopMessage}: a tool-calling assistant
370
- * turn carries `tool_calls`, and each tool result is a `role: 'tool'` message.
371
- * A backend that reads only `role` + `content` is unaffected. */
372
- streamTurn: (messages: LoopMessage[]) => AsyncIterable<LoopEvent>;
373
- /** Execute one tool call. The app routes to its integration executor / app-tool
374
- * executor and returns the outcome. */
375
- executeToolCall: (call: LoopToolCall) => Promise<AppToolOutcome>;
376
- /** Which emitted tool names are executable (others are ignored — e.g. a UI-only
377
- * tool the app renders but doesn't run here). */
378
- isExecutableTool: (toolName: string) => boolean;
379
- /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow.
380
- * For per-workflow limits use `maxCostUsd` or `deadlineMs` instead. */
381
- maxToolTurns?: number;
382
- /** Wall-clock deadline in ms since epoch (Date.now()-based). When exceeded the
383
- * loop stops with stopReason `deadline`. */
384
- deadlineMs?: number;
385
- /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */
386
- maxCostUsd?: number;
387
- /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */
388
- costOf?: (call: LoopToolCall, outcome: AppToolOutcome) => number;
389
- /** Render one tool outcome as the `content` of its `role: 'tool'` message.
390
- * Default is a compact `<label> → ok/failed: …`. */
391
- renderResult?: (label: string, outcome: AppToolOutcome) => string;
392
- /** Map a tool call to the label its result is keyed under (default: toolName). */
393
- labelFor?: (call: LoopToolCall) => string;
394
- }
395
- /**
396
- * Run the bounded tool loop and return the final text + every executed tool
397
- * outcome. Yields nothing — it's an awaitable driver; callers that need to
398
- * re-emit events to a UI stream should do so inside `streamTurn`. (A streaming
399
- * variant can wrap this later; keeping the core awaitable makes it trivially
400
- * testable.)
401
- */
402
- declare function runAppToolLoop(opts: AppToolLoopOptions): Promise<ToolLoopResult>;
403
- type StreamLoopYield<Raw> = {
404
- kind: 'event';
405
- event: Raw;
406
- } | {
407
- kind: 'tool_result';
408
- toolName: string;
409
- toolCallId?: string;
410
- label: string;
411
- outcome: AppToolOutcome;
412
- } | {
413
- kind: 'capped';
414
- pending: number;
415
- stopReason: Exclude<ToolLoopStopReason, 'completed'>;
416
- };
417
- interface StreamAppToolLoopOptions<Raw> {
418
- systemPrompt: string;
419
- userMessage: string;
420
- priorMessages?: Array<{
421
- role: string;
422
- content: string;
423
- }>;
424
- /** Stream one model turn (the app wraps its backend / runAgentTaskStream).
425
- * Messages follow {@link LoopMessage}: a tool-calling assistant turn carries
426
- * `tool_calls`, and each tool result is a `role: 'tool'` message. */
427
- streamTurn: (messages: LoopMessage[]) => AsyncIterable<Raw>;
428
- /** Text contribution of a raw event, '' if none — used to record the
429
- * assistant's turn so the next turn has its context. */
430
- extractText: (event: Raw) => string;
431
- /** The tool call a raw event represents, or null. */
432
- extractToolCall: (event: Raw) => LoopToolCall | null;
433
- /** Which tool names are executable here (others pass through, unexecuted). */
434
- isExecutableTool: (toolName: string) => boolean;
435
- /** Execute one call — the app routes to its integration / app-tool executor. */
436
- executeToolCall: (call: LoopToolCall) => Promise<AppToolOutcome>;
437
- /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow. */
438
- maxToolTurns?: number;
439
- /** Wall-clock deadline in ms since epoch (Date.now()-based). */
440
- deadlineMs?: number;
441
- /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */
442
- maxCostUsd?: number;
443
- /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */
444
- costOf?: (call: LoopToolCall, outcome: AppToolOutcome) => number;
445
- renderResult?: (label: string, outcome: AppToolOutcome) => string;
446
- labelFor?: (call: LoopToolCall) => string;
447
- }
448
- /**
449
- * The streaming bounded tool loop. Yields `event` for each raw turn event and
450
- * `tool_result` for each executed tool; emits a single `capped` (with stopReason)
451
- * when it stops for any non-completed reason. The app drives telemetry + UI
452
- * emission off the yielded items.
453
- */
454
- declare function streamAppToolLoop<Raw>(opts: StreamAppToolLoopOptions<Raw>): AsyncGenerator<StreamLoopYield<Raw>, void, unknown>;
455
313
 
456
- export { type AgentRuntime, type AgentRuntimeModelConfig, type AgentTurnOptions, type AnySurfaceKind, type AppToolLoopOptions, type CreateAgentRuntimeOptions, type LoopAssistantToolCall, type LoopEvent, type LoopMessage, type LoopToolCall, type OpenAICompatStreamTurnOptions, type OpenAIStreamChunk, type StreamAppToolLoopOptions, type StreamLoopYield, type SurfaceKindDefinition, type SurfaceMcpServer, type SurfaceMergeBase, type SurfaceOverlay, type SurfacePermissionValue, type SurfaceRegistry, type ToolLoopResult, type ToolLoopStopReason, createAgentRuntime, createOpenAICompatStreamTurn, createSurfaceRegistry, defineSurfaceKind, mergeSurfaceOverlay, runAppToolLoop, streamAppToolLoop, toLoopEvents };
314
+ export { type AgentRuntime, type AgentRuntimeModelConfig, type AgentTurnOptions, type AnySurfaceKind, type CreateAgentRuntimeOptions, type LoopEvent, type OpenAICompatStreamTurnOptions, type OpenAIStreamChunk, type SurfaceKindDefinition, type SurfaceMcpServer, type SurfaceMergeBase, type SurfaceOverlay, type SurfacePermissionValue, type SurfaceRegistry, createAgentRuntime, createOpenAICompatStreamTurn, createSurfaceRegistry, defineSurfaceKind, mergeSurfaceOverlay, toLoopEvents };
@@ -8,10 +8,10 @@ import {
8
8
  fetchModelCatalog,
9
9
  mergeSurfaceOverlay,
10
10
  normalizeModelId,
11
- runAppToolLoop,
12
- streamAppToolLoop,
11
+ runToolLoop,
12
+ streamToolLoop,
13
13
  toLoopEvents
14
- } from "../chunk-ETX4O4BB.js";
14
+ } from "../chunk-HD4NFA4U.js";
15
15
  import {
16
16
  DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
17
17
  DEFAULT_TANGLE_ROUTER_BASE_URL,
@@ -25,7 +25,7 @@ import {
25
25
  resolveUserTangleExecutionKeyForUser,
26
26
  tangleExecutionKeyHttpError
27
27
  } from "../chunk-EHPK7GKR.js";
28
- import "../chunk-QAQBR6KQ.js";
28
+ import "../chunk-JZZ6AWF4.js";
29
29
  export {
30
30
  DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
31
31
  DEFAULT_TANGLE_ROUTER_BASE_URL,
@@ -46,8 +46,8 @@ export {
46
46
  resolveTangleModelConfig,
47
47
  resolveUserTangleExecutionKey,
48
48
  resolveUserTangleExecutionKeyForUser,
49
- runAppToolLoop,
50
- streamAppToolLoop,
49
+ runToolLoop as runAppToolLoop,
50
+ streamToolLoop as streamAppToolLoop,
51
51
  tangleExecutionKeyHttpError,
52
52
  toLoopEvents
53
53
  };
@@ -1,8 +1,8 @@
1
1
  import { j as SequenceMediaKind, o as SequenceTimeline, r as TimelineInterval, m as SequenceStore } from '../store-gckrNq-g.js';
2
2
  export { M as MIN_SEQUENCE_CLIP_FRAMES, N as NewSequenceClip, a as NewSequenceDecision, b as NewSequenceTrack, S as SequenceClip, c as SequenceClipMedia, d as SequenceClipPatch, e as SequenceDecision, f as SequenceExportFormat, g as SequenceExportRecord, h as SequenceExportStatus, i as SequenceFrameSnapshot, k as SequenceMeta, l as SequenceStatus, n as SequenceStoreScope, p as SequenceTrack, q as SequenceTrackKind, T as TimelineClipBounds, s as assertClipFitsSequence, t as chooseCaptionPlacement, u as clampClipDuration, v as clampClipStart, w as formatSeconds, x as formatTimecode, y as framesToSeconds, z as secondsToFrames, A as snapshotFrame, B as trackIntervals } from '../store-gckrNq-g.js';
3
3
  export { A as AddCaptionOperation, C as CaptionTargetResolution, a as CreateTrackOperation, D as DeleteClipOperation, E as ExtendSequenceOperation, M as MoveClipOperation, P as PlaceClipOperation, Q as QueueExportOperation, S as SEQUENCE_OPERATION_TYPES, b as SequenceApplyResult, c as SequenceOperation, d as SequenceOperationContext, e as SequenceOperationType, f as SequencePlan, g as SetClipDisabledOperation, h as SetClipTextOperation, i as SplitClipOperation, T as TrimClipOperation, j as applySequenceOperation, k as applySequenceOperations, l as assertSequenceMediaUrl, m as captionTrackNameForLanguage, n as lastClipEndFrame, p as parseSequenceOperations, r as resolveCaptionPlacement, o as resolveCaptionTarget, q as resolvePlaceClipTrack, v as validateAddCaption, s as validateCreateTrack, t as validateDeleteClip, u as validateExtendSequence, w as validateMoveClip, x as validatePlaceClip, y as validateQueueExport, z as validateSequenceOperation, B as validateSequenceOperations, F as validateSetClipDisabled, G as validateSetClipText, H as validateSplitClip, I as validateTrimClip } from '../apply-Cp8c3K9D.js';
4
- import { f as ToolHeaderNames, a as AppToolMcpServer } from '../mcp-CIupfjxV.js';
5
- import { b as AppToolContext } from '../types-By4B3K37.js';
4
+ import { f as ToolHeaderNames, a as AppToolMcpServer } from '../mcp-eZCmkgCF.js';
5
+ import { b as AppToolContext } from '../types-2rOJo8Hc.js';
6
6
 
7
7
  /**
8
8
  * Pure interchange-format builders over `SequenceTimeline` — SRT, WebVTT,
@@ -1,7 +1,7 @@
1
- import { b as AppToolName, f as ToolHeaderNames } from '../mcp-CIupfjxV.js';
2
- export { A as APP_TOOL_NAMES, a as AppToolMcpServer, c as AuthenticateOptions, B as BuildHttpMcpServerOptions, d as BuildMcpServerOptions, D as DEFAULT_APP_TOOL_PATHS, e as DEFAULT_HEADER_NAMES, O as OpenAIFunctionTool, T as ToolAuthResult, g as authenticateToolRequest, h as buildAppToolMcpServer, i as buildAppToolOpenAITools, j as buildHttpMcpServer, k as isAppToolName, r as readToolArgs } from '../mcp-CIupfjxV.js';
3
- import { f as AppToolTaxonomy, c as AppToolHandlers, b as AppToolContext, e as AppToolProducedEvent, d as AppToolOutcome } from '../types-By4B3K37.js';
4
- 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';
1
+ import { b as AppToolName, f as ToolHeaderNames } from '../mcp-eZCmkgCF.js';
2
+ export { A as APP_TOOL_NAMES, a as AppToolMcpServer, c as AuthenticateOptions, B as BuildHttpMcpServerOptions, d as BuildMcpServerOptions, D as DEFAULT_APP_TOOL_PATHS, e as DEFAULT_HEADER_NAMES, O as OpenAIFunctionTool, T as ToolAuthResult, g as authenticateToolRequest, h as buildAppToolMcpServer, i as buildAppToolOpenAITools, j as buildHttpMcpServer, k as isAppToolName, r as readToolArgs } from '../mcp-eZCmkgCF.js';
3
+ import { f as AppToolTaxonomy, c as AppToolHandlers, b as AppToolContext, e as AppToolProducedEvent, d as AppToolOutcome } from '../types-2rOJo8Hc.js';
4
+ 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-2rOJo8Hc.js';
5
5
  export { C as CreateMcpToolHandlerOptions, M as MCP_PROTOCOL_VERSIONS, a as McpProtocolVersion, b as McpServerInfo, c as McpToolDefinition, d as createMcpToolHandler } from '../mcp-rpc-DLw_r9PQ.js';
6
6
 
7
7
  /** A correctable bad-input error a tool handler throws; the HTTP layer maps it
@@ -6,7 +6,7 @@ import {
6
6
  restrictTaxonomy,
7
7
  verifyCapabilityToken,
8
8
  verifyExpiringCapabilityToken
9
- } from "../chunk-SSX2A6XX.js";
9
+ } from "../chunk-MH6AVXQ7.js";
10
10
  import {
11
11
  DEFAULT_APP_TOOL_PATHS,
12
12
  DEFAULT_HEADER_NAMES,
@@ -25,7 +25,7 @@ import {
25
25
  dispatchAppTool,
26
26
  isAppToolName,
27
27
  outcomeStatus
28
- } from "../chunk-QAQBR6KQ.js";
28
+ } from "../chunk-JZZ6AWF4.js";
29
29
  export {
30
30
  APP_TOOL_NAMES,
31
31
  DEFAULT_APP_TOOL_PATHS,
@@ -110,14 +110,17 @@ type AppToolProducedEvent = {
110
110
  proposalId: string;
111
111
  title: string;
112
112
  status: 'pending' | 'executed';
113
+ content?: string;
113
114
  } | {
114
115
  type: 'artifact';
115
116
  path: string;
116
117
  content: string;
117
118
  };
118
- /** Outcome of one tool dispatch — structurally compatible with the integration
119
- * tool-outcome union the agent-runtime chat loop already folds into a
120
- * tool_result. */
119
+ /** Outcome of one tool dispatch — structurally identical to the agent-runtime
120
+ * tool-loop's `ToolCallOutcome`, so a dispatched outcome folds straight into
121
+ * the loop's `role: 'tool'` result message. Defined here (not imported) to keep
122
+ * the `tools/` module substrate-free: it depends only on Web `Request`/
123
+ * `Response`, never on the agent runtime. */
121
124
  type AppToolOutcome = {
122
125
  ok: true;
123
126
  result: unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.14.0",
3
+ "version": "0.15.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": [
@@ -192,8 +192,9 @@
192
192
  "typecheck": "tsc --noEmit"
193
193
  },
194
194
  "devDependencies": {
195
- "@tangle-network/agent-eval": "^0.82.0",
195
+ "@tangle-network/agent-eval": "^0.83.0",
196
196
  "@tangle-network/agent-integrations": "^0.32.0",
197
+ "@tangle-network/agent-runtime": "^0.52.0",
197
198
  "@tangle-network/agent-knowledge": "^1.5.2",
198
199
  "@testing-library/dom": "^10.4.1",
199
200
  "@testing-library/react": "^16.3.2",
@@ -214,10 +215,10 @@
214
215
  },
215
216
  "peerDependencies": {
216
217
  "@huggingface/transformers": ">=3",
217
- "@tangle-network/agent-eval": ">=0.82.0",
218
+ "@tangle-network/agent-eval": ">=0.83.0",
218
219
  "@tangle-network/agent-integrations": ">=0.32.0",
219
220
  "@tangle-network/agent-knowledge": ">=1.5.0",
220
- "@tangle-network/agent-runtime": ">=0.21.0",
221
+ "@tangle-network/agent-runtime": ">=0.52.0",
221
222
  "drizzle-orm": ">=0.36",
222
223
  "react": ">=18",
223
224
  "konva": ">=9",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/eval/index.ts"],"sourcesContent":["/**\n * Eval — the app-shell BRIDGE to `@tangle-network/agent-eval`, not a reimpl.\n *\n * The completion/scoring ENGINE lives in agent-eval (a peer dependency):\n * `verifyCompletion`, `extractProducedState`, `weightedComposite`,\n * `createLlmCorrectnessChecker`, and the `CompletionRequirement` / `TaskGold` /\n * `ProducedState` types — all re-exported here so a consumer has one import\n * root. This module adds only what agent-eval doesn't have and what is\n * app-shell-specific:\n *\n * 1. {@link producedFromToolEvents} — the bridge: turn the structured app-tool\n * side channel's `AppToolProducedEvent`s (from a tool runtime executor's\n * `onProduced`) into the `RuntimeEventLike`s agent-eval's\n * `extractProducedState` consumes. This is the one piece that knows about\n * the app-tool channel, so it belongs here, not in the engine.\n * 2. {@link createTokenRecallChecker} — a deterministic, no-LLM\n * `CorrectnessChecker` (agent-eval ships only the LLM one). For apps/tests\n * that gate completion without a judge call.\n *\n * Full campaigns (persona simulation, traces, scorecards, held-out gates) are\n * agent-eval's `runEvalCampaign` / `AgentDriver` / `BenchmarkRunner` — use them\n * directly; this module composes with them.\n */\nimport type { RuntimeEventLike, CompletionRequirement } from '@tangle-network/agent-eval'\nimport type { AppToolProducedEvent } from '../tools/types'\n\n// Re-export the engine so consumers import completion + scoring from one place.\nexport { verifyCompletion, extractProducedState, weightedComposite, createLlmCorrectnessChecker } from '@tangle-network/agent-eval'\nexport type {\n CompletionRequirement,\n TaskGold,\n ProducedState,\n SatisfiedBy,\n CompletionVerdict,\n CorrectnessChecker,\n RuntimeEventLike,\n} from '@tangle-network/agent-eval'\n\n/**\n * Bridge the app-tool side channel's produced events into the runtime-event\n * shape agent-eval's `extractProducedState` reads. Pipe it:\n * `verifyCompletion(taskGold, extractProducedState(producedFromToolEvents(events)), checker)`\n */\nexport function producedFromToolEvents(events: readonly AppToolProducedEvent[]): RuntimeEventLike[] {\n return events.map((e) =>\n e.type === 'proposal_created'\n ? { type: 'proposal_created', proposalId: e.proposalId, title: e.title, status: e.status }\n : { type: 'artifact', artifactId: `vault:${e.path}`, name: e.path, uri: `vault://${e.path}`, mimeType: 'text/markdown', content: e.content },\n )\n}\n\nconst STOPWORDS = new Set(['the', 'a', 'an', 'and', 'or', 'for', 'to', 'of', 'in', 'on', 'with', 'review', 'update', 'new', 'proposed'])\n\n/**\n * A deterministic `CorrectnessChecker` (agent-eval exports only\n * `createLlmCorrectnessChecker`). A produced item fulfils a requirement when\n * its content is substantive and recalls ≥ `minRecall` of the requirement\n * title's significant tokens. No network — the default gate for apps/tests\n * without an LLM judge. Pass to `verifyCompletion` as the checker.\n */\nexport function createTokenRecallChecker(opts: { minRecall?: number; minContentLength?: number } = {}): (\n requirement: CompletionRequirement,\n content: string,\n) => Promise<{ correct: boolean; reason: string }> {\n const minRecall = opts.minRecall ?? 0.5\n const minLen = opts.minContentLength ?? 120\n return async (requirement, content) => {\n const body = content.trim()\n if (body.length < minLen) return { correct: false, reason: `content too thin (${body.length} chars) to be the deliverable` }\n const tokens = requirement.title.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2 && !STOPWORDS.has(t))\n if (tokens.length === 0) return { correct: true, reason: 'requirement title has no significant tokens — structural match accepted' }\n const lower = body.toLowerCase()\n const hits = tokens.filter((t) => lower.includes(t)).length\n const recall = hits / tokens.length\n return recall >= minRecall\n ? { correct: true, reason: `content recalls ${hits}/${tokens.length} requirement tokens` }\n : { correct: false, reason: `content recalls only ${hits}/${tokens.length} requirement tokens` }\n }\n}\n"],"mappings":";AA2BA,SAAS,kBAAkB,sBAAsB,mBAAmB,mCAAmC;AAgBhG,SAAS,uBAAuB,QAA6D;AAClG,SAAO,OAAO;AAAA,IAAI,CAAC,MACjB,EAAE,SAAS,qBACP,EAAE,MAAM,oBAAoB,YAAY,EAAE,YAAY,OAAO,EAAE,OAAO,QAAQ,EAAE,OAAO,IACvF,EAAE,MAAM,YAAY,YAAY,SAAS,EAAE,IAAI,IAAI,MAAM,EAAE,MAAM,KAAK,WAAW,EAAE,IAAI,IAAI,UAAU,iBAAiB,SAAS,EAAE,QAAQ;AAAA,EAC/I;AACF;AAEA,IAAM,YAAY,oBAAI,IAAI,CAAC,OAAO,KAAK,MAAM,OAAO,MAAM,OAAO,MAAM,MAAM,MAAM,MAAM,QAAQ,UAAU,UAAU,OAAO,UAAU,CAAC;AAShI,SAAS,yBAAyB,OAA0D,CAAC,GAGjD;AACjD,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,SAAS,KAAK,oBAAoB;AACxC,SAAO,OAAO,aAAa,YAAY;AACrC,UAAM,OAAO,QAAQ,KAAK;AAC1B,QAAI,KAAK,SAAS,OAAQ,QAAO,EAAE,SAAS,OAAO,QAAQ,qBAAqB,KAAK,MAAM,gCAAgC;AAC3H,UAAM,SAAS,YAAY,MAAM,YAAY,EAAE,MAAM,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;AAClH,QAAI,OAAO,WAAW,EAAG,QAAO,EAAE,SAAS,MAAM,QAAQ,+EAA0E;AACnI,UAAM,QAAQ,KAAK,YAAY;AAC/B,UAAM,OAAO,OAAO,OAAO,CAAC,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE;AACrD,UAAM,SAAS,OAAO,OAAO;AAC7B,WAAO,UAAU,YACb,EAAE,SAAS,MAAM,QAAQ,mBAAmB,IAAI,IAAI,OAAO,MAAM,sBAAsB,IACvF,EAAE,SAAS,OAAO,QAAQ,wBAAwB,IAAI,IAAI,OAAO,MAAM,sBAAsB;AAAA,EACnG;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/runtime/model-catalog.ts","../src/runtime/openai-stream.ts","../src/runtime/agent.ts","../src/runtime/surface-profile.ts","../src/runtime/index.ts"],"sourcesContent":["/**\n * Model catalogue — computed live from the Tangle Router, never hand-curated.\n * Lifted from tuner-agent so every agent app's model picker shares one\n * filter/dedupe/rank/feature pipeline instead of re-deriving it.\n *\n * The router's /models endpoint returns every routeable model (~200), which is\n * unusable as a picker list: it mixes chat models with TTS/embedding/realtime\n * endpoints, dated snapshots alias their parents, and provider-prefixed ids\n * duplicate canonical ones. This module turns that into a product catalogue:\n *\n * filter (chat-capable, routeable) → dedupe (snapshot/prefix/:free aliases)\n * → rank (provider tier, family, version) → feature (best model per family)\n * → default (env override or first featured)\n *\n * Freshness is automatic: everything is derived from the live router response,\n * so new models surface as soon as the router lists them. The only static\n * knowledge here is slow-moving: provider display order and family name\n * patterns (e.g. \"claude-sonnet-*\", \"gpt-N\"). A new Sonnet or GPT release\n * outranks its predecessor by version comparison with zero code change; only\n * a brand-new *family name* (rare) needs a one-line rule addition.\n */\n\nexport interface RouterModel {\n id: string\n name?: string\n description?: string\n _provider?: string\n pricing?: { prompt?: string | null; completion?: string | null }\n context_length?: number\n architecture?: {\n modality?: string\n input_modalities?: string[]\n output_modalities?: string[]\n }\n supported_parameters?: string[]\n routeability?: {\n status?: string\n routeable?: boolean\n provider?: string\n }\n}\n\nexport interface CatalogModel {\n id: string\n name: string\n provider: string\n description?: string\n contextLength?: number\n pricing?: { prompt?: string; completion?: string }\n supportsTools: boolean\n supportsReasoning: boolean\n featured: boolean\n}\n\nexport interface ModelCatalog {\n defaultModelId: string | null\n fetchedAt: string\n models: CatalogModel[]\n}\n\n/** Display order. Unlisted providers sort after these, alphabetically. */\nconst PROVIDER_TIER: string[] = [\n 'anthropic',\n 'openai',\n 'google',\n 'xai',\n 'deepseek',\n 'moonshotai',\n 'moonshot',\n 'zai',\n 'z-ai',\n 'mistral',\n 'groq',\n 'nvidia',\n 'cohere',\n 'cerebras',\n]\n\n/** Non-chat endpoints that pollute the router list (matched on normalized id). */\nconst EXCLUDED_ID = /(embedding|tts|transcribe|whisper|audio|realtime|image|lyria|sora|dall-e|moderation|content-safety|search-preview|search-api|deep-research)/\n\n/**\n * Featured families, in display order. Each rule surfaces the highest-version\n * routeable model whose normalized id matches. Patterns anchor on the family\n * name and stop before specialty suffixes (codex, nano, lite, …) so the\n * mainline model wins.\n */\nconst FEATURED_RULES: Array<{ providers: string[]; match: RegExp }> = [\n { providers: ['anthropic'], match: /^claude-sonnet-[\\d-]+$/ },\n { providers: ['anthropic'], match: /^claude-opus-[\\d-]+$/ },\n { providers: ['anthropic'], match: /^claude-haiku-[\\d-]+$/ },\n { providers: ['openai'], match: /^gpt-\\d+(\\.\\d+)?$/ },\n { providers: ['openai'], match: /^gpt-\\d+(\\.\\d+)?-mini$/ },\n { providers: ['google'], match: /^gemini-[\\d.]+-pro(-preview)?$/ },\n { providers: ['google'], match: /^gemini-[\\d.]+-flash(-preview)?$/ },\n { providers: ['xai'], match: /^grok-[\\d.]+$/ },\n { providers: ['deepseek'], match: /^deepseek-(chat|v[\\d.]+(-\\w+)?)$/ },\n { providers: ['moonshotai', 'moonshot'], match: /^kimi-k[\\d.]+$/ },\n { providers: ['zai', 'z-ai'], match: /^glm-[\\d.]+$/ },\n { providers: ['mistral'], match: /^mistral-(large|medium)-?[\\d.-]*$/ },\n]\n\n/** Families known to support tool calls even when router metadata omits it\n * (dated snapshots often lack the supported_parameters of their parent). */\nconst TOOL_CAPABLE_FAMILY = /^(claude|gpt-[45]|gpt-oss|o[134]|gemini|grok|deepseek|glm|kimi|mistral|ministral|magistral|command|nemotron|llama)/\n\n/** Strip provider prefix, :free suffix, and trailing date stamps. */\nexport function normalizeModelId(id: string): string {\n let tail = id.split('/').pop() ?? id\n tail = tail.replace(/:free$/, '')\n tail = tail.replace(/-\\d{8}$/, '')\n tail = tail.replace(/-\\d{4}-\\d{2}-\\d{2}$/, '')\n return tail\n}\n\n/** All numeric groups in a normalized id, for version comparison. */\nfunction versionOf(normId: string): number[] {\n return (normId.match(/\\d+/g) ?? []).map(Number)\n}\n\nfunction compareVersions(a: number[], b: number[]): number {\n const len = Math.max(a.length, b.length)\n for (let i = 0; i < len; i++) {\n const d = (a[i] ?? -1) - (b[i] ?? -1)\n if (d !== 0) return d\n }\n return 0\n}\n\n/** Lower = preferred representative for an alias group. */\nfunction aliasPenalty(id: string): number {\n let p = 0\n if (id.includes('/')) p += 4\n if (/-\\d{8}$|-\\d{4}-\\d{2}-\\d{2}$/.test(id.replace(/:free$/, ''))) p += 2\n if (id.endsWith(':free')) p += 1\n return p\n}\n\nfunction providerRank(provider: string): number {\n const i = PROVIDER_TIER.indexOf(provider)\n return i === -1 ? PROVIDER_TIER.length : i\n}\n\nfunction isChatModel(m: RouterModel): boolean {\n const arch = m.architecture\n if (!arch?.input_modalities || !arch?.output_modalities) return true\n return arch.input_modalities.includes('text') && arch.output_modalities.includes('text')\n}\n\nfunction isRouteable(m: RouterModel): boolean {\n return m.routeability?.routeable !== false && m.routeability?.status !== 'unavailable'\n}\n\nfunction familyOf(normId: string): string {\n return normId.replace(/[\\d.]+/g, '').replace(/-+/g, '-').replace(/-$/, '')\n}\n\n/**\n * Pure catalogue pipeline. `preferredDefault` (typically the MODEL_NAME env\n * var) wins when it survives filtering; otherwise the first featured model.\n */\nexport function buildCatalog(raw: RouterModel[], opts?: { preferredDefault?: string }): ModelCatalog {\n // Filter to chat-capable, routeable, non-specialty models\n const candidates = raw.filter(\n (m) => m.id && isRouteable(m) && isChatModel(m) && !EXCLUDED_ID.test(normalizeModelId(m.id)),\n )\n\n // Dedupe alias groups (dated snapshots, provider prefixes, :free variants).\n // Within a group, merge metadata so the representative keeps the richest\n // supported_parameters claim (snapshots often omit what the parent lists).\n const groups = new Map<string, RouterModel[]>()\n for (const m of candidates) {\n const key = `${m._provider ?? ''}::${normalizeModelId(m.id)}`\n const g = groups.get(key)\n if (g) g.push(m)\n else groups.set(key, [m])\n }\n\n const reps: Array<{ model: RouterModel; normId: string; mergedParams: Set<string> }> = []\n for (const group of groups.values()) {\n group.sort((a, b) => aliasPenalty(a.id) - aliasPenalty(b.id) || a.id.length - b.id.length)\n const rep = group[0]!\n const mergedParams = new Set<string>(group.flatMap((m) => m.supported_parameters ?? []))\n reps.push({ model: rep, normId: normalizeModelId(rep.id), mergedParams })\n }\n\n // Featured: best version per family rule, in rule order\n const featuredIds: string[] = []\n for (const rule of FEATURED_RULES) {\n const matches = reps.filter(\n (r) =>\n rule.providers.includes(r.model._provider ?? '') &&\n rule.match.test(r.normId) &&\n !featuredIds.includes(r.model.id),\n )\n if (!matches.length) continue\n matches.sort(\n (a, b) =>\n compareVersions(versionOf(b.normId), versionOf(a.normId)) ||\n Number(a.normId.includes('preview')) - Number(b.normId.includes('preview')) ||\n a.model.id.length - b.model.id.length,\n )\n featuredIds.push(matches[0]!.model.id)\n }\n\n const toCatalogModel = (r: (typeof reps)[number]): CatalogModel => {\n const m = r.model\n const provider = m._provider ?? 'unknown'\n return {\n id: m.id,\n name: m.name ?? m.id,\n provider,\n description: m.description ? m.description.slice(0, 160) : undefined,\n contextLength: m.context_length,\n pricing:\n m.pricing?.prompt || m.pricing?.completion\n ? { prompt: m.pricing.prompt ?? undefined, completion: m.pricing.completion ?? undefined }\n : undefined,\n supportsTools: r.mergedParams.has('tools') || TOOL_CAPABLE_FAMILY.test(r.normId),\n supportsReasoning: r.mergedParams.has('reasoning') || r.mergedParams.has('include_reasoning'),\n featured: featuredIds.includes(m.id),\n }\n }\n\n // Sort: featured first (rule order), then provider tier → family → version desc\n const featured = featuredIds\n .map((id) => reps.find((r) => r.model.id === id)!)\n .map(toCatalogModel)\n const rest = reps\n .filter((r) => !featuredIds.includes(r.model.id))\n .sort((a, b) => {\n const pa = providerRank(a.model._provider ?? '')\n const pb = providerRank(b.model._provider ?? '')\n if (pa !== pb) return pa - pb\n const fa = familyOf(a.normId)\n const fb = familyOf(b.normId)\n if (fa !== fb) return fa.localeCompare(fb)\n return compareVersions(versionOf(b.normId), versionOf(a.normId)) || a.model.id.localeCompare(b.model.id)\n })\n .map(toCatalogModel)\n\n const models = [...featured, ...rest]\n\n const preferred = opts?.preferredDefault\n const defaultModelId =\n (preferred && models.find((m) => m.id === preferred || normalizeModelId(m.id) === normalizeModelId(preferred))?.id) ||\n featured.find((m) => m.supportsTools)?.id ||\n models[0]?.id ||\n null\n\n return { defaultModelId, fetchedAt: new Date().toISOString(), models }\n}\n\n// ── Cached fetch ─────────────────────────────────────────────────────────\n\nconst CATALOG_TTL_MS = 5 * 60 * 1000\n\nlet _cache: { catalog: ModelCatalog; at: number } | null = null\n\n/**\n * Fetch the router model list and build the catalogue, with an in-isolate\n * cache (TTL 5 min). On router failure a stale catalogue is served rather\n * than erroring the picker.\n */\nexport async function fetchModelCatalog(cfg: {\n baseUrl: string\n apiKey: string\n preferredDefault?: string\n}): Promise<ModelCatalog> {\n if (_cache && Date.now() - _cache.at < CATALOG_TTL_MS) {\n return _cache.catalog\n }\n try {\n const res = await fetch(`${cfg.baseUrl}/models`, {\n headers: { Authorization: `Bearer ${cfg.apiKey}` },\n })\n if (!res.ok) throw new Error(`Router /models returned ${res.status}`)\n const data = (await res.json()) as { data?: RouterModel[] }\n const catalog = buildCatalog(data.data ?? [], { preferredDefault: cfg.preferredDefault })\n _cache = { catalog, at: Date.now() }\n return catalog\n } catch (err) {\n if (_cache) return _cache.catalog\n throw err\n }\n}\n\n/** Test-only: clear the catalogue cache. */\nexport function __resetCatalogCache(): void {\n _cache = null\n}\n","/**\n * OpenAI-compatible stream → `LoopEvent` adapter, for NON-sandbox copilots.\n *\n * `streamAppToolLoop` takes a `streamTurn` seam that yields `LoopEvent`s. A\n * sandboxed agent produces those from its container; a browser/edge copilot\n * instead calls a model directly. The Tangle Router, the tcloud SDK, and most\n * providers all speak the OpenAI Chat Completions streaming shape — so the ONE\n * reusable piece is assembling that stream (content deltas + FRAGMENTED\n * tool-call deltas) into `LoopEvent`s. That assembly is the boilerplate every\n * copilot would re-write (and get wrong — OpenAI streams tool-call arguments in\n * pieces across chunks).\n *\n * This does NOT implement an HTTP client beyond a minimal `fetch` + SSE reader\n * (browser/edge/Node-safe, zero deps). For richer transport use the tcloud SDK\n * or the Vercel AI SDK and pipe their stream through {@link toLoopEvents}.\n */\nimport type { LoopEvent, LoopMessage, LoopToolCall } from './index'\n\n/** Minimal OpenAI Chat Completions streaming chunk (structural — no `openai` dep). */\nexport interface OpenAIStreamChunk {\n choices?: Array<{\n delta?: {\n content?: string | null\n /** Reasoning deltas — DeepSeek/router use `reasoning_content`; some proxies use `thinking`. */\n reasoning_content?: string | null\n thinking?: string | null\n tool_calls?: Array<{\n index: number\n id?: string\n function?: { name?: string; arguments?: string }\n }>\n }\n finish_reason?: string | null\n }>\n /** Final-chunk token accounting (requires `stream_options.include_usage`). */\n usage?: {\n prompt_tokens?: number\n completion_tokens?: number\n } | null\n}\n\ninterface PartialToolCall {\n id?: string\n name: string\n args: string\n}\n\n/**\n * Map an OpenAI-compat streaming chunk iterator to `LoopEvent`s: each content\n * delta → a `text` event; tool-call deltas are accumulated by index across\n * chunks and emitted as one complete `tool_call` event when the stream finishes\n * (arguments JSON-parsed; an empty/garbled args string yields `{}` rather than\n * throwing). Works for the Tangle Router, tcloud, or any OpenAI-compat source.\n */\nexport async function* toLoopEvents(chunks: AsyncIterable<OpenAIStreamChunk>): AsyncIterable<LoopEvent> {\n const calls = new Map<number, PartialToolCall>()\n for await (const chunk of chunks) {\n // Usage rides the final chunk, which has an empty choices array — handle\n // it before the choice guard.\n if (chunk.usage?.prompt_tokens != null || chunk.usage?.completion_tokens != null) {\n yield {\n type: 'usage',\n usage: {\n promptTokens: chunk.usage.prompt_tokens ?? 0,\n completionTokens: chunk.usage.completion_tokens ?? 0,\n },\n }\n }\n const choice = chunk.choices?.[0]\n if (!choice) continue\n const content = choice.delta?.content\n if (content) yield { type: 'text', text: content }\n const reasoning = choice.delta?.reasoning_content ?? choice.delta?.thinking\n if (reasoning) yield { type: 'reasoning', text: reasoning }\n for (const tc of choice.delta?.tool_calls ?? []) {\n const cur = calls.get(tc.index) ?? { name: '', args: '' }\n if (tc.id) cur.id = tc.id\n if (tc.function?.name) cur.name += tc.function.name\n if (tc.function?.arguments) cur.args += tc.function.arguments\n calls.set(tc.index, cur)\n }\n }\n for (const [, c] of [...calls.entries()].sort((a, b) => a[0] - b[0])) {\n if (!c.name) continue\n yield { type: 'tool_call', call: { toolCallId: c.id, toolName: c.name, args: safeParse(c.args) } satisfies LoopToolCall }\n }\n}\n\nfunction safeParse(s: string): Record<string, unknown> {\n if (!s.trim()) return {}\n try {\n const v = JSON.parse(s)\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : {}\n } catch {\n return {}\n }\n}\n\nexport interface OpenAICompatStreamTurnOptions {\n /** OpenAI-compat base URL (e.g. the Tangle Router `https://router.tangle.tools/v1`). */\n baseUrl: string\n apiKey: string\n model: string\n /** OpenAI tool definitions — pass `buildAppToolOpenAITools(taxonomy)` so the\n * model can call the app tools. Omit for a tool-free copilot. */\n tools?: unknown[]\n temperature?: number\n fetchImpl?: typeof fetch\n /** Extra body fields (e.g. `max_tokens`). */\n extraBody?: Record<string, unknown>\n}\n\n/**\n * Build a `streamTurn` that calls an OpenAI-compatible `/chat/completions`\n * endpoint (Tangle Router / tcloud / any compat provider) with `stream: true`\n * and yields `LoopEvent`s via {@link toLoopEvents}. Browser/edge/Node-safe —\n * just `fetch` + an SSE reader. Drop straight into `streamAppToolLoop`:\n *\n * const cfg = resolveTangleModelConfig() // or { baseUrl, apiKey, model }\n * streamAppToolLoop({ streamTurn: createOpenAICompatStreamTurn({ ...cfg, tools }), executeToolCall, ... })\n */\nexport function createOpenAICompatStreamTurn(\n opts: OpenAICompatStreamTurnOptions,\n): (messages: LoopMessage[]) => AsyncIterable<LoopEvent> {\n const base = opts.baseUrl.replace(/\\/+$/, '')\n const doFetch = opts.fetchImpl ?? fetch\n return (messages) =>\n toLoopEvents(\n streamChatCompletions(doFetch, `${base}/chat/completions`, opts.apiKey, {\n model: opts.model,\n messages,\n stream: true,\n stream_options: { include_usage: true },\n ...(opts.tools && opts.tools.length > 0 ? { tools: opts.tools } : {}),\n ...(opts.temperature != null ? { temperature: opts.temperature } : {}),\n ...opts.extraBody,\n }),\n )\n}\n\n/** Stream + parse an OpenAI-compat SSE response into chunks. Tolerates `data:`\n * framing, multi-line buffers, and the terminal `[DONE]`. */\nasync function* streamChatCompletions(\n doFetch: typeof fetch,\n url: string,\n apiKey: string,\n body: Record<string, unknown>,\n): AsyncIterable<OpenAIStreamChunk> {\n const res = await doFetch(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', Accept: 'text/event-stream' },\n body: JSON.stringify(body),\n })\n if (!res.ok || !res.body) {\n const text = res.body ? await res.text().catch(() => '') : ''\n throw new Error(`OpenAI-compat stream failed (HTTP ${res.status})${text ? `: ${text.slice(0, 200)}` : ''}`)\n }\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('data:')) continue\n const data = trimmed.slice(5).trim()\n if (data === '[DONE]') return\n try {\n yield JSON.parse(data) as OpenAIStreamChunk\n } catch {\n /* skip a partial/garbled SSE frame */\n }\n }\n }\n}\n","/**\n * `createAgentRuntime` — the in-process agent core, assembled.\n *\n * The bricks to run an agent turn WITHOUT a sandbox already exist in this\n * package, but a consumer must hand-wire five of them every time: resolve the\n * model config, build the OpenAI tool schemas from the taxonomy, build a\n * `streamTurn` over the model endpoint, build an `executeToolCall` over the\n * product's handlers, and drive `runAppToolLoop` / `streamAppToolLoop` with an\n * `isExecutableTool` predicate. That boilerplate is identical across every\n * sandbox-free surface (an edge/browser copilot, an eval harness, a Node CLI),\n * and getting it subtly wrong — e.g. NOT advertising the tools, so the model\n * never emits a `tool_call` and no side effect ever fires — is exactly the\n * failure that makes a tool-driven agent score zero off-sandbox.\n *\n * This factory bundles those five into one object configured for ONE agent:\n *\n * const runtime = createAgentRuntime({ model, taxonomy, handlers, systemPrompt })\n * const result = await runtime.run(userMessage, { ctx }) // awaitable\n * for await (const y of runtime.stream(userMessage, { ctx })) {…} // streaming\n *\n * The model is advertised the app tools (so it CAN call them); each call is\n * dispatched against the product's `handlers` (so the side effect is real); the\n * `onProduced` hook fires at the real side-effect site (so an eval/UI credits a\n * persisted proposal or artifact). Substrate-free: no `@tangle-network/sandbox`,\n * no Durable Object, no `@tangle-network/agent-runtime` import. The SAME core\n * the Cloudflare Worker runs, runnable anywhere a `fetch` to an OpenAI-compatible\n * endpoint works.\n *\n * Domain stays out: the proposal taxonomy, the handlers, and the system prompt\n * are all injected — the factory knows nothing about insurance, law, tax, etc.\n */\nimport {\n type AppToolHandlers,\n type AppToolContext,\n type AppToolOutcome,\n type AppToolProducedEvent,\n type AppToolTaxonomy,\n} from '../tools/types'\nimport { buildAppToolOpenAITools, isAppToolName } from '../tools/openai'\nimport { createAppToolRuntimeExecutor } from '../tools/runtime'\nimport {\n runAppToolLoop,\n streamAppToolLoop,\n type LoopEvent,\n type LoopToolCall,\n type StreamLoopYield,\n type ToolLoopResult,\n} from './index'\nimport { createOpenAICompatStreamTurn } from './openai-stream'\n\n/** OpenAI-compatible model endpoint (Tangle Router / tcloud / any compat\n * provider). Build from {@link resolveTangleModelConfig} or pass literals. */\nexport interface AgentRuntimeModelConfig {\n baseUrl: string\n apiKey: string\n model: string\n temperature?: number\n fetchImpl?: typeof fetch\n /** Extra request-body fields (e.g. `max_tokens`, a `reasoning` block). */\n extraBody?: Record<string, unknown>\n}\n\nexport interface CreateAgentRuntimeOptions {\n /** The model endpoint the turns stream from. */\n model: AgentRuntimeModelConfig\n /** The product's proposal taxonomy — advertises `submit_proposal`'s `type`\n * enum to the model and labels the regulated subset on the result. */\n taxonomy: AppToolTaxonomy\n /** Domain handlers persisting each tool to the product's store/vault. */\n handlers: AppToolHandlers\n /** Default agent identity / system prompt. A turn may override it. */\n systemPrompt: string\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow.\n * For per-workflow limits use `deadlineMs` or `maxCostUsd` on the loop options. */\n maxToolTurns?: number\n /** Extra OpenAI tool definitions advertised ALONGSIDE the four app tools\n * (e.g. `integration_invoke`). Pair with {@link executeOtherTool}. */\n extraTools?: unknown[]\n /** Execute a tool that is NOT one of the four app tools (e.g. an integration\n * action). Only consulted for names {@link isOtherExecutableTool} accepts. */\n executeOtherTool?: (call: LoopToolCall, ctx: AppToolContext) => Promise<AppToolOutcome>\n /** Which non-app tool names are executable here. Required if {@link executeOtherTool} is set. */\n isOtherExecutableTool?: (toolName: string) => boolean\n}\n\nexport interface AgentTurnOptions {\n /** The trusted per-turn context (who/where the turn runs as). */\n ctx: AppToolContext\n /** Prior conversation turns, in order. */\n priorMessages?: Array<{ role: string; content: string }>\n /** Override the factory's default system prompt for this turn. */\n systemPrompt?: string\n /** Fires at the real side-effect site for each produced proposal/artifact. */\n onProduced?: (event: AppToolProducedEvent) => void\n}\n\nexport interface AgentRuntime {\n /** Run the bounded tool loop to completion; resolve with final text + every\n * executed tool outcome. */\n run(userMessage: string, turn: AgentTurnOptions): Promise<ToolLoopResult>\n /** Stream the bounded tool loop: yields each raw model event and each executed\n * tool result as it happens (for SSE re-emission + telemetry). */\n stream(userMessage: string, turn: AgentTurnOptions): AsyncGenerator<StreamLoopYield<LoopEvent>, void, unknown>\n}\n\n/**\n * Create an in-process agent runtime for one agent. See the module doc for the\n * full rationale; the short version: it advertises the app tools to the model,\n * dispatches each emitted call against `handlers`, and drives the bounded loop —\n * the whole agent core, sandbox-free.\n */\nexport function createAgentRuntime(opts: CreateAgentRuntimeOptions): AgentRuntime {\n if (opts.executeOtherTool && !opts.isOtherExecutableTool) {\n throw new Error('createAgentRuntime: isOtherExecutableTool is required when executeOtherTool is set')\n }\n\n // Tool schemas + the streamTurn are stable across turns — build once. The\n // model MUST be advertised the tools or it never emits a tool_call (the exact\n // failure that scores a tool-driven agent zero off-sandbox).\n const tools = [...buildAppToolOpenAITools(opts.taxonomy), ...(opts.extraTools ?? [])]\n const m = opts.model\n const streamTurn = createOpenAICompatStreamTurn({\n baseUrl: m.baseUrl,\n apiKey: m.apiKey,\n model: m.model,\n tools,\n temperature: m.temperature,\n fetchImpl: m.fetchImpl,\n extraBody: m.extraBody,\n })\n\n const isExecutableTool = (name: string): boolean =>\n isAppToolName(name) || (opts.isOtherExecutableTool?.(name) ?? false)\n\n const buildExecutor = (turn: AgentTurnOptions) => {\n const appExecutor = createAppToolRuntimeExecutor({\n handlers: opts.handlers,\n taxonomy: opts.taxonomy,\n ctx: turn.ctx,\n onProduced: turn.onProduced,\n })\n return async (call: LoopToolCall): Promise<AppToolOutcome> => {\n if (isAppToolName(call.toolName)) return appExecutor({ toolName: call.toolName, args: call.args })\n if (opts.executeOtherTool && opts.isOtherExecutableTool?.(call.toolName)) {\n return opts.executeOtherTool(call, turn.ctx)\n }\n return { ok: false, code: 'unknown_tool', message: `No executor for tool: ${call.toolName}` }\n }\n }\n\n return {\n run(userMessage, turn) {\n return runAppToolLoop({\n systemPrompt: turn.systemPrompt ?? opts.systemPrompt,\n userMessage,\n priorMessages: turn.priorMessages,\n streamTurn,\n executeToolCall: buildExecutor(turn),\n isExecutableTool,\n maxToolTurns: opts.maxToolTurns,\n })\n },\n stream(userMessage, turn) {\n return streamAppToolLoop<LoopEvent>({\n systemPrompt: turn.systemPrompt ?? opts.systemPrompt,\n userMessage,\n priorMessages: turn.priorMessages,\n streamTurn,\n extractText: (ev) => (ev.type === 'text' ? ev.text : ''),\n extractToolCall: (ev) => (ev.type === 'tool_call' ? ev.call : null),\n isExecutableTool,\n executeToolCall: buildExecutor(turn),\n maxToolTurns: opts.maxToolTurns,\n })\n },\n }\n}\n","/**\n * Surface-scoped profile overlay — the seam letting any product page (a\n * sequence editor, a brief composer, a dataset view) add MCP servers, a prompt\n * addendum, and permission tightening to the workspace agent profile for turns\n * initiated FROM that surface, without the chat orchestrator knowing any\n * surface's specifics. The orchestrator resolves `(kind, ctx)` through a\n * registry the REQUEST HANDLER constructs per request (construction is a Map\n * build — cheap) and merges the result into the base profile it was about to\n * send to the sandbox. Per-request construction is the trust mechanism, not an\n * optimization target: each `build()` closes over server-trusted request state\n * (env bindings, secrets, the AUTHENTICATED user/workspace), which on Workers\n * exists only per request — a startup-built registry would force identity\n * through the untrusted client `ctx`.\n *\n * SECURITY INVARIANT: the surface `kind` and the ids inside `ctx` arrive on\n * the client request and are pure ROUTING data — never trusted content, never\n * identity. Identity comes from the closure (see above). The registered\n * `build()` runs server-side only: it validates the routing ids against the\n * product's access control, then mints its own URLs and capability tokens from\n * server configuration (`buildHttpMcpServer` + `createCapabilityToken` in\n * ../tools). A client can therefore never inject an arbitrary MCP url, header,\n * or token into the agent profile: the overlay's `mcp` values are typed as\n * {@link SurfaceMcpServer} (= the server-built `AppToolMcpServer` entry shape),\n * and only build() constructs them.\n */\n\nimport type { AppToolMcpServer } from '../tools/mcp'\n\n/** Sandbox permission posture values, ranked deny > ask > allow for merging. */\nexport type SurfacePermissionValue = 'allow' | 'ask' | 'deny'\n\n/** The only MCP entry shape an overlay may carry: the server-built bridge\n * entry from ../tools/mcp (transport, url, headers, and capability token all\n * assembled server-side). The alias exists so overlay authors reach for the\n * builders in ../tools rather than hand-rolling `{ url: ctx.url }` shapes\n * that would let request data become a dialable endpoint. */\nexport type SurfaceMcpServer = AppToolMcpServer\n\n/** What one surface contributes to the agent profile for a single turn. */\nexport interface SurfaceOverlay {\n /** MCP servers to mount for this turn, keyed by tool-routing name. Names\n * must not collide with the base profile's — see {@link mergeSurfaceOverlay}. */\n mcp?: Record<string, SurfaceMcpServer>\n /** Appended to the base system-prompt addendum with a blank-line separator. */\n promptAddendum?: string\n /** Per-key posture the surface wants for its turns. Merging is monotone\n * fail-closed: the stricter of base/overlay wins, so a surface can tighten\n * the workspace posture but never relax it. */\n permissions?: Record<string, SurfacePermissionValue>\n}\n\n/**\n * One registered surface kind. `TCtx` is the shape build() expects — a CLAIM\n * about the client payload, not a guarantee: the registry hands build() the\n * request's `ctx` unvalidated, so build() must treat every field as an\n * untrusted id (resolve it through access control that throws on a bad or\n * foreign id) before minting anything from it.\n */\nexport interface SurfaceKindDefinition<TCtx> {\n kind: string\n build: (ctx: TCtx) => SurfaceOverlay | Promise<SurfaceOverlay>\n}\n\n/** The variance-erased form a registry accepts (`build` is contravariant in\n * `TCtx`, so every concrete definition is assignable to this). */\nexport type AnySurfaceKind = SurfaceKindDefinition<never>\n\n/**\n * Declare one surface kind. The `kind` string is the client-visible routing\n * key (e.g. `'sequences'`); `build` is the server-side factory that turns a\n * validated ctx into the overlay for one turn.\n */\nexport function defineSurfaceKind<TCtx>(opts: {\n kind: string\n build: (ctx: TCtx) => SurfaceOverlay | Promise<SurfaceOverlay>\n}): SurfaceKindDefinition<TCtx> {\n if (typeof opts.kind !== 'string' || opts.kind.length === 0 || /\\s/.test(opts.kind)) {\n throw new Error(`surface kind must be a non-empty string without whitespace (got ${JSON.stringify(opts.kind)})`)\n }\n if (typeof opts.build !== 'function') {\n throw new Error(`surface kind '${opts.kind}' requires a build function`)\n }\n return { kind: opts.kind, build: opts.build }\n}\n\nexport interface SurfaceRegistry {\n /** Build the overlay for one turn. Throws on an unknown kind — an unknown\n * surface is a routing bug (client and server registries drifted), and\n * silently returning an empty overlay would strip the surface's tools from\n * the turn with no signal anywhere. Build errors propagate unwrapped. */\n resolve(kind: string, ctx: unknown): Promise<SurfaceOverlay>\n}\n\n/**\n * Assemble the product's surface registry from its registered kinds. Duplicate\n * kinds throw at construction: two builders behind one routing key would make\n * the mounted toolset depend on registration order.\n */\nexport function createSurfaceRegistry(kinds: readonly AnySurfaceKind[]): SurfaceRegistry {\n const byKind = new Map<string, AnySurfaceKind>()\n for (const definition of kinds) {\n if (byKind.has(definition.kind)) {\n throw new Error(`duplicate surface kind '${definition.kind}' — each kind must be registered exactly once`)\n }\n byKind.set(definition.kind, definition)\n }\n\n return {\n async resolve(kind, ctx) {\n const definition = byKind.get(kind)\n if (!definition) {\n const known = [...byKind.keys()].join(', ') || '(none)'\n throw new Error(\n `unknown surface kind '${kind}' — registered kinds: ${known}. ` +\n 'An unknown surface is a routing bug: register the kind via defineSurfaceKind before clients can reference it.',\n )\n }\n // The trust boundary where static ctx typing ends: the request payload is\n // handed to build() as-is, and build() validates it (see SurfaceKindDefinition).\n const overlay = await definition.build(ctx as never)\n assertSurfaceOverlay(overlay, `surface kind '${kind}'`)\n return overlay\n },\n }\n}\n\n/** Base-profile slice the merge reads/writes. Real callers pass their full\n * profile object; every field outside this slice passes through untouched. */\nexport interface SurfaceMergeBase {\n mcp?: Record<string, unknown>\n systemPromptAddendum?: string\n permissions?: Record<string, SurfacePermissionValue>\n}\n\nconst PERMISSION_SEVERITY: Record<SurfacePermissionValue, number> = { allow: 0, ask: 1, deny: 2 }\n\n/**\n * Merge one surface overlay into a base profile, returning a new object\n * (the base is never mutated; untouched nested records are shared by\n * reference).\n *\n * - `mcp`: overlay servers are added under their own names. A name already\n * present on the base THROWS — a collision is two servers claiming one\n * routing name, and renaming either silently would corrupt tool routing for\n * whichever caller expected the original binding.\n * - `systemPromptAddendum`: the overlay's `promptAddendum` appends after a\n * blank-line separator (no separator when the base has no addendum).\n * - `permissions`: per key the STRICTER value wins (deny > ask > allow). A\n * surface can tighten the base posture for its turns; a base 'deny' survives\n * any overlay.\n */\nexport function mergeSurfaceOverlay<TBase extends SurfaceMergeBase>(\n base: TBase,\n overlay: SurfaceOverlay,\n): TBase & SurfaceMergeBase {\n assertSurfaceOverlay(overlay, 'surface overlay')\n const merged: SurfaceMergeBase = { ...base }\n\n if (overlay.mcp && Object.keys(overlay.mcp).length > 0) {\n const baseMcp = base.mcp ?? {}\n const collisions = Object.keys(overlay.mcp).filter((name) => name in baseMcp)\n if (collisions.length > 0) {\n throw new Error(\n `surface overlay MCP name collision: ${collisions.map((n) => `'${n}'`).join(', ')} already exist on the base profile. ` +\n 'Two servers cannot claim one name — give the surface server a distinct name.',\n )\n }\n merged.mcp = { ...baseMcp, ...overlay.mcp }\n }\n\n if (overlay.promptAddendum !== undefined) {\n merged.systemPromptAddendum = base.systemPromptAddendum\n ? `${base.systemPromptAddendum}\\n\\n${overlay.promptAddendum}`\n : overlay.promptAddendum\n }\n\n if (overlay.permissions && Object.keys(overlay.permissions).length > 0) {\n const permissions: Record<string, SurfacePermissionValue> = { ...(base.permissions ?? {}) }\n for (const [key, value] of Object.entries(overlay.permissions)) {\n const existing = permissions[key]\n permissions[key] =\n existing === undefined || PERMISSION_SEVERITY[value] > PERMISSION_SEVERITY[existing] ? value : existing\n }\n merged.permissions = permissions\n }\n\n // merged began as a shallow copy of base; only the three slice fields were\n // replaced, so the intersection type is the true shape.\n return merged as TBase & SurfaceMergeBase\n}\n\n/** Reject overlays a build() (or hand-rolled caller) malformed, with the exact\n * field named: a relative MCP url, a blank addendum, or an off-vocabulary\n * permission would otherwise surface only as an opaque sandbox failure. */\nfunction assertSurfaceOverlay(overlay: SurfaceOverlay, label: string): void {\n if (overlay.promptAddendum !== undefined) {\n if (typeof overlay.promptAddendum !== 'string' || overlay.promptAddendum.trim().length === 0) {\n throw new Error(`${label}: promptAddendum must be a non-blank string when provided`)\n }\n }\n if (overlay.mcp !== undefined) {\n for (const [name, server] of Object.entries(overlay.mcp)) {\n if (name.trim().length === 0) throw new Error(`${label}: MCP server names must be non-empty`)\n if (server.transport !== 'http') {\n throw new Error(`${label}: MCP server '${name}' must use transport 'http' (got ${JSON.stringify((server as { transport?: unknown }).transport)})`)\n }\n let parsed: URL\n try {\n parsed = new URL(server.url)\n } catch {\n throw new Error(`${label}: MCP server '${name}' url must be an absolute URL (got ${JSON.stringify(server.url)})`)\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`${label}: MCP server '${name}' url must be http(s) (got ${JSON.stringify(server.url)})`)\n }\n }\n }\n if (overlay.permissions !== undefined) {\n for (const [key, value] of Object.entries(overlay.permissions)) {\n if (!(value in PERMISSION_SEVERITY)) {\n throw new Error(`${label}: permission '${key}' must be 'allow' | 'ask' | 'deny' (got ${JSON.stringify(value)})`)\n }\n }\n }\n}\n","export * from './model-catalog'\nexport * from './model'\nexport * from './openai-stream'\nexport * from './agent'\nexport * from './surface-profile'\n/**\n * The bounded agent tool-loop — the mechanism every app's chat runtime\n * hand-rolls on top of `@tangle-network/agent-runtime`.\n *\n * A model turn may emit tool calls (integration-hub actions, the app tools from\n * `../tools`, delegation). The loop: stream a turn, collect the executable tool\n * calls, stop if there are none / no executor / the turn cap is hit, otherwise\n * execute each, append the results to history in OpenAI function-calling shape,\n * and re-run so the model reads them. Bounded by `maxToolTurns` so a model\n * looping on a failing action can't run forever.\n *\n * The history shape is the OpenAI function-calling contract: the assistant turn\n * that emitted tool calls is preserved as an `assistant` message carrying its\n * `tool_calls` array, and each result is its own `{ role: 'tool', tool_call_id,\n * content }` message keyed to the call. A strict model (Claude, and any model\n * that validates tool history) needs this to read its own tool use back; folding\n * results into a `user` message instead makes such models re-issue the same call\n * in a loop.\n *\n * Substrate-free by design: the app supplies `streamTurn` (wrapping whatever\n * backend / `runAgentTaskStream` it uses) and `executeToolCall` (routing to its\n * integration + app-tool executors). This package owns the LOOP; the app owns\n * the model and the executors.\n *\n * LAYERING NOTE: this turn-level tool-dispatch loop is a generic RUNTIME\n * capability. It has been CONTRIBUTED DOWN and MERGED into\n * `@tangle-network/agent-runtime` as `runToolLoop` / `streamToolLoop` (PR #137),\n * but is not yet PUBLISHED (agent-runtime main is ahead of its last npm release;\n * cutting that release is the agent-runtime maintainer's call). TERMINAL STATE:\n * the moment agent-runtime publishes a version carrying #137, bump the\n * `@tangle-network/agent-runtime` peer-dep here and replace the bodies below with\n * a thin re-export — `streamAppToolLoop = streamToolLoop`, `runAppToolLoop =\n * runToolLoop` (types alias 1:1; `AppToolOutcome` ≡ `ToolCallOutcome`). Kept\n * substrate-free + shipping until then so consumers aren't blocked on the release.\n */\nimport type { AppToolOutcome } from '../tools/types'\n\nexport interface LoopToolCall {\n toolCallId?: string\n toolName: string\n args: Record<string, unknown>\n}\n\n/** One OpenAI-shaped tool-call entry on an assistant message. */\nexport interface LoopAssistantToolCall {\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n}\n\n/**\n * A message in the running conversation the loop sends to `streamTurn`.\n *\n * The base `{ role, content }` covers `system` / `user` / plain `assistant`\n * turns. Two optional fields carry the OpenAI function-calling contract so the\n * model reads its own tool use back correctly instead of re-issuing it:\n *\n * - an assistant turn that emitted tool calls carries `tool_calls`, and its\n * `content` is `null` when the turn was tool-only;\n * - each tool result is its own `{ role: 'tool', tool_call_id, content }`\n * message keyed to the call that produced it.\n *\n * Widening is additive: a `streamTurn` that reads only `role` + `content` still\n * works; one that forwards the whole message to an OpenAI-compatible endpoint\n * now gets correct tool history. */\nexport interface LoopMessage {\n role: string\n content: string | null\n tool_calls?: LoopAssistantToolCall[]\n tool_call_id?: string\n}\n\n/** A tool-call id is required to key a `role: 'tool'` result back to its call.\n * When the model omitted one, derive a stable id from the tool name so the\n * assistant `tool_calls` entry and its `tool` result still match. */\nfunction toolCallId(call: LoopToolCall): string {\n return call.toolCallId ?? `call_${call.toolName}`\n}\n\n/** The assistant turn that emitted `pending`, in OpenAI shape: text content\n * (null when the turn was tool-only) plus its `tool_calls` array. */\nfunction assistantToolCallMessage(turnText: string, pending: LoopToolCall[]): LoopMessage {\n return {\n role: 'assistant',\n content: turnText.trim() || null,\n tool_calls: pending.map((call) => ({\n id: toolCallId(call),\n type: 'function',\n function: { name: call.toolName, arguments: JSON.stringify(call.args) },\n })),\n }\n}\n\n/** One `role: 'tool'` result message keyed to its call by `tool_call_id`. */\nfunction toolResultMessage(call: LoopToolCall, content: string): LoopMessage {\n return { role: 'tool', tool_call_id: toolCallId(call), content }\n}\n\n/** Events a turn stream yields. `text` accumulates into the final answer;\n * `tool_call` is collected for dispatch; `reasoning` and `usage` pass through\n * for UIs that render thinking sections and per-message token/cost metrics.\n * Extra event types pass through untouched (the caller re-emits them to its\n * own UI stream). */\nexport type LoopEvent =\n | { type: 'text'; text: string }\n | { type: 'reasoning'; text: string }\n | { type: 'tool_call'; call: LoopToolCall }\n | { type: 'usage'; usage: { promptTokens: number; completionTokens: number } }\n | { type: 'other'; event: unknown }\n\n/** Why the loop stopped. `completed` = model finished naturally; `stuck-loop` =\n * ≥3 consecutive identical tool calls (same tool + args); `backstop` = hit the\n * runaway-backstop cap (200 by default); `deadline` = wall-clock deadlineMs\n * exceeded; `budget` = maxCostUsd exhausted. Non-`completed` stops are infra /\n * resource outcomes — eval scoring must distinguish them from capability failure. */\nexport type ToolLoopStopReason = 'completed' | 'stuck-loop' | 'backstop' | 'deadline' | 'budget'\n\nexport interface ToolLoopResult {\n /** The model's final text across the loop. */\n finalText: string\n /** Every tool call executed, with its outcome, in order. */\n toolResults: Array<{ call: LoopToolCall; label: string; outcome: AppToolOutcome }>\n /** Number of model turns run (1 + tool-driven re-runs). */\n turns: number\n /** Why the loop stopped. */\n stopReason: ToolLoopStopReason\n /** @deprecated Use `stopReason !== 'completed'` instead. */\n cappedOut: boolean\n}\n\nexport interface AppToolLoopOptions {\n systemPrompt: string\n userMessage: string\n priorMessages?: Array<{ role: string; content: string }>\n /** Stream one model turn over the running message list. The app wraps its\n * backend here. Messages follow {@link LoopMessage}: a tool-calling assistant\n * turn carries `tool_calls`, and each tool result is a `role: 'tool'` message.\n * A backend that reads only `role` + `content` is unaffected. */\n streamTurn: (messages: LoopMessage[]) => AsyncIterable<LoopEvent>\n /** Execute one tool call. The app routes to its integration executor / app-tool\n * executor and returns the outcome. */\n executeToolCall: (call: LoopToolCall) => Promise<AppToolOutcome>\n /** Which emitted tool names are executable (others are ignored — e.g. a UI-only\n * tool the app renders but doesn't run here). */\n isExecutableTool: (toolName: string) => boolean\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow.\n * For per-workflow limits use `maxCostUsd` or `deadlineMs` instead. */\n maxToolTurns?: number\n /** Wall-clock deadline in ms since epoch (Date.now()-based). When exceeded the\n * loop stops with stopReason `deadline`. */\n deadlineMs?: number\n /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */\n maxCostUsd?: number\n /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */\n costOf?: (call: LoopToolCall, outcome: AppToolOutcome) => number\n /** Render one tool outcome as the `content` of its `role: 'tool'` message.\n * Default is a compact `<label> → ok/failed: …`. */\n renderResult?: (label: string, outcome: AppToolOutcome) => string\n /** Map a tool call to the label its result is keyed under (default: toolName). */\n labelFor?: (call: LoopToolCall) => string\n}\n\n/** Runaway-backstop: stops an infinite tool loop where cost is unmetered. Set\n * far above any legitimate workflow — this is a watchdog, not a policy cap. */\nconst RUNAWAY_BACKSTOP_TURNS = 200\n/** Consecutive identical calls (same tool + canonical-JSON args) that trigger\n * stuck-loop detection. The window resets on any different call. */\nconst STUCK_LOOP_THRESHOLD = 3\n\n/** Canonical identifier for a tool call used by stuck-loop detection.\n * Keys are sorted so {b:1,a:2} and {a:2,b:1} produce the same hash. */\nfunction canonicalCallHash(call: LoopToolCall): string {\n const sortedArgs = Object.fromEntries(\n Object.entries(call.args).sort(([a], [b]) => a.localeCompare(b)),\n )\n return `${call.toolName}:${JSON.stringify(sortedArgs)}`\n}\n\nfunction defaultRender(label: string, outcome: AppToolOutcome): string {\n if (outcome.ok) return `${label} → ok: ${JSON.stringify(outcome.result)}`\n return `${label} → failed (${outcome.code}): ${outcome.message}`\n}\n\n/**\n * Run the bounded tool loop and return the final text + every executed tool\n * outcome. Yields nothing — it's an awaitable driver; callers that need to\n * re-emit events to a UI stream should do so inside `streamTurn`. (A streaming\n * variant can wrap this later; keeping the core awaitable makes it trivially\n * testable.)\n */\nexport async function runAppToolLoop(opts: AppToolLoopOptions): Promise<ToolLoopResult> {\n const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS\n const render = opts.renderResult ?? defaultRender\n const labelFor = opts.labelFor ?? ((c: LoopToolCall) => c.toolName)\n\n const messages: LoopMessage[] = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.priorMessages ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n\n const toolResults: ToolLoopResult['toolResults'] = []\n let finalText = ''\n let turns = 0\n let accumulatedCostUsd = 0\n let lastCallHash: string | null = null\n let consecutiveCount = 0\n\n for (let toolTurn = 0; ; toolTurn++) {\n turns++\n\n // Wall-clock deadline check before every new turn.\n if (opts.deadlineMs !== undefined && Date.now() >= opts.deadlineMs) {\n return { finalText, toolResults, turns, stopReason: 'deadline', cappedOut: true }\n }\n\n let turnText = ''\n const pending: LoopToolCall[] = []\n\n for await (const ev of opts.streamTurn([...messages])) {\n if (ev.type === 'text') {\n turnText += ev.text\n finalText += ev.text\n } else if (ev.type === 'tool_call' && opts.isExecutableTool(ev.call.toolName)) {\n pending.push(ev.call)\n }\n }\n\n if (pending.length === 0) break\n\n // Runaway backstop — the model keeps emitting calls past the safety ceiling.\n if (toolTurn >= backstop) {\n return { finalText, toolResults, turns, stopReason: 'backstop', cappedOut: true }\n }\n\n // The assistant turn that emitted the calls — with its tool_calls array —\n // so the model sees its own tool use in history.\n messages.push(assistantToolCallMessage(turnText, pending))\n\n for (const call of pending) {\n // Stuck-loop detection.\n const callHash = canonicalCallHash(call)\n if (callHash === lastCallHash) {\n consecutiveCount++\n } else {\n lastCallHash = callHash\n consecutiveCount = 1\n }\n if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {\n return { finalText, toolResults, turns, stopReason: 'stuck-loop', cappedOut: true }\n }\n\n let outcome: AppToolOutcome\n try {\n outcome = await opts.executeToolCall(call)\n } catch (err) {\n outcome = { ok: false, code: 'executor_error', message: err instanceof Error ? err.message : String(err) }\n }\n\n // Budget check after each tool call.\n if (opts.maxCostUsd !== undefined && opts.costOf !== undefined) {\n accumulatedCostUsd += opts.costOf(call, outcome)\n if (accumulatedCostUsd >= opts.maxCostUsd) {\n const label = labelFor(call)\n toolResults.push({ call, label, outcome })\n messages.push(toolResultMessage(call, render(label, outcome)))\n return { finalText, toolResults, turns, stopReason: 'budget', cappedOut: true }\n }\n }\n\n const label = labelFor(call)\n toolResults.push({ call, label, outcome })\n // One role:'tool' message per result, keyed to its call by tool_call_id.\n messages.push(toolResultMessage(call, render(label, outcome)))\n }\n }\n\n return { finalText, toolResults, turns, stopReason: 'completed', cappedOut: false }\n}\n\n// ── Streaming variant ──────────────────────────────────────────────────────\n//\n// `runAppToolLoop` is awaitable — perfect for tests and drain-only callers. A\n// real chat runtime instead needs to STREAM each model event to the client (SSE)\n// AND record telemetry per event as it happens. `streamAppToolLoop` is the same\n// bounded loop as an async generator: it yields every raw turn event (the app\n// maps + telemetries + re-emits it) and every executed tool result (same), while\n// owning the loop control flow (collect → stop/dispatch → append → re-run, capped).\n// `Raw` is the app's own runtime-event type — this package stays substrate-free.\n\nexport type StreamLoopYield<Raw> =\n | { kind: 'event'; event: Raw }\n | { kind: 'tool_result'; toolName: string; toolCallId?: string; label: string; outcome: AppToolOutcome }\n | { kind: 'capped'; pending: number; stopReason: Exclude<ToolLoopStopReason, 'completed'> }\n\nexport interface StreamAppToolLoopOptions<Raw> {\n systemPrompt: string\n userMessage: string\n priorMessages?: Array<{ role: string; content: string }>\n /** Stream one model turn (the app wraps its backend / runAgentTaskStream).\n * Messages follow {@link LoopMessage}: a tool-calling assistant turn carries\n * `tool_calls`, and each tool result is a `role: 'tool'` message. */\n streamTurn: (messages: LoopMessage[]) => AsyncIterable<Raw>\n /** Text contribution of a raw event, '' if none — used to record the\n * assistant's turn so the next turn has its context. */\n extractText: (event: Raw) => string\n /** The tool call a raw event represents, or null. */\n extractToolCall: (event: Raw) => LoopToolCall | null\n /** Which tool names are executable here (others pass through, unexecuted). */\n isExecutableTool: (toolName: string) => boolean\n /** Execute one call — the app routes to its integration / app-tool executor. */\n executeToolCall: (call: LoopToolCall) => Promise<AppToolOutcome>\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow. */\n maxToolTurns?: number\n /** Wall-clock deadline in ms since epoch (Date.now()-based). */\n deadlineMs?: number\n /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */\n maxCostUsd?: number\n /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */\n costOf?: (call: LoopToolCall, outcome: AppToolOutcome) => number\n renderResult?: (label: string, outcome: AppToolOutcome) => string\n labelFor?: (call: LoopToolCall) => string\n}\n\n/**\n * The streaming bounded tool loop. Yields `event` for each raw turn event and\n * `tool_result` for each executed tool; emits a single `capped` (with stopReason)\n * when it stops for any non-completed reason. The app drives telemetry + UI\n * emission off the yielded items.\n */\nexport async function* streamAppToolLoop<Raw>(opts: StreamAppToolLoopOptions<Raw>): AsyncGenerator<StreamLoopYield<Raw>, void, unknown> {\n const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS\n const render = opts.renderResult ?? defaultRender\n const labelFor = opts.labelFor ?? ((c: LoopToolCall) => c.toolName)\n\n const messages: LoopMessage[] = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.priorMessages ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n\n let accumulatedCostUsd = 0\n let lastCallHash: string | null = null\n let consecutiveCount = 0\n\n for (let toolTurn = 0; ; toolTurn++) {\n // Wall-clock deadline check before every new turn.\n if (opts.deadlineMs !== undefined && Date.now() >= opts.deadlineMs) {\n yield { kind: 'capped', pending: 0, stopReason: 'deadline' }\n return\n }\n\n let turnText = ''\n const pending: LoopToolCall[] = []\n\n for await (const event of opts.streamTurn([...messages])) {\n yield { kind: 'event', event }\n turnText += opts.extractText(event)\n const call = opts.extractToolCall(event)\n if (call && opts.isExecutableTool(call.toolName)) pending.push(call)\n }\n\n if (pending.length === 0) return\n\n // Runaway backstop.\n if (toolTurn >= backstop) {\n yield { kind: 'capped', pending: pending.length, stopReason: 'backstop' }\n return\n }\n\n // The assistant turn that emitted the calls — with its tool_calls array.\n messages.push(assistantToolCallMessage(turnText, pending))\n\n for (const call of pending) {\n // Stuck-loop detection.\n const callHash = canonicalCallHash(call)\n if (callHash === lastCallHash) {\n consecutiveCount++\n } else {\n lastCallHash = callHash\n consecutiveCount = 1\n }\n if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {\n yield { kind: 'capped', pending: pending.length, stopReason: 'stuck-loop' }\n return\n }\n\n let outcome: AppToolOutcome\n try {\n outcome = await opts.executeToolCall(call)\n } catch (err) {\n outcome = { ok: false, code: 'executor_error', message: err instanceof Error ? err.message : String(err) }\n }\n\n // Budget check after each tool call.\n if (opts.maxCostUsd !== undefined && opts.costOf !== undefined) {\n accumulatedCostUsd += opts.costOf(call, outcome)\n if (accumulatedCostUsd >= opts.maxCostUsd) {\n const label = labelFor(call)\n yield { kind: 'tool_result', toolName: call.toolName, toolCallId: call.toolCallId, label, outcome }\n messages.push(toolResultMessage(call, render(label, outcome)))\n yield { kind: 'capped', pending: pending.length, stopReason: 'budget' }\n return\n }\n }\n\n const label = labelFor(call)\n yield { kind: 'tool_result', toolName: call.toolName, toolCallId: call.toolCallId, label, outcome }\n // One role:'tool' message per result, keyed to its call by tool_call_id.\n messages.push(toolResultMessage(call, render(label, outcome)))\n }\n }\n}\n"],"mappings":";;;;;;;AA6DA,IAAM,gBAA0B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,IAAM,cAAc;AAQpB,IAAM,iBAAgE;AAAA,EACpE,EAAE,WAAW,CAAC,WAAW,GAAG,OAAO,yBAAyB;AAAA,EAC5D,EAAE,WAAW,CAAC,WAAW,GAAG,OAAO,uBAAuB;AAAA,EAC1D,EAAE,WAAW,CAAC,WAAW,GAAG,OAAO,wBAAwB;AAAA,EAC3D,EAAE,WAAW,CAAC,QAAQ,GAAG,OAAO,oBAAoB;AAAA,EACpD,EAAE,WAAW,CAAC,QAAQ,GAAG,OAAO,yBAAyB;AAAA,EACzD,EAAE,WAAW,CAAC,QAAQ,GAAG,OAAO,iCAAiC;AAAA,EACjE,EAAE,WAAW,CAAC,QAAQ,GAAG,OAAO,mCAAmC;AAAA,EACnE,EAAE,WAAW,CAAC,KAAK,GAAG,OAAO,gBAAgB;AAAA,EAC7C,EAAE,WAAW,CAAC,UAAU,GAAG,OAAO,mCAAmC;AAAA,EACrE,EAAE,WAAW,CAAC,cAAc,UAAU,GAAG,OAAO,iBAAiB;AAAA,EACjE,EAAE,WAAW,CAAC,OAAO,MAAM,GAAG,OAAO,eAAe;AAAA,EACpD,EAAE,WAAW,CAAC,SAAS,GAAG,OAAO,oCAAoC;AACvE;AAIA,IAAM,sBAAsB;AAGrB,SAAS,iBAAiB,IAAoB;AACnD,MAAI,OAAO,GAAG,MAAM,GAAG,EAAE,IAAI,KAAK;AAClC,SAAO,KAAK,QAAQ,UAAU,EAAE;AAChC,SAAO,KAAK,QAAQ,WAAW,EAAE;AACjC,SAAO,KAAK,QAAQ,uBAAuB,EAAE;AAC7C,SAAO;AACT;AAGA,SAAS,UAAU,QAA0B;AAC3C,UAAQ,OAAO,MAAM,MAAM,KAAK,CAAC,GAAG,IAAI,MAAM;AAChD;AAEA,SAAS,gBAAgB,GAAa,GAAqB;AACzD,QAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AACvC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAM,KAAK,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC,KAAK;AAClC,QAAI,MAAM,EAAG,QAAO;AAAA,EACtB;AACA,SAAO;AACT;AAGA,SAAS,aAAa,IAAoB;AACxC,MAAI,IAAI;AACR,MAAI,GAAG,SAAS,GAAG,EAAG,MAAK;AAC3B,MAAI,8BAA8B,KAAK,GAAG,QAAQ,UAAU,EAAE,CAAC,EAAG,MAAK;AACvE,MAAI,GAAG,SAAS,OAAO,EAAG,MAAK;AAC/B,SAAO;AACT;AAEA,SAAS,aAAa,UAA0B;AAC9C,QAAM,IAAI,cAAc,QAAQ,QAAQ;AACxC,SAAO,MAAM,KAAK,cAAc,SAAS;AAC3C;AAEA,SAAS,YAAY,GAAyB;AAC5C,QAAM,OAAO,EAAE;AACf,MAAI,CAAC,MAAM,oBAAoB,CAAC,MAAM,kBAAmB,QAAO;AAChE,SAAO,KAAK,iBAAiB,SAAS,MAAM,KAAK,KAAK,kBAAkB,SAAS,MAAM;AACzF;AAEA,SAAS,YAAY,GAAyB;AAC5C,SAAO,EAAE,cAAc,cAAc,SAAS,EAAE,cAAc,WAAW;AAC3E;AAEA,SAAS,SAAS,QAAwB;AACxC,SAAO,OAAO,QAAQ,WAAW,EAAE,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,MAAM,EAAE;AAC3E;AAMO,SAAS,aAAa,KAAoB,MAAoD;AAEnG,QAAM,aAAa,IAAI;AAAA,IACrB,CAAC,MAAM,EAAE,MAAM,YAAY,CAAC,KAAK,YAAY,CAAC,KAAK,CAAC,YAAY,KAAK,iBAAiB,EAAE,EAAE,CAAC;AAAA,EAC7F;AAKA,QAAM,SAAS,oBAAI,IAA2B;AAC9C,aAAW,KAAK,YAAY;AAC1B,UAAM,MAAM,GAAG,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,EAAE,CAAC;AAC3D,UAAM,IAAI,OAAO,IAAI,GAAG;AACxB,QAAI,EAAG,GAAE,KAAK,CAAC;AAAA,QACV,QAAO,IAAI,KAAK,CAAC,CAAC,CAAC;AAAA,EAC1B;AAEA,QAAM,OAAiF,CAAC;AACxF,aAAW,SAAS,OAAO,OAAO,GAAG;AACnC,UAAM,KAAK,CAAC,GAAG,MAAM,aAAa,EAAE,EAAE,IAAI,aAAa,EAAE,EAAE,KAAK,EAAE,GAAG,SAAS,EAAE,GAAG,MAAM;AACzF,UAAM,MAAM,MAAM,CAAC;AACnB,UAAM,eAAe,IAAI,IAAY,MAAM,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC;AACvF,SAAK,KAAK,EAAE,OAAO,KAAK,QAAQ,iBAAiB,IAAI,EAAE,GAAG,aAAa,CAAC;AAAA,EAC1E;AAGA,QAAM,cAAwB,CAAC;AAC/B,aAAW,QAAQ,gBAAgB;AACjC,UAAM,UAAU,KAAK;AAAA,MACnB,CAAC,MACC,KAAK,UAAU,SAAS,EAAE,MAAM,aAAa,EAAE,KAC/C,KAAK,MAAM,KAAK,EAAE,MAAM,KACxB,CAAC,YAAY,SAAS,EAAE,MAAM,EAAE;AAAA,IACpC;AACA,QAAI,CAAC,QAAQ,OAAQ;AACrB,YAAQ;AAAA,MACN,CAAC,GAAG,MACF,gBAAgB,UAAU,EAAE,MAAM,GAAG,UAAU,EAAE,MAAM,CAAC,KACxD,OAAO,EAAE,OAAO,SAAS,SAAS,CAAC,IAAI,OAAO,EAAE,OAAO,SAAS,SAAS,CAAC,KAC1E,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,GAAG;AAAA,IACnC;AACA,gBAAY,KAAK,QAAQ,CAAC,EAAG,MAAM,EAAE;AAAA,EACvC;AAEA,QAAM,iBAAiB,CAAC,MAA2C;AACjE,UAAM,IAAI,EAAE;AACZ,UAAM,WAAW,EAAE,aAAa;AAChC,WAAO;AAAA,MACL,IAAI,EAAE;AAAA,MACN,MAAM,EAAE,QAAQ,EAAE;AAAA,MAClB;AAAA,MACA,aAAa,EAAE,cAAc,EAAE,YAAY,MAAM,GAAG,GAAG,IAAI;AAAA,MAC3D,eAAe,EAAE;AAAA,MACjB,SACE,EAAE,SAAS,UAAU,EAAE,SAAS,aAC5B,EAAE,QAAQ,EAAE,QAAQ,UAAU,QAAW,YAAY,EAAE,QAAQ,cAAc,OAAU,IACvF;AAAA,MACN,eAAe,EAAE,aAAa,IAAI,OAAO,KAAK,oBAAoB,KAAK,EAAE,MAAM;AAAA,MAC/E,mBAAmB,EAAE,aAAa,IAAI,WAAW,KAAK,EAAE,aAAa,IAAI,mBAAmB;AAAA,MAC5F,UAAU,YAAY,SAAS,EAAE,EAAE;AAAA,IACrC;AAAA,EACF;AAGA,QAAM,WAAW,YACd,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,MAAM,EAAE,MAAM,OAAO,EAAE,CAAE,EAChD,IAAI,cAAc;AACrB,QAAM,OAAO,KACV,OAAO,CAAC,MAAM,CAAC,YAAY,SAAS,EAAE,MAAM,EAAE,CAAC,EAC/C,KAAK,CAAC,GAAG,MAAM;AACd,UAAM,KAAK,aAAa,EAAE,MAAM,aAAa,EAAE;AAC/C,UAAM,KAAK,aAAa,EAAE,MAAM,aAAa,EAAE;AAC/C,QAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,UAAM,KAAK,SAAS,EAAE,MAAM;AAC5B,UAAM,KAAK,SAAS,EAAE,MAAM;AAC5B,QAAI,OAAO,GAAI,QAAO,GAAG,cAAc,EAAE;AACzC,WAAO,gBAAgB,UAAU,EAAE,MAAM,GAAG,UAAU,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,EAAE,MAAM,EAAE;AAAA,EACzG,CAAC,EACA,IAAI,cAAc;AAErB,QAAM,SAAS,CAAC,GAAG,UAAU,GAAG,IAAI;AAEpC,QAAM,YAAY,MAAM;AACxB,QAAM,iBACH,aAAa,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,aAAa,iBAAiB,EAAE,EAAE,MAAM,iBAAiB,SAAS,CAAC,GAAG,MAChH,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,GAAG,MACvC,OAAO,CAAC,GAAG,MACX;AAEF,SAAO,EAAE,gBAAgB,YAAW,oBAAI,KAAK,GAAE,YAAY,GAAG,OAAO;AACvE;AAIA,IAAM,iBAAiB,IAAI,KAAK;AAEhC,IAAI,SAAuD;AAO3D,eAAsB,kBAAkB,KAId;AACxB,MAAI,UAAU,KAAK,IAAI,IAAI,OAAO,KAAK,gBAAgB;AACrD,WAAO,OAAO;AAAA,EAChB;AACA,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,IAAI,OAAO,WAAW;AAAA,MAC/C,SAAS,EAAE,eAAe,UAAU,IAAI,MAAM,GAAG;AAAA,IACnD,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,2BAA2B,IAAI,MAAM,EAAE;AACpE,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,UAAM,UAAU,aAAa,KAAK,QAAQ,CAAC,GAAG,EAAE,kBAAkB,IAAI,iBAAiB,CAAC;AACxF,aAAS,EAAE,SAAS,IAAI,KAAK,IAAI,EAAE;AACnC,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,OAAQ,QAAO,OAAO;AAC1B,UAAM;AAAA,EACR;AACF;AAGO,SAAS,sBAA4B;AAC1C,WAAS;AACX;;;AC5OA,gBAAuB,aAAa,QAAoE;AACtG,QAAM,QAAQ,oBAAI,IAA6B;AAC/C,mBAAiB,SAAS,QAAQ;AAGhC,QAAI,MAAM,OAAO,iBAAiB,QAAQ,MAAM,OAAO,qBAAqB,MAAM;AAChF,YAAM;AAAA,QACJ,MAAM;AAAA,QACN,OAAO;AAAA,UACL,cAAc,MAAM,MAAM,iBAAiB;AAAA,UAC3C,kBAAkB,MAAM,MAAM,qBAAqB;AAAA,QACrD;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAS,MAAM,UAAU,CAAC;AAChC,QAAI,CAAC,OAAQ;AACb,UAAM,UAAU,OAAO,OAAO;AAC9B,QAAI,QAAS,OAAM,EAAE,MAAM,QAAQ,MAAM,QAAQ;AACjD,UAAM,YAAY,OAAO,OAAO,qBAAqB,OAAO,OAAO;AACnE,QAAI,UAAW,OAAM,EAAE,MAAM,aAAa,MAAM,UAAU;AAC1D,eAAW,MAAM,OAAO,OAAO,cAAc,CAAC,GAAG;AAC/C,YAAM,MAAM,MAAM,IAAI,GAAG,KAAK,KAAK,EAAE,MAAM,IAAI,MAAM,GAAG;AACxD,UAAI,GAAG,GAAI,KAAI,KAAK,GAAG;AACvB,UAAI,GAAG,UAAU,KAAM,KAAI,QAAQ,GAAG,SAAS;AAC/C,UAAI,GAAG,UAAU,UAAW,KAAI,QAAQ,GAAG,SAAS;AACpD,YAAM,IAAI,GAAG,OAAO,GAAG;AAAA,IACzB;AAAA,EACF;AACA,aAAW,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,MAAM,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG;AACpE,QAAI,CAAC,EAAE,KAAM;AACb,UAAM,EAAE,MAAM,aAAa,MAAM,EAAE,YAAY,EAAE,IAAI,UAAU,EAAE,MAAM,MAAM,UAAU,EAAE,IAAI,EAAE,EAAyB;AAAA,EAC1H;AACF;AAEA,SAAS,UAAU,GAAoC;AACrD,MAAI,CAAC,EAAE,KAAK,EAAG,QAAO,CAAC;AACvB,MAAI;AACF,UAAM,IAAI,KAAK,MAAM,CAAC;AACtB,WAAO,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC,IAAK,IAAgC,CAAC;AAAA,EAC7F,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAyBO,SAAS,6BACd,MACuD;AACvD,QAAM,OAAO,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AAC5C,QAAM,UAAU,KAAK,aAAa;AAClC,SAAO,CAAC,aACN;AAAA,IACE,sBAAsB,SAAS,GAAG,IAAI,qBAAqB,KAAK,QAAQ;AAAA,MACtE,OAAO,KAAK;AAAA,MACZ;AAAA,MACA,QAAQ;AAAA,MACR,gBAAgB,EAAE,eAAe,KAAK;AAAA,MACtC,GAAI,KAAK,SAAS,KAAK,MAAM,SAAS,IAAI,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,MACnE,GAAI,KAAK,eAAe,OAAO,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,MACpE,GAAG,KAAK;AAAA,IACV,CAAC;AAAA,EACH;AACJ;AAIA,gBAAgB,sBACd,SACA,KACA,QACA,MACkC;AAClC,QAAM,MAAM,MAAM,QAAQ,KAAK;AAAA,IAC7B,QAAQ;AAAA,IACR,SAAS,EAAE,eAAe,UAAU,MAAM,IAAI,gBAAgB,oBAAoB,QAAQ,oBAAoB;AAAA,IAC9G,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AACD,MAAI,CAAC,IAAI,MAAM,CAAC,IAAI,MAAM;AACxB,UAAM,OAAO,IAAI,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE,IAAI;AAC3D,UAAM,IAAI,MAAM,qCAAqC,IAAI,MAAM,IAAI,OAAO,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,EAAE;AAAA,EAC5G;AACA,QAAM,SAAS,IAAI,KAAK,UAAU;AAClC,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,SAAS;AACb,aAAS;AACP,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,QAAI,KAAM;AACV,cAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAChD,UAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,aAAS,MAAM,IAAI,KAAK;AACxB,eAAW,QAAQ,OAAO;AACxB,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,CAAC,QAAQ,WAAW,OAAO,EAAG;AAClC,YAAM,OAAO,QAAQ,MAAM,CAAC,EAAE,KAAK;AACnC,UAAI,SAAS,SAAU;AACvB,UAAI;AACF,cAAM,KAAK,MAAM,IAAI;AAAA,MACvB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;;;ACnEO,SAAS,mBAAmB,MAA+C;AAChF,MAAI,KAAK,oBAAoB,CAAC,KAAK,uBAAuB;AACxD,UAAM,IAAI,MAAM,oFAAoF;AAAA,EACtG;AAKA,QAAM,QAAQ,CAAC,GAAG,wBAAwB,KAAK,QAAQ,GAAG,GAAI,KAAK,cAAc,CAAC,CAAE;AACpF,QAAM,IAAI,KAAK;AACf,QAAM,aAAa,6BAA6B;AAAA,IAC9C,SAAS,EAAE;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,OAAO,EAAE;AAAA,IACT;AAAA,IACA,aAAa,EAAE;AAAA,IACf,WAAW,EAAE;AAAA,IACb,WAAW,EAAE;AAAA,EACf,CAAC;AAED,QAAM,mBAAmB,CAAC,SACxB,cAAc,IAAI,MAAM,KAAK,wBAAwB,IAAI,KAAK;AAEhE,QAAM,gBAAgB,CAAC,SAA2B;AAChD,UAAM,cAAc,6BAA6B;AAAA,MAC/C,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,MACf,KAAK,KAAK;AAAA,MACV,YAAY,KAAK;AAAA,IACnB,CAAC;AACD,WAAO,OAAO,SAAgD;AAC5D,UAAI,cAAc,KAAK,QAAQ,EAAG,QAAO,YAAY,EAAE,UAAU,KAAK,UAAU,MAAM,KAAK,KAAK,CAAC;AACjG,UAAI,KAAK,oBAAoB,KAAK,wBAAwB,KAAK,QAAQ,GAAG;AACxE,eAAO,KAAK,iBAAiB,MAAM,KAAK,GAAG;AAAA,MAC7C;AACA,aAAO,EAAE,IAAI,OAAO,MAAM,gBAAgB,SAAS,yBAAyB,KAAK,QAAQ,GAAG;AAAA,IAC9F;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,aAAa,MAAM;AACrB,aAAO,eAAe;AAAA,QACpB,cAAc,KAAK,gBAAgB,KAAK;AAAA,QACxC;AAAA,QACA,eAAe,KAAK;AAAA,QACpB;AAAA,QACA,iBAAiB,cAAc,IAAI;AAAA,QACnC;AAAA,QACA,cAAc,KAAK;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,IACA,OAAO,aAAa,MAAM;AACxB,aAAO,kBAA6B;AAAA,QAClC,cAAc,KAAK,gBAAgB,KAAK;AAAA,QACxC;AAAA,QACA,eAAe,KAAK;AAAA,QACpB;AAAA,QACA,aAAa,CAAC,OAAQ,GAAG,SAAS,SAAS,GAAG,OAAO;AAAA,QACrD,iBAAiB,CAAC,OAAQ,GAAG,SAAS,cAAc,GAAG,OAAO;AAAA,QAC9D;AAAA,QACA,iBAAiB,cAAc,IAAI;AAAA,QACnC,cAAc,KAAK;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;ACxGO,SAAS,kBAAwB,MAGR;AAC9B,MAAI,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,WAAW,KAAK,KAAK,KAAK,KAAK,IAAI,GAAG;AACnF,UAAM,IAAI,MAAM,mEAAmE,KAAK,UAAU,KAAK,IAAI,CAAC,GAAG;AAAA,EACjH;AACA,MAAI,OAAO,KAAK,UAAU,YAAY;AACpC,UAAM,IAAI,MAAM,iBAAiB,KAAK,IAAI,6BAA6B;AAAA,EACzE;AACA,SAAO,EAAE,MAAM,KAAK,MAAM,OAAO,KAAK,MAAM;AAC9C;AAeO,SAAS,sBAAsB,OAAmD;AACvF,QAAM,SAAS,oBAAI,IAA4B;AAC/C,aAAW,cAAc,OAAO;AAC9B,QAAI,OAAO,IAAI,WAAW,IAAI,GAAG;AAC/B,YAAM,IAAI,MAAM,2BAA2B,WAAW,IAAI,oDAA+C;AAAA,IAC3G;AACA,WAAO,IAAI,WAAW,MAAM,UAAU;AAAA,EACxC;AAEA,SAAO;AAAA,IACL,MAAM,QAAQ,MAAM,KAAK;AACvB,YAAM,aAAa,OAAO,IAAI,IAAI;AAClC,UAAI,CAAC,YAAY;AACf,cAAM,QAAQ,CAAC,GAAG,OAAO,KAAK,CAAC,EAAE,KAAK,IAAI,KAAK;AAC/C,cAAM,IAAI;AAAA,UACR,yBAAyB,IAAI,8BAAyB,KAAK;AAAA,QAE7D;AAAA,MACF;AAGA,YAAM,UAAU,MAAM,WAAW,MAAM,GAAY;AACnD,2BAAqB,SAAS,iBAAiB,IAAI,GAAG;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAUA,IAAM,sBAA8D,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,EAAE;AAiBzF,SAAS,oBACd,MACA,SAC0B;AAC1B,uBAAqB,SAAS,iBAAiB;AAC/C,QAAM,SAA2B,EAAE,GAAG,KAAK;AAE3C,MAAI,QAAQ,OAAO,OAAO,KAAK,QAAQ,GAAG,EAAE,SAAS,GAAG;AACtD,UAAM,UAAU,KAAK,OAAO,CAAC;AAC7B,UAAM,aAAa,OAAO,KAAK,QAAQ,GAAG,EAAE,OAAO,CAAC,SAAS,QAAQ,OAAO;AAC5E,QAAI,WAAW,SAAS,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,uCAAuC,WAAW,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,MAEnF;AAAA,IACF;AACA,WAAO,MAAM,EAAE,GAAG,SAAS,GAAG,QAAQ,IAAI;AAAA,EAC5C;AAEA,MAAI,QAAQ,mBAAmB,QAAW;AACxC,WAAO,uBAAuB,KAAK,uBAC/B,GAAG,KAAK,oBAAoB;AAAA;AAAA,EAAO,QAAQ,cAAc,KACzD,QAAQ;AAAA,EACd;AAEA,MAAI,QAAQ,eAAe,OAAO,KAAK,QAAQ,WAAW,EAAE,SAAS,GAAG;AACtE,UAAM,cAAsD,EAAE,GAAI,KAAK,eAAe,CAAC,EAAG;AAC1F,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,WAAW,GAAG;AAC9D,YAAM,WAAW,YAAY,GAAG;AAChC,kBAAY,GAAG,IACb,aAAa,UAAa,oBAAoB,KAAK,IAAI,oBAAoB,QAAQ,IAAI,QAAQ;AAAA,IACnG;AACA,WAAO,cAAc;AAAA,EACvB;AAIA,SAAO;AACT;AAKA,SAAS,qBAAqB,SAAyB,OAAqB;AAC1E,MAAI,QAAQ,mBAAmB,QAAW;AACxC,QAAI,OAAO,QAAQ,mBAAmB,YAAY,QAAQ,eAAe,KAAK,EAAE,WAAW,GAAG;AAC5F,YAAM,IAAI,MAAM,GAAG,KAAK,2DAA2D;AAAA,IACrF;AAAA,EACF;AACA,MAAI,QAAQ,QAAQ,QAAW;AAC7B,eAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,QAAQ,GAAG,GAAG;AACxD,UAAI,KAAK,KAAK,EAAE,WAAW,EAAG,OAAM,IAAI,MAAM,GAAG,KAAK,sCAAsC;AAC5F,UAAI,OAAO,cAAc,QAAQ;AAC/B,cAAM,IAAI,MAAM,GAAG,KAAK,iBAAiB,IAAI,oCAAoC,KAAK,UAAW,OAAmC,SAAS,CAAC,GAAG;AAAA,MACnJ;AACA,UAAI;AACJ,UAAI;AACF,iBAAS,IAAI,IAAI,OAAO,GAAG;AAAA,MAC7B,QAAQ;AACN,cAAM,IAAI,MAAM,GAAG,KAAK,iBAAiB,IAAI,sCAAsC,KAAK,UAAU,OAAO,GAAG,CAAC,GAAG;AAAA,MAClH;AACA,UAAI,OAAO,aAAa,WAAW,OAAO,aAAa,UAAU;AAC/D,cAAM,IAAI,MAAM,GAAG,KAAK,iBAAiB,IAAI,8BAA8B,KAAK,UAAU,OAAO,GAAG,CAAC,GAAG;AAAA,MAC1G;AAAA,IACF;AAAA,EACF;AACA,MAAI,QAAQ,gBAAgB,QAAW;AACrC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,WAAW,GAAG;AAC9D,UAAI,EAAE,SAAS,sBAAsB;AACnC,cAAM,IAAI,MAAM,GAAG,KAAK,iBAAiB,GAAG,2CAA2C,KAAK,UAAU,KAAK,CAAC,GAAG;AAAA,MACjH;AAAA,IACF;AAAA,EACF;AACF;;;AChJA,SAAS,WAAW,MAA4B;AAC9C,SAAO,KAAK,cAAc,QAAQ,KAAK,QAAQ;AACjD;AAIA,SAAS,yBAAyB,UAAkB,SAAsC;AACxF,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,SAAS,KAAK,KAAK;AAAA,IAC5B,YAAY,QAAQ,IAAI,CAAC,UAAU;AAAA,MACjC,IAAI,WAAW,IAAI;AAAA,MACnB,MAAM;AAAA,MACN,UAAU,EAAE,MAAM,KAAK,UAAU,WAAW,KAAK,UAAU,KAAK,IAAI,EAAE;AAAA,IACxE,EAAE;AAAA,EACJ;AACF;AAGA,SAAS,kBAAkB,MAAoB,SAA8B;AAC3E,SAAO,EAAE,MAAM,QAAQ,cAAc,WAAW,IAAI,GAAG,QAAQ;AACjE;AAoEA,IAAM,yBAAyB;AAG/B,IAAM,uBAAuB;AAI7B,SAAS,kBAAkB,MAA4B;AACrD,QAAM,aAAa,OAAO;AAAA,IACxB,OAAO,QAAQ,KAAK,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAAA,EACjE;AACA,SAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,UAAU,UAAU,CAAC;AACvD;AAEA,SAAS,cAAc,OAAe,SAAiC;AACrE,MAAI,QAAQ,GAAI,QAAO,GAAG,KAAK,eAAU,KAAK,UAAU,QAAQ,MAAM,CAAC;AACvE,SAAO,GAAG,KAAK,mBAAc,QAAQ,IAAI,MAAM,QAAQ,OAAO;AAChE;AASA,eAAsB,eAAe,MAAmD;AACtF,QAAM,WAAW,KAAK,gBAAgB;AACtC,QAAM,SAAS,KAAK,gBAAgB;AACpC,QAAM,WAAW,KAAK,aAAa,CAAC,MAAoB,EAAE;AAE1D,QAAM,WAA0B;AAAA,IAC9B,EAAE,MAAM,UAAU,SAAS,KAAK,aAAa;AAAA,IAC7C,GAAI,KAAK,iBAAiB,CAAC;AAAA,IAC3B,EAAE,MAAM,QAAQ,SAAS,KAAK,YAAY;AAAA,EAC5C;AAEA,QAAM,cAA6C,CAAC;AACpD,MAAI,YAAY;AAChB,MAAI,QAAQ;AACZ,MAAI,qBAAqB;AACzB,MAAI,eAA8B;AAClC,MAAI,mBAAmB;AAEvB,WAAS,WAAW,KAAK,YAAY;AACnC;AAGA,QAAI,KAAK,eAAe,UAAa,KAAK,IAAI,KAAK,KAAK,YAAY;AAClE,aAAO,EAAE,WAAW,aAAa,OAAO,YAAY,YAAY,WAAW,KAAK;AAAA,IAClF;AAEA,QAAI,WAAW;AACf,UAAM,UAA0B,CAAC;AAEjC,qBAAiB,MAAM,KAAK,WAAW,CAAC,GAAG,QAAQ,CAAC,GAAG;AACrD,UAAI,GAAG,SAAS,QAAQ;AACtB,oBAAY,GAAG;AACf,qBAAa,GAAG;AAAA,MAClB,WAAW,GAAG,SAAS,eAAe,KAAK,iBAAiB,GAAG,KAAK,QAAQ,GAAG;AAC7E,gBAAQ,KAAK,GAAG,IAAI;AAAA,MACtB;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,EAAG;AAG1B,QAAI,YAAY,UAAU;AACxB,aAAO,EAAE,WAAW,aAAa,OAAO,YAAY,YAAY,WAAW,KAAK;AAAA,IAClF;AAIA,aAAS,KAAK,yBAAyB,UAAU,OAAO,CAAC;AAEzD,eAAW,QAAQ,SAAS;AAE1B,YAAM,WAAW,kBAAkB,IAAI;AACvC,UAAI,aAAa,cAAc;AAC7B;AAAA,MACF,OAAO;AACL,uBAAe;AACf,2BAAmB;AAAA,MACrB;AACA,UAAI,oBAAoB,sBAAsB;AAC5C,eAAO,EAAE,WAAW,aAAa,OAAO,YAAY,cAAc,WAAW,KAAK;AAAA,MACpF;AAEA,UAAI;AACJ,UAAI;AACF,kBAAU,MAAM,KAAK,gBAAgB,IAAI;AAAA,MAC3C,SAAS,KAAK;AACZ,kBAAU,EAAE,IAAI,OAAO,MAAM,kBAAkB,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAC3G;AAGA,UAAI,KAAK,eAAe,UAAa,KAAK,WAAW,QAAW;AAC9D,8BAAsB,KAAK,OAAO,MAAM,OAAO;AAC/C,YAAI,sBAAsB,KAAK,YAAY;AACzC,gBAAMA,SAAQ,SAAS,IAAI;AAC3B,sBAAY,KAAK,EAAE,MAAM,OAAAA,QAAO,QAAQ,CAAC;AACzC,mBAAS,KAAK,kBAAkB,MAAM,OAAOA,QAAO,OAAO,CAAC,CAAC;AAC7D,iBAAO,EAAE,WAAW,aAAa,OAAO,YAAY,UAAU,WAAW,KAAK;AAAA,QAChF;AAAA,MACF;AAEA,YAAM,QAAQ,SAAS,IAAI;AAC3B,kBAAY,KAAK,EAAE,MAAM,OAAO,QAAQ,CAAC;AAEzC,eAAS,KAAK,kBAAkB,MAAM,OAAO,OAAO,OAAO,CAAC,CAAC;AAAA,IAC/D;AAAA,EACF;AAEA,SAAO,EAAE,WAAW,aAAa,OAAO,YAAY,aAAa,WAAW,MAAM;AACpF;AAoDA,gBAAuB,kBAAuB,MAA0F;AACtI,QAAM,WAAW,KAAK,gBAAgB;AACtC,QAAM,SAAS,KAAK,gBAAgB;AACpC,QAAM,WAAW,KAAK,aAAa,CAAC,MAAoB,EAAE;AAE1D,QAAM,WAA0B;AAAA,IAC9B,EAAE,MAAM,UAAU,SAAS,KAAK,aAAa;AAAA,IAC7C,GAAI,KAAK,iBAAiB,CAAC;AAAA,IAC3B,EAAE,MAAM,QAAQ,SAAS,KAAK,YAAY;AAAA,EAC5C;AAEA,MAAI,qBAAqB;AACzB,MAAI,eAA8B;AAClC,MAAI,mBAAmB;AAEvB,WAAS,WAAW,KAAK,YAAY;AAEnC,QAAI,KAAK,eAAe,UAAa,KAAK,IAAI,KAAK,KAAK,YAAY;AAClE,YAAM,EAAE,MAAM,UAAU,SAAS,GAAG,YAAY,WAAW;AAC3D;AAAA,IACF;AAEA,QAAI,WAAW;AACf,UAAM,UAA0B,CAAC;AAEjC,qBAAiB,SAAS,KAAK,WAAW,CAAC,GAAG,QAAQ,CAAC,GAAG;AACxD,YAAM,EAAE,MAAM,SAAS,MAAM;AAC7B,kBAAY,KAAK,YAAY,KAAK;AAClC,YAAM,OAAO,KAAK,gBAAgB,KAAK;AACvC,UAAI,QAAQ,KAAK,iBAAiB,KAAK,QAAQ,EAAG,SAAQ,KAAK,IAAI;AAAA,IACrE;AAEA,QAAI,QAAQ,WAAW,EAAG;AAG1B,QAAI,YAAY,UAAU;AACxB,YAAM,EAAE,MAAM,UAAU,SAAS,QAAQ,QAAQ,YAAY,WAAW;AACxE;AAAA,IACF;AAGA,aAAS,KAAK,yBAAyB,UAAU,OAAO,CAAC;AAEzD,eAAW,QAAQ,SAAS;AAE1B,YAAM,WAAW,kBAAkB,IAAI;AACvC,UAAI,aAAa,cAAc;AAC7B;AAAA,MACF,OAAO;AACL,uBAAe;AACf,2BAAmB;AAAA,MACrB;AACA,UAAI,oBAAoB,sBAAsB;AAC5C,cAAM,EAAE,MAAM,UAAU,SAAS,QAAQ,QAAQ,YAAY,aAAa;AAC1E;AAAA,MACF;AAEA,UAAI;AACJ,UAAI;AACF,kBAAU,MAAM,KAAK,gBAAgB,IAAI;AAAA,MAC3C,SAAS,KAAK;AACZ,kBAAU,EAAE,IAAI,OAAO,MAAM,kBAAkB,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAC3G;AAGA,UAAI,KAAK,eAAe,UAAa,KAAK,WAAW,QAAW;AAC9D,8BAAsB,KAAK,OAAO,MAAM,OAAO;AAC/C,YAAI,sBAAsB,KAAK,YAAY;AACzC,gBAAMA,SAAQ,SAAS,IAAI;AAC3B,gBAAM,EAAE,MAAM,eAAe,UAAU,KAAK,UAAU,YAAY,KAAK,YAAY,OAAAA,QAAO,QAAQ;AAClG,mBAAS,KAAK,kBAAkB,MAAM,OAAOA,QAAO,OAAO,CAAC,CAAC;AAC7D,gBAAM,EAAE,MAAM,UAAU,SAAS,QAAQ,QAAQ,YAAY,SAAS;AACtE;AAAA,QACF;AAAA,MACF;AAEA,YAAM,QAAQ,SAAS,IAAI;AAC3B,YAAM,EAAE,MAAM,eAAe,UAAU,KAAK,UAAU,YAAY,KAAK,YAAY,OAAO,QAAQ;AAElG,eAAS,KAAK,kBAAkB,MAAM,OAAO,OAAO,OAAO,CAAC,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;","names":["label"]}