@tangle-network/agent-app 0.60.209 → 0.60.210
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-profiles/d1.js +2 -2
- package/dist/agent-profiles/index.js +2 -2
- package/dist/chat-routes/index.js +3 -3
- package/dist/{chunk-QDKY5TBG.js → chunk-6CKOEPXS.js} +6 -6
- package/dist/chunk-6CKOEPXS.js.map +1 -0
- package/dist/{chunk-IJAXOQJU.js → chunk-LJASCU5V.js} +7 -2
- package/dist/chunk-LJASCU5V.js.map +1 -0
- package/dist/{chunk-HCFVEJQL.js → chunk-T3IO7QIW.js} +2 -2
- package/dist/chunk-WHK5CFDB.js +28 -0
- package/dist/chunk-WHK5CFDB.js.map +1 -0
- package/dist/preset-cloudflare/index.js +3 -4
- package/dist/preset-cloudflare/index.js.map +1 -1
- package/dist/sandbox/index.js +3 -3
- package/dist/tools/index.js +3 -3
- package/dist/tools/render-ui-schema.d.ts +2 -2
- package/dist/work-product/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-2RCPPOQ4.js +0 -17
- package/dist/chunk-2RCPPOQ4.js.map +0 -1
- package/dist/chunk-IJAXOQJU.js.map +0 -1
- package/dist/chunk-QDKY5TBG.js.map +0 -1
- /package/dist/{chunk-HCFVEJQL.js.map → chunk-T3IO7QIW.js.map} +0 -0
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
ProfileConflictError,
|
|
3
3
|
parseProfileRevisionDiff,
|
|
4
4
|
serializeProfileRevisionDiff
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-T3IO7QIW.js";
|
|
6
6
|
import "../chunk-I6BZVIRH.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-LJASCU5V.js";
|
|
8
8
|
import "../chunk-UGWQLQDS.js";
|
|
9
9
|
import "../chunk-E65YV7VV.js";
|
|
10
10
|
|
|
@@ -31,12 +31,12 @@ import {
|
|
|
31
31
|
selfMountTable,
|
|
32
32
|
serializeProfileRevisionDiff,
|
|
33
33
|
switchProfile
|
|
34
|
-
} from "../chunk-
|
|
34
|
+
} from "../chunk-T3IO7QIW.js";
|
|
35
35
|
import {
|
|
36
36
|
normalizeProfileName,
|
|
37
37
|
parseProfileSwitch
|
|
38
38
|
} from "../chunk-I6BZVIRH.js";
|
|
39
|
-
import "../chunk-
|
|
39
|
+
import "../chunk-LJASCU5V.js";
|
|
40
40
|
import "../chunk-UGWQLQDS.js";
|
|
41
41
|
import "../chunk-E65YV7VV.js";
|
|
42
42
|
export {
|
|
@@ -52,12 +52,12 @@ import {
|
|
|
52
52
|
} from "../chunk-ZLX4BA27.js";
|
|
53
53
|
import "../chunk-VBRUG4N5.js";
|
|
54
54
|
import "../chunk-LWSJK546.js";
|
|
55
|
-
import "../chunk-
|
|
55
|
+
import "../chunk-6CKOEPXS.js";
|
|
56
56
|
import "../chunk-6A7MYOUI.js";
|
|
57
57
|
import "../chunk-O7UX55EU.js";
|
|
58
|
-
import "../chunk-
|
|
58
|
+
import "../chunk-WHK5CFDB.js";
|
|
59
59
|
import "../chunk-S5SRJJQG.js";
|
|
60
|
-
import "../chunk-
|
|
60
|
+
import "../chunk-LJASCU5V.js";
|
|
61
61
|
import "../chunk-UGWQLQDS.js";
|
|
62
62
|
import "../chunk-E65YV7VV.js";
|
|
63
63
|
import {
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
createMcpToolHandler
|
|
4
4
|
} from "./chunk-6A7MYOUI.js";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
} from "./chunk-
|
|
6
|
+
parseRenderUiSchema
|
|
7
|
+
} from "./chunk-WHK5CFDB.js";
|
|
8
8
|
import {
|
|
9
9
|
buildAppToolOpenAITools,
|
|
10
10
|
findCustomTool,
|
|
11
11
|
isAppToolName
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-LJASCU5V.js";
|
|
13
13
|
import {
|
|
14
14
|
ToolInputError
|
|
15
15
|
} from "./chunk-E65YV7VV.js";
|
|
@@ -94,8 +94,8 @@ async function dispatchAppTool(toolName2, rawArgs, ctx, opts) {
|
|
|
94
94
|
return { ok: true, result: { followupId: r2.id, dueDate: r2.dueDate, deduped: r2.deduped } };
|
|
95
95
|
}
|
|
96
96
|
if (toolName2 === "render_ui") {
|
|
97
|
-
|
|
98
|
-
const r2 = await opts.handlers.renderUi({ title: String(rawArgs.title ?? ""), schema
|
|
97
|
+
const schema = parseRenderUiSchema(rawArgs.schema);
|
|
98
|
+
const r2 = await opts.handlers.renderUi({ title: String(rawArgs.title ?? ""), schema }, ctx);
|
|
99
99
|
opts.onProduced?.({ type: "artifact", path: r2.path, content: r2.content });
|
|
100
100
|
return { ok: true, result: { path: r2.path } };
|
|
101
101
|
}
|
|
@@ -212,4 +212,4 @@ export {
|
|
|
212
212
|
createAppToolRuntimeExecutor,
|
|
213
213
|
handleAppToolRequest
|
|
214
214
|
};
|
|
215
|
-
//# sourceMappingURL=chunk-
|
|
215
|
+
//# sourceMappingURL=chunk-6CKOEPXS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/gating.ts","../src/tools/dispatch.ts","../src/tools/runtime.ts","../src/tools/http.ts"],"sourcesContent":["/**\n * Capability gating — compose an agent session's tool surface from a\n * product-defined capability registry.\n *\n * Products that let users pick what an agent can do (a \"Studio\" agent with the\n * full build toolset vs a clean \"Assistant\" with none) all need the same\n * mechanics: a registry of named capabilities, each unlocking proposal types\n * and/or named tool groups, resolved against the product's\n * {@link AppToolTaxonomy} into the concrete tools to expose. The mechanics are\n * generic and live here; the capability VOCABULARY (ids, labels, which\n * proposal types, which tool groups) is the product's.\n */\n\nimport type { AppToolTaxonomy } from './types'\n\n/** One toggleable tool group in a product's capability registry. */\nexport interface ToolCapability {\n id: string\n label: string\n description: string\n /** Proposal types this capability unlocks (intersected with the taxonomy,\n * so a capability can never widen the product's proposal surface). */\n proposalTypes?: readonly string[]\n /** Unlocks every taxonomy proposal type beyond the base set — the domain's\n * specialized long tail (risk_assessment, vuln_report, …). */\n domainActions?: boolean\n /** Named product tool groups (e.g. 'sandbox', 'integrations') this\n * capability unlocks. The vocabulary is the product's; the resolver only\n * unions them. */\n toolGroups?: readonly string[]\n}\n\n/** Resolve options for determining tool capabilities based on taxonomy, capabilities, and enabled IDs */\nexport interface ResolveToolCapabilitiesOptions {\n taxonomy: AppToolTaxonomy\n /** The product's full capability registry. */\n capabilities: readonly ToolCapability[]\n /** Enabled capability ids. `undefined` means full access (legacy callers\n * that don't send a capability set); an explicit `[]` means a pure chat\n * agent with no tools. Unknown ids are ignored. */\n enabled: readonly string[] | undefined\n /** The shared base proposal types `domainActions` excludes. Defaults to\n * every type some capability names explicitly via `proposalTypes` — i.e.\n * \"domain actions\" are the taxonomy types no capability claims. */\n baseProposalTypes?: readonly string[]\n}\n\n/** Describe resolved capabilities including proposal types and product tool groups to expose */\nexport interface ResolvedToolCapabilities {\n /** Proposal types to keep — feed to {@link restrictTaxonomy}. */\n proposalTypes: string[]\n /** Product tool groups to expose (deduped union across enabled caps). */\n toolGroups: string[]\n}\n\n/**\n * Resolve an enabled capability-id set against a taxonomy into the concrete\n * tool surface. Fail-closed: only types present in the taxonomy survive, and\n * an empty `enabled` set yields no tools at all.\n */\nexport function resolveToolCapabilities(\n opts: ResolveToolCapabilitiesOptions,\n): ResolvedToolCapabilities {\n const { taxonomy, capabilities, enabled } = opts\n if (enabled === undefined) {\n return {\n proposalTypes: [...taxonomy.proposalTypes],\n toolGroups: [...new Set(capabilities.flatMap((c) => c.toolGroups ?? []))],\n }\n }\n const base = new Set(\n opts.baseProposalTypes ?? capabilities.flatMap((c) => c.proposalTypes ?? []),\n )\n const domainTypes = taxonomy.proposalTypes.filter((t) => !base.has(t))\n const byId = new Map(capabilities.map((c) => [c.id, c]))\n\n const proposalTypes = new Set<string>()\n const toolGroups = new Set<string>()\n for (const id of enabled) {\n const cap = byId.get(id)\n if (!cap) continue\n for (const t of cap.proposalTypes ?? []) {\n if (taxonomy.proposalTypes.includes(t)) proposalTypes.add(t)\n }\n if (cap.domainActions) for (const t of domainTypes) proposalTypes.add(t)\n for (const g of cap.toolGroups ?? []) toolGroups.add(g)\n }\n return { proposalTypes: [...proposalTypes], toolGroups: [...toolGroups] }\n}\n\n/**\n * Restrict a taxonomy to a subset of proposal types, intersecting the\n * regulated subset too — the regulated label survives restriction, so a\n * narrowed agent can never launder a regulated type into an unregulated one.\n */\nexport function restrictTaxonomy(\n taxonomy: AppToolTaxonomy,\n allowed: readonly string[],\n): AppToolTaxonomy {\n const allow = new Set(allowed)\n return {\n proposalTypes: taxonomy.proposalTypes.filter((t) => allow.has(t)),\n regulatedTypes: taxonomy.regulatedTypes.filter((t) => allow.has(t)),\n }\n}\n","import { ToolInputError } from './errors'\nimport { isAppToolName } from './openai'\nimport { findCustomTool, type AppToolDefinition } from './registry'\nimport { parseRenderUiSchema } from './render-ui-schema'\nimport type {\n AppToolContext,\n AppToolHandlers,\n AppToolOutcome,\n AppToolProducedEvent,\n AppToolTaxonomy,\n} from './types'\n\n/** Define options for dispatching tools including handlers, taxonomy, custom tools, and approval policies */\nexport interface DispatchOptions {\n handlers: AppToolHandlers\n taxonomy: AppToolTaxonomy\n /** Product-registered tools beyond the four built-ins. A called name that is\n * not a built-in is dispatched to the matching {@link AppToolDefinition.execute}\n * through this same validation/outcome path. */\n customTools?: readonly AppToolDefinition[]\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 const custom = findCustomTool(toolName, opts.customTools)\n if (!custom) return { ok: false, code: 'unknown_tool', message: `${toolName} is not an app tool.` }\n // Custom tools own their own arg validation (their execute throws\n // ToolInputError for correctable input); the outer try/catch maps it.\n const result = await custom.execute(rawArgs, ctx)\n return { ok: true, result }\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 schema = parseRenderUiSchema(rawArgs.schema)\n const r = await opts.handlers.renderUi({ title: String(rawArgs.title ?? ''), 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\n/** Define options for executing runtime tasks with a trusted per-turn context */\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","import { authenticateToolRequest, type ToolHeaderNames } from './auth'\nimport { dispatchAppTool, outcomeStatus, type DispatchOptions } from './dispatch'\nimport { buildAppToolOpenAITools, type AppToolName } from './openai'\nimport type { AppToolDefinition } from './registry'\nimport { createMcpToolHandler, type McpToolCallContent } from './mcp-rpc'\nimport type { AppToolOutcome } from './types'\n\n/** Define options for handling tool requests including tool identification and token verification */\nexport interface HandleToolRequestOptions extends DispatchOptions {\n /** Which app tool this route serves — a built-in name or a product-registered\n * {@link AppToolDefinition} (auto-added to `customTools` for dispatch). */\n tool: AppToolName | AppToolDefinition\n /** Verify the bearer capability token belongs to the header user. */\n verifyToken: (userId: string, bearer: string) => Promise<boolean>\n headerNames?: ToolHeaderNames\n /** Optional model-facing description used by the MCP tools/list response. */\n description?: string\n /** Product-specific values for the schedule_followup.priority schema. */\n priorityValues?: readonly string[]\n /** Optional success-message builder for a friendlier tool result. */\n message?: (result: unknown) => string\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction isMcpRequest(request: Request, value: unknown): boolean {\n // A valid request declares jsonrpc. An incomplete envelope has no reliable\n // body marker, so use the Streamable HTTP Accept contract as the explicit\n // transport discriminator before falling back to direct tool arguments.\n const acceptsMcp = request.headers\n .get('accept')\n ?.split(',')\n .some((part) => part.trim().toLowerCase() === 'text/event-stream')\n return (\n isRecord(value)\n && (\n 'jsonrpc' in value\n || acceptsMcp === true\n || request.headers.has('mcp-session-id')\n || request.headers.has('mcp-protocol-version')\n )\n )\n}\n\nfunction toolName(tool: AppToolName | AppToolDefinition): string {\n return typeof tool === 'string' ? tool : tool.name\n}\n\nfunction dispatchOptions(opts: HandleToolRequestOptions): DispatchOptions {\n return {\n ...opts,\n customTools:\n typeof opts.tool === 'string' ? opts.customTools : [...(opts.customTools ?? []), opts.tool],\n }\n}\n\nfunction mcpToolManifest(opts: HandleToolRequestOptions): {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n} {\n const name = toolName(opts.tool)\n const tools = buildAppToolOpenAITools(opts.taxonomy, {\n ...(opts.description && typeof opts.tool === 'string'\n ? { descriptions: { [opts.tool]: opts.description } }\n : {}),\n ...(opts.priorityValues ? { priorityValues: opts.priorityValues } : {}),\n ...(typeof opts.tool === 'string' ? {} : { customTools: [opts.tool] }),\n })\n const definition = tools.find((entry) => entry.function.name === name)\n if (!definition) {\n throw new Error(`No MCP manifest exists for app tool ${name}`)\n }\n return {\n name,\n description: definition.function.description,\n inputSchema: definition.function.parameters,\n }\n}\n\nfunction mcpToolResult(outcome: AppToolOutcome, opts: HandleToolRequestOptions): McpToolCallContent {\n if (!outcome.ok) {\n return {\n content: [{ type: 'text', text: `${outcome.code}: ${outcome.message}` }],\n isError: true,\n }\n }\n\n const value = outcome.result\n const payload = value !== null && typeof value === 'object' && !Array.isArray(value)\n ? { ok: true, ...value }\n : { ok: true, value }\n return {\n content: [{ type: 'text', text: JSON.stringify({\n ...payload,\n ...(opts.message ? { message: opts.message(value) } : {}),\n }) }],\n }\n}\n\nfunction createAppToolMcpHandler(\n authContext: Parameters<DispatchOptions['handlers']['submitProposal']>[1],\n opts: HandleToolRequestOptions,\n): (request: Request) => Promise<Response> {\n const manifest = mcpToolManifest(opts)\n return createMcpToolHandler({\n serverInfo: { name: 'agent-app-tool', version: '1' },\n tools: [{\n ...manifest,\n run: (args) => dispatchAppTool(manifest.name, args, authContext, dispatchOptions(opts)),\n }],\n buildEnv: () => ({}),\n formatResult: (result) => mcpToolResult(result as AppToolOutcome, opts),\n })\n}\n\n/**\n * Handle one app-tool HTTP request end to end — the sandbox MCP path. The\n * agent's per-turn HTTP MCP server POSTs here; this authenticates (header user\n * + capability token), reads the args (MCP-alias tolerant), dispatches to the\n * product handler, and returns a JSON Response. A product's route file becomes\n * a one-liner: `export const action = ({ request }) => handleAppToolRequest(request, cfg)`.\n */\nexport async function handleAppToolRequest(request: Request, opts: HandleToolRequestOptions): Promise<Response> {\n if (request.method !== 'POST') return Response.json({ error: 'Method not allowed' }, { status: 405 })\n\n const auth = await authenticateToolRequest(request, { verifyToken: opts.verifyToken, headerNames: opts.headerNames })\n if (!auth.ok) return auth.response\n\n let body: unknown\n try {\n body = await request.clone().json()\n } catch {\n return Response.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n // The same route supports the direct app-tool HTTP shape used by the\n // OpenAI/runtime surfaces and stateless Streamable HTTP MCP JSON-RPC used by\n // OpenCode. Keeping both at this boundary prevents every consumer from\n // writing a second route just to translate protocol envelopes.\n if (isMcpRequest(request, body)) {\n return createAppToolMcpHandler(auth.ctx, opts)(request)\n }\n\n if (!isRecord(body)) return Response.json({ error: 'Invalid JSON' }, { status: 400 })\n const args = (body.args ?? body.arguments ?? body) as Record<string, unknown>\n\n // A custom tool passed as `tool` is registered for this dispatch so the route\n // file stays a one-liner (no separate customTools wiring needed).\n const toolName = typeof opts.tool === 'string' ? opts.tool : opts.tool.name\n const outcome = await dispatchAppTool(toolName, args, auth.ctx, dispatchOptions(opts))\n if (!outcome.ok) {\n return Response.json({ error: outcome.code, message: outcome.message }, { status: outcomeStatus(outcome) })\n }\n const payload = outcome.result as Record<string, unknown>\n return Response.json({ ok: true, ...payload, ...(opts.message ? { message: opts.message(outcome.result) } : {}) })\n}\n"],"mappings":"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","names":["toolName","r","toolName","toolName"]}
|
|
@@ -64,7 +64,12 @@ ${OPENUI_INTERACTIVE_AUTHORING_GUIDE}` : ""),
|
|
|
64
64
|
$defs: OPENUI_NODE_JSON_SCHEMA.$defs,
|
|
65
65
|
properties: {
|
|
66
66
|
title: { type: "string" },
|
|
67
|
-
schema: {
|
|
67
|
+
schema: {
|
|
68
|
+
anyOf: [
|
|
69
|
+
{ $ref: "#/$defs/node" },
|
|
70
|
+
{ type: "string", description: "JSON text encoding exactly one OpenUI node object. Encode once; do not double-encode." }
|
|
71
|
+
]
|
|
72
|
+
}
|
|
68
73
|
},
|
|
69
74
|
required: ["title", "schema"]
|
|
70
75
|
}
|
|
@@ -112,4 +117,4 @@ export {
|
|
|
112
117
|
customToolToOpenAI,
|
|
113
118
|
findCustomTool
|
|
114
119
|
};
|
|
115
|
-
//# sourceMappingURL=chunk-
|
|
120
|
+
//# sourceMappingURL=chunk-LJASCU5V.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/openai.ts","../src/tools/registry.ts"],"sourcesContent":["import { OPENUI_INTERACTIVE_AUTHORING_GUIDE } from '../openui/authoring'\nimport { OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES } from '@tangle-network/ui/openui-schema'\nimport type { AppToolTaxonomy, BuildAppToolsOptions } 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\n/** Resolve a valid application tool name from the predefined list of tool names */\nexport type AppToolName = (typeof APP_TOOL_NAMES)[number]\n\nconst NAME_SET = new Set<string>(APP_TOOL_NAMES)\n/** Determine if a string matches a valid application tool name */\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\n * model-facing descriptions and the follow-up priority enum default to the\n * Tangle reference vocabulary and can be retuned via {@link BuildAppToolsOptions}\n * (the tool names + JSON-Schema shapes stay fixed — they are mechanism). Pass\n * the result to the agent-runtime backend's `tools`.\n */\nexport function buildAppToolOpenAITools(\n taxonomy: AppToolTaxonomy,\n opts?: BuildAppToolsOptions,\n): OpenAIFunctionTool[] {\n const d = opts?.descriptions\n const priorityValues = opts?.priorityValues ?? ['low', 'medium', 'high']\n const custom: OpenAIFunctionTool[] = (opts?.customTools ?? []).map((t) => ({\n type: 'function',\n function: { name: t.name, description: t.description, parameters: t.parameters },\n }))\n return [\n {\n type: 'function',\n function: {\n name: 'submit_proposal',\n description:\n d?.submit_proposal ??\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:\n d?.schedule_followup ??\n '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: [...priorityValues] },\n },\n required: ['title', 'dueDate'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'render_ui',\n description:\n (d?.render_ui ??\n `Show a generated view live in the workspace. Validates the OpenUI JSON and persists the artifact. Use only these nodes: ${OPENUI_NODE_TYPES.join(', ')}. Stack, grid, and card may contain nested children. Executes immediately.`) +\n (opts?.interactiveUi ? `\\n\\n${OPENUI_INTERACTIVE_AUTHORING_GUIDE}` : ''),\n parameters: {\n type: 'object',\n $defs: OPENUI_NODE_JSON_SCHEMA.$defs,\n properties: {\n title: { type: 'string' },\n schema: {\n anyOf: [\n { $ref: '#/$defs/node' },\n { type: 'string', description: 'JSON text encoding exactly one OpenUI node object. Encode once; do not double-encode.' },\n ],\n },\n },\n required: ['title', 'schema'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'add_citation',\n description:\n d?.add_citation ??\n '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 ...custom,\n ]\n}\n","import { isAppToolName } from './openai'\nimport type { AppToolContext } from './types'\nimport type { OpenAIFunctionTool } from './openai'\n\n/**\n * A product-defined app tool — the open registration seam.\n *\n * The four built-ins (`submit_proposal`/`schedule_followup`/`render_ui`/\n * `add_citation`) are mechanism and stay hard-typed. This is how a product adds\n * a fifth+ tool (e.g. gtm-agent's `set_config`) WITHOUT forking the shell: the\n * `name` + JSON-Schema `parameters` are what the model sees, and `execute` is\n * dispatched through the SAME validation/outcome path as the built-ins — a\n * thrown {@link ToolInputError} becomes a correctable 4xx, any other throw an\n * internal error, and a tool call never silently \"succeeds\" without its effect.\n */\nexport interface AppToolDefinition<Args = Record<string, unknown>> {\n /** Stable identifier the model calls (and the MCP server name). Must not\n * collide with a built-in app tool. */\n name: string\n /** Model-facing description. */\n description: string\n /** JSON-Schema for the parameters (the OpenAI `function.parameters` object). */\n parameters: Record<string, unknown>\n /** Default route path the per-turn MCP server / HTTP handler is mounted at.\n * Overridable per call via `paths`/`buildHttpMcpServer`. */\n path?: string\n /** Fulfil the call; the return value is the tool result the model sees.\n * `ctx` is the trusted per-turn identity (never from tool args). */\n execute: (args: Args, ctx: AppToolContext) => Promise<unknown> | unknown\n}\n\n/**\n * Validate + brand a product tool definition. Throws when the name is empty or\n * collides with a built-in (those are reserved mechanism). Identity otherwise —\n * call it at module scope so a bad definition fails at boot, not first use.\n */\nexport function defineAppTool<Args = Record<string, unknown>>(def: AppToolDefinition<Args>): AppToolDefinition<Args> {\n const name = def.name?.trim()\n if (!name) throw new Error('defineAppTool: name is required')\n if (isAppToolName(name)) throw new Error(`defineAppTool: \"${name}\" is a built-in app tool — choose a different name`)\n if (typeof def.execute !== 'function') throw new Error(`defineAppTool: \"${name}\" needs an execute() handler`)\n return def\n}\n\n/** The OpenAI function-tool def for a custom tool — appended to the built-ins by\n * `buildAppToolOpenAITools`. */\nexport function customToolToOpenAI(def: AppToolDefinition): OpenAIFunctionTool {\n return { type: 'function', function: { name: def.name, description: def.description, parameters: def.parameters } }\n}\n\n/** Find a registered custom tool by the name the model called. */\nexport function findCustomTool(\n name: string,\n tools: readonly AppToolDefinition[] | undefined,\n): AppToolDefinition | undefined {\n return tools?.find((t) => t.name === name)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-I6BZVIRH.js";
|
|
5
5
|
import {
|
|
6
6
|
defineAppTool
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-LJASCU5V.js";
|
|
8
8
|
import {
|
|
9
9
|
ToolInputError
|
|
10
10
|
} from "./chunk-E65YV7VV.js";
|
|
@@ -772,4 +772,4 @@ export {
|
|
|
772
772
|
PROFILE_MEMORY_PREFIX,
|
|
773
773
|
profileTools
|
|
774
774
|
};
|
|
775
|
-
//# sourceMappingURL=chunk-
|
|
775
|
+
//# sourceMappingURL=chunk-T3IO7QIW.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ToolInputError,
|
|
3
|
+
validateOpenUIJsonNode
|
|
4
|
+
} from "./chunk-E65YV7VV.js";
|
|
5
|
+
|
|
6
|
+
// src/tools/render-ui-schema.ts
|
|
7
|
+
function parseRenderUiSchema(value) {
|
|
8
|
+
if (typeof value === "string") {
|
|
9
|
+
try {
|
|
10
|
+
value = JSON.parse(value);
|
|
11
|
+
} catch {
|
|
12
|
+
throw new ToolInputError("invalid_schema", "$: schema JSON text is malformed.");
|
|
13
|
+
}
|
|
14
|
+
if (typeof value === "string") {
|
|
15
|
+
throw new ToolInputError("invalid_schema", "$: schema must encode one OpenUI node object, not another JSON string.");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const result = validateOpenUIJsonNode(value);
|
|
19
|
+
if (!result.ok) {
|
|
20
|
+
throw new ToolInputError("invalid_schema", `${result.issue.path}: ${result.issue.message}`);
|
|
21
|
+
}
|
|
22
|
+
return result.value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
parseRenderUiSchema
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=chunk-WHK5CFDB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/render-ui-schema.ts"],"sourcesContent":["import { validateOpenUIJsonNode } from '@tangle-network/ui/openui-schema'\nimport { ToolInputError } from './errors'\n\n/** Decode one tool-transport JSON text, then enforce the renderer's node contract. */\nexport function parseRenderUiSchema(value: unknown) {\n if (typeof value === 'string') {\n try {\n value = JSON.parse(value) as unknown\n } catch {\n throw new ToolInputError('invalid_schema', '$: schema JSON text is malformed.')\n }\n if (typeof value === 'string') {\n throw new ToolInputError('invalid_schema', '$: schema must encode one OpenUI node object, not another JSON string.')\n }\n }\n const result = validateOpenUIJsonNode(value)\n if (!result.ok) {\n throw new ToolInputError('invalid_schema', `${result.issue.path}: ${result.issue.message}`)\n }\n return result.value\n}\n"],"mappings":";;;;;;AAIO,SAAS,oBAAoB,OAAgB;AAClD,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI;AACF,cAAQ,KAAK,MAAM,KAAK;AAAA,IAC1B,QAAQ;AACN,YAAM,IAAI,eAAe,kBAAkB,mCAAmC;AAAA,IAChF;AACA,QAAI,OAAO,UAAU,UAAU;AAC7B,YAAM,IAAI,eAAe,kBAAkB,wEAAwE;AAAA,IACrH;AAAA,EACF;AACA,QAAM,SAAS,uBAAuB,KAAK;AAC3C,MAAI,CAAC,OAAO,IAAI;AACd,UAAM,IAAI,eAAe,kBAAkB,GAAG,OAAO,MAAM,IAAI,KAAK,OAAO,MAAM,OAAO,EAAE;AAAA,EAC5F;AACA,SAAO,OAAO;AAChB;","names":[]}
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
sandboxTransientBackoffMs
|
|
12
12
|
} from "../chunk-4LBCTFBP.js";
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
} from "../chunk-
|
|
14
|
+
parseRenderUiSchema
|
|
15
|
+
} from "../chunk-WHK5CFDB.js";
|
|
16
16
|
import "../chunk-E65YV7VV.js";
|
|
17
17
|
|
|
18
18
|
// src/preset-cloudflare/workflow-instance.ts
|
|
@@ -384,8 +384,7 @@ function createPresetToolHandlers(opts) {
|
|
|
384
384
|
return { id, dueDate: args.dueDate, deduped: false };
|
|
385
385
|
},
|
|
386
386
|
async renderUi(args, ctx) {
|
|
387
|
-
|
|
388
|
-
const content = JSON.stringify(args.schema);
|
|
387
|
+
const content = JSON.stringify(parseRenderUiSchema(args.schema));
|
|
389
388
|
const path = `${uiPrefix}/${ctx.threadId ?? "global"}/${slug(args.title)}.json`;
|
|
390
389
|
await persistArtifact(path, content);
|
|
391
390
|
await insertKnowledge(ctx.workspaceId, path, "ui", args.title, content);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/preset-cloudflare/workflow-instance.ts","../../src/preset-cloudflare/detached-turn-workflow.ts","../../src/preset-cloudflare/native-completion-workflow.ts","../../src/preset-cloudflare/headers.ts","../../src/preset-cloudflare/index.ts"],"sourcesContent":["/**\n * The structural part of a Cloudflare Workflow instance used to prove that a\n * failed create call actually left an addressable durable instance behind.\n */\nexport interface CloudflareWorkflowInstanceLike<TStatus = unknown> {\n status(): Promise<TStatus>\n}\n\n/** The structural binding implemented by Cloudflare's `Workflow<TParams>`. */\nexport interface CloudflareWorkflowBindingLike<\n TParams,\n TStatus = unknown,\n TInstance extends CloudflareWorkflowInstanceLike<TStatus> =\n CloudflareWorkflowInstanceLike<TStatus>,\n> {\n create(input: { id: string; params: TParams }): Promise<TInstance>\n get(id: string): Promise<TInstance>\n}\n\nexport interface EnsureCloudflareWorkflowInstanceResult<\n TStatus,\n TInstance extends CloudflareWorkflowInstanceLike<TStatus>,\n> {\n instance: TInstance\n /** False only when create failed but the same id was proven readable. */\n created: boolean\n /** Present on reuse because reading status is the proof the instance exists. */\n status?: TStatus\n}\n\n/**\n * Create one durable Workflow instance, or prove an instance with the same id\n * already exists before accepting a create error as an idempotent retry.\n *\n * Cloudflare does not expose a stable duplicate-error class. Matching error\n * text can therefore swallow unrelated failures. This helper uses the durable\n * resource itself as proof: `get(id).status()` must succeed, otherwise the\n * original create error is rethrown.\n */\nexport async function ensureCloudflareWorkflowInstance<\n TParams,\n TStatus,\n TInstance extends CloudflareWorkflowInstanceLike<TStatus>,\n>(\n binding: CloudflareWorkflowBindingLike<TParams, TStatus, TInstance>,\n input: { id: string; params: TParams },\n): Promise<EnsureCloudflareWorkflowInstanceResult<TStatus, TInstance>> {\n try {\n return {\n instance: await binding.create(input),\n created: true,\n }\n } catch (createError) {\n try {\n const instance = await binding.get(input.id)\n const status = await instance.status()\n return { instance, created: false, status }\n } catch {\n throw createError\n }\n }\n}\n","import type { TurnDriveResult } from '@tangle-network/sandbox'\nimport type { Outcome } from '../sandbox/outcome'\nimport {\n SANDBOX_TRANSIENT_DEADLINE_MS,\n SandboxTransientDeadlineError,\n classifySandboxTransientFailure,\n sandboxTransientBackoffMs,\n type SandboxTransientFailure,\n} from '../sandbox/transient-failure'\n\n/** The stable identity a Workflow reuses on every retry of one turn. */\nexport interface DetachedTurnWorkflowIdentity {\n /** Sandbox session resume key. */\n sessionId: string\n /** Sandbox completed-turn idempotency key. */\n turnId: string\n}\n\n/** The part of Cloudflare's Workflow event needed by the tick. */\nexport interface CloudflareWorkflowEventLike<TPayload> {\n payload: TPayload\n}\n\n/** A duration accepted by Cloudflare Workflow `step.sleep`. */\nexport type CloudflareWorkflowSleepDuration =\n | `${number} ${\n | 'second'\n | 'seconds'\n | 'minute'\n | 'minutes'\n | 'hour'\n | 'hours'\n | 'day'\n | 'days'\n | 'week'\n | 'weeks'\n | 'month'\n | 'months'\n | 'year'\n | 'years'}`\n | number\n\n/** The durable Workflow operations used by the tick. */\nexport interface CloudflareWorkflowStepLike {\n do<T>(name: string, callback: (context: unknown) => Promise<T>): Promise<T>\n sleep(name: string, duration: CloudflareWorkflowSleepDuration): Promise<void>\n}\n\n/** The states returned by the Sandbox `driveTurn` primitive. */\nexport type DetachedTurnDriveState = TurnDriveResult['state']\n\n/** The small state contract a durable tick needs. Product observers may carry\n * a richer terminal receipt than Sandbox's `TurnDriveResult`. */\nexport interface DetachedTurnDriveResultLike {\n state: DetachedTurnDriveState\n}\n\n/** The terminal result passed to product settlement. */\nexport type DetachedTurnTerminalResult<\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n> = Exclude<TResult, { state: 'running' }>\n\n/** A drive call's retryable transport boundary. */\nexport type DetachedTurnDriveOutcome<\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n> = Outcome<TResult>\n\n/** Options for one durable detached-turn Workflow run. */\nexport interface DetachedTurnWorkflowTickOptions<\n TPayload extends DetachedTurnWorkflowIdentity,\n TSettled,\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n> {\n event: CloudflareWorkflowEventLike<TPayload>\n step: CloudflareWorkflowStepLike\n /** One SDK drive pass. Rejected results must not enter the Workflow cache. */\n drive: (payload: TPayload) => Promise<DetachedTurnDriveOutcome<TResult>>\n /** Must be idempotent: the Worker can stop after the write but before commit. */\n settle: (payload: TPayload, result: DetachedTurnTerminalResult<TResult>) => Promise<TSettled>\n /** Wait between passes: one duration, or one per zero-based pass so a long\n * turn polls less often. It must depend only on the pass number, so a\n * replayed Workflow takes the same steps. Default: 5 seconds. */\n pollDelay?: CloudflareWorkflowSleepDuration | ((attempt: number) => CloudflareWorkflowSleepDuration)\n stepName?: string\n /**\n * Wait out transient Sandbox failures instead of failing the pass. A drive\n * failure the policy classifies is checkpointed as a running pass, the next\n * pass waits the policy's backoff, and the tick throws\n * {@link SandboxTransientDeadlineError} once one transient stretch outlasts\n * the deadline. Unclassified failures throw from the step as before.\n * Default: the shared Sandbox transient policy.\n */\n transient?: DetachedTurnTransientPolicy | false\n}\n\n/** Which drive failures a tick waits out, and for how long. */\nexport interface DetachedTurnTransientPolicy {\n classify(error: unknown): SandboxTransientFailure | null\n deadlineMs: number\n /** Wait before pass `attempt` (1-based) of one transient stretch. */\n backoffMs(attempt: number): number\n}\n\n/** The shared Sandbox transient policy: 5 s doubling to 60 s, for at most 15 minutes. */\nexport const SANDBOX_TRANSIENT_TICK_POLICY: DetachedTurnTransientPolicy = {\n classify: classifySandboxTransientFailure,\n deadlineMs: SANDBOX_TRANSIENT_DEADLINE_MS,\n backoffMs: sandboxTransientBackoffMs,\n}\n\n/** A pass that met a transient failure, as the step checkpoints it. */\ninterface TransientPass {\n state: 'running'\n transient: SandboxTransientFailure & { at: number }\n}\n\nfunction transientPass(value: unknown): TransientPass['transient'] | undefined {\n const transient = (value as Partial<TransientPass> | null)?.transient\n return transient && typeof transient.at === 'number' && typeof transient.code === 'string' ? transient : undefined\n}\n\nfunction assertIdentity(payload: DetachedTurnWorkflowIdentity): void {\n if (!payload || typeof payload.sessionId !== 'string' || !payload.sessionId.trim()) {\n throw new Error('detached turn Workflow payload requires a non-empty sessionId')\n }\n if (typeof payload.turnId !== 'string' || !payload.turnId.trim()) {\n throw new Error('detached turn Workflow payload requires a non-empty turnId')\n }\n}\n\n// Keyed by the SDK's own union, so a state a new Sandbox adds fails this\n// package's build instead of retrying a settled turn as \"unknown\" forever.\nconst DRIVE_STATES: Record<DetachedTurnDriveState, true> = {\n running: true,\n completed: true,\n failed: true,\n awaiting_plan_decision: true,\n blocked_on_approval: true,\n awaiting_question: true,\n awaiting_interaction: true,\n}\n\nfunction isKnownDriveState(state: unknown): state is DetachedTurnDriveState {\n return typeof state === 'string' && Object.hasOwn(DRIVE_STATES, state)\n}\n\nfunction checkedDriveResult<TResult extends DetachedTurnDriveResultLike>(value: TResult): TResult {\n const state = (value as { state?: unknown } | null)?.state\n if (!isKnownDriveState(state)) {\n throw new Error(`detached turn drive returned unknown state: ${String(state)}`)\n }\n return value\n}\n\n/**\n * Drive a detached SDK turn with durable steps, not an HTTP waitUntil lifetime.\n * Stable step names let an evicted Workflow resume without repeating committed\n * passes. Validation belongs inside step.do so a transient malformed response\n * is retried rather than committed permanently as a poisoned checkpoint.\n */\nexport async function runDetachedTurnWorkflowTick<\n TPayload extends DetachedTurnWorkflowIdentity,\n TSettled,\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n>(\n options: DetachedTurnWorkflowTickOptions<TPayload, TSettled, TResult>,\n): Promise<TSettled> {\n assertIdentity(options.event?.payload)\n // Never allow a callback to change the admission identity for later passes.\n const payload = Object.freeze({ ...options.event.payload }) as TPayload\n const name = options.stepName ?? 'detached-turn'\n const pollDelay = options.pollDelay ?? '5 seconds'\n const delayFor = (attempt: number): CloudflareWorkflowSleepDuration =>\n typeof pollDelay === 'function' ? pollDelay(attempt) : pollDelay\n const transient = options.transient === false ? undefined : options.transient ?? SANDBOX_TRANSIENT_TICK_POLICY\n let attempt = 0\n // One stretch of consecutive transient passes. Both come from checkpointed\n // step results, so a replayed Workflow takes the same waits.\n let transientSince: number | undefined\n let transientAttempts = 0\n let terminalResult: DetachedTurnTerminalResult<TResult>\n while (true) {\n const driveResult = checkedDriveResult(await options.step.do<TResult>(`${name}:drive:${attempt}`, async () => {\n let outcome: DetachedTurnDriveOutcome<TResult>\n try {\n outcome = await options.drive(payload)\n } catch (error) {\n outcome = { succeeded: false, error: error instanceof Error ? error : new Error(String(error)) }\n }\n if (!outcome.succeeded) {\n const failure = transient?.classify(outcome.error)\n if (!failure) throw outcome.error\n return { state: 'running', transient: { ...failure, at: Date.now() } } satisfies TransientPass as unknown as TResult\n }\n return checkedDriveResult(outcome.value)\n }))\n // Validate replayed values as well, including checkpoints from older code.\n if (driveResult.state !== 'running') {\n terminalResult = driveResult as DetachedTurnTerminalResult<TResult>\n break\n }\n const pass = transient ? transientPass(driveResult) : undefined\n if (pass && transient) {\n transientSince ??= pass.at\n transientAttempts += 1\n const delayMs = transient.backoffMs(transientAttempts)\n if (pass.at + delayMs - transientSince > transient.deadlineMs) {\n throw new SandboxTransientDeadlineError(pass, transientSince, transientAttempts)\n }\n await options.step.sleep(`${name}:wait:${attempt}`, delayMs)\n } else {\n transientSince = undefined\n transientAttempts = 0\n await options.step.sleep(`${name}:wait:${attempt}`, delayFor(attempt))\n }\n attempt += 1\n }\n return options.step.do(`${name}:settle`, () => options.settle(payload, terminalResult))\n}\n","/**\n * Durable terminal assembly for a native turn admitted by another request.\n * Product callbacks own domain effects and may use their own Workflow steps;\n * this helper never nests those callbacks inside `step.do`.\n */\n\nimport type {\n NativeCompletionObservation,\n NativeCompletionReceipt,\n} from '../chat-routes/native-completion'\nimport {\n runDetachedTurnWorkflowTick,\n type CloudflareWorkflowEventLike,\n type CloudflareWorkflowSleepDuration,\n type CloudflareWorkflowStepLike,\n type DetachedTurnWorkflowIdentity,\n} from './detached-turn-workflow'\n\n/** Stable identity required to resume a registered native completion. */\nexport interface NativeCompletionWorkflowPayload extends DetachedTurnWorkflowIdentity {\n registeredAt: number\n}\n\ntype NativeCompletionDriveResult = NativeCompletionObservation\n\nexport interface NativeCompletionWorkflowOptions<\n TPayload extends NativeCompletionWorkflowPayload,\n TMessageId,\n> {\n event: CloudflareWorkflowEventLike<TPayload>\n step: CloudflareWorkflowStepLike\n /** Read-only exact observation. It must never dispatch, drive, or cancel. */\n observe(payload: TPayload): Promise<NativeCompletionObservation>\n /**\n * Product-owned idempotent preparation, for example artifact promotion and\n * output classification. It runs outside an enclosing Workflow step so a\n * product may use its own named steps without nesting `step.do`.\n */\n prepare?(payload: TPayload, receipt: NativeCompletionReceipt): Promise<NativeCompletionReceipt>\n /**\n * Durable transcript write. Without a message id it must upsert the turn's\n * one row: the terminal observation step checkpoints the observed receipt\n * with it before preparation, and the transcript step writes the prepared\n * receipt over that checkpoint. A supplied message id updates the existing row.\n */\n persistTranscript(\n payload: TPayload,\n receipt: NativeCompletionReceipt,\n existingMessageId?: TMessageId,\n ): Promise<TMessageId>\n /**\n * Product-owned idempotent settlement. A rejection leaves the lock held and\n * skips finalization, making the failed dependency observable and retryable.\n */\n settle(\n payload: TPayload,\n receipt: NativeCompletionReceipt,\n messageId: TMessageId,\n ): Promise<NativeCompletionReceipt | void>\n /** Optional product buffer completion after successful settlement. */\n finalizeBuffer?(payload: TPayload, receipt: NativeCompletionReceipt, messageId: TMessageId): Promise<void>\n /** Release the product lock only after every terminal effect completed. */\n releaseLock(payload: TPayload): Promise<void>\n /** Default: 5 s for the first two minutes, 15 s until thirty minutes, then 30 s. */\n pollDelay?: CloudflareWorkflowSleepDuration | ((attempt: number) => CloudflareWorkflowSleepDuration)\n stepName?: string\n}\n\n/**\n * Wait before observation pass `attempt + 1`: 5 s for the first two minutes,\n * 15 s until thirty minutes, then 30 s. Each pass is two Workflow steps and a\n * Workflow allows 10,000, so a fixed 5 s wait ends an instance after about\n * seven hours; this schedule leaves room for more than forty.\n */\nfunction nativeCompletionPollDelay(attempt: number): CloudflareWorkflowSleepDuration {\n if (attempt < 24) return '5 seconds'\n if (attempt < 136) return '15 seconds'\n return '30 seconds'\n}\n\nfunction validObservation(value: NativeCompletionObservation): NativeCompletionDriveResult {\n if (!value || (value.state !== 'running' && value.state !== 'completed' && value.state !== 'failed')) {\n throw new Error(`native completion observer returned unknown state: ${String((value as { state?: unknown } | null)?.state)}`)\n }\n if (value.state !== 'running' && !value.receipt) {\n throw new Error('native completion observer returned a terminal state without a receipt')\n }\n return value\n}\n\n/**\n * Drive exact observation through durable retry steps, then run the terminal\n * sequence once. `prepare` and `settle` are deliberately outside `step.do` so\n * product callbacks can compose their own named Cloudflare Workflow steps.\n */\nexport async function runNativeCompletionWorkflow<\n TPayload extends NativeCompletionWorkflowPayload,\n TMessageId,\n>(options: NativeCompletionWorkflowOptions<TPayload, TMessageId>): Promise<NativeCompletionReceipt> {\n const name = options.stepName ?? 'native-completion'\n let receipt = await runDetachedTurnWorkflowTick<\n TPayload,\n NativeCompletionReceipt,\n NativeCompletionDriveResult\n >({\n event: options.event,\n step: options.step,\n pollDelay: options.pollDelay ?? nativeCompletionPollDelay,\n stepName: `${name}:observe`,\n drive: async (payload) => {\n const observation = validObservation(await options.observe(payload))\n // The observed answer is written in the same step that observed it, so\n // a later preparation, settlement or Workflow failure cannot lose it.\n if (observation.state !== 'running') await options.persistTranscript(payload, observation.receipt)\n return { succeeded: true, value: observation }\n },\n settle: async (_payload, result) => result.receipt,\n })\n\n if (options.prepare) receipt = await options.prepare(options.event.payload, receipt)\n const messageId = await options.step.do(`${name}:persist-transcript`, () => (\n options.persistTranscript(options.event.payload, receipt)\n ))\n const settledReceipt = await options.settle(options.event.payload, receipt, messageId)\n if (settledReceipt) {\n receipt = settledReceipt\n await options.step.do(`${name}:persist-final-transcript`, () => (\n options.persistTranscript(options.event.payload, receipt, messageId)\n ))\n }\n if (options.finalizeBuffer) {\n await options.step.do(`${name}:finalize-buffer`, () => (\n options.finalizeBuffer!(options.event.payload, receipt, messageId)\n ))\n }\n await options.step.do(`${name}:release-lock`, () => options.releaseLock(options.event.payload))\n return receipt\n}\n","const HEADER_NAME = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/\n\n/** One path rule in Cloudflare's static-asset `_headers` file. */\nexport interface CloudflareHeadersRule {\n pattern: string\n headers: Readonly<Record<string, string>>\n}\n\nfunction assertSingleLine(value: string, label: string): void {\n if (value.length === 0 || /[\\r\\n]/.test(value)) {\n throw new Error(`${label} must be a non-empty single line`)\n }\n}\n\n/**\n * Render native Cloudflare static-asset header rules.\n *\n * Values are validated before serialization so a product setting cannot add a\n * second path rule or response header through a newline.\n */\nexport function renderCloudflareHeadersFile(\n rules: readonly CloudflareHeadersRule[],\n): string {\n if (rules.length === 0) {\n throw new Error('At least one Cloudflare header rule is required')\n }\n\n const renderedRules = rules.map(({ pattern, headers }) => {\n assertSingleLine(pattern, 'Cloudflare header pattern')\n if (!pattern.startsWith('/') || /\\s/.test(pattern)) {\n throw new Error('Cloudflare header patterns must start with / and contain no whitespace')\n }\n\n const entries = Object.entries(headers)\n if (entries.length === 0) {\n throw new Error(`Cloudflare header rule ${pattern} must contain at least one header`)\n }\n\n const renderedHeaders = entries.map(([name, value]) => {\n if (!HEADER_NAME.test(name)) {\n throw new Error(`Invalid HTTP header name: ${name}`)\n }\n assertSingleLine(value, `Cloudflare header ${name}`)\n return ` ${name}: ${value}`\n })\n\n return [pattern, ...renderedHeaders].join('\\n')\n })\n\n return `${renderedRules.join('\\n\\n')}\\n`\n}\n","/**\n * `@tangle-network/agent-app/preset-cloudflare` — the batteries-included default\n * stack.\n *\n * Every fleet agent runs the SAME backend: Cloudflare D1 (SQLite) through\n * Drizzle for state, a KV namespace as the artifact vault, AES-GCM field crypto\n * for PII, and per-workspace budget-capped model keys. The other agent-app\n * modules are pure SEAMS — `./tools` needs an `AppToolHandlers`, `./knowledge`\n * needs a `KnowledgeStateAccessor`, `./billing` needs a `WorkspaceKeyStore` +\n * `KeyCrypto`. This module is the ONE implementation of those seams against the\n * house stack, so a consumer that runs D1 + KV stands the whole shell up with\n * config + bindings and ZERO handler code.\n *\n * Layering:\n * - Drizzle is a PEER (the consumer installs `drizzle-orm`, never bundled). The\n * schema is therefore expressed two ways that need no import here: the plain\n * DDL ({@link PRESET_MIGRATION_SQL}) a consumer runs to create the tables, and\n * a {@link createPresetDrizzleSchema} factory that takes the consumer's\n * `drizzle-orm/sqlite-core` builder module and returns the typed tables. The\n * column names in {@link PRESET_TABLES} are the contract the handlers,\n * accessor, and DDL all agree on.\n * - D1 + KV are STRUCTURAL: {@link D1Like} (Cloudflare `D1Database` satisfies it)\n * and `KvLike` from `../web` (Cloudflare `KVNamespace` satisfies it). No\n * `@cloudflare/workers-types` dependency.\n * - Crypto/billing reuse `../crypto` + `../billing` exactly — this only wires\n * them to the D1 key table.\n */\n\nimport { createFieldCrypto } from '../crypto/index'\nimport {\n createWorkspaceKeyManager,\n type KeyCrypto,\n type KeyProvisioner,\n type WorkspaceKeyManager,\n type WorkspaceKeyRecord,\n type WorkspaceKeyStore,\n} from '../billing/index'\nimport type { KnowledgeStateAccessor } from '../knowledge/index'\nimport type {\n AddCitationArgs,\n AddCitationResult,\n AppToolContext,\n AppToolHandlers,\n RenderUiArgs,\n RenderUiResult,\n ScheduleFollowupArgs,\n ScheduleFollowupResult,\n SubmitProposalArgs,\n SubmitProposalResult,\n} from '../tools/index'\nimport { assertRenderUiSchema } from '../tools/render-ui-schema'\nimport type { KvLike } from '../web/index'\n\nexport {\n ensureCloudflareWorkflowInstance,\n type CloudflareWorkflowBindingLike,\n type CloudflareWorkflowInstanceLike,\n type EnsureCloudflareWorkflowInstanceResult,\n} from './workflow-instance'\n\nexport {\n runDetachedTurnWorkflowTick,\n SANDBOX_TRANSIENT_TICK_POLICY,\n type DetachedTurnTransientPolicy,\n type CloudflareWorkflowEventLike,\n type CloudflareWorkflowSleepDuration,\n type CloudflareWorkflowStepLike,\n type DetachedTurnDriveOutcome,\n type DetachedTurnDriveState,\n type DetachedTurnTerminalResult,\n type DetachedTurnWorkflowIdentity,\n type DetachedTurnWorkflowTickOptions,\n} from './detached-turn-workflow'\n\nexport {\n runNativeCompletionWorkflow,\n type NativeCompletionWorkflowOptions,\n type NativeCompletionWorkflowPayload,\n} from './native-completion-workflow'\n\nexport {\n renderCloudflareHeadersFile,\n type CloudflareHeadersRule,\n} from './headers'\n\n// ---------------------------------------------------------------------------\n// D1 structural seam\n//\n// The minimal surface of Cloudflare's `D1Database` the handlers + accessor use.\n// `D1Database` satisfies it structurally, so the consumer passes `env.DB`\n// directly and tests pass an in-memory fake. We keep it to the\n// prepare/bind/first/run/all shape D1 already exposes — no Drizzle here, so the\n// default handlers run on a fresh Worker with only the D1 binding.\n// ---------------------------------------------------------------------------\n\n/** A prepared, bound D1 statement. */\nexport interface D1PreparedLike {\n bind(...values: unknown[]): D1PreparedLike\n first<T = Record<string, unknown>>(colName?: string): Promise<T | null>\n run(): Promise<unknown>\n all<T = Record<string, unknown>>(): Promise<{ results: T[] }>\n}\n\n/** The D1 surface the preset needs. Cloudflare `D1Database` satisfies it. */\nexport interface D1Like {\n prepare(query: string): D1PreparedLike\n}\n\n// ---------------------------------------------------------------------------\n// Default schema\n//\n// The four tables the default handlers + accessor read/write. Column names are\n// the single source of truth shared by the DDL, the Drizzle factory, the\n// handlers, and the accessor. Every table is workspace-scoped on `workspace_id`\n// (the accessor's default `where` column) so the knowledge `count` rule and the\n// tool writes agree without per-consumer wiring.\n//\n// Deliberately NOT here: chat thread/message tables. `/chat-store` is the\n// single thread-schema owner (`createChatTables` + `createChatStore`); the\n// preset once declared an unconsumed `threads` DDL, removed so two schemas\n// can't drift.\n// ---------------------------------------------------------------------------\n\n/** The preset table + column names — the contract the DDL, Drizzle schema,\n * handlers, and accessor share. Exposed so a consumer can reference a column\n * without a string literal. */\nexport const PRESET_TABLES = {\n proposals: {\n name: 'proposals',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n threadId: 'thread_id',\n type: 'type',\n title: 'title',\n description: 'description',\n status: 'status',\n createdBy: 'created_by',\n createdAt: 'created_at',\n },\n },\n knowledge: {\n name: 'knowledge',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n path: 'path',\n kind: 'kind',\n label: 'label',\n content: 'content',\n createdAt: 'created_at',\n },\n },\n deadlines: {\n name: 'deadlines',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n threadId: 'thread_id',\n title: 'title',\n dueDate: 'due_date',\n priority: 'priority',\n status: 'status',\n createdAt: 'created_at',\n },\n },\n workspaceKeys: {\n name: 'workspace_keys',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n keyId: 'key_id',\n keyEncrypted: 'key_encrypted',\n budgetUsd: 'budget_usd',\n expiresAt: 'expires_at',\n revokedAt: 'revoked_at',\n createdAt: 'created_at',\n },\n },\n} as const\n\n/**\n * Plain DDL for the preset schema — run by a consumer to create the tables with\n * ZERO drizzle (`for (const sql of PRESET_MIGRATION_SQL) await db.prepare(sql).run()`,\n * or paste into a `.sql` migration). One statement per table so D1's\n * single-statement `prepare` accepts each. Matches {@link PRESET_TABLES} exactly.\n */\nexport const PRESET_MIGRATION_SQL: readonly string[] = [\n `CREATE TABLE IF NOT EXISTS proposals (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n thread_id TEXT,\n type TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n created_by TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS knowledge (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n path TEXT NOT NULL,\n kind TEXT NOT NULL,\n label TEXT,\n content TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS deadlines (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n thread_id TEXT,\n title TEXT NOT NULL,\n due_date TEXT NOT NULL,\n priority TEXT,\n status TEXT NOT NULL DEFAULT 'scheduled',\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS workspace_keys (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n key_id TEXT NOT NULL,\n key_encrypted TEXT NOT NULL,\n budget_usd REAL NOT NULL,\n expires_at INTEGER NOT NULL,\n revoked_at INTEGER,\n created_at INTEGER NOT NULL\n )`,\n `CREATE INDEX IF NOT EXISTS idx_proposals_ws ON proposals (workspace_id, status)`,\n `CREATE INDEX IF NOT EXISTS idx_deadlines_ws ON deadlines (workspace_id, status)`,\n `CREATE INDEX IF NOT EXISTS idx_knowledge_ws ON knowledge (workspace_id)`,\n `CREATE INDEX IF NOT EXISTS idx_workspace_keys_ws ON workspace_keys (workspace_id, revoked_at)`,\n]\n\n/** A chainable column builder — every modifier returns the builder so calls\n * like `.notNull().default('pending')` typecheck. The concrete drizzle builders\n * satisfy this structurally. */\nexport interface DrizzleColumnLike {\n primaryKey: () => DrizzleColumnLike\n notNull: () => DrizzleColumnLike\n default: (v: unknown) => DrizzleColumnLike\n}\n\n/** The shape of a `drizzle-orm/sqlite-core` module — the few builders the\n * preset schema uses. The consumer passes the real module; agent-app never\n * imports it (it stays a peer). */\nexport interface DrizzleSqliteCoreLike {\n sqliteTable: (name: string, columns: Record<string, DrizzleColumnLike>) => unknown\n text: (name?: string) => DrizzleColumnLike\n integer: (name?: string, config?: unknown) => DrizzleColumnLike\n real: (name?: string) => DrizzleColumnLike\n}\n\n/**\n * Build the typed Drizzle schema for the preset, given the consumer's\n * `drizzle-orm/sqlite-core` module. Returns one table object per\n * {@link PRESET_TABLES} entry — pass to `drizzle(db, { schema })` for typed\n * queries, or to drizzle-kit for migration generation. agent-app never imports\n * drizzle; the builder module is the seam.\n *\n * ```ts\n * import * as d from 'drizzle-orm/sqlite-core'\n * const schema = createPresetDrizzleSchema(d)\n * ```\n */\nexport function createPresetDrizzleSchema(d: DrizzleSqliteCoreLike) {\n const { sqliteTable, text, integer, real } = d\n const C = PRESET_TABLES\n return {\n proposals: sqliteTable(C.proposals.name, {\n id: text(C.proposals.columns.id).primaryKey(),\n workspaceId: text(C.proposals.columns.workspaceId).notNull(),\n threadId: text(C.proposals.columns.threadId),\n type: text(C.proposals.columns.type).notNull(),\n title: text(C.proposals.columns.title).notNull(),\n description: text(C.proposals.columns.description),\n status: text(C.proposals.columns.status).notNull().default('pending'),\n createdBy: text(C.proposals.columns.createdBy),\n createdAt: integer(C.proposals.columns.createdAt).notNull(),\n }),\n knowledge: sqliteTable(C.knowledge.name, {\n id: text(C.knowledge.columns.id).primaryKey(),\n workspaceId: text(C.knowledge.columns.workspaceId).notNull(),\n path: text(C.knowledge.columns.path).notNull(),\n kind: text(C.knowledge.columns.kind).notNull(),\n label: text(C.knowledge.columns.label),\n content: text(C.knowledge.columns.content),\n createdAt: integer(C.knowledge.columns.createdAt).notNull(),\n }),\n deadlines: sqliteTable(C.deadlines.name, {\n id: text(C.deadlines.columns.id).primaryKey(),\n workspaceId: text(C.deadlines.columns.workspaceId).notNull(),\n threadId: text(C.deadlines.columns.threadId),\n title: text(C.deadlines.columns.title).notNull(),\n dueDate: text(C.deadlines.columns.dueDate).notNull(),\n priority: text(C.deadlines.columns.priority),\n status: text(C.deadlines.columns.status).notNull().default('scheduled'),\n createdAt: integer(C.deadlines.columns.createdAt).notNull(),\n }),\n workspaceKeys: sqliteTable(C.workspaceKeys.name, {\n id: text(C.workspaceKeys.columns.id).primaryKey(),\n workspaceId: text(C.workspaceKeys.columns.workspaceId).notNull(),\n keyId: text(C.workspaceKeys.columns.keyId).notNull(),\n keyEncrypted: text(C.workspaceKeys.columns.keyEncrypted).notNull(),\n budgetUsd: real(C.workspaceKeys.columns.budgetUsd).notNull(),\n expiresAt: integer(C.workspaceKeys.columns.expiresAt).notNull(),\n revokedAt: integer(C.workspaceKeys.columns.revokedAt),\n createdAt: integer(C.workspaceKeys.columns.createdAt).notNull(),\n }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Default AppToolHandlers over D1 + KV\n// ---------------------------------------------------------------------------\n\n/** The KV-backed vault. `KvLike` (from `../web`) is the structural KV contract;\n * Cloudflare `KVNamespace` satisfies it. Artifacts are stored under their path. */\nexport type VaultKv = KvLike\n\n/** Define configuration options for handling preset tools including database, vault, and optional utilities */\nexport interface PresetToolHandlerOptions {\n /** The D1 database (Cloudflare `D1Database` satisfies {@link D1Like}). */\n db: D1Like\n /** The KV namespace used as the artifact vault. */\n vault: VaultKv\n /** Id generator. Default `crypto.randomUUID`. Injectable for deterministic tests. */\n newId?: () => string\n /** Clock (epoch ms). Default `Date.now`. Injectable for deterministic tests. */\n now?: () => number\n /** Vault path prefix for `render_ui` artifacts. Default `'ui'`. */\n uiPathPrefix?: string\n /** Vault path prefix for `add_citation` artifacts. Default `'citations'`. */\n citationPathPrefix?: string\n}\n\nfunction slug(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 64) || 'item'\n}\n\n/**\n * The default {@link AppToolHandlers} for the house stack:\n * - `submit_proposal` → insert a `proposals` row (`status='pending'`), deduped\n * on (workspace, title) so a retried turn doesn't double-queue.\n * - `schedule_followup` → insert a `deadlines` row, deduped on (workspace, title, due_date).\n * - `render_ui` → write the schema JSON as a `ui/<thread>/<slug>.json`\n * vault artifact AND a `knowledge` row pointing at it.\n * - `add_citation` → write the quote as a `citations/<slug>.json` artifact AND\n * a `knowledge` row.\n *\n * Returns the EXACT persisted content from `render_ui` (per the seam contract) so\n * a completion oracle sees real bytes. Pure seam wiring: a consumer that runs\n * D1 + KV gets all four tools with no handler code.\n */\nexport function createPresetToolHandlers(opts: PresetToolHandlerOptions): AppToolHandlers {\n const { db, vault } = opts\n const newId = opts.newId ?? (() => crypto.randomUUID())\n const now = opts.now ?? (() => Date.now())\n const uiPrefix = opts.uiPathPrefix ?? 'ui'\n const citationPrefix = opts.citationPathPrefix ?? 'citations'\n const P = PRESET_TABLES.proposals\n const D = PRESET_TABLES.deadlines\n const K = PRESET_TABLES.knowledge\n\n async function persistArtifact(path: string, body: string): Promise<void> {\n await vault.put(path, body)\n }\n\n async function insertKnowledge(workspaceId: string, path: string, kind: string, label: string | null, content: string): Promise<void> {\n await db\n .prepare(\n `INSERT INTO ${K.name} (${K.columns.id}, ${K.columns.workspaceId}, ${K.columns.path}, ${K.columns.kind}, ${K.columns.label}, ${K.columns.content}, ${K.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(newId(), workspaceId, path, kind, label, content, now())\n .run()\n }\n\n return {\n async submitProposal(args: SubmitProposalArgs, ctx: AppToolContext): Promise<SubmitProposalResult> {\n const existing = await db\n .prepare(`SELECT ${P.columns.id} AS id FROM ${P.name} WHERE ${P.columns.workspaceId} = ? AND ${P.columns.title} = ? LIMIT 1`)\n .bind(ctx.workspaceId, args.title)\n .first<{ id: string }>()\n if (existing) return { proposalId: existing.id, deduped: true }\n\n const id = newId()\n await db\n .prepare(\n `INSERT INTO ${P.name} (${P.columns.id}, ${P.columns.workspaceId}, ${P.columns.threadId}, ${P.columns.type}, ${P.columns.title}, ${P.columns.description}, ${P.columns.status}, ${P.columns.createdBy}, ${P.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?)`,\n )\n .bind(id, ctx.workspaceId, ctx.threadId, args.type, args.title, args.description ?? null, ctx.userId, now())\n .run()\n return { proposalId: id, deduped: false }\n },\n\n async scheduleFollowup(args: ScheduleFollowupArgs, ctx: AppToolContext): Promise<ScheduleFollowupResult> {\n const existing = await db\n .prepare(\n `SELECT ${D.columns.id} AS id, ${D.columns.dueDate} AS dueDate FROM ${D.name} WHERE ${D.columns.workspaceId} = ? AND ${D.columns.title} = ? AND ${D.columns.dueDate} = ? LIMIT 1`,\n )\n .bind(ctx.workspaceId, args.title, args.dueDate)\n .first<{ id: string; dueDate: string }>()\n if (existing) return { id: existing.id, dueDate: existing.dueDate, deduped: true }\n\n const id = newId()\n await db\n .prepare(\n `INSERT INTO ${D.name} (${D.columns.id}, ${D.columns.workspaceId}, ${D.columns.threadId}, ${D.columns.title}, ${D.columns.dueDate}, ${D.columns.priority}, ${D.columns.status}, ${D.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, 'scheduled', ?)`,\n )\n .bind(id, ctx.workspaceId, ctx.threadId, args.title, args.dueDate, args.priority ?? null, now())\n .run()\n return { id, dueDate: args.dueDate, deduped: false }\n },\n\n async renderUi(args: RenderUiArgs, ctx: AppToolContext): Promise<RenderUiResult> {\n assertRenderUiSchema(args.schema)\n const content = JSON.stringify(args.schema)\n const path = `${uiPrefix}/${ctx.threadId ?? 'global'}/${slug(args.title)}.json`\n await persistArtifact(path, content)\n await insertKnowledge(ctx.workspaceId, path, 'ui', args.title, content)\n return { path, content }\n },\n\n async addCitation(args: AddCitationArgs, ctx: AppToolContext): Promise<AddCitationResult> {\n const citationId = newId()\n const path = `${citationPrefix}/${slug(args.label ?? args.path)}-${citationId.slice(0, 8)}.json`\n const body = JSON.stringify({ sourcePath: args.path, quote: args.quote, label: args.label ?? null })\n await persistArtifact(path, body)\n await insertKnowledge(ctx.workspaceId, path, 'citation', args.label ?? null, body)\n return { citationId, path }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// D1-backed KnowledgeStateAccessor\n// ---------------------------------------------------------------------------\n\n/** Define options for accessing preset knowledge scoped to a specific workspace and configuration */\nexport interface PresetKnowledgeAccessorOptions {\n db: D1Like\n /** The active workspace — every `count` is scoped to it. */\n workspaceId: string\n /** Workspace config the `satisfiedBy: { config }` rules read. A resolved\n * object (dot-path lookup), or a function the accessor calls per path. */\n config: Record<string, unknown> | ((path: string) => unknown)\n /** The default workspace fk column a `count` rule scopes on when its rule\n * omits `where`. Default `'workspace_id'` (the preset schema convention). */\n defaultWhereColumn?: string\n}\n\nfunction readDotPath(obj: Record<string, unknown>, path: string): unknown {\n let cur: unknown = obj\n for (const part of path.split('.')) {\n if (cur == null || typeof cur !== 'object') return undefined\n cur = (cur as Record<string, unknown>)[part]\n }\n return cur\n}\n\n/**\n * The {@link KnowledgeStateAccessor} over the preset D1 schema — the seam that\n * lets the declarative `satisfiedBy` rules resolve with ZERO consumer code:\n * - `config(path)` reads the supplied workspace config by dot-path.\n * - `count({ table, where, statusIn })` runs `SELECT count(*)` scoped to the\n * active workspace (the rule's `where` column, default `workspace_id`),\n * optionally filtered to `statusIn` via a parameterized `IN (...)`.\n *\n * Identifiers (table/column) are validated against a safe pattern before\n * interpolation — they originate from the product's own config, never model\n * input, but we fail loud rather than build a malformed/injectable query.\n */\nexport function createD1KnowledgeStateAccessor(opts: PresetKnowledgeAccessorOptions): KnowledgeStateAccessor {\n const { db, workspaceId } = opts\n const defaultWhere = opts.defaultWhereColumn ?? 'workspace_id'\n const configFn = typeof opts.config === 'function' ? opts.config : (path: string) => readDotPath(opts.config as Record<string, unknown>, path)\n\n const isIdentifier = (s: string) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s)\n\n return {\n config: configFn,\n async count(query) {\n if (!isIdentifier(query.table)) throw new Error(`unsafe table identifier: ${query.table}`)\n const whereCol = query.where ?? defaultWhere\n if (!isIdentifier(whereCol)) throw new Error(`unsafe where identifier: ${whereCol}`)\n\n let sql = `SELECT count(*) AS n FROM ${query.table} WHERE ${whereCol} = ?`\n const binds: unknown[] = [workspaceId]\n if (query.statusIn && query.statusIn.length > 0) {\n sql += ` AND status IN (${query.statusIn.map(() => '?').join(', ')})`\n binds.push(...query.statusIn)\n }\n const row = await db.prepare(sql).bind(...binds).first<{ n: number }>()\n return row?.n ?? 0\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Crypto + per-workspace key wiring\n// ---------------------------------------------------------------------------\n\n/** Build the {@link KeyCrypto} the billing key store uses — AES-256-GCM field\n * crypto bound to the product's 64-char-hex `ENCRYPTION_KEY` (or a resolver).\n * This is the concrete impl behind the `../billing` `KeyCrypto` seam. */\nexport function createPresetFieldCrypto(key: string | (() => string)): KeyCrypto {\n return createFieldCrypto(key)\n}\n\n/**\n * The {@link WorkspaceKeyStore} over the preset `workspace_keys` table — the\n * persistence seam the per-workspace key manager needs. \"Active\" = a row with a\n * null `revoked_at`. Pure D1 wiring; no key minting (that's the provisioner).\n */\nexport function createPresetWorkspaceKeyStore(db: D1Like): WorkspaceKeyStore {\n const W = PRESET_TABLES.workspaceKeys\n return {\n async getActive(workspaceId: string): Promise<WorkspaceKeyRecord | null> {\n const row = await db\n .prepare(\n `SELECT ${W.columns.id} AS id, ${W.columns.keyId} AS keyId, ${W.columns.keyEncrypted} AS keyEncrypted, ${W.columns.budgetUsd} AS budgetUsd, ${W.columns.expiresAt} AS expiresAt FROM ${W.name} WHERE ${W.columns.workspaceId} = ? AND ${W.columns.revokedAt} IS NULL ORDER BY ${W.columns.createdAt} DESC LIMIT 1`,\n )\n .bind(workspaceId)\n .first<{ id: string; keyId: string; keyEncrypted: string; budgetUsd: number; expiresAt: number | null }>()\n if (!row) return null\n return {\n id: row.id,\n keyId: row.keyId,\n keyEncrypted: row.keyEncrypted,\n budgetUsd: row.budgetUsd,\n expiresAt: row.expiresAt == null ? null : new Date(row.expiresAt),\n }\n },\n async listActive(workspaceId: string): Promise<Array<{ id: string; keyId: string }>> {\n const res = await db\n .prepare(`SELECT ${W.columns.id} AS id, ${W.columns.keyId} AS keyId FROM ${W.name} WHERE ${W.columns.workspaceId} = ? AND ${W.columns.revokedAt} IS NULL`)\n .bind(workspaceId)\n .all<{ id: string; keyId: string }>()\n return res.results\n },\n async insert(record): Promise<void> {\n await db\n .prepare(\n `INSERT INTO ${W.name} (${W.columns.id}, ${W.columns.workspaceId}, ${W.columns.keyId}, ${W.columns.keyEncrypted}, ${W.columns.budgetUsd}, ${W.columns.expiresAt}, ${W.columns.revokedAt}, ${W.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, NULL, ?)`,\n )\n .bind(crypto.randomUUID(), record.workspaceId, record.keyId, record.keyEncrypted, record.budgetUsd, record.expiresAt.getTime(), Date.now())\n .run()\n },\n async markRevoked(id: string, now: Date): Promise<void> {\n await db\n .prepare(`UPDATE ${W.name} SET ${W.columns.revokedAt} = ? WHERE ${W.columns.id} = ?`)\n .bind(now.getTime(), id)\n .run()\n },\n }\n}\n\n/** Define preset billing options including database, provisioner, encryption key, budget, and optional settings */\nexport interface PresetBillingOptions {\n db: D1Like\n /** The key provisioner (`@tangle-network/tcloud`'s client satisfies it structurally). */\n provisioner: KeyProvisioner\n /** Field-crypto key (64-char hex) or resolver — encrypts the minted key at rest. */\n encryptionKey: string | (() => string)\n /** Default monthly USD allowance when a call doesn't specify one. */\n defaultBudgetUsd: number\n /** Injectable clock. */\n now?: () => Date\n /** tcloud product the key is scoped to. Default `'router'`. */\n product?: string\n}\n\n/**\n * Stand up the per-workspace budget-capped {@link WorkspaceKeyManager} on the\n * house stack: the preset `workspace_keys` D1 store + AES-GCM field crypto +\n * the consumer's tcloud provisioner. The mint/rotate/rollover/usage LOGIC lives\n * in `../billing`; this only binds it to the preset table + crypto.\n */\nexport function createPresetWorkspaceKeyManager(opts: PresetBillingOptions): WorkspaceKeyManager {\n return createWorkspaceKeyManager({\n provisioner: opts.provisioner,\n store: createPresetWorkspaceKeyStore(opts.db),\n crypto: createPresetFieldCrypto(opts.encryptionKey),\n defaultBudgetUsd: opts.defaultBudgetUsd,\n now: opts.now,\n product: opts.product,\n })\n}\n"],"mappings":"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","names":["attempt"]}
|
|
1
|
+
{"version":3,"sources":["../../src/preset-cloudflare/workflow-instance.ts","../../src/preset-cloudflare/detached-turn-workflow.ts","../../src/preset-cloudflare/native-completion-workflow.ts","../../src/preset-cloudflare/headers.ts","../../src/preset-cloudflare/index.ts"],"sourcesContent":["/**\n * The structural part of a Cloudflare Workflow instance used to prove that a\n * failed create call actually left an addressable durable instance behind.\n */\nexport interface CloudflareWorkflowInstanceLike<TStatus = unknown> {\n status(): Promise<TStatus>\n}\n\n/** The structural binding implemented by Cloudflare's `Workflow<TParams>`. */\nexport interface CloudflareWorkflowBindingLike<\n TParams,\n TStatus = unknown,\n TInstance extends CloudflareWorkflowInstanceLike<TStatus> =\n CloudflareWorkflowInstanceLike<TStatus>,\n> {\n create(input: { id: string; params: TParams }): Promise<TInstance>\n get(id: string): Promise<TInstance>\n}\n\nexport interface EnsureCloudflareWorkflowInstanceResult<\n TStatus,\n TInstance extends CloudflareWorkflowInstanceLike<TStatus>,\n> {\n instance: TInstance\n /** False only when create failed but the same id was proven readable. */\n created: boolean\n /** Present on reuse because reading status is the proof the instance exists. */\n status?: TStatus\n}\n\n/**\n * Create one durable Workflow instance, or prove an instance with the same id\n * already exists before accepting a create error as an idempotent retry.\n *\n * Cloudflare does not expose a stable duplicate-error class. Matching error\n * text can therefore swallow unrelated failures. This helper uses the durable\n * resource itself as proof: `get(id).status()` must succeed, otherwise the\n * original create error is rethrown.\n */\nexport async function ensureCloudflareWorkflowInstance<\n TParams,\n TStatus,\n TInstance extends CloudflareWorkflowInstanceLike<TStatus>,\n>(\n binding: CloudflareWorkflowBindingLike<TParams, TStatus, TInstance>,\n input: { id: string; params: TParams },\n): Promise<EnsureCloudflareWorkflowInstanceResult<TStatus, TInstance>> {\n try {\n return {\n instance: await binding.create(input),\n created: true,\n }\n } catch (createError) {\n try {\n const instance = await binding.get(input.id)\n const status = await instance.status()\n return { instance, created: false, status }\n } catch {\n throw createError\n }\n }\n}\n","import type { TurnDriveResult } from '@tangle-network/sandbox'\nimport type { Outcome } from '../sandbox/outcome'\nimport {\n SANDBOX_TRANSIENT_DEADLINE_MS,\n SandboxTransientDeadlineError,\n classifySandboxTransientFailure,\n sandboxTransientBackoffMs,\n type SandboxTransientFailure,\n} from '../sandbox/transient-failure'\n\n/** The stable identity a Workflow reuses on every retry of one turn. */\nexport interface DetachedTurnWorkflowIdentity {\n /** Sandbox session resume key. */\n sessionId: string\n /** Sandbox completed-turn idempotency key. */\n turnId: string\n}\n\n/** The part of Cloudflare's Workflow event needed by the tick. */\nexport interface CloudflareWorkflowEventLike<TPayload> {\n payload: TPayload\n}\n\n/** A duration accepted by Cloudflare Workflow `step.sleep`. */\nexport type CloudflareWorkflowSleepDuration =\n | `${number} ${\n | 'second'\n | 'seconds'\n | 'minute'\n | 'minutes'\n | 'hour'\n | 'hours'\n | 'day'\n | 'days'\n | 'week'\n | 'weeks'\n | 'month'\n | 'months'\n | 'year'\n | 'years'}`\n | number\n\n/** The durable Workflow operations used by the tick. */\nexport interface CloudflareWorkflowStepLike {\n do<T>(name: string, callback: (context: unknown) => Promise<T>): Promise<T>\n sleep(name: string, duration: CloudflareWorkflowSleepDuration): Promise<void>\n}\n\n/** The states returned by the Sandbox `driveTurn` primitive. */\nexport type DetachedTurnDriveState = TurnDriveResult['state']\n\n/** The small state contract a durable tick needs. Product observers may carry\n * a richer terminal receipt than Sandbox's `TurnDriveResult`. */\nexport interface DetachedTurnDriveResultLike {\n state: DetachedTurnDriveState\n}\n\n/** The terminal result passed to product settlement. */\nexport type DetachedTurnTerminalResult<\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n> = Exclude<TResult, { state: 'running' }>\n\n/** A drive call's retryable transport boundary. */\nexport type DetachedTurnDriveOutcome<\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n> = Outcome<TResult>\n\n/** Options for one durable detached-turn Workflow run. */\nexport interface DetachedTurnWorkflowTickOptions<\n TPayload extends DetachedTurnWorkflowIdentity,\n TSettled,\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n> {\n event: CloudflareWorkflowEventLike<TPayload>\n step: CloudflareWorkflowStepLike\n /** One SDK drive pass. Rejected results must not enter the Workflow cache. */\n drive: (payload: TPayload) => Promise<DetachedTurnDriveOutcome<TResult>>\n /** Must be idempotent: the Worker can stop after the write but before commit. */\n settle: (payload: TPayload, result: DetachedTurnTerminalResult<TResult>) => Promise<TSettled>\n /** Wait between passes: one duration, or one per zero-based pass so a long\n * turn polls less often. It must depend only on the pass number, so a\n * replayed Workflow takes the same steps. Default: 5 seconds. */\n pollDelay?: CloudflareWorkflowSleepDuration | ((attempt: number) => CloudflareWorkflowSleepDuration)\n stepName?: string\n /**\n * Wait out transient Sandbox failures instead of failing the pass. A drive\n * failure the policy classifies is checkpointed as a running pass, the next\n * pass waits the policy's backoff, and the tick throws\n * {@link SandboxTransientDeadlineError} once one transient stretch outlasts\n * the deadline. Unclassified failures throw from the step as before.\n * Default: the shared Sandbox transient policy.\n */\n transient?: DetachedTurnTransientPolicy | false\n}\n\n/** Which drive failures a tick waits out, and for how long. */\nexport interface DetachedTurnTransientPolicy {\n classify(error: unknown): SandboxTransientFailure | null\n deadlineMs: number\n /** Wait before pass `attempt` (1-based) of one transient stretch. */\n backoffMs(attempt: number): number\n}\n\n/** The shared Sandbox transient policy: 5 s doubling to 60 s, for at most 15 minutes. */\nexport const SANDBOX_TRANSIENT_TICK_POLICY: DetachedTurnTransientPolicy = {\n classify: classifySandboxTransientFailure,\n deadlineMs: SANDBOX_TRANSIENT_DEADLINE_MS,\n backoffMs: sandboxTransientBackoffMs,\n}\n\n/** A pass that met a transient failure, as the step checkpoints it. */\ninterface TransientPass {\n state: 'running'\n transient: SandboxTransientFailure & { at: number }\n}\n\nfunction transientPass(value: unknown): TransientPass['transient'] | undefined {\n const transient = (value as Partial<TransientPass> | null)?.transient\n return transient && typeof transient.at === 'number' && typeof transient.code === 'string' ? transient : undefined\n}\n\nfunction assertIdentity(payload: DetachedTurnWorkflowIdentity): void {\n if (!payload || typeof payload.sessionId !== 'string' || !payload.sessionId.trim()) {\n throw new Error('detached turn Workflow payload requires a non-empty sessionId')\n }\n if (typeof payload.turnId !== 'string' || !payload.turnId.trim()) {\n throw new Error('detached turn Workflow payload requires a non-empty turnId')\n }\n}\n\n// Keyed by the SDK's own union, so a state a new Sandbox adds fails this\n// package's build instead of retrying a settled turn as \"unknown\" forever.\nconst DRIVE_STATES: Record<DetachedTurnDriveState, true> = {\n running: true,\n completed: true,\n failed: true,\n awaiting_plan_decision: true,\n blocked_on_approval: true,\n awaiting_question: true,\n awaiting_interaction: true,\n}\n\nfunction isKnownDriveState(state: unknown): state is DetachedTurnDriveState {\n return typeof state === 'string' && Object.hasOwn(DRIVE_STATES, state)\n}\n\nfunction checkedDriveResult<TResult extends DetachedTurnDriveResultLike>(value: TResult): TResult {\n const state = (value as { state?: unknown } | null)?.state\n if (!isKnownDriveState(state)) {\n throw new Error(`detached turn drive returned unknown state: ${String(state)}`)\n }\n return value\n}\n\n/**\n * Drive a detached SDK turn with durable steps, not an HTTP waitUntil lifetime.\n * Stable step names let an evicted Workflow resume without repeating committed\n * passes. Validation belongs inside step.do so a transient malformed response\n * is retried rather than committed permanently as a poisoned checkpoint.\n */\nexport async function runDetachedTurnWorkflowTick<\n TPayload extends DetachedTurnWorkflowIdentity,\n TSettled,\n TResult extends DetachedTurnDriveResultLike = TurnDriveResult,\n>(\n options: DetachedTurnWorkflowTickOptions<TPayload, TSettled, TResult>,\n): Promise<TSettled> {\n assertIdentity(options.event?.payload)\n // Never allow a callback to change the admission identity for later passes.\n const payload = Object.freeze({ ...options.event.payload }) as TPayload\n const name = options.stepName ?? 'detached-turn'\n const pollDelay = options.pollDelay ?? '5 seconds'\n const delayFor = (attempt: number): CloudflareWorkflowSleepDuration =>\n typeof pollDelay === 'function' ? pollDelay(attempt) : pollDelay\n const transient = options.transient === false ? undefined : options.transient ?? SANDBOX_TRANSIENT_TICK_POLICY\n let attempt = 0\n // One stretch of consecutive transient passes. Both come from checkpointed\n // step results, so a replayed Workflow takes the same waits.\n let transientSince: number | undefined\n let transientAttempts = 0\n let terminalResult: DetachedTurnTerminalResult<TResult>\n while (true) {\n const driveResult = checkedDriveResult(await options.step.do<TResult>(`${name}:drive:${attempt}`, async () => {\n let outcome: DetachedTurnDriveOutcome<TResult>\n try {\n outcome = await options.drive(payload)\n } catch (error) {\n outcome = { succeeded: false, error: error instanceof Error ? error : new Error(String(error)) }\n }\n if (!outcome.succeeded) {\n const failure = transient?.classify(outcome.error)\n if (!failure) throw outcome.error\n return { state: 'running', transient: { ...failure, at: Date.now() } } satisfies TransientPass as unknown as TResult\n }\n return checkedDriveResult(outcome.value)\n }))\n // Validate replayed values as well, including checkpoints from older code.\n if (driveResult.state !== 'running') {\n terminalResult = driveResult as DetachedTurnTerminalResult<TResult>\n break\n }\n const pass = transient ? transientPass(driveResult) : undefined\n if (pass && transient) {\n transientSince ??= pass.at\n transientAttempts += 1\n const delayMs = transient.backoffMs(transientAttempts)\n if (pass.at + delayMs - transientSince > transient.deadlineMs) {\n throw new SandboxTransientDeadlineError(pass, transientSince, transientAttempts)\n }\n await options.step.sleep(`${name}:wait:${attempt}`, delayMs)\n } else {\n transientSince = undefined\n transientAttempts = 0\n await options.step.sleep(`${name}:wait:${attempt}`, delayFor(attempt))\n }\n attempt += 1\n }\n return options.step.do(`${name}:settle`, () => options.settle(payload, terminalResult))\n}\n","/**\n * Durable terminal assembly for a native turn admitted by another request.\n * Product callbacks own domain effects and may use their own Workflow steps;\n * this helper never nests those callbacks inside `step.do`.\n */\n\nimport type {\n NativeCompletionObservation,\n NativeCompletionReceipt,\n} from '../chat-routes/native-completion'\nimport {\n runDetachedTurnWorkflowTick,\n type CloudflareWorkflowEventLike,\n type CloudflareWorkflowSleepDuration,\n type CloudflareWorkflowStepLike,\n type DetachedTurnWorkflowIdentity,\n} from './detached-turn-workflow'\n\n/** Stable identity required to resume a registered native completion. */\nexport interface NativeCompletionWorkflowPayload extends DetachedTurnWorkflowIdentity {\n registeredAt: number\n}\n\ntype NativeCompletionDriveResult = NativeCompletionObservation\n\nexport interface NativeCompletionWorkflowOptions<\n TPayload extends NativeCompletionWorkflowPayload,\n TMessageId,\n> {\n event: CloudflareWorkflowEventLike<TPayload>\n step: CloudflareWorkflowStepLike\n /** Read-only exact observation. It must never dispatch, drive, or cancel. */\n observe(payload: TPayload): Promise<NativeCompletionObservation>\n /**\n * Product-owned idempotent preparation, for example artifact promotion and\n * output classification. It runs outside an enclosing Workflow step so a\n * product may use its own named steps without nesting `step.do`.\n */\n prepare?(payload: TPayload, receipt: NativeCompletionReceipt): Promise<NativeCompletionReceipt>\n /**\n * Durable transcript write. Without a message id it must upsert the turn's\n * one row: the terminal observation step checkpoints the observed receipt\n * with it before preparation, and the transcript step writes the prepared\n * receipt over that checkpoint. A supplied message id updates the existing row.\n */\n persistTranscript(\n payload: TPayload,\n receipt: NativeCompletionReceipt,\n existingMessageId?: TMessageId,\n ): Promise<TMessageId>\n /**\n * Product-owned idempotent settlement. A rejection leaves the lock held and\n * skips finalization, making the failed dependency observable and retryable.\n */\n settle(\n payload: TPayload,\n receipt: NativeCompletionReceipt,\n messageId: TMessageId,\n ): Promise<NativeCompletionReceipt | void>\n /** Optional product buffer completion after successful settlement. */\n finalizeBuffer?(payload: TPayload, receipt: NativeCompletionReceipt, messageId: TMessageId): Promise<void>\n /** Release the product lock only after every terminal effect completed. */\n releaseLock(payload: TPayload): Promise<void>\n /** Default: 5 s for the first two minutes, 15 s until thirty minutes, then 30 s. */\n pollDelay?: CloudflareWorkflowSleepDuration | ((attempt: number) => CloudflareWorkflowSleepDuration)\n stepName?: string\n}\n\n/**\n * Wait before observation pass `attempt + 1`: 5 s for the first two minutes,\n * 15 s until thirty minutes, then 30 s. Each pass is two Workflow steps and a\n * Workflow allows 10,000, so a fixed 5 s wait ends an instance after about\n * seven hours; this schedule leaves room for more than forty.\n */\nfunction nativeCompletionPollDelay(attempt: number): CloudflareWorkflowSleepDuration {\n if (attempt < 24) return '5 seconds'\n if (attempt < 136) return '15 seconds'\n return '30 seconds'\n}\n\nfunction validObservation(value: NativeCompletionObservation): NativeCompletionDriveResult {\n if (!value || (value.state !== 'running' && value.state !== 'completed' && value.state !== 'failed')) {\n throw new Error(`native completion observer returned unknown state: ${String((value as { state?: unknown } | null)?.state)}`)\n }\n if (value.state !== 'running' && !value.receipt) {\n throw new Error('native completion observer returned a terminal state without a receipt')\n }\n return value\n}\n\n/**\n * Drive exact observation through durable retry steps, then run the terminal\n * sequence once. `prepare` and `settle` are deliberately outside `step.do` so\n * product callbacks can compose their own named Cloudflare Workflow steps.\n */\nexport async function runNativeCompletionWorkflow<\n TPayload extends NativeCompletionWorkflowPayload,\n TMessageId,\n>(options: NativeCompletionWorkflowOptions<TPayload, TMessageId>): Promise<NativeCompletionReceipt> {\n const name = options.stepName ?? 'native-completion'\n let receipt = await runDetachedTurnWorkflowTick<\n TPayload,\n NativeCompletionReceipt,\n NativeCompletionDriveResult\n >({\n event: options.event,\n step: options.step,\n pollDelay: options.pollDelay ?? nativeCompletionPollDelay,\n stepName: `${name}:observe`,\n drive: async (payload) => {\n const observation = validObservation(await options.observe(payload))\n // The observed answer is written in the same step that observed it, so\n // a later preparation, settlement or Workflow failure cannot lose it.\n if (observation.state !== 'running') await options.persistTranscript(payload, observation.receipt)\n return { succeeded: true, value: observation }\n },\n settle: async (_payload, result) => result.receipt,\n })\n\n if (options.prepare) receipt = await options.prepare(options.event.payload, receipt)\n const messageId = await options.step.do(`${name}:persist-transcript`, () => (\n options.persistTranscript(options.event.payload, receipt)\n ))\n const settledReceipt = await options.settle(options.event.payload, receipt, messageId)\n if (settledReceipt) {\n receipt = settledReceipt\n await options.step.do(`${name}:persist-final-transcript`, () => (\n options.persistTranscript(options.event.payload, receipt, messageId)\n ))\n }\n if (options.finalizeBuffer) {\n await options.step.do(`${name}:finalize-buffer`, () => (\n options.finalizeBuffer!(options.event.payload, receipt, messageId)\n ))\n }\n await options.step.do(`${name}:release-lock`, () => options.releaseLock(options.event.payload))\n return receipt\n}\n","const HEADER_NAME = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/\n\n/** One path rule in Cloudflare's static-asset `_headers` file. */\nexport interface CloudflareHeadersRule {\n pattern: string\n headers: Readonly<Record<string, string>>\n}\n\nfunction assertSingleLine(value: string, label: string): void {\n if (value.length === 0 || /[\\r\\n]/.test(value)) {\n throw new Error(`${label} must be a non-empty single line`)\n }\n}\n\n/**\n * Render native Cloudflare static-asset header rules.\n *\n * Values are validated before serialization so a product setting cannot add a\n * second path rule or response header through a newline.\n */\nexport function renderCloudflareHeadersFile(\n rules: readonly CloudflareHeadersRule[],\n): string {\n if (rules.length === 0) {\n throw new Error('At least one Cloudflare header rule is required')\n }\n\n const renderedRules = rules.map(({ pattern, headers }) => {\n assertSingleLine(pattern, 'Cloudflare header pattern')\n if (!pattern.startsWith('/') || /\\s/.test(pattern)) {\n throw new Error('Cloudflare header patterns must start with / and contain no whitespace')\n }\n\n const entries = Object.entries(headers)\n if (entries.length === 0) {\n throw new Error(`Cloudflare header rule ${pattern} must contain at least one header`)\n }\n\n const renderedHeaders = entries.map(([name, value]) => {\n if (!HEADER_NAME.test(name)) {\n throw new Error(`Invalid HTTP header name: ${name}`)\n }\n assertSingleLine(value, `Cloudflare header ${name}`)\n return ` ${name}: ${value}`\n })\n\n return [pattern, ...renderedHeaders].join('\\n')\n })\n\n return `${renderedRules.join('\\n\\n')}\\n`\n}\n","/**\n * `@tangle-network/agent-app/preset-cloudflare` — the batteries-included default\n * stack.\n *\n * Every fleet agent runs the SAME backend: Cloudflare D1 (SQLite) through\n * Drizzle for state, a KV namespace as the artifact vault, AES-GCM field crypto\n * for PII, and per-workspace budget-capped model keys. The other agent-app\n * modules are pure SEAMS — `./tools` needs an `AppToolHandlers`, `./knowledge`\n * needs a `KnowledgeStateAccessor`, `./billing` needs a `WorkspaceKeyStore` +\n * `KeyCrypto`. This module is the ONE implementation of those seams against the\n * house stack, so a consumer that runs D1 + KV stands the whole shell up with\n * config + bindings and ZERO handler code.\n *\n * Layering:\n * - Drizzle is a PEER (the consumer installs `drizzle-orm`, never bundled). The\n * schema is therefore expressed two ways that need no import here: the plain\n * DDL ({@link PRESET_MIGRATION_SQL}) a consumer runs to create the tables, and\n * a {@link createPresetDrizzleSchema} factory that takes the consumer's\n * `drizzle-orm/sqlite-core` builder module and returns the typed tables. The\n * column names in {@link PRESET_TABLES} are the contract the handlers,\n * accessor, and DDL all agree on.\n * - D1 + KV are STRUCTURAL: {@link D1Like} (Cloudflare `D1Database` satisfies it)\n * and `KvLike` from `../web` (Cloudflare `KVNamespace` satisfies it). No\n * `@cloudflare/workers-types` dependency.\n * - Crypto/billing reuse `../crypto` + `../billing` exactly — this only wires\n * them to the D1 key table.\n */\n\nimport { createFieldCrypto } from '../crypto/index'\nimport {\n createWorkspaceKeyManager,\n type KeyCrypto,\n type KeyProvisioner,\n type WorkspaceKeyManager,\n type WorkspaceKeyRecord,\n type WorkspaceKeyStore,\n} from '../billing/index'\nimport type { KnowledgeStateAccessor } from '../knowledge/index'\nimport type {\n AddCitationArgs,\n AddCitationResult,\n AppToolContext,\n AppToolHandlers,\n RenderUiArgs,\n RenderUiResult,\n ScheduleFollowupArgs,\n ScheduleFollowupResult,\n SubmitProposalArgs,\n SubmitProposalResult,\n} from '../tools/index'\nimport { parseRenderUiSchema } from '../tools/render-ui-schema'\nimport type { KvLike } from '../web/index'\n\nexport {\n ensureCloudflareWorkflowInstance,\n type CloudflareWorkflowBindingLike,\n type CloudflareWorkflowInstanceLike,\n type EnsureCloudflareWorkflowInstanceResult,\n} from './workflow-instance'\n\nexport {\n runDetachedTurnWorkflowTick,\n SANDBOX_TRANSIENT_TICK_POLICY,\n type DetachedTurnTransientPolicy,\n type CloudflareWorkflowEventLike,\n type CloudflareWorkflowSleepDuration,\n type CloudflareWorkflowStepLike,\n type DetachedTurnDriveOutcome,\n type DetachedTurnDriveState,\n type DetachedTurnTerminalResult,\n type DetachedTurnWorkflowIdentity,\n type DetachedTurnWorkflowTickOptions,\n} from './detached-turn-workflow'\n\nexport {\n runNativeCompletionWorkflow,\n type NativeCompletionWorkflowOptions,\n type NativeCompletionWorkflowPayload,\n} from './native-completion-workflow'\n\nexport {\n renderCloudflareHeadersFile,\n type CloudflareHeadersRule,\n} from './headers'\n\n// ---------------------------------------------------------------------------\n// D1 structural seam\n//\n// The minimal surface of Cloudflare's `D1Database` the handlers + accessor use.\n// `D1Database` satisfies it structurally, so the consumer passes `env.DB`\n// directly and tests pass an in-memory fake. We keep it to the\n// prepare/bind/first/run/all shape D1 already exposes — no Drizzle here, so the\n// default handlers run on a fresh Worker with only the D1 binding.\n// ---------------------------------------------------------------------------\n\n/** A prepared, bound D1 statement. */\nexport interface D1PreparedLike {\n bind(...values: unknown[]): D1PreparedLike\n first<T = Record<string, unknown>>(colName?: string): Promise<T | null>\n run(): Promise<unknown>\n all<T = Record<string, unknown>>(): Promise<{ results: T[] }>\n}\n\n/** The D1 surface the preset needs. Cloudflare `D1Database` satisfies it. */\nexport interface D1Like {\n prepare(query: string): D1PreparedLike\n}\n\n// ---------------------------------------------------------------------------\n// Default schema\n//\n// The four tables the default handlers + accessor read/write. Column names are\n// the single source of truth shared by the DDL, the Drizzle factory, the\n// handlers, and the accessor. Every table is workspace-scoped on `workspace_id`\n// (the accessor's default `where` column) so the knowledge `count` rule and the\n// tool writes agree without per-consumer wiring.\n//\n// Deliberately NOT here: chat thread/message tables. `/chat-store` is the\n// single thread-schema owner (`createChatTables` + `createChatStore`); the\n// preset once declared an unconsumed `threads` DDL, removed so two schemas\n// can't drift.\n// ---------------------------------------------------------------------------\n\n/** The preset table + column names — the contract the DDL, Drizzle schema,\n * handlers, and accessor share. Exposed so a consumer can reference a column\n * without a string literal. */\nexport const PRESET_TABLES = {\n proposals: {\n name: 'proposals',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n threadId: 'thread_id',\n type: 'type',\n title: 'title',\n description: 'description',\n status: 'status',\n createdBy: 'created_by',\n createdAt: 'created_at',\n },\n },\n knowledge: {\n name: 'knowledge',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n path: 'path',\n kind: 'kind',\n label: 'label',\n content: 'content',\n createdAt: 'created_at',\n },\n },\n deadlines: {\n name: 'deadlines',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n threadId: 'thread_id',\n title: 'title',\n dueDate: 'due_date',\n priority: 'priority',\n status: 'status',\n createdAt: 'created_at',\n },\n },\n workspaceKeys: {\n name: 'workspace_keys',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n keyId: 'key_id',\n keyEncrypted: 'key_encrypted',\n budgetUsd: 'budget_usd',\n expiresAt: 'expires_at',\n revokedAt: 'revoked_at',\n createdAt: 'created_at',\n },\n },\n} as const\n\n/**\n * Plain DDL for the preset schema — run by a consumer to create the tables with\n * ZERO drizzle (`for (const sql of PRESET_MIGRATION_SQL) await db.prepare(sql).run()`,\n * or paste into a `.sql` migration). One statement per table so D1's\n * single-statement `prepare` accepts each. Matches {@link PRESET_TABLES} exactly.\n */\nexport const PRESET_MIGRATION_SQL: readonly string[] = [\n `CREATE TABLE IF NOT EXISTS proposals (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n thread_id TEXT,\n type TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n created_by TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS knowledge (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n path TEXT NOT NULL,\n kind TEXT NOT NULL,\n label TEXT,\n content TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS deadlines (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n thread_id TEXT,\n title TEXT NOT NULL,\n due_date TEXT NOT NULL,\n priority TEXT,\n status TEXT NOT NULL DEFAULT 'scheduled',\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS workspace_keys (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n key_id TEXT NOT NULL,\n key_encrypted TEXT NOT NULL,\n budget_usd REAL NOT NULL,\n expires_at INTEGER NOT NULL,\n revoked_at INTEGER,\n created_at INTEGER NOT NULL\n )`,\n `CREATE INDEX IF NOT EXISTS idx_proposals_ws ON proposals (workspace_id, status)`,\n `CREATE INDEX IF NOT EXISTS idx_deadlines_ws ON deadlines (workspace_id, status)`,\n `CREATE INDEX IF NOT EXISTS idx_knowledge_ws ON knowledge (workspace_id)`,\n `CREATE INDEX IF NOT EXISTS idx_workspace_keys_ws ON workspace_keys (workspace_id, revoked_at)`,\n]\n\n/** A chainable column builder — every modifier returns the builder so calls\n * like `.notNull().default('pending')` typecheck. The concrete drizzle builders\n * satisfy this structurally. */\nexport interface DrizzleColumnLike {\n primaryKey: () => DrizzleColumnLike\n notNull: () => DrizzleColumnLike\n default: (v: unknown) => DrizzleColumnLike\n}\n\n/** The shape of a `drizzle-orm/sqlite-core` module — the few builders the\n * preset schema uses. The consumer passes the real module; agent-app never\n * imports it (it stays a peer). */\nexport interface DrizzleSqliteCoreLike {\n sqliteTable: (name: string, columns: Record<string, DrizzleColumnLike>) => unknown\n text: (name?: string) => DrizzleColumnLike\n integer: (name?: string, config?: unknown) => DrizzleColumnLike\n real: (name?: string) => DrizzleColumnLike\n}\n\n/**\n * Build the typed Drizzle schema for the preset, given the consumer's\n * `drizzle-orm/sqlite-core` module. Returns one table object per\n * {@link PRESET_TABLES} entry — pass to `drizzle(db, { schema })` for typed\n * queries, or to drizzle-kit for migration generation. agent-app never imports\n * drizzle; the builder module is the seam.\n *\n * ```ts\n * import * as d from 'drizzle-orm/sqlite-core'\n * const schema = createPresetDrizzleSchema(d)\n * ```\n */\nexport function createPresetDrizzleSchema(d: DrizzleSqliteCoreLike) {\n const { sqliteTable, text, integer, real } = d\n const C = PRESET_TABLES\n return {\n proposals: sqliteTable(C.proposals.name, {\n id: text(C.proposals.columns.id).primaryKey(),\n workspaceId: text(C.proposals.columns.workspaceId).notNull(),\n threadId: text(C.proposals.columns.threadId),\n type: text(C.proposals.columns.type).notNull(),\n title: text(C.proposals.columns.title).notNull(),\n description: text(C.proposals.columns.description),\n status: text(C.proposals.columns.status).notNull().default('pending'),\n createdBy: text(C.proposals.columns.createdBy),\n createdAt: integer(C.proposals.columns.createdAt).notNull(),\n }),\n knowledge: sqliteTable(C.knowledge.name, {\n id: text(C.knowledge.columns.id).primaryKey(),\n workspaceId: text(C.knowledge.columns.workspaceId).notNull(),\n path: text(C.knowledge.columns.path).notNull(),\n kind: text(C.knowledge.columns.kind).notNull(),\n label: text(C.knowledge.columns.label),\n content: text(C.knowledge.columns.content),\n createdAt: integer(C.knowledge.columns.createdAt).notNull(),\n }),\n deadlines: sqliteTable(C.deadlines.name, {\n id: text(C.deadlines.columns.id).primaryKey(),\n workspaceId: text(C.deadlines.columns.workspaceId).notNull(),\n threadId: text(C.deadlines.columns.threadId),\n title: text(C.deadlines.columns.title).notNull(),\n dueDate: text(C.deadlines.columns.dueDate).notNull(),\n priority: text(C.deadlines.columns.priority),\n status: text(C.deadlines.columns.status).notNull().default('scheduled'),\n createdAt: integer(C.deadlines.columns.createdAt).notNull(),\n }),\n workspaceKeys: sqliteTable(C.workspaceKeys.name, {\n id: text(C.workspaceKeys.columns.id).primaryKey(),\n workspaceId: text(C.workspaceKeys.columns.workspaceId).notNull(),\n keyId: text(C.workspaceKeys.columns.keyId).notNull(),\n keyEncrypted: text(C.workspaceKeys.columns.keyEncrypted).notNull(),\n budgetUsd: real(C.workspaceKeys.columns.budgetUsd).notNull(),\n expiresAt: integer(C.workspaceKeys.columns.expiresAt).notNull(),\n revokedAt: integer(C.workspaceKeys.columns.revokedAt),\n createdAt: integer(C.workspaceKeys.columns.createdAt).notNull(),\n }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Default AppToolHandlers over D1 + KV\n// ---------------------------------------------------------------------------\n\n/** The KV-backed vault. `KvLike` (from `../web`) is the structural KV contract;\n * Cloudflare `KVNamespace` satisfies it. Artifacts are stored under their path. */\nexport type VaultKv = KvLike\n\n/** Define configuration options for handling preset tools including database, vault, and optional utilities */\nexport interface PresetToolHandlerOptions {\n /** The D1 database (Cloudflare `D1Database` satisfies {@link D1Like}). */\n db: D1Like\n /** The KV namespace used as the artifact vault. */\n vault: VaultKv\n /** Id generator. Default `crypto.randomUUID`. Injectable for deterministic tests. */\n newId?: () => string\n /** Clock (epoch ms). Default `Date.now`. Injectable for deterministic tests. */\n now?: () => number\n /** Vault path prefix for `render_ui` artifacts. Default `'ui'`. */\n uiPathPrefix?: string\n /** Vault path prefix for `add_citation` artifacts. Default `'citations'`. */\n citationPathPrefix?: string\n}\n\nfunction slug(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 64) || 'item'\n}\n\n/**\n * The default {@link AppToolHandlers} for the house stack:\n * - `submit_proposal` → insert a `proposals` row (`status='pending'`), deduped\n * on (workspace, title) so a retried turn doesn't double-queue.\n * - `schedule_followup` → insert a `deadlines` row, deduped on (workspace, title, due_date).\n * - `render_ui` → write the schema JSON as a `ui/<thread>/<slug>.json`\n * vault artifact AND a `knowledge` row pointing at it.\n * - `add_citation` → write the quote as a `citations/<slug>.json` artifact AND\n * a `knowledge` row.\n *\n * Returns the EXACT persisted content from `render_ui` (per the seam contract) so\n * a completion oracle sees real bytes. Pure seam wiring: a consumer that runs\n * D1 + KV gets all four tools with no handler code.\n */\nexport function createPresetToolHandlers(opts: PresetToolHandlerOptions): AppToolHandlers {\n const { db, vault } = opts\n const newId = opts.newId ?? (() => crypto.randomUUID())\n const now = opts.now ?? (() => Date.now())\n const uiPrefix = opts.uiPathPrefix ?? 'ui'\n const citationPrefix = opts.citationPathPrefix ?? 'citations'\n const P = PRESET_TABLES.proposals\n const D = PRESET_TABLES.deadlines\n const K = PRESET_TABLES.knowledge\n\n async function persistArtifact(path: string, body: string): Promise<void> {\n await vault.put(path, body)\n }\n\n async function insertKnowledge(workspaceId: string, path: string, kind: string, label: string | null, content: string): Promise<void> {\n await db\n .prepare(\n `INSERT INTO ${K.name} (${K.columns.id}, ${K.columns.workspaceId}, ${K.columns.path}, ${K.columns.kind}, ${K.columns.label}, ${K.columns.content}, ${K.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(newId(), workspaceId, path, kind, label, content, now())\n .run()\n }\n\n return {\n async submitProposal(args: SubmitProposalArgs, ctx: AppToolContext): Promise<SubmitProposalResult> {\n const existing = await db\n .prepare(`SELECT ${P.columns.id} AS id FROM ${P.name} WHERE ${P.columns.workspaceId} = ? AND ${P.columns.title} = ? LIMIT 1`)\n .bind(ctx.workspaceId, args.title)\n .first<{ id: string }>()\n if (existing) return { proposalId: existing.id, deduped: true }\n\n const id = newId()\n await db\n .prepare(\n `INSERT INTO ${P.name} (${P.columns.id}, ${P.columns.workspaceId}, ${P.columns.threadId}, ${P.columns.type}, ${P.columns.title}, ${P.columns.description}, ${P.columns.status}, ${P.columns.createdBy}, ${P.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?)`,\n )\n .bind(id, ctx.workspaceId, ctx.threadId, args.type, args.title, args.description ?? null, ctx.userId, now())\n .run()\n return { proposalId: id, deduped: false }\n },\n\n async scheduleFollowup(args: ScheduleFollowupArgs, ctx: AppToolContext): Promise<ScheduleFollowupResult> {\n const existing = await db\n .prepare(\n `SELECT ${D.columns.id} AS id, ${D.columns.dueDate} AS dueDate FROM ${D.name} WHERE ${D.columns.workspaceId} = ? AND ${D.columns.title} = ? AND ${D.columns.dueDate} = ? LIMIT 1`,\n )\n .bind(ctx.workspaceId, args.title, args.dueDate)\n .first<{ id: string; dueDate: string }>()\n if (existing) return { id: existing.id, dueDate: existing.dueDate, deduped: true }\n\n const id = newId()\n await db\n .prepare(\n `INSERT INTO ${D.name} (${D.columns.id}, ${D.columns.workspaceId}, ${D.columns.threadId}, ${D.columns.title}, ${D.columns.dueDate}, ${D.columns.priority}, ${D.columns.status}, ${D.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, 'scheduled', ?)`,\n )\n .bind(id, ctx.workspaceId, ctx.threadId, args.title, args.dueDate, args.priority ?? null, now())\n .run()\n return { id, dueDate: args.dueDate, deduped: false }\n },\n\n async renderUi(args: RenderUiArgs, ctx: AppToolContext): Promise<RenderUiResult> {\n const content = JSON.stringify(parseRenderUiSchema(args.schema))\n const path = `${uiPrefix}/${ctx.threadId ?? 'global'}/${slug(args.title)}.json`\n await persistArtifact(path, content)\n await insertKnowledge(ctx.workspaceId, path, 'ui', args.title, content)\n return { path, content }\n },\n\n async addCitation(args: AddCitationArgs, ctx: AppToolContext): Promise<AddCitationResult> {\n const citationId = newId()\n const path = `${citationPrefix}/${slug(args.label ?? args.path)}-${citationId.slice(0, 8)}.json`\n const body = JSON.stringify({ sourcePath: args.path, quote: args.quote, label: args.label ?? null })\n await persistArtifact(path, body)\n await insertKnowledge(ctx.workspaceId, path, 'citation', args.label ?? null, body)\n return { citationId, path }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// D1-backed KnowledgeStateAccessor\n// ---------------------------------------------------------------------------\n\n/** Define options for accessing preset knowledge scoped to a specific workspace and configuration */\nexport interface PresetKnowledgeAccessorOptions {\n db: D1Like\n /** The active workspace — every `count` is scoped to it. */\n workspaceId: string\n /** Workspace config the `satisfiedBy: { config }` rules read. A resolved\n * object (dot-path lookup), or a function the accessor calls per path. */\n config: Record<string, unknown> | ((path: string) => unknown)\n /** The default workspace fk column a `count` rule scopes on when its rule\n * omits `where`. Default `'workspace_id'` (the preset schema convention). */\n defaultWhereColumn?: string\n}\n\nfunction readDotPath(obj: Record<string, unknown>, path: string): unknown {\n let cur: unknown = obj\n for (const part of path.split('.')) {\n if (cur == null || typeof cur !== 'object') return undefined\n cur = (cur as Record<string, unknown>)[part]\n }\n return cur\n}\n\n/**\n * The {@link KnowledgeStateAccessor} over the preset D1 schema — the seam that\n * lets the declarative `satisfiedBy` rules resolve with ZERO consumer code:\n * - `config(path)` reads the supplied workspace config by dot-path.\n * - `count({ table, where, statusIn })` runs `SELECT count(*)` scoped to the\n * active workspace (the rule's `where` column, default `workspace_id`),\n * optionally filtered to `statusIn` via a parameterized `IN (...)`.\n *\n * Identifiers (table/column) are validated against a safe pattern before\n * interpolation — they originate from the product's own config, never model\n * input, but we fail loud rather than build a malformed/injectable query.\n */\nexport function createD1KnowledgeStateAccessor(opts: PresetKnowledgeAccessorOptions): KnowledgeStateAccessor {\n const { db, workspaceId } = opts\n const defaultWhere = opts.defaultWhereColumn ?? 'workspace_id'\n const configFn = typeof opts.config === 'function' ? opts.config : (path: string) => readDotPath(opts.config as Record<string, unknown>, path)\n\n const isIdentifier = (s: string) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s)\n\n return {\n config: configFn,\n async count(query) {\n if (!isIdentifier(query.table)) throw new Error(`unsafe table identifier: ${query.table}`)\n const whereCol = query.where ?? defaultWhere\n if (!isIdentifier(whereCol)) throw new Error(`unsafe where identifier: ${whereCol}`)\n\n let sql = `SELECT count(*) AS n FROM ${query.table} WHERE ${whereCol} = ?`\n const binds: unknown[] = [workspaceId]\n if (query.statusIn && query.statusIn.length > 0) {\n sql += ` AND status IN (${query.statusIn.map(() => '?').join(', ')})`\n binds.push(...query.statusIn)\n }\n const row = await db.prepare(sql).bind(...binds).first<{ n: number }>()\n return row?.n ?? 0\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Crypto + per-workspace key wiring\n// ---------------------------------------------------------------------------\n\n/** Build the {@link KeyCrypto} the billing key store uses — AES-256-GCM field\n * crypto bound to the product's 64-char-hex `ENCRYPTION_KEY` (or a resolver).\n * This is the concrete impl behind the `../billing` `KeyCrypto` seam. */\nexport function createPresetFieldCrypto(key: string | (() => string)): KeyCrypto {\n return createFieldCrypto(key)\n}\n\n/**\n * The {@link WorkspaceKeyStore} over the preset `workspace_keys` table — the\n * persistence seam the per-workspace key manager needs. \"Active\" = a row with a\n * null `revoked_at`. Pure D1 wiring; no key minting (that's the provisioner).\n */\nexport function createPresetWorkspaceKeyStore(db: D1Like): WorkspaceKeyStore {\n const W = PRESET_TABLES.workspaceKeys\n return {\n async getActive(workspaceId: string): Promise<WorkspaceKeyRecord | null> {\n const row = await db\n .prepare(\n `SELECT ${W.columns.id} AS id, ${W.columns.keyId} AS keyId, ${W.columns.keyEncrypted} AS keyEncrypted, ${W.columns.budgetUsd} AS budgetUsd, ${W.columns.expiresAt} AS expiresAt FROM ${W.name} WHERE ${W.columns.workspaceId} = ? AND ${W.columns.revokedAt} IS NULL ORDER BY ${W.columns.createdAt} DESC LIMIT 1`,\n )\n .bind(workspaceId)\n .first<{ id: string; keyId: string; keyEncrypted: string; budgetUsd: number; expiresAt: number | null }>()\n if (!row) return null\n return {\n id: row.id,\n keyId: row.keyId,\n keyEncrypted: row.keyEncrypted,\n budgetUsd: row.budgetUsd,\n expiresAt: row.expiresAt == null ? null : new Date(row.expiresAt),\n }\n },\n async listActive(workspaceId: string): Promise<Array<{ id: string; keyId: string }>> {\n const res = await db\n .prepare(`SELECT ${W.columns.id} AS id, ${W.columns.keyId} AS keyId FROM ${W.name} WHERE ${W.columns.workspaceId} = ? AND ${W.columns.revokedAt} IS NULL`)\n .bind(workspaceId)\n .all<{ id: string; keyId: string }>()\n return res.results\n },\n async insert(record): Promise<void> {\n await db\n .prepare(\n `INSERT INTO ${W.name} (${W.columns.id}, ${W.columns.workspaceId}, ${W.columns.keyId}, ${W.columns.keyEncrypted}, ${W.columns.budgetUsd}, ${W.columns.expiresAt}, ${W.columns.revokedAt}, ${W.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, NULL, ?)`,\n )\n .bind(crypto.randomUUID(), record.workspaceId, record.keyId, record.keyEncrypted, record.budgetUsd, record.expiresAt.getTime(), Date.now())\n .run()\n },\n async markRevoked(id: string, now: Date): Promise<void> {\n await db\n .prepare(`UPDATE ${W.name} SET ${W.columns.revokedAt} = ? WHERE ${W.columns.id} = ?`)\n .bind(now.getTime(), id)\n .run()\n },\n }\n}\n\n/** Define preset billing options including database, provisioner, encryption key, budget, and optional settings */\nexport interface PresetBillingOptions {\n db: D1Like\n /** The key provisioner (`@tangle-network/tcloud`'s client satisfies it structurally). */\n provisioner: KeyProvisioner\n /** Field-crypto key (64-char hex) or resolver — encrypts the minted key at rest. */\n encryptionKey: string | (() => string)\n /** Default monthly USD allowance when a call doesn't specify one. */\n defaultBudgetUsd: number\n /** Injectable clock. */\n now?: () => Date\n /** tcloud product the key is scoped to. Default `'router'`. */\n product?: string\n}\n\n/**\n * Stand up the per-workspace budget-capped {@link WorkspaceKeyManager} on the\n * house stack: the preset `workspace_keys` D1 store + AES-GCM field crypto +\n * the consumer's tcloud provisioner. The mint/rotate/rollover/usage LOGIC lives\n * in `../billing`; this only binds it to the preset table + crypto.\n */\nexport function createPresetWorkspaceKeyManager(opts: PresetBillingOptions): WorkspaceKeyManager {\n return createWorkspaceKeyManager({\n provisioner: opts.provisioner,\n store: createPresetWorkspaceKeyStore(opts.db),\n crypto: createPresetFieldCrypto(opts.encryptionKey),\n defaultBudgetUsd: opts.defaultBudgetUsd,\n now: opts.now,\n product: opts.product,\n })\n}\n"],"mappings":"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","names":["attempt"]}
|
package/dist/sandbox/index.js
CHANGED
|
@@ -139,12 +139,12 @@ import {
|
|
|
139
139
|
} from "../chunk-4LBCTFBP.js";
|
|
140
140
|
import "../chunk-VBRUG4N5.js";
|
|
141
141
|
import "../chunk-LWSJK546.js";
|
|
142
|
-
import "../chunk-
|
|
142
|
+
import "../chunk-6CKOEPXS.js";
|
|
143
143
|
import "../chunk-6A7MYOUI.js";
|
|
144
144
|
import "../chunk-O7UX55EU.js";
|
|
145
|
-
import "../chunk-
|
|
145
|
+
import "../chunk-WHK5CFDB.js";
|
|
146
146
|
import "../chunk-S5SRJJQG.js";
|
|
147
|
-
import "../chunk-
|
|
147
|
+
import "../chunk-LJASCU5V.js";
|
|
148
148
|
import "../chunk-UGWQLQDS.js";
|
|
149
149
|
import "../chunk-E65YV7VV.js";
|
|
150
150
|
import "../chunk-J63KUZBE.js";
|
package/dist/tools/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
outcomeStatus,
|
|
6
6
|
resolveToolCapabilities,
|
|
7
7
|
restrictTaxonomy
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-6CKOEPXS.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_APP_TOOL_PATHS,
|
|
11
11
|
DEFAULT_HEADER_NAMES,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
verifyCapabilityToken,
|
|
25
25
|
verifyExpiringCapabilityToken
|
|
26
26
|
} from "../chunk-O7UX55EU.js";
|
|
27
|
-
import "../chunk-
|
|
27
|
+
import "../chunk-WHK5CFDB.js";
|
|
28
28
|
import "../chunk-S5SRJJQG.js";
|
|
29
29
|
import {
|
|
30
30
|
APP_TOOL_NAMES,
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
defineAppTool,
|
|
34
34
|
findCustomTool,
|
|
35
35
|
isAppToolName
|
|
36
|
-
} from "../chunk-
|
|
36
|
+
} from "../chunk-LJASCU5V.js";
|
|
37
37
|
import "../chunk-UGWQLQDS.js";
|
|
38
38
|
import {
|
|
39
39
|
ToolInputError
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
export declare function
|
|
1
|
+
/** Decode one tool-transport JSON text, then enforce the renderer's node contract. */
|
|
2
|
+
export declare function parseRenderUiSchema(value: unknown): import("@tangle-network/ui/openui-schema").OpenUIComponentNode;
|
package/package.json
CHANGED
package/dist/chunk-2RCPPOQ4.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ToolInputError,
|
|
3
|
-
validateOpenUIJsonNode
|
|
4
|
-
} from "./chunk-E65YV7VV.js";
|
|
5
|
-
|
|
6
|
-
// src/tools/render-ui-schema.ts
|
|
7
|
-
function assertRenderUiSchema(value) {
|
|
8
|
-
const result = validateOpenUIJsonNode(value);
|
|
9
|
-
if (!result.ok) {
|
|
10
|
-
throw new ToolInputError("invalid_schema", `${result.issue.path}: ${result.issue.message}`);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export {
|
|
15
|
-
assertRenderUiSchema
|
|
16
|
-
};
|
|
17
|
-
//# sourceMappingURL=chunk-2RCPPOQ4.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tools/render-ui-schema.ts"],"sourcesContent":["import { validateOpenUIJsonNode } from '@tangle-network/ui/openui-schema'\nimport { ToolInputError } from './errors'\n\n/** Map the renderer-owned JSON contract to a correctable tool error. */\nexport function assertRenderUiSchema(value: unknown): void {\n const result = validateOpenUIJsonNode(value)\n if (!result.ok) {\n throw new ToolInputError('invalid_schema', `${result.issue.path}: ${result.issue.message}`)\n }\n}\n"],"mappings":";;;;;;AAIO,SAAS,qBAAqB,OAAsB;AACzD,QAAM,SAAS,uBAAuB,KAAK;AAC3C,MAAI,CAAC,OAAO,IAAI;AACd,UAAM,IAAI,eAAe,kBAAkB,GAAG,OAAO,MAAM,IAAI,KAAK,OAAO,MAAM,OAAO,EAAE;AAAA,EAC5F;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tools/openai.ts","../src/tools/registry.ts"],"sourcesContent":["import { OPENUI_INTERACTIVE_AUTHORING_GUIDE } from '../openui/authoring'\nimport { OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES } from '@tangle-network/ui/openui-schema'\nimport type { AppToolTaxonomy, BuildAppToolsOptions } 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\n/** Resolve a valid application tool name from the predefined list of tool names */\nexport type AppToolName = (typeof APP_TOOL_NAMES)[number]\n\nconst NAME_SET = new Set<string>(APP_TOOL_NAMES)\n/** Determine if a string matches a valid application tool name */\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\n * model-facing descriptions and the follow-up priority enum default to the\n * Tangle reference vocabulary and can be retuned via {@link BuildAppToolsOptions}\n * (the tool names + JSON-Schema shapes stay fixed — they are mechanism). Pass\n * the result to the agent-runtime backend's `tools`.\n */\nexport function buildAppToolOpenAITools(\n taxonomy: AppToolTaxonomy,\n opts?: BuildAppToolsOptions,\n): OpenAIFunctionTool[] {\n const d = opts?.descriptions\n const priorityValues = opts?.priorityValues ?? ['low', 'medium', 'high']\n const custom: OpenAIFunctionTool[] = (opts?.customTools ?? []).map((t) => ({\n type: 'function',\n function: { name: t.name, description: t.description, parameters: t.parameters },\n }))\n return [\n {\n type: 'function',\n function: {\n name: 'submit_proposal',\n description:\n d?.submit_proposal ??\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:\n d?.schedule_followup ??\n '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: [...priorityValues] },\n },\n required: ['title', 'dueDate'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'render_ui',\n description:\n (d?.render_ui ??\n `Show a generated view live in the workspace. Validates the OpenUI JSON and persists the artifact. Use only these nodes: ${OPENUI_NODE_TYPES.join(', ')}. Stack, grid, and card may contain nested children. Executes immediately.`) +\n (opts?.interactiveUi ? `\\n\\n${OPENUI_INTERACTIVE_AUTHORING_GUIDE}` : ''),\n parameters: {\n type: 'object',\n $defs: OPENUI_NODE_JSON_SCHEMA.$defs,\n properties: {\n title: { type: 'string' },\n schema: { $ref: '#/$defs/node' },\n },\n required: ['title', 'schema'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'add_citation',\n description:\n d?.add_citation ??\n '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 ...custom,\n ]\n}\n","import { isAppToolName } from './openai'\nimport type { AppToolContext } from './types'\nimport type { OpenAIFunctionTool } from './openai'\n\n/**\n * A product-defined app tool — the open registration seam.\n *\n * The four built-ins (`submit_proposal`/`schedule_followup`/`render_ui`/\n * `add_citation`) are mechanism and stay hard-typed. This is how a product adds\n * a fifth+ tool (e.g. gtm-agent's `set_config`) WITHOUT forking the shell: the\n * `name` + JSON-Schema `parameters` are what the model sees, and `execute` is\n * dispatched through the SAME validation/outcome path as the built-ins — a\n * thrown {@link ToolInputError} becomes a correctable 4xx, any other throw an\n * internal error, and a tool call never silently \"succeeds\" without its effect.\n */\nexport interface AppToolDefinition<Args = Record<string, unknown>> {\n /** Stable identifier the model calls (and the MCP server name). Must not\n * collide with a built-in app tool. */\n name: string\n /** Model-facing description. */\n description: string\n /** JSON-Schema for the parameters (the OpenAI `function.parameters` object). */\n parameters: Record<string, unknown>\n /** Default route path the per-turn MCP server / HTTP handler is mounted at.\n * Overridable per call via `paths`/`buildHttpMcpServer`. */\n path?: string\n /** Fulfil the call; the return value is the tool result the model sees.\n * `ctx` is the trusted per-turn identity (never from tool args). */\n execute: (args: Args, ctx: AppToolContext) => Promise<unknown> | unknown\n}\n\n/**\n * Validate + brand a product tool definition. Throws when the name is empty or\n * collides with a built-in (those are reserved mechanism). Identity otherwise —\n * call it at module scope so a bad definition fails at boot, not first use.\n */\nexport function defineAppTool<Args = Record<string, unknown>>(def: AppToolDefinition<Args>): AppToolDefinition<Args> {\n const name = def.name?.trim()\n if (!name) throw new Error('defineAppTool: name is required')\n if (isAppToolName(name)) throw new Error(`defineAppTool: \"${name}\" is a built-in app tool — choose a different name`)\n if (typeof def.execute !== 'function') throw new Error(`defineAppTool: \"${name}\" needs an execute() handler`)\n return def\n}\n\n/** The OpenAI function-tool def for a custom tool — appended to the built-ins by\n * `buildAppToolOpenAITools`. */\nexport function customToolToOpenAI(def: AppToolDefinition): OpenAIFunctionTool {\n return { type: 'function', function: { name: def.name, description: def.description, parameters: def.parameters } }\n}\n\n/** Find a registered custom tool by the name the model called. */\nexport function findCustomTool(\n name: string,\n tools: readonly AppToolDefinition[] | undefined,\n): AppToolDefinition | undefined {\n return tools?.find((t) => t.name === name)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tools/gating.ts","../src/tools/dispatch.ts","../src/tools/runtime.ts","../src/tools/http.ts"],"sourcesContent":["/**\n * Capability gating — compose an agent session's tool surface from a\n * product-defined capability registry.\n *\n * Products that let users pick what an agent can do (a \"Studio\" agent with the\n * full build toolset vs a clean \"Assistant\" with none) all need the same\n * mechanics: a registry of named capabilities, each unlocking proposal types\n * and/or named tool groups, resolved against the product's\n * {@link AppToolTaxonomy} into the concrete tools to expose. The mechanics are\n * generic and live here; the capability VOCABULARY (ids, labels, which\n * proposal types, which tool groups) is the product's.\n */\n\nimport type { AppToolTaxonomy } from './types'\n\n/** One toggleable tool group in a product's capability registry. */\nexport interface ToolCapability {\n id: string\n label: string\n description: string\n /** Proposal types this capability unlocks (intersected with the taxonomy,\n * so a capability can never widen the product's proposal surface). */\n proposalTypes?: readonly string[]\n /** Unlocks every taxonomy proposal type beyond the base set — the domain's\n * specialized long tail (risk_assessment, vuln_report, …). */\n domainActions?: boolean\n /** Named product tool groups (e.g. 'sandbox', 'integrations') this\n * capability unlocks. The vocabulary is the product's; the resolver only\n * unions them. */\n toolGroups?: readonly string[]\n}\n\n/** Resolve options for determining tool capabilities based on taxonomy, capabilities, and enabled IDs */\nexport interface ResolveToolCapabilitiesOptions {\n taxonomy: AppToolTaxonomy\n /** The product's full capability registry. */\n capabilities: readonly ToolCapability[]\n /** Enabled capability ids. `undefined` means full access (legacy callers\n * that don't send a capability set); an explicit `[]` means a pure chat\n * agent with no tools. Unknown ids are ignored. */\n enabled: readonly string[] | undefined\n /** The shared base proposal types `domainActions` excludes. Defaults to\n * every type some capability names explicitly via `proposalTypes` — i.e.\n * \"domain actions\" are the taxonomy types no capability claims. */\n baseProposalTypes?: readonly string[]\n}\n\n/** Describe resolved capabilities including proposal types and product tool groups to expose */\nexport interface ResolvedToolCapabilities {\n /** Proposal types to keep — feed to {@link restrictTaxonomy}. */\n proposalTypes: string[]\n /** Product tool groups to expose (deduped union across enabled caps). */\n toolGroups: string[]\n}\n\n/**\n * Resolve an enabled capability-id set against a taxonomy into the concrete\n * tool surface. Fail-closed: only types present in the taxonomy survive, and\n * an empty `enabled` set yields no tools at all.\n */\nexport function resolveToolCapabilities(\n opts: ResolveToolCapabilitiesOptions,\n): ResolvedToolCapabilities {\n const { taxonomy, capabilities, enabled } = opts\n if (enabled === undefined) {\n return {\n proposalTypes: [...taxonomy.proposalTypes],\n toolGroups: [...new Set(capabilities.flatMap((c) => c.toolGroups ?? []))],\n }\n }\n const base = new Set(\n opts.baseProposalTypes ?? capabilities.flatMap((c) => c.proposalTypes ?? []),\n )\n const domainTypes = taxonomy.proposalTypes.filter((t) => !base.has(t))\n const byId = new Map(capabilities.map((c) => [c.id, c]))\n\n const proposalTypes = new Set<string>()\n const toolGroups = new Set<string>()\n for (const id of enabled) {\n const cap = byId.get(id)\n if (!cap) continue\n for (const t of cap.proposalTypes ?? []) {\n if (taxonomy.proposalTypes.includes(t)) proposalTypes.add(t)\n }\n if (cap.domainActions) for (const t of domainTypes) proposalTypes.add(t)\n for (const g of cap.toolGroups ?? []) toolGroups.add(g)\n }\n return { proposalTypes: [...proposalTypes], toolGroups: [...toolGroups] }\n}\n\n/**\n * Restrict a taxonomy to a subset of proposal types, intersecting the\n * regulated subset too — the regulated label survives restriction, so a\n * narrowed agent can never launder a regulated type into an unregulated one.\n */\nexport function restrictTaxonomy(\n taxonomy: AppToolTaxonomy,\n allowed: readonly string[],\n): AppToolTaxonomy {\n const allow = new Set(allowed)\n return {\n proposalTypes: taxonomy.proposalTypes.filter((t) => allow.has(t)),\n regulatedTypes: taxonomy.regulatedTypes.filter((t) => allow.has(t)),\n }\n}\n","import { ToolInputError } from './errors'\nimport { isAppToolName } from './openai'\nimport { findCustomTool, type AppToolDefinition } from './registry'\nimport { assertRenderUiSchema } from './render-ui-schema'\nimport type {\n AppToolContext,\n AppToolHandlers,\n AppToolOutcome,\n AppToolProducedEvent,\n AppToolTaxonomy,\n} from './types'\n\n/** Define options for dispatching tools including handlers, taxonomy, custom tools, and approval policies */\nexport interface DispatchOptions {\n handlers: AppToolHandlers\n taxonomy: AppToolTaxonomy\n /** Product-registered tools beyond the four built-ins. A called name that is\n * not a built-in is dispatched to the matching {@link AppToolDefinition.execute}\n * through this same validation/outcome path. */\n customTools?: readonly AppToolDefinition[]\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 const custom = findCustomTool(toolName, opts.customTools)\n if (!custom) return { ok: false, code: 'unknown_tool', message: `${toolName} is not an app tool.` }\n // Custom tools own their own arg validation (their execute throws\n // ToolInputError for correctable input); the outer try/catch maps it.\n const result = await custom.execute(rawArgs, ctx)\n return { ok: true, result }\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 assertRenderUiSchema(rawArgs.schema)\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\n/** Define options for executing runtime tasks with a trusted per-turn context */\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","import { authenticateToolRequest, type ToolHeaderNames } from './auth'\nimport { dispatchAppTool, outcomeStatus, type DispatchOptions } from './dispatch'\nimport { buildAppToolOpenAITools, type AppToolName } from './openai'\nimport type { AppToolDefinition } from './registry'\nimport { createMcpToolHandler, type McpToolCallContent } from './mcp-rpc'\nimport type { AppToolOutcome } from './types'\n\n/** Define options for handling tool requests including tool identification and token verification */\nexport interface HandleToolRequestOptions extends DispatchOptions {\n /** Which app tool this route serves — a built-in name or a product-registered\n * {@link AppToolDefinition} (auto-added to `customTools` for dispatch). */\n tool: AppToolName | AppToolDefinition\n /** Verify the bearer capability token belongs to the header user. */\n verifyToken: (userId: string, bearer: string) => Promise<boolean>\n headerNames?: ToolHeaderNames\n /** Optional model-facing description used by the MCP tools/list response. */\n description?: string\n /** Product-specific values for the schedule_followup.priority schema. */\n priorityValues?: readonly string[]\n /** Optional success-message builder for a friendlier tool result. */\n message?: (result: unknown) => string\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction isMcpRequest(request: Request, value: unknown): boolean {\n // A valid request declares jsonrpc. An incomplete envelope has no reliable\n // body marker, so use the Streamable HTTP Accept contract as the explicit\n // transport discriminator before falling back to direct tool arguments.\n const acceptsMcp = request.headers\n .get('accept')\n ?.split(',')\n .some((part) => part.trim().toLowerCase() === 'text/event-stream')\n return (\n isRecord(value)\n && (\n 'jsonrpc' in value\n || acceptsMcp === true\n || request.headers.has('mcp-session-id')\n || request.headers.has('mcp-protocol-version')\n )\n )\n}\n\nfunction toolName(tool: AppToolName | AppToolDefinition): string {\n return typeof tool === 'string' ? tool : tool.name\n}\n\nfunction dispatchOptions(opts: HandleToolRequestOptions): DispatchOptions {\n return {\n ...opts,\n customTools:\n typeof opts.tool === 'string' ? opts.customTools : [...(opts.customTools ?? []), opts.tool],\n }\n}\n\nfunction mcpToolManifest(opts: HandleToolRequestOptions): {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n} {\n const name = toolName(opts.tool)\n const tools = buildAppToolOpenAITools(opts.taxonomy, {\n ...(opts.description && typeof opts.tool === 'string'\n ? { descriptions: { [opts.tool]: opts.description } }\n : {}),\n ...(opts.priorityValues ? { priorityValues: opts.priorityValues } : {}),\n ...(typeof opts.tool === 'string' ? {} : { customTools: [opts.tool] }),\n })\n const definition = tools.find((entry) => entry.function.name === name)\n if (!definition) {\n throw new Error(`No MCP manifest exists for app tool ${name}`)\n }\n return {\n name,\n description: definition.function.description,\n inputSchema: definition.function.parameters,\n }\n}\n\nfunction mcpToolResult(outcome: AppToolOutcome, opts: HandleToolRequestOptions): McpToolCallContent {\n if (!outcome.ok) {\n return {\n content: [{ type: 'text', text: `${outcome.code}: ${outcome.message}` }],\n isError: true,\n }\n }\n\n const value = outcome.result\n const payload = value !== null && typeof value === 'object' && !Array.isArray(value)\n ? { ok: true, ...value }\n : { ok: true, value }\n return {\n content: [{ type: 'text', text: JSON.stringify({\n ...payload,\n ...(opts.message ? { message: opts.message(value) } : {}),\n }) }],\n }\n}\n\nfunction createAppToolMcpHandler(\n authContext: Parameters<DispatchOptions['handlers']['submitProposal']>[1],\n opts: HandleToolRequestOptions,\n): (request: Request) => Promise<Response> {\n const manifest = mcpToolManifest(opts)\n return createMcpToolHandler({\n serverInfo: { name: 'agent-app-tool', version: '1' },\n tools: [{\n ...manifest,\n run: (args) => dispatchAppTool(manifest.name, args, authContext, dispatchOptions(opts)),\n }],\n buildEnv: () => ({}),\n formatResult: (result) => mcpToolResult(result as AppToolOutcome, opts),\n })\n}\n\n/**\n * Handle one app-tool HTTP request end to end — the sandbox MCP path. The\n * agent's per-turn HTTP MCP server POSTs here; this authenticates (header user\n * + capability token), reads the args (MCP-alias tolerant), dispatches to the\n * product handler, and returns a JSON Response. A product's route file becomes\n * a one-liner: `export const action = ({ request }) => handleAppToolRequest(request, cfg)`.\n */\nexport async function handleAppToolRequest(request: Request, opts: HandleToolRequestOptions): Promise<Response> {\n if (request.method !== 'POST') return Response.json({ error: 'Method not allowed' }, { status: 405 })\n\n const auth = await authenticateToolRequest(request, { verifyToken: opts.verifyToken, headerNames: opts.headerNames })\n if (!auth.ok) return auth.response\n\n let body: unknown\n try {\n body = await request.clone().json()\n } catch {\n return Response.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n // The same route supports the direct app-tool HTTP shape used by the\n // OpenAI/runtime surfaces and stateless Streamable HTTP MCP JSON-RPC used by\n // OpenCode. Keeping both at this boundary prevents every consumer from\n // writing a second route just to translate protocol envelopes.\n if (isMcpRequest(request, body)) {\n return createAppToolMcpHandler(auth.ctx, opts)(request)\n }\n\n if (!isRecord(body)) return Response.json({ error: 'Invalid JSON' }, { status: 400 })\n const args = (body.args ?? body.arguments ?? body) as Record<string, unknown>\n\n // A custom tool passed as `tool` is registered for this dispatch so the route\n // file stays a one-liner (no separate customTools wiring needed).\n const toolName = typeof opts.tool === 'string' ? opts.tool : opts.tool.name\n const outcome = await dispatchAppTool(toolName, args, auth.ctx, dispatchOptions(opts))\n if (!outcome.ok) {\n return Response.json({ error: outcome.code, message: outcome.message }, { status: outcomeStatus(outcome) })\n }\n const payload = outcome.result as Record<string, unknown>\n return Response.json({ ok: true, ...payload, ...(opts.message ? { message: opts.message(outcome.result) } : {}) })\n}\n"],"mappings":"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","names":["toolName","r","toolName","toolName"]}
|
|
File without changes
|