@tangle-network/agent-app 0.60.198 → 0.60.200

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.
Files changed (41) hide show
  1. package/dist/chat-routes/index.js +6 -4
  2. package/dist/chat-routes/index.js.map +1 -1
  3. package/dist/chat-routes/turn-routes.d.ts +28 -0
  4. package/dist/{chunk-XJEIGVMQ.js → chunk-2OWT3TNV.js} +2 -2
  5. package/dist/{chunk-XP77DJEF.js → chunk-6XXKFYIM.js} +72 -30
  6. package/dist/chunk-6XXKFYIM.js.map +1 -0
  7. package/dist/chunk-7XQIY2LU.js +151 -0
  8. package/dist/chunk-7XQIY2LU.js.map +1 -0
  9. package/dist/chunk-BKPJFES3.js +229 -0
  10. package/dist/chunk-BKPJFES3.js.map +1 -0
  11. package/dist/{chunk-FPOOBGRG.js → chunk-T4BLISYH.js} +40 -5
  12. package/dist/chunk-T4BLISYH.js.map +1 -0
  13. package/dist/{chunk-SUIH5B55.js → chunk-ZHJ2TDWA.js} +199 -17
  14. package/dist/chunk-ZHJ2TDWA.js.map +1 -0
  15. package/dist/public-consultation/index.d.ts +1 -1
  16. package/dist/public-consultation/index.js +2 -1
  17. package/dist/public-consultation/index.js.map +1 -1
  18. package/dist/runtime/index.js +4 -2
  19. package/dist/runtime/index.js.map +1 -1
  20. package/dist/runtime/router-turn.d.ts +14 -1
  21. package/dist/runtime/router-turn.js +3 -1
  22. package/dist/sandbox/index.d.ts +20 -0
  23. package/dist/sandbox/index.js +4 -1
  24. package/dist/sandbox/prompt-trace.d.ts +26 -0
  25. package/dist/sandbox/sandbox-run-tool.d.ts +8 -0
  26. package/dist/trace/index.d.ts +0 -1
  27. package/dist/trace/index.js +0 -240
  28. package/dist/trace/index.js.map +1 -1
  29. package/dist/turn-trace/index.d.ts +11 -0
  30. package/dist/turn-trace/index.js +121 -0
  31. package/dist/turn-trace/index.js.map +1 -0
  32. package/dist/turn-trace/otel.d.ts +36 -0
  33. package/dist/turn-trace/recorder.d.ts +122 -0
  34. package/dist/turn-trace/server-timing.d.ts +15 -0
  35. package/dist/turn-trace/traced-fetch.d.ts +31 -0
  36. package/package.json +6 -1
  37. package/dist/chunk-FPOOBGRG.js.map +0 -1
  38. package/dist/chunk-SUIH5B55.js.map +0 -1
  39. package/dist/chunk-XP77DJEF.js.map +0 -1
  40. package/dist/trace/stage-timing.d.ts +0 -61
  41. /package/dist/{chunk-XJEIGVMQ.js.map → chunk-2OWT3TNV.js.map} +0 -0
@@ -121,9 +121,9 @@ export declare function createPublicConsultation(options: PublicConsultationOpti
121
121
  executionBudget?: ChatTurnExecutionLimits;
122
122
  maxOutputTokens?: number;
123
123
  }) => Promise<{
124
+ start?: undefined;
124
125
  status: 'unsupported';
125
126
  reason: string;
126
- start?: undefined;
127
127
  } | {
128
128
  reason?: undefined;
129
129
  status: 'prepared';
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createChatTurnRoutes,
3
3
  streamChatRouteAsSandboxEvents
4
- } from "../chunk-XP77DJEF.js";
4
+ } from "../chunk-6XXKFYIM.js";
5
5
  import "../chunk-Q7WEEN7P.js";
6
6
  import "../chunk-UTAIRVOK.js";
7
7
  import "../chunk-RN4WPTBH.js";
@@ -17,6 +17,7 @@ import "../chunk-ZGUXI5SI.js";
17
17
  import "../chunk-6VQCFJWP.js";
18
18
  import "../chunk-LQYIBL3G.js";
19
19
  import "../chunk-XELWWXJJ.js";
20
+ import "../chunk-BKPJFES3.js";
20
21
 
21
22
  // src/public-consultation/index.ts
22
23
  import { searchKnowledge } from "@tangle-network/agent-knowledge";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/public-consultation/index.ts"],"sourcesContent":["import { searchKnowledge, type KnowledgeIndex, type KnowledgePage } from '@tangle-network/agent-knowledge'\nimport { createChatTurnRoutes, type ChatTurnExecutionLimits, type ChatTurnLock, type ChatTurnMessageStore, type ChatTurnRouteProducer } from '../chat-routes/turn-routes'\nimport { streamChatRouteAsSandboxEvents } from '../chat-routes/gateway-adapter'\nimport type { TurnEventStore } from '../stream/turn-buffer'\nimport type { McpToolDefinition } from '../tools/mcp-rpc'\n\nexport interface PublishedKnowledgePage {\n readonly id: string\n readonly title: string\n readonly text: string\n}\n\n/** A server-created publication. Every selected page's complete text is public. */\nexport interface KnowledgePublication {\n readonly id: string\n readonly revision: string\n readonly ownerId: string\n readonly systemPrompt: string\n read(pageId: string): PublishedKnowledgePage | null\n search(query: string): PublishedKnowledgePage[]\n}\n\n/** Select content at publication time, before any payer or model input exists. */\nexport function createKnowledgePublication(input: {\n id: string\n revision: string\n ownerId: string\n systemPrompt: string\n index: KnowledgeIndex\n pageIds: readonly string[]\n}): KnowledgePublication {\n for (const value of [input.id, input.revision, input.ownerId]) requireIdentity(value)\n const pages: KnowledgePage[] = []\n for (const id of new Set(input.pageIds)) {\n const matches = input.index.pages.filter(page => page.id === id)\n if (matches.length !== 1) throw new Error('Publication page must resolve exactly once')\n const page = matches[0]!\n // Owner metadata, paths, links and source records can refer to private resources.\n pages.push({ id: page.id, title: page.title, text: page.text, path: page.id,\n frontmatter: {}, sourceIds: [], tags: [], outLinks: [] })\n }\n const index: KnowledgeIndex = { root: '', generatedAt: '', sources: [], pages, graph: { nodes: [], edges: [] } }\n const publicPage = (page: KnowledgePage): PublishedKnowledgePage =>\n Object.freeze({ id: page.id, title: page.title, text: page.text })\n return Object.freeze({\n id: input.id, revision: input.revision, ownerId: input.ownerId, systemPrompt: input.systemPrompt,\n read: (id: string) => {\n const page = pages.find(candidate => candidate.id === id)\n return page ? publicPage(page) : null\n },\n search: (query: string) => searchKnowledge(index, query, { limit: 10 }).map(hit => publicPage(hit.page)),\n })\n}\n\n/** Authenticated server identity. Never construct this from model arguments or request JSON. */\nexport interface ConsultationConsumer {\n method: string\n consumerId: string\n keyId?: string\n ownerId?: string\n requestId: string\n threadId?: string\n}\n\nexport interface ConsultationAgent {\n id: string\n ownerId: string\n}\n\nexport interface ConsultationIdentity {\n readonly publicationId: string\n readonly publicationRevision: string\n readonly consumerId: string\n readonly paymentMethod: string\n readonly workspaceId: string\n readonly threadId: string\n}\n\nexport interface ConsultationExecutionControl {\n readonly identity: ConsultationIdentity\n readonly signal: AbortSignal\n readonly executionLimits?: Readonly<ChatTurnExecutionLimits>\n}\n\nexport type PreparedConsultationExecution =\n | { status: 'unsupported'; reason: string }\n | { status: 'prepared'; produce(input: ConsultationExecution): ChatTurnRouteProducer | Promise<ChatTurnRouteProducer> }\n\nexport interface ConsultationExecution extends ConsultationExecutionControl {\n readonly identity: ConsultationIdentity\n readonly systemPrompt: string\n readonly prompt: string\n readonly priorMessages: ReadonlyArray<{ role: string; content: string }>\n readonly tools: McpToolDefinition[]\n}\n\nexport interface PublicConsultationOptions {\n /** Resolve only deliberately published snapshots. Return null after revocation. */\n resolvePublication(agentId: string): Promise<KnowledgePublication | null>\n /** Payment authentication does not substitute for this admission decision. */\n allowConsumer(publication: KnowledgePublication, consumer: ConsultationConsumer): Promise<boolean>\n store: ChatTurnMessageStore\n turnStore: TurnEventStore\n /** Shared durable single-flight lock. Use createDurableTurnLock with thread scope. */\n turnLock: ChatTurnLock<void>\n /** Create or read the stored parent row. Return its actual persisted ownership. */\n ensureConversation(identity: ConsultationIdentity): Promise<{ workspaceId: string; threadId: string }>\n /** Prepare without compute. Refuse limits the isolated executor cannot enforce across all model and tool calls. */\n prepareExecution(input: ConsultationExecutionControl): Promise<PreparedConsultationExecution>\n}\n\ninterface GatewayConsultationContext {\n consumerId: string\n paymentMethod: string\n requestId: string\n threadId?: string\n keyInfo: { keyId: string; ownerId?: string } | null\n messages: Array<{ role: string; content: string }>\n}\n\n/** Compose gateway admission, scoped knowledge and the maintained persisted chat route. */\nexport function createPublicConsultation(options: PublicConsultationOptions) {\n if (typeof options.turnLock?.acquire !== 'function' || typeof options.turnLock.release !== 'function') {\n throw new Error('Public consultation requires a shared turn lock')\n }\n async function ownsConversation(identity: ConsultationIdentity) {\n const conversation = await options.ensureConversation(identity)\n return conversation?.workspaceId === identity.workspaceId && conversation.threadId === identity.threadId\n }\n async function resolve(agent: ConsultationAgent, consumer: ConsultationConsumer) {\n requireIdentity(consumer.consumerId)\n requireIdentity(consumer.method)\n requireIdentity(consumer.threadId)\n const publication = await options.resolvePublication(agent.id)\n if (!publication || publication.id !== agent.id || publication.ownerId !== agent.ownerId\n || await options.allowConsumer(publication, consumer) !== true) return null\n const workspaceId = await scopedId([publication.ownerId, publication.id, publication.revision, consumer.method, consumer.consumerId])\n const threadId = await scopedId([workspaceId, consumer.threadId!])\n const identity: ConsultationIdentity = Object.freeze({\n publicationId: publication.id, publicationRevision: publication.revision,\n consumerId: consumer.consumerId, paymentMethod: consumer.method, workspaceId, threadId,\n })\n return { publication, identity }\n }\n\n return {\n async authorizeConsumer(agent: ConsultationAgent, consumer: ConsultationConsumer) {\n const scope = await resolve(agent, consumer)\n return scope\n ? { allow: true as const }\n : { allow: false as const, reason: 'Consultation access denied', code: 'consultation_forbidden' }\n },\n async readKnowledge(agent: ConsultationAgent, consumer: ConsultationConsumer, pageId: string) {\n const scope = await resolve(agent, consumer)\n if (!scope) throw new Error('Consultation access denied')\n return scope.publication.read(pageId)\n },\n async readHistory(agent: ConsultationAgent, consumer: ConsultationConsumer) {\n const scope = await resolve(agent, consumer)\n if (!scope) throw new Error('Consultation access denied')\n if (!await ownsConversation(scope.identity)) throw new Error('Consultation conversation conflict')\n return (await options.store.listMessages(scope.identity.threadId))\n .map(({ role, content }) => ({ role, content }))\n },\n async getSandbox(agent: ConsultationAgent, context?: GatewayConsultationContext) {\n if (!context) throw new Error('Authenticated consultation identity is required')\n const consumer: ConsultationConsumer = {\n consumerId: context.consumerId, method: context.paymentMethod,\n requestId: context.requestId, threadId: context.threadId,\n keyId: context.keyInfo?.keyId, ownerId: context.keyInfo?.ownerId,\n }\n const scope = await resolve(agent, consumer)\n if (!scope) throw new Error('Consultation access denied')\n // Client-supplied history and owner system prompts never enter this producer.\n const prompt = context.messages.filter(message => message.role === 'user').at(-1)?.content\n if (!prompt) throw new Error('Consultation requires a user message')\n type PromptOptions = { signal?: AbortSignal; executionBudget?: ChatTurnExecutionLimits; maxOutputTokens?: number }\n const prepare = async (streamOptions?: PromptOptions) => {\n const signal = streamOptions?.signal ?? new AbortController().signal\n signal.throwIfAborted()\n const supplied = streamOptions?.executionBudget\n const executionLimits = supplied || streamOptions?.maxOutputTokens !== undefined\n ? Object.freeze({ ...supplied, ...(streamOptions?.maxOutputTokens !== undefined\n ? { maxOutputTokens: Math.min(streamOptions.maxOutputTokens, supplied?.maxOutputTokens ?? Infinity) } : {}) })\n : undefined\n if (executionLimits && Object.values(executionLimits).some(value => typeof value !== 'number' || !Number.isFinite(value) || value < 0)) {\n return { status: 'unsupported' as const, reason: 'Invalid consultation execution limits' }\n }\n const control = Object.freeze({ identity: scope.identity, signal, executionLimits })\n const prepared = await options.prepareExecution(control)\n signal.throwIfAborted()\n if (prepared?.status !== 'prepared' || typeof prepared.produce !== 'function') {\n return { status: 'unsupported' as const, reason: prepared?.status === 'unsupported'\n ? prepared.reason : 'Invalid consultation execution preparation' }\n }\n let started = false\n const start = () => {\n signal.throwIfAborted()\n if (started) throw new Error('Consultation execution already started')\n started = true\n const routes = createChatTurnRoutes({\n projectId: 'public-consultation', store: options.store, turnStore: options.turnStore, turnLock: options.turnLock,\n authorize: async ({ intent, body }) => {\n const current = await resolve(agent, consumer)\n if (intent !== 'turn' || !current\n || current.identity.workspaceId !== scope.identity.workspaceId\n || body?.workspaceId !== scope.identity.workspaceId || body.threadId !== scope.identity.threadId) {\n return { ok: false, response: Response.json({ error: 'Consultation access denied' }, { status: 403 }) }\n }\n if (!await ownsConversation(scope.identity)) {\n return { ok: false, response: Response.json({ error: 'Consultation conversation conflict' }, { status: 403 }) }\n }\n return { ok: true, tenantId: scope.identity.workspaceId,\n userId: scope.identity.consumerId, context: undefined }\n },\n produce: args => {\n signal.throwIfAborted()\n return prepared.produce({\n ...control,\n identity: scope.identity, systemPrompt: scope.publication.systemPrompt, prompt,\n priorMessages: args.priorMessages.map(({ role, content }) => ({ role, content })),\n tools: publicationTools(scope.publication, async () => {\n signal.throwIfAborted()\n const current = await resolve(agent, consumer)\n signal.throwIfAborted()\n if (!current || current.identity.workspaceId !== scope.identity.workspaceId) {\n throw new Error('Consultation access denied')\n }\n }),\n })\n },\n })\n return streamChatRouteAsSandboxEvents({\n routes, request: new Request('https://consultation.invalid/internal'),\n payload: { workspaceId: scope.identity.workspaceId, threadId: scope.identity.threadId,\n content: prompt, turnId: consumer.requestId }, signal, executionLimits,\n })\n }\n return { status: 'prepared' as const, start }\n }\n return {\n prepareBudgetedPrompt: (_message: string, streamOptions: PromptOptions) => prepare(streamOptions),\n streamPrompt: async function* (_message: string, streamOptions?: PromptOptions) {\n const prepared = await prepare(streamOptions)\n if (prepared.status !== 'prepared') throw new Error(prepared.reason)\n yield* prepared.start()\n },\n }\n },\n }\n}\n\nfunction publicationTools(publication: KnowledgePublication, authorize: () => Promise<void>): McpToolDefinition[] {\n return [\n { name: 'knowledge_search', description: 'Search published consultation knowledge.',\n inputSchema: { type: 'object', required: ['query'], properties: { query: { type: 'string' } }, additionalProperties: false },\n run: async args => { await authorize(); return publication.search(singleArgument(args, 'query')) } },\n { name: 'knowledge_read', description: 'Read a published page by its exact citation id.',\n inputSchema: { type: 'object', required: ['pageId'], properties: { pageId: { type: 'string' } }, additionalProperties: false },\n run: async args => { await authorize(); return publication.read(singleArgument(args, 'pageId')) } },\n ]\n}\n\nfunction singleArgument(args: Record<string, unknown>, name: string): string {\n const value = args[name]\n if (Object.keys(args).length !== 1 || typeof value !== 'string' || !value.trim() || value.length > 8192) {\n throw new Error('Invalid published knowledge arguments')\n }\n return value\n}\n\nfunction requireIdentity(value: unknown): asserts value is string {\n if (typeof value !== 'string' || !value.trim() || value.length > 512) throw new Error('Invalid consultation identity')\n}\n\nasync function scopedId(parts: string[]): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(parts)))\n return `consultation:${Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('')}`\n}\n"],"mappings":"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","names":["page"]}
1
+ {"version":3,"sources":["../../src/public-consultation/index.ts"],"sourcesContent":["import { searchKnowledge, type KnowledgeIndex, type KnowledgePage } from '@tangle-network/agent-knowledge'\nimport { createChatTurnRoutes, type ChatTurnExecutionLimits, type ChatTurnLock, type ChatTurnMessageStore, type ChatTurnRouteProducer } from '../chat-routes/turn-routes'\nimport { streamChatRouteAsSandboxEvents } from '../chat-routes/gateway-adapter'\nimport type { TurnEventStore } from '../stream/turn-buffer'\nimport type { McpToolDefinition } from '../tools/mcp-rpc'\n\nexport interface PublishedKnowledgePage {\n readonly id: string\n readonly title: string\n readonly text: string\n}\n\n/** A server-created publication. Every selected page's complete text is public. */\nexport interface KnowledgePublication {\n readonly id: string\n readonly revision: string\n readonly ownerId: string\n readonly systemPrompt: string\n read(pageId: string): PublishedKnowledgePage | null\n search(query: string): PublishedKnowledgePage[]\n}\n\n/** Select content at publication time, before any payer or model input exists. */\nexport function createKnowledgePublication(input: {\n id: string\n revision: string\n ownerId: string\n systemPrompt: string\n index: KnowledgeIndex\n pageIds: readonly string[]\n}): KnowledgePublication {\n for (const value of [input.id, input.revision, input.ownerId]) requireIdentity(value)\n const pages: KnowledgePage[] = []\n for (const id of new Set(input.pageIds)) {\n const matches = input.index.pages.filter(page => page.id === id)\n if (matches.length !== 1) throw new Error('Publication page must resolve exactly once')\n const page = matches[0]!\n // Owner metadata, paths, links and source records can refer to private resources.\n pages.push({ id: page.id, title: page.title, text: page.text, path: page.id,\n frontmatter: {}, sourceIds: [], tags: [], outLinks: [] })\n }\n const index: KnowledgeIndex = { root: '', generatedAt: '', sources: [], pages, graph: { nodes: [], edges: [] } }\n const publicPage = (page: KnowledgePage): PublishedKnowledgePage =>\n Object.freeze({ id: page.id, title: page.title, text: page.text })\n return Object.freeze({\n id: input.id, revision: input.revision, ownerId: input.ownerId, systemPrompt: input.systemPrompt,\n read: (id: string) => {\n const page = pages.find(candidate => candidate.id === id)\n return page ? publicPage(page) : null\n },\n search: (query: string) => searchKnowledge(index, query, { limit: 10 }).map(hit => publicPage(hit.page)),\n })\n}\n\n/** Authenticated server identity. Never construct this from model arguments or request JSON. */\nexport interface ConsultationConsumer {\n method: string\n consumerId: string\n keyId?: string\n ownerId?: string\n requestId: string\n threadId?: string\n}\n\nexport interface ConsultationAgent {\n id: string\n ownerId: string\n}\n\nexport interface ConsultationIdentity {\n readonly publicationId: string\n readonly publicationRevision: string\n readonly consumerId: string\n readonly paymentMethod: string\n readonly workspaceId: string\n readonly threadId: string\n}\n\nexport interface ConsultationExecutionControl {\n readonly identity: ConsultationIdentity\n readonly signal: AbortSignal\n readonly executionLimits?: Readonly<ChatTurnExecutionLimits>\n}\n\nexport type PreparedConsultationExecution =\n | { status: 'unsupported'; reason: string }\n | { status: 'prepared'; produce(input: ConsultationExecution): ChatTurnRouteProducer | Promise<ChatTurnRouteProducer> }\n\nexport interface ConsultationExecution extends ConsultationExecutionControl {\n readonly identity: ConsultationIdentity\n readonly systemPrompt: string\n readonly prompt: string\n readonly priorMessages: ReadonlyArray<{ role: string; content: string }>\n readonly tools: McpToolDefinition[]\n}\n\nexport interface PublicConsultationOptions {\n /** Resolve only deliberately published snapshots. Return null after revocation. */\n resolvePublication(agentId: string): Promise<KnowledgePublication | null>\n /** Payment authentication does not substitute for this admission decision. */\n allowConsumer(publication: KnowledgePublication, consumer: ConsultationConsumer): Promise<boolean>\n store: ChatTurnMessageStore\n turnStore: TurnEventStore\n /** Shared durable single-flight lock. Use createDurableTurnLock with thread scope. */\n turnLock: ChatTurnLock<void>\n /** Create or read the stored parent row. Return its actual persisted ownership. */\n ensureConversation(identity: ConsultationIdentity): Promise<{ workspaceId: string; threadId: string }>\n /** Prepare without compute. Refuse limits the isolated executor cannot enforce across all model and tool calls. */\n prepareExecution(input: ConsultationExecutionControl): Promise<PreparedConsultationExecution>\n}\n\ninterface GatewayConsultationContext {\n consumerId: string\n paymentMethod: string\n requestId: string\n threadId?: string\n keyInfo: { keyId: string; ownerId?: string } | null\n messages: Array<{ role: string; content: string }>\n}\n\n/** Compose gateway admission, scoped knowledge and the maintained persisted chat route. */\nexport function createPublicConsultation(options: PublicConsultationOptions) {\n if (typeof options.turnLock?.acquire !== 'function' || typeof options.turnLock.release !== 'function') {\n throw new Error('Public consultation requires a shared turn lock')\n }\n async function ownsConversation(identity: ConsultationIdentity) {\n const conversation = await options.ensureConversation(identity)\n return conversation?.workspaceId === identity.workspaceId && conversation.threadId === identity.threadId\n }\n async function resolve(agent: ConsultationAgent, consumer: ConsultationConsumer) {\n requireIdentity(consumer.consumerId)\n requireIdentity(consumer.method)\n requireIdentity(consumer.threadId)\n const publication = await options.resolvePublication(agent.id)\n if (!publication || publication.id !== agent.id || publication.ownerId !== agent.ownerId\n || await options.allowConsumer(publication, consumer) !== true) return null\n const workspaceId = await scopedId([publication.ownerId, publication.id, publication.revision, consumer.method, consumer.consumerId])\n const threadId = await scopedId([workspaceId, consumer.threadId!])\n const identity: ConsultationIdentity = Object.freeze({\n publicationId: publication.id, publicationRevision: publication.revision,\n consumerId: consumer.consumerId, paymentMethod: consumer.method, workspaceId, threadId,\n })\n return { publication, identity }\n }\n\n return {\n async authorizeConsumer(agent: ConsultationAgent, consumer: ConsultationConsumer) {\n const scope = await resolve(agent, consumer)\n return scope\n ? { allow: true as const }\n : { allow: false as const, reason: 'Consultation access denied', code: 'consultation_forbidden' }\n },\n async readKnowledge(agent: ConsultationAgent, consumer: ConsultationConsumer, pageId: string) {\n const scope = await resolve(agent, consumer)\n if (!scope) throw new Error('Consultation access denied')\n return scope.publication.read(pageId)\n },\n async readHistory(agent: ConsultationAgent, consumer: ConsultationConsumer) {\n const scope = await resolve(agent, consumer)\n if (!scope) throw new Error('Consultation access denied')\n if (!await ownsConversation(scope.identity)) throw new Error('Consultation conversation conflict')\n return (await options.store.listMessages(scope.identity.threadId))\n .map(({ role, content }) => ({ role, content }))\n },\n async getSandbox(agent: ConsultationAgent, context?: GatewayConsultationContext) {\n if (!context) throw new Error('Authenticated consultation identity is required')\n const consumer: ConsultationConsumer = {\n consumerId: context.consumerId, method: context.paymentMethod,\n requestId: context.requestId, threadId: context.threadId,\n keyId: context.keyInfo?.keyId, ownerId: context.keyInfo?.ownerId,\n }\n const scope = await resolve(agent, consumer)\n if (!scope) throw new Error('Consultation access denied')\n // Client-supplied history and owner system prompts never enter this producer.\n const prompt = context.messages.filter(message => message.role === 'user').at(-1)?.content\n if (!prompt) throw new Error('Consultation requires a user message')\n type PromptOptions = { signal?: AbortSignal; executionBudget?: ChatTurnExecutionLimits; maxOutputTokens?: number }\n const prepare = async (streamOptions?: PromptOptions) => {\n const signal = streamOptions?.signal ?? new AbortController().signal\n signal.throwIfAborted()\n const supplied = streamOptions?.executionBudget\n const executionLimits = supplied || streamOptions?.maxOutputTokens !== undefined\n ? Object.freeze({ ...supplied, ...(streamOptions?.maxOutputTokens !== undefined\n ? { maxOutputTokens: Math.min(streamOptions.maxOutputTokens, supplied?.maxOutputTokens ?? Infinity) } : {}) })\n : undefined\n if (executionLimits && Object.values(executionLimits).some(value => typeof value !== 'number' || !Number.isFinite(value) || value < 0)) {\n return { status: 'unsupported' as const, reason: 'Invalid consultation execution limits' }\n }\n const control = Object.freeze({ identity: scope.identity, signal, executionLimits })\n const prepared = await options.prepareExecution(control)\n signal.throwIfAborted()\n if (prepared?.status !== 'prepared' || typeof prepared.produce !== 'function') {\n return { status: 'unsupported' as const, reason: prepared?.status === 'unsupported'\n ? prepared.reason : 'Invalid consultation execution preparation' }\n }\n let started = false\n const start = () => {\n signal.throwIfAborted()\n if (started) throw new Error('Consultation execution already started')\n started = true\n const routes = createChatTurnRoutes({\n projectId: 'public-consultation', store: options.store, turnStore: options.turnStore, turnLock: options.turnLock,\n authorize: async ({ intent, body }) => {\n const current = await resolve(agent, consumer)\n if (intent !== 'turn' || !current\n || current.identity.workspaceId !== scope.identity.workspaceId\n || body?.workspaceId !== scope.identity.workspaceId || body.threadId !== scope.identity.threadId) {\n return { ok: false, response: Response.json({ error: 'Consultation access denied' }, { status: 403 }) }\n }\n if (!await ownsConversation(scope.identity)) {\n return { ok: false, response: Response.json({ error: 'Consultation conversation conflict' }, { status: 403 }) }\n }\n return { ok: true, tenantId: scope.identity.workspaceId,\n userId: scope.identity.consumerId, context: undefined }\n },\n produce: args => {\n signal.throwIfAborted()\n return prepared.produce({\n ...control,\n identity: scope.identity, systemPrompt: scope.publication.systemPrompt, prompt,\n priorMessages: args.priorMessages.map(({ role, content }) => ({ role, content })),\n tools: publicationTools(scope.publication, async () => {\n signal.throwIfAborted()\n const current = await resolve(agent, consumer)\n signal.throwIfAborted()\n if (!current || current.identity.workspaceId !== scope.identity.workspaceId) {\n throw new Error('Consultation access denied')\n }\n }),\n })\n },\n })\n return streamChatRouteAsSandboxEvents({\n routes, request: new Request('https://consultation.invalid/internal'),\n payload: { workspaceId: scope.identity.workspaceId, threadId: scope.identity.threadId,\n content: prompt, turnId: consumer.requestId }, signal, executionLimits,\n })\n }\n return { status: 'prepared' as const, start }\n }\n return {\n prepareBudgetedPrompt: (_message: string, streamOptions: PromptOptions) => prepare(streamOptions),\n streamPrompt: async function* (_message: string, streamOptions?: PromptOptions) {\n const prepared = await prepare(streamOptions)\n if (prepared.status !== 'prepared') throw new Error(prepared.reason)\n yield* prepared.start()\n },\n }\n },\n }\n}\n\nfunction publicationTools(publication: KnowledgePublication, authorize: () => Promise<void>): McpToolDefinition[] {\n return [\n { name: 'knowledge_search', description: 'Search published consultation knowledge.',\n inputSchema: { type: 'object', required: ['query'], properties: { query: { type: 'string' } }, additionalProperties: false },\n run: async args => { await authorize(); return publication.search(singleArgument(args, 'query')) } },\n { name: 'knowledge_read', description: 'Read a published page by its exact citation id.',\n inputSchema: { type: 'object', required: ['pageId'], properties: { pageId: { type: 'string' } }, additionalProperties: false },\n run: async args => { await authorize(); return publication.read(singleArgument(args, 'pageId')) } },\n ]\n}\n\nfunction singleArgument(args: Record<string, unknown>, name: string): string {\n const value = args[name]\n if (Object.keys(args).length !== 1 || typeof value !== 'string' || !value.trim() || value.length > 8192) {\n throw new Error('Invalid published knowledge arguments')\n }\n return value\n}\n\nfunction requireIdentity(value: unknown): asserts value is string {\n if (typeof value !== 'string' || !value.trim() || value.length > 512) throw new Error('Invalid consultation identity')\n}\n\nasync function scopedId(parts: string[]): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(parts)))\n return `consultation:${Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('')}`\n}\n"],"mappings":"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","names":["page"]}
@@ -2,7 +2,7 @@ import {
2
2
  ProtectedModelSettlementError,
3
3
  createRouterProtectedModelPort,
4
4
  runProtectedRouterTurn
5
- } from "../chunk-XJEIGVMQ.js";
5
+ } from "../chunk-2OWT3TNV.js";
6
6
  import {
7
7
  DURABLE_HOP_BUDGET_MS,
8
8
  asksForLanguage,
@@ -24,7 +24,8 @@ import {
24
24
  turnKnowledgeInstructions,
25
25
  workspaceKnowledgeForTurn,
26
26
  workspaceTurnCheckpoints
27
- } from "../chunk-FPOOBGRG.js";
27
+ } from "../chunk-T4BLISYH.js";
28
+ import "../chunk-7XQIY2LU.js";
28
29
  import {
29
30
  DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
30
31
  DEFAULT_TANGLE_ROUTER_BASE_URL,
@@ -51,6 +52,7 @@ import {
51
52
  resolveCatalogModelId,
52
53
  sortModelsByFreshness
53
54
  } from "../chunk-AMUXRAUO.js";
55
+ import "../chunk-BKPJFES3.js";
54
56
 
55
57
  // src/runtime/certified-delivery.ts
56
58
  import {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/runtime/certified-delivery.ts","../../src/runtime/surface-profile.ts"],"sourcesContent":["/**\n * Application profile composition using Runtime's certified-guidance source.\n *\n * The shell adapts the prompt source to the application's resolved profile;\n * Runtime owns pulling, cache lifetime, concurrent refresh and retained guidance.\n * Only prompt-compatible guidance is folded here. Tool definitions, execution\n * permissions, MCP servers and files are not installed from certified material.\n * The complete profile remains available through `current()` for inspection and\n * the product's separately authorized materialization path.\n */\nimport {\n createCertifiedPromptSource,\n type CertifiedProfile,\n type CertifiedPromptSourceOptions,\n} from '@tangle-network/agent-runtime/intelligence'\nimport type { ResolvedAgentProfile } from './agent'\n\n/** Pull coordinates, refresh cadence and timeout owned by the shared source. */\nexport type CertifiedDeliveryConfig = CertifiedPromptSourceOptions\n\n/** Adapt certified prompt guidance without granting execution authority. */\nexport interface CertifiedDelivery {\n /** Refresh on the source's cadence and compose the current prompt guidance.\n * Other resolved profile fields pass through unchanged. */\n composeProfile(base: ResolvedAgentProfile): Promise<ResolvedAgentProfile>\n /** Force a pull now, coalescing with an in-flight pull. Best-effort; not a\n * post-activation revision barrier or a permission-revocation protocol. */\n refresh(): Promise<void>\n /** Last successfully pulled profile, or null before a successful pull. */\n current(): CertifiedProfile | null\n}\n\nexport function createCertifiedDelivery(config: CertifiedDeliveryConfig): CertifiedDelivery {\n const source = createCertifiedPromptSource(config)\n return {\n async composeProfile(base) {\n return { ...base, systemPrompt: await source.compose(base.systemPrompt) }\n },\n refresh: () => source.refresh({ force: true }),\n current: source.current,\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 from server configuration\n * (`buildHttpMcpServer` in ../tools). A client can therefore never inject an\n * arbitrary MCP url, header, or credential into the agent profile: the\n * overlay's `mcp` values are typed as {@link SurfaceMcpServer} (= the\n * server-built `AppToolMcpServer` entry shape), and only build() constructs\n * them.\n *\n * The credential itself never appears in the overlay. Since the tagged-config\n * contract (`agent-interface` 0.38.0) the `Authorization` header is a\n * `secret-ref` naming a box-environment variable, which the sandbox resolves\n * privately; `build()` supplies that key NAME from server configuration and the\n * product writes the value through `SandboxRuntimeConfig.env` at creation or\n * refreshes it through `SandboxRuntimeConfig.runtimeEnv` before retained-box\n * bootstrap. The value remains workspace-scoped because every member and turn\n * shares the box; runtime renewal does not create a per-user secret channel.\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\n/** Resolve and build the overlay for a given surface kind within a turn context */\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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/runtime/certified-delivery.ts","../../src/runtime/surface-profile.ts"],"sourcesContent":["/**\n * Application profile composition using Runtime's certified-guidance source.\n *\n * The shell adapts the prompt source to the application's resolved profile;\n * Runtime owns pulling, cache lifetime, concurrent refresh and retained guidance.\n * Only prompt-compatible guidance is folded here. Tool definitions, execution\n * permissions, MCP servers and files are not installed from certified material.\n * The complete profile remains available through `current()` for inspection and\n * the product's separately authorized materialization path.\n */\nimport {\n createCertifiedPromptSource,\n type CertifiedProfile,\n type CertifiedPromptSourceOptions,\n} from '@tangle-network/agent-runtime/intelligence'\nimport type { ResolvedAgentProfile } from './agent'\n\n/** Pull coordinates, refresh cadence and timeout owned by the shared source. */\nexport type CertifiedDeliveryConfig = CertifiedPromptSourceOptions\n\n/** Adapt certified prompt guidance without granting execution authority. */\nexport interface CertifiedDelivery {\n /** Refresh on the source's cadence and compose the current prompt guidance.\n * Other resolved profile fields pass through unchanged. */\n composeProfile(base: ResolvedAgentProfile): Promise<ResolvedAgentProfile>\n /** Force a pull now, coalescing with an in-flight pull. Best-effort; not a\n * post-activation revision barrier or a permission-revocation protocol. */\n refresh(): Promise<void>\n /** Last successfully pulled profile, or null before a successful pull. */\n current(): CertifiedProfile | null\n}\n\nexport function createCertifiedDelivery(config: CertifiedDeliveryConfig): CertifiedDelivery {\n const source = createCertifiedPromptSource(config)\n return {\n async composeProfile(base) {\n return { ...base, systemPrompt: await source.compose(base.systemPrompt) }\n },\n refresh: () => source.refresh({ force: true }),\n current: source.current,\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 from server configuration\n * (`buildHttpMcpServer` in ../tools). A client can therefore never inject an\n * arbitrary MCP url, header, or credential into the agent profile: the\n * overlay's `mcp` values are typed as {@link SurfaceMcpServer} (= the\n * server-built `AppToolMcpServer` entry shape), and only build() constructs\n * them.\n *\n * The credential itself never appears in the overlay. Since the tagged-config\n * contract (`agent-interface` 0.38.0) the `Authorization` header is a\n * `secret-ref` naming a box-environment variable, which the sandbox resolves\n * privately; `build()` supplies that key NAME from server configuration and the\n * product writes the value through `SandboxRuntimeConfig.env` at creation or\n * refreshes it through `SandboxRuntimeConfig.runtimeEnv` before retained-box\n * bootstrap. The value remains workspace-scoped because every member and turn\n * shares the box; runtime renewal does not create a per-user secret channel.\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\n/** Resolve and build the overlay for a given surface kind within a turn context */\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA;AAAA,EACE;AAAA,OAGK;AAkBA,SAAS,wBAAwB,QAAoD;AAC1F,QAAM,SAAS,4BAA4B,MAAM;AACjD,SAAO;AAAA,IACL,MAAM,eAAe,MAAM;AACzB,aAAO,EAAE,GAAG,MAAM,cAAc,MAAM,OAAO,QAAQ,KAAK,YAAY,EAAE;AAAA,IAC1E;AAAA,IACA,SAAS,MAAM,OAAO,QAAQ,EAAE,OAAO,KAAK,CAAC;AAAA,IAC7C,SAAS,OAAO;AAAA,EAClB;AACF;;;ACwCO,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;AAgBO,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;","names":[]}
@@ -21,6 +21,7 @@
21
21
  import { type LoopMessage } from './loop';
22
22
  import { type TurnKnowledgeFile } from './turn-knowledge';
23
23
  import { type PlacementRecord, type VacuousAxis } from '@tangle-network/agent-runtime/placement';
24
+ import { type TurnSpan, type TurnTrace } from '../turn-trace/recorder';
24
25
  export { DURABLE_HOP_BUDGET_MS, turnCheckpointPath, workspaceTurnCheckpoints, type CheckpointWorkspaceStore, type TurnCheckpointStore } from './turn-continuation';
25
26
  export { budgetTurnKnowledge, turnKnowledgeInstructions, workspaceKnowledgeForTurn, type TurnKnowledgeBudget, type TurnKnowledgeFile } from './turn-knowledge';
26
27
  export { asksForLanguage, replyLanguageInstruction } from './reply-language';
@@ -42,6 +43,10 @@ export interface RouterTurnToolContext {
42
43
  emit(kind: string, data?: unknown): void;
43
44
  /** The sender's message this turn answers, so a tool can act only on what was asked (see {@link asksForLanguage}). */
44
45
  userMessage: string;
46
+ /** The turn's trace, when the turn records one. A tool that waits on a box or a service adds its own child spans under {@link span}. */
47
+ trace?: TurnTrace;
48
+ /** This tool call's `tool.call` span; the parent of any span the tool records. */
49
+ span?: TurnSpan;
45
50
  }
46
51
  /** One side event a tool reported while it ran. */
47
52
  export interface RouterTurnToolEvent {
@@ -199,8 +204,16 @@ export interface RouterTurnOptions {
199
204
  /** Runaway backstop on model turns. Default 12. */
200
205
  maxToolTurns?: number;
201
206
  maxTokens?: number;
202
- /** Sent as `x-tangle-trace-id` so the router's generation joins the app's trace. */
207
+ /** Sent as `x-tangle-trace-id` so the router's generation joins the app's trace. Characters the Router
208
+ * refuses in that header (the `:` of an execution id) become `_`; see {@link turnTraceId}. Default: the
209
+ * `trace`'s id. */
203
210
  traceId?: string;
211
+ /**
212
+ * The turn's trace (`@tangle-network/agent-app/turn-trace`). Every model request is recorded as a
213
+ * `model.call` span with the Router's `Server-Timing` split into network and server time, and every tool
214
+ * call as a `tool.call` span that tool-side spans hang from. Without it nothing is recorded.
215
+ */
216
+ trace?: TurnTrace;
204
217
  fetchImpl?: typeof fetch;
205
218
  onEvent?: (event: RouterTurnEvent) => void;
206
219
  /** Map a thrown tool error to what the model reads. Default: an error carrying string `code`
@@ -11,9 +11,11 @@ import {
11
11
  turnKnowledgeInstructions,
12
12
  workspaceKnowledgeForTurn,
13
13
  workspaceTurnCheckpoints
14
- } from "../chunk-FPOOBGRG.js";
14
+ } from "../chunk-T4BLISYH.js";
15
+ import "../chunk-7XQIY2LU.js";
15
16
  import "../chunk-D6UJEOZQ.js";
16
17
  import "../chunk-AMUXRAUO.js";
18
+ import "../chunk-BKPJFES3.js";
17
19
  export {
18
20
  DURABLE_HOP_BUDGET_MS,
19
21
  asksForLanguage,
@@ -5,6 +5,8 @@ import { type AppToolName, type AppToolContext, type ToolHeaderNames } from '../
5
5
  import { type Harness } from '../harness/index';
6
6
  import { type TangleExecutionEnvironment } from '../runtime/model';
7
7
  import { type Outcome } from './outcome';
8
+ import type { TurnSpan, TurnTrace } from '../turn-trace/recorder';
9
+ export { traceSandboxPromptEvents, type TraceSandboxPromptOptions } from './prompt-trace';
8
10
  import { type ProfileFingerprint } from '../profile/fingerprint';
9
11
  import { type ComposeProfileBudget } from '../profile/budget';
10
12
  import { resolveModel, resolveModelSelection, backendModelFields, requireTransportableModel, SandboxModelResolutionError, type ProviderResolutionConfig, type ResolvedModel, type ModelSelection, type ModelSelectionFailure, type ModelSelectionError, type ModelSelectionSource } from './model';
@@ -282,6 +284,15 @@ export interface EnsureWorkspaceSandboxOptions {
282
284
  * product can see which round trip a slow acquisition spent its time in.
283
285
  * Omitted, nothing is measured. */
284
286
  measure?: <T>(stage: string, operation: () => Promise<T>) => Promise<T>;
287
+ /**
288
+ * The turn's trace. The ensure is recorded as one `sandbox.claim` span whose children are the
289
+ * `sandbox.ensure.*` steps (`credentials`, `list_running`, `finalize` for a reused box; `list_stopped`,
290
+ * `resume` for a stopped one; `create`, `wait_running`, `bootstrap` for a new one), so a slow claim names
291
+ * the step it waited on. Omitted, nothing is recorded; `measure` is unchanged and still called.
292
+ */
293
+ trace?: TurnTrace;
294
+ /** The span the claim hangs from. Default: the trace's root. */
295
+ traceParent?: TurnSpan;
285
296
  }
286
297
  /** What `/sandbox` reports once a box is provisioned, reused, or resumed. */
287
298
  export interface SandboxProvisionedObservation {
@@ -695,6 +706,15 @@ export interface StreamSandboxPromptOptions {
695
706
  detach?: boolean;
696
707
  onProfileResolved?: (fingerprint: ProfileFingerprint) => void;
697
708
  spend?: SandboxSpendHooks;
709
+ /**
710
+ * The turn's trace (`@tangle-network/agent-app/turn-trace`). Its id is sent as `traceId`, the
711
+ * `x-tangle-trace-id` the Sandbox API, orchestrator, sidecar and the Router's dispatch rows all record, and
712
+ * the stream is recorded as a `sandbox.prompt` span: the sidecar's first-text phases, the generation and
713
+ * each tool the box agent ran. Omitted, the SDK mints a trace id the app does not keep.
714
+ */
715
+ trace?: TurnTrace;
716
+ /** The span the prompt hangs from. Default: the trace's root. */
717
+ traceParent?: TurnSpan;
698
718
  }
699
719
  /**
700
720
  * The small event shape consumed by agent-gateway's streaming adapters.
@@ -93,11 +93,12 @@ import {
93
93
  syncSandboxMemberAdd,
94
94
  syncSandboxMemberRemove,
95
95
  syncSandboxMemberRole,
96
+ traceSandboxPromptEvents,
96
97
  utcDay,
97
98
  warmBinOf,
98
99
  warmWeekdayOf,
99
100
  writeProfileFilesToBox
100
- } from "../chunk-SUIH5B55.js";
101
+ } from "../chunk-ZHJ2TDWA.js";
101
102
  import {
102
103
  EGRESS_PROXY_RECOVERY_PHASE,
103
104
  EGRESS_PROXY_RECOVERY_REQUIRED,
@@ -146,6 +147,7 @@ import "../chunk-S5SRJJQG.js";
146
147
  import "../chunk-IJAXOQJU.js";
147
148
  import "../chunk-UGWQLQDS.js";
148
149
  import "../chunk-E65YV7VV.js";
150
+ import "../chunk-7XQIY2LU.js";
149
151
  import "../chunk-D6UJEOZQ.js";
150
152
  export {
151
153
  DEFAULT_ESCALATION_TOOLS,
@@ -274,6 +276,7 @@ export {
274
276
  syncSandboxMemberAdd,
275
277
  syncSandboxMemberRemove,
276
278
  syncSandboxMemberRole,
279
+ traceSandboxPromptEvents,
277
280
  utcDay,
278
281
  warmBinOf,
279
282
  warmWeekdayOf,
@@ -0,0 +1,26 @@
1
+ /**
2
+ * What a sandbox prompt stream tells the turn's trace: one `sandbox.prompt`
3
+ * span for the whole stream, the sidecar's own first-text phases as child
4
+ * spans, one `sandbox.generation` span from the first visible text, and one
5
+ * `tool.call` span for each tool the box agent ran.
6
+ *
7
+ * Every time here is read on this side of the wire, from when each event
8
+ * arrived. Event arrival is quantized by the stream's chunking, so the tool
9
+ * spans carry `approx: true`. The sidecar's phases are offsets on its own
10
+ * clock; only their durations are used, and the wait that no phase holds (the
11
+ * SDK, the API, the orchestrator and the host, both ways) is `sandbox.transit`.
12
+ */
13
+ import type { TurnSpan, TurnSpanAttrs, TurnTrace } from '../turn-trace/recorder';
14
+ export interface TraceSandboxPromptOptions {
15
+ trace: TurnTrace;
16
+ /** The span the prompt hangs from. Default: the trace's root. */
17
+ parent?: TurnSpan;
18
+ /** Attributes for the `sandbox.prompt` span: the sandbox id, the model, the harness. */
19
+ meta?: TurnSpanAttrs;
20
+ }
21
+ /**
22
+ * Pass a sandbox prompt stream through unchanged while recording it in the turn's trace (see {@link createPromptTrace}).
23
+ * `streamSandboxPrompt` does this itself when given a `trace`; a product that calls `box.streamPrompt` directly wraps
24
+ * its stream here, and sends `trace.traceId` as the prompt's `traceId` so the Sandbox API records join the turn.
25
+ */
26
+ export declare function traceSandboxPromptEvents(events: AsyncIterable<unknown>, options: TraceSandboxPromptOptions): AsyncGenerator<unknown>;
@@ -18,6 +18,7 @@
18
18
  * one the speculator listens for (`DEFAULT_ESCALATION_TOOLS`).
19
19
  */
20
20
  import type { SandboxInstance } from '@tangle-network/sandbox';
21
+ import type { TurnSpan, TurnTrace } from '../turn-trace/recorder';
21
22
  import { type SandboxRuntimeConfig, type StreamSandboxPromptOptions } from './index';
22
23
  export type SandboxRunFailureCode = 'sandbox_unavailable' | 'sandbox_failed' | 'sandbox_empty' | 'invalid_arguments';
23
24
  /** A typed `sandbox_run` failure. `code` and `message` are what the model reads. */
@@ -30,12 +31,19 @@ export declare class SandboxRunError extends Error {
30
31
  /** The engine's per-call tool context (`RouterTurnToolContext`), kept structural so this module does not import the engine. */
31
32
  export interface SandboxToolContext {
32
33
  emit(kind: string, data?: unknown): void;
34
+ /** The turn's trace and this call's `tool.call` span (`runRouterTurn` supplies both). The box claim, the box run and the
35
+ * trace id sent to the Sandbox API follow from them; without them nothing is recorded. */
36
+ trace?: TurnTrace;
37
+ span?: TurnSpan;
33
38
  }
34
39
  export interface SandboxTaskOptions {
35
40
  signal: AbortSignal;
36
41
  timeoutMs: number;
37
42
  /** Streams a typed side event from the box run (a progress step, a file written) as the turn's `tool_event`. */
38
43
  emit(kind: string, data?: unknown): void;
44
+ /** The turn's trace and this call's span, for a runner that streams a prompt (`sandboxPromptRunner` does). */
45
+ trace?: TurnTrace;
46
+ span?: TurnSpan;
39
47
  }
40
48
  export interface SandboxRunToolOptions {
41
49
  /** The box this call runs in. The product provisions, binds and bills it; this module never
@@ -16,7 +16,6 @@
16
16
  export * from './mission-trace';
17
17
  export * from './flow-types';
18
18
  export * from './mission-flow';
19
- export * from './stage-timing';
20
19
  import type { FlowTrace } from './flow-types';
21
20
  /** Represent a timed event with a timestamp and associated event data */
22
21
  export interface TimedEvent {