@tangle-network/agent-app 0.44.33 → 0.44.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +3 -3
  2. package/dist/assistant/index.d.ts +4 -4
  3. package/dist/assistant/index.js +5 -5
  4. package/dist/{attachment-validation-CNkH91Gs.d.ts → attachment-validation-CsmUzuI3.d.ts} +1 -1
  5. package/dist/chat-react/index.d.ts +2 -2
  6. package/dist/chat-react/index.js +3 -3
  7. package/dist/chat-routes/index.d.ts +4 -4
  8. package/dist/chat-routes/index.js +4 -4
  9. package/dist/chat-store/index.d.ts +3 -3
  10. package/dist/chat-store/index.js +2 -2
  11. package/dist/{chunk-4OO7P3ZF.js → chunk-6MUJROBT.js} +2 -2
  12. package/dist/{chunk-NDVTYHLN.js → chunk-AX63276Q.js} +3 -3
  13. package/dist/{chunk-7WXG4ZFP.js → chunk-JYHMNFFU.js} +1 -1
  14. package/dist/{chunk-7WXG4ZFP.js.map → chunk-JYHMNFFU.js.map} +1 -1
  15. package/dist/{chunk-QYAQGCHF.js → chunk-KWXUBMXU.js} +1 -1
  16. package/dist/chunk-KWXUBMXU.js.map +1 -0
  17. package/dist/{chunk-4PUMUTLU.js → chunk-UXMIPX3Z.js} +2 -2
  18. package/dist/{chunk-IYLJS6VW.js → chunk-WBHPN5DY.js} +2 -2
  19. package/dist/{parts-7fbe2rj8.d.ts → parts-F8W3-iry.d.ts} +1 -1
  20. package/dist/sandbox/index.d.ts +3 -3
  21. package/dist/sandbox/index.js +1 -1
  22. package/dist/{use-file-mentions-E6a7_cbH.d.ts → use-file-mentions-CZ-Ua_sb.d.ts} +1 -1
  23. package/dist/web-react/index.d.ts +6 -6
  24. package/dist/web-react/index.js +5 -5
  25. package/dist/{wire-DSp4LzEE.d.ts → wire-DOZ-O6hD.d.ts} +9 -5
  26. package/package.json +7 -7
  27. package/dist/chunk-QYAQGCHF.js.map +0 -1
  28. /package/dist/{chunk-4OO7P3ZF.js.map → chunk-6MUJROBT.js.map} +0 -0
  29. /package/dist/{chunk-NDVTYHLN.js.map → chunk-AX63276Q.js.map} +0 -0
  30. /package/dist/{chunk-4PUMUTLU.js.map → chunk-UXMIPX3Z.js.map} +0 -0
  31. /package/dist/{chunk-IYLJS6VW.js.map → chunk-WBHPN5DY.js.map} +0 -0
@@ -224,4 +224,4 @@ export {
224
224
  parseFileMentions,
225
225
  parseChatTurnParts
226
226
  };
227
- //# sourceMappingURL=chunk-QYAQGCHF.js.map
227
+ //# sourceMappingURL=chunk-KWXUBMXU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/chat-routes/wire.ts"],"sourcesContent":["import type { ReasoningEffort } from '@tangle-network/agent-interface'\n\n/**\n * Wire contract between the chat client (composer + `streamChatTurn`) and the\n * assembled server vertical (`createChatTurnRoutes`). Runtime-import-free on\n * purpose: `/web-react` re-exports these types into browser bundles, so nothing\n * here may reach a Node builtin or an engine package.\n *\n * The client part shape permits an absolute file path until the server converts\n * it to the URL required by the sandbox SDK. It is derived here, not imported,\n * so the client bundle never touches the SDK.\n */\n\nexport interface ChatTurnTextPartInput {\n type: 'text'\n text: string\n}\n\n/** A non-text prompt part the upload route hands back and the client echoes\n * on send. `url` carries an inline `data:` URI for small files; `path` is a\n * sandbox workspace reference for large ones (the >1 MiB gateway body cap\n * makes the two-step upload mandatory). */\nexport interface ChatTurnFilePartInput {\n type: 'image' | 'file'\n filename?: string\n mediaType?: string\n url?: string\n path?: string\n}\n\n/** Resolve input as either a text part or a file part of a chat turn */\nexport type ChatTurnPartInput = ChatTurnTextPartInput | ChatTurnFilePartInput\n\n/** A chat turn's automatic sentinel plus the canonical agent reasoning levels. */\nexport type ChatReasoningEffort = 'auto' | ReasoningEffort\n\n// ── producer stream vocabulary ───────────────────────────────────────────────\n\n/** Represent a text event produced by a source with a fixed type and associated text content */\nexport interface ProducerTextEvent {\n type: 'text'\n text: string\n}\n\n/** Define an event representing reasoning output with a fixed type and associated text */\nexport interface ProducerReasoningEvent {\n type: 'reasoning'\n text: string\n}\n\n/** Represent an event triggered by a producer tool call with its identifier, name, and arguments */\nexport interface ProducerToolCallEvent {\n type: 'tool_call'\n call: {\n toolCallId: string\n toolName: string\n args: Record<string, unknown>\n }\n}\n\n/** Describe the structure of an event representing the result of a producer tool call */\nexport interface ProducerToolResultEvent {\n type: 'tool_result'\n toolCallId: string\n toolName: string\n outcome: {\n ok: boolean\n result?: unknown\n message?: string\n }\n}\n\n/** Describe usage event with prompt and completion token counts for a producer */\nexport interface ProducerUsageEvent {\n type: 'usage'\n usage: {\n promptTokens: number\n completionTokens: number\n }\n}\n\n/** Define the structure for a producer notice event with type, id, kind, and text fields */\nexport interface ProducerNoticeEvent {\n type: 'notice'\n id: string\n /** Kept inline with `/interactions`' `NoticeKind` so this file stays import-free. */\n noticeKind: 'warning' | 'auto-declined'\n text: string\n}\n\n/** Represent an error event emitted by a producer containing message, code, and optional details */\nexport interface ProducerErrorEvent {\n type: 'error'\n data: {\n message: string\n code?: string\n details?: Record<string, unknown>\n }\n}\n\n/** Stable raw lifecycle/interaction/plan/route events forwarded unchanged. */\nexport type ProducerPassthroughEventType =\n | 'turn'\n | 'metadata'\n | 'interaction'\n | 'interaction.cancel'\n | 'plan.submitted'\n | 'done'\n | 'warning'\n | 'session.run.started'\n | 'session.run.completed'\n | 'session.run.failed'\n | 'turn_status'\n\n/** Define an event carrying passthrough data with flexible properties for producer communication */\nexport interface ProducerPassthroughEvent {\n type: ProducerPassthroughEventType\n data?: Record<string, unknown>\n /** Route markers and raw passthroughs may carry `turnId`, `status`, `seq`, etc. */\n [key: string]: unknown\n}\n\n/** Represent events emitted by a producer during its operation for processing and handling */\nexport type ProducerWireEvent =\n | ProducerTextEvent\n | ProducerReasoningEvent\n | ProducerToolCallEvent\n | ProducerToolResultEvent\n | ProducerUsageEvent\n | ProducerNoticeEvent\n | ProducerErrorEvent\n | ProducerPassthroughEvent\n\n/** The image/file split an attachment is rendered and persisted under — the\n * same discriminant as {@link ChatMentionKind}, but a distinct name because an\n * attachment carries content the product uploaded (`ChatAttachmentInput`)\n * while a mention points at a file the box already has. Defined HERE (the\n * import-free layer) so `ChatAttachmentInput` can reference it and the client\n * composer imports it without pulling the persisted-part vocabulary;\n * `/chat-store`'s parts module re-exports it alongside the attachment helpers. */\nexport type ChatAttachmentKind = 'image' | 'file'\n\n/** `POST` turn-body entry describing a file already uploaded to the product's\n * store (vault/object-store) — distinct from an inline {@link\n * ChatTurnFilePartInput} (which carries bytes) and from a {@link FileMention}\n * (a sandbox path the box already holds). The route resolves this field with\n * {@link resolveChatAttachments}: every path is re-validated and every size is\n * re-derived from the stored body, so nothing here is trusted as sent. */\nexport interface ChatAttachmentInput {\n path: string\n name: string\n size: number\n mediaType: string\n kind: ChatAttachmentKind\n}\n\n/** POST body for the turn route. `content` may be empty when `parts` carry the\n * message (an image-only send). Product routing fields (workspaceId etc.) ride\n * alongside and are read by the product's `authorize` seam. */\nexport interface ChatTurnRequestPayload {\n threadId: string\n content?: string\n /** Non-text parts from the upload route, echoed back verbatim. */\n parts?: ChatTurnFilePartInput[]\n /** `@`-picked file mentions for this turn — path references into the\n * workspace sandbox, NOT uploads, so they travel in their own field rather\n * than as `parts` entries. A product whose `parts` field is already spoken\n * for (an attachment sentinel) can still send mentions, and mentions\n * persist as their own `ChatMentionPart`s so a retry rebuilds them. The\n * route validates this field with {@link parseFileMentions} and replaces it\n * on the payload with the validated, deduped list. */\n mentions?: FileMention[]\n /** Files uploaded to the product's store ahead of the turn — path\n * references, NOT inline bytes (those ride `parts`). Validated and\n * size-re-derived by {@link resolveChatAttachments} into persistable\n * attachment parts; a product whose `parts` field is spoken for by inline\n * uploads still sends store-backed files here. */\n attachments?: ChatAttachmentInput[]\n model?: string\n effort?: ChatReasoningEffort\n harness?: string\n /** Client-generated idempotency key for the logical turn (retry-safe). */\n turnId?: string\n [key: string]: unknown\n}\n\n/** `fetch` init for the turn route — the one place the client wire shape is\n * serialized, so composer glue and products never drift from the server's\n * parser. */\nexport function chatTurnRequestInit(payload: ChatTurnRequestPayload): RequestInit {\n return {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }\n}\n\n// ── inline-part byte budget ─────────────────────────────────────────────────\n//\n// The sandbox gateway caps request bodies at 1 MiB; a turn body whose inline\n// `data:` parts exceed it dies at the gateway with an opaque 413. Enforce the\n// budget at the route boundary instead, with headroom for the JSON envelope\n// (same fail-loud-at-the-choke-point style as /sandbox's provision-payload and\n// env-size gates).\n\n/** Define the maximum byte size allowed for inline parts in data processing */\nexport const INLINE_PARTS_MAX_BYTES = 950_000\n\n// ── dispatch (parts[]) budget vocabulary ────────────────────────────────────\n//\n// The default caps `buildDispatchParts` sizes an attachment/mention dispatch\n// against — the sidecar/proxy limits an assembled `parts` array crosses, one\n// step past `INLINE_PARTS_MAX_BYTES` (which gates the raw turn BODY). Grouped\n// here, in the import-free layer, so the numbers are one overridable\n// vocabulary the client can read and a product can tune per call rather than\n// constants buried in the server module. NOTE: these model sidecar/proxy caps,\n// not a product's MIME accept-list or vault bucketing — those are DOMAIN\n// values the product supplies, never defaulted here.\n\n/** Hard cap on the whole `/prompt` request body as it crosses the sandbox\n * proxy — smaller in practice than a raw-file write cap because a dispatch\n * carries several inline parts plus the flattened history in one request. */\nexport const DISPATCH_REQUEST_MAX_BYTES = 1024 * 1024\n\n/** Bytes reserved off the top of {@link DISPATCH_REQUEST_MAX_BYTES} for the\n * JSON structure around the parts array (keys, delimiters, per-part\n * `type`/`filename`/`mediaType` fields) that {@link base64WireLen} does not\n * account for — keeps the inline budget off the exact proxy cap where one\n * stray byte trips the 413. */\nexport const DISPATCH_STRUCTURAL_RESERVE_BYTES = 64 * 1024\n\n/** Sidecar's hard cap on the `parts` array of one prompt request — a dispatch\n * must never assemble more parts than this or the whole turn 400s. */\nexport const DISPATCH_MAX_PARTS = 64\n\n/** Product-side cap on media parts per dispatch (current turn + carried\n * history), well under {@link DISPATCH_MAX_PARTS}. History trimming that keeps\n * a transcript's native media under this is a PRODUCT concern (the pointer\n * block keeps trimmed media reachable); `buildDispatchParts` enforces only the\n * total {@link DISPATCH_MAX_PARTS} cap. */\nexport const DISPATCH_MAX_MEDIA_PARTS = 24\n\n/** Size a base64-encoded string occupies on the wire given the raw\n * (pre-encoding) byte length: base64 packs 3 raw bytes into 4 output\n * characters, rounded up to the next multiple of 4. */\nexport function base64WireLen(byteLen: number): number {\n return Math.ceil(byteLen / 3) * 4\n}\n\n/**\n * Render a raw byte count as a human-readable size (`512B`, `3KB`, `12MB\n * 500KB`). Ported EXACTLY from gtm-agent's `attachment-limits.ts` — byte-\n * identical implementation, not a reinterpretation — so `resolve-attachments`'s\n * and `promote-file-part`'s error strings match gtm's wording verbatim. Lives\n * in the import-free wire layer (not `resolve-attachments.ts` alone) because\n * BOTH the aggregate-cap message here and the per-file oversize message in\n * `promote-file-part.ts` need it; a browser composer wanting the same\n * formatting for a client-side pre-check can also import it with no engine\n * pulled in.\n */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes}B`\n if (bytes >= 1024 * 1024) {\n const megabytes = Math.floor(bytes / (1024 * 1024))\n const remainder = bytes % (1024 * 1024)\n return remainder === 0 ? `${megabytes}MB` : `${megabytes}MB ${formatBytes(remainder)}`\n }\n return `${Math.round(bytes / 1024)}KB`\n}\n\n/** Represent errors for invalid chat turn inputs with status and code properties */\nexport class ChatTurnInputError extends Error {\n constructor(message: string, readonly status = 400, readonly code = 'INVALID_CHAT_TURN') {\n super(message)\n this.name = 'ChatTurnInputError'\n }\n}\n\nfunction partByteSize(part: ChatTurnPartInput): number {\n let bytes = 0\n if (part.type === 'text') return part.text.length\n if (part.url) bytes += part.url.length\n if (part.path) bytes += part.path.length\n return bytes\n}\n\n/** Calculate the total byte size of an array of chat turn parts */\nexport function promptPartsByteSize(parts: ChatTurnPartInput[]): number {\n return parts.reduce((total, part) => total + partByteSize(part), 0)\n}\n\n/** Throws `ChatTurnInputError` (413) when the parts' inline payload would blow\n * the gateway cap. Path-ref parts are tiny by construction and always pass. */\nexport function assertPromptPartsWithinCap(\n parts: ChatTurnPartInput[],\n maxBytes = INLINE_PARTS_MAX_BYTES,\n): void {\n const total = promptPartsByteSize(parts)\n if (total <= maxBytes) return\n const largest = [...parts].sort((a, b) => partByteSize(b) - partByteSize(a))[0]\n const largestName = largest && largest.type !== 'text' ? largest.filename ?? largest.path ?? largest.type : 'text'\n throw new ChatTurnInputError(\n `Inline prompt parts total ${total}B, over the ${maxBytes}B budget (largest: ${largestName}, ${largest ? partByteSize(largest) : 0}B). ` +\n 'Upload large files through the upload route so they travel as sandbox path references.',\n 413,\n 'PROMPT_PARTS_TOO_LARGE',\n )\n}\n\n// ── file mentions ────────────────────────────────────────────────────────\n//\n// A file mention (`@`-picked in the composer, sandbox-ui#184) is a path\n// reference into the workspace sandbox — no byte upload. These helpers turn\n// a resolved mention list into wire parts and the prompt pointer block that\n// tells the agent where to read them from.\n\n/** A file mention resolved from the composer's `@`-picker: the\n * workspace-relative path plus enough metadata to build a prompt part and\n * pointer text. `path` is the canonical identity — the mention pill's\n * `MentionItem.id` for the file kind (`/web-react`'s `useFileMentions`). */\nexport interface FileMention {\n path: string\n name: string\n size?: number\n}\n\nconst MENTION_IMAGE_MEDIA_TYPES: ReadonlyMap<string, string> = new Map([\n ['.png', 'image/png'],\n ['.jpg', 'image/jpeg'],\n ['.jpeg', 'image/jpeg'],\n ['.gif', 'image/gif'],\n ['.webp', 'image/webp'],\n ['.svg', 'image/svg+xml'],\n ['.bmp', 'image/bmp'],\n ['.heic', 'image/heic'],\n ['.heif', 'image/heif'],\n ['.avif', 'image/avif'],\n])\n\nfunction extensionOf(path: string): string {\n const base = path.split('/').filter(Boolean).pop() ?? path\n const dot = base.lastIndexOf('.')\n return dot > 0 ? base.slice(dot).toLowerCase() : ''\n}\n\n/** The `image/*` mime for a mention path by extension, or `undefined` for\n * anything not in the known image set (dispatched as `type: 'file'`). */\nexport function mediaTypeForMentionPath(path: string): string | undefined {\n return MENTION_IMAGE_MEDIA_TYPES.get(extensionOf(path))\n}\n\n/** The image/file split a mention is rendered and persisted under — the\n * composer pill's icon, the dispatched part's `type`, and\n * `ChatMentionPart.mentionKind` are all this one value. */\nexport type ChatMentionKind = 'image' | 'file'\n\n/** `image` when the path's extension is in the known image set (the same table\n * {@link mediaTypeForMentionPath} reads), `file` otherwise. Exported so a\n * client that needs only the discriminant — a pill icon, a persisted part's\n * `mentionKind` — never re-declares the extension table; two frozen copies of\n * one mime table is how one gains a format and the other doesn't. */\nexport function mentionKindForPath(path: string): ChatMentionKind {\n return mediaTypeForMentionPath(path) ? 'image' : 'file'\n}\n\n/** Define options to resolve mention paths when converting file mentions to parts */\nexport interface FileMentionsToPartsOptions {\n /** Resolve a mention's workspace-relative path to the absolute path the\n * dispatched part should carry (e.g. a host prefixing the in-box vault\n * root). Default: identity — the path travels unchanged. */\n resolvePath?: (path: string) => string\n}\n\n/** Maps resolved file mentions to path-only `ChatTurnFilePartInput`s —\n * `image` vs `file` by extension, and always a `path`, never a `url` (the\n * url/path XOR invariant: a mention is a sandbox path reference, never\n * inline bytes). */\nexport function fileMentionsToParts(\n mentions: readonly FileMention[],\n opts: FileMentionsToPartsOptions = {},\n): ChatTurnFilePartInput[] {\n const resolvePath = opts.resolvePath ?? ((path: string) => path)\n return mentions.map((mention) => {\n const mediaType = mediaTypeForMentionPath(mention.path)\n const part: ChatTurnFilePartInput = {\n type: mediaType ? 'image' : 'file',\n filename: mention.name,\n path: resolvePath(mention.path),\n }\n if (mediaType) part.mediaType = mediaType\n return part\n })\n}\n\n/** The agent-facing pointer block appended to the dispatched prompt — never\n * persisted in message `content`. Empty array → `''` so callers can append\n * unconditionally. This is the sole producer of that text: the current\n * turn's dispatch and any history projection built from the same mention\n * list both route through here, so the two can't drift apart. */\nexport function buildMentionPromptBlock(\n mentions: readonly Pick<FileMention, 'name' | 'path'>[],\n): string {\n if (mentions.length === 0) return ''\n const lines = mentions.map((m) => `- ${m.name} (${m.path})`)\n return `\\n\\nMentioned files — read them from these paths:\\n${lines.join('\\n')}`\n}\n\n// ── mention validation ───────────────────────────────────────────────────\n//\n// This package owns BOTH ends of the mention path contract — it emits paths\n// from `createSandboxFileIndexRoute` and consumes them in `fileMentionsToParts`\n// / `buildMentionPromptBlock` — so the validation belongs here rather than in\n// each app that wires the pair up. A mention names a file that already exists\n// in the sandbox, so validation is a pure path/charset/count check with no\n// I/O: existence is proven later, when the agent reads the path and the turn\n// fails loudly if it is gone.\n\n/** Hard cap on mentions per turn. Bounds the prompt pointer block, the\n * persisted parts, and whatever media budget a dispatch draws from them. */\nexport const MENTION_MAX_COUNT = 16\n\n/** Longest mention display name accepted — bounds the pointer-block text and\n * the transcript pill label. */\nconst MAX_MENTION_NAME_LENGTH = 256\n/** Longest mention path accepted. */\nconst MAX_MENTION_PATH_LENGTH = 1024\n\n/** Represent the result of a sandbox mention path check indicating success or failure with an error message */\nexport type SandboxMentionPathCheck =\n | { succeeded: true }\n | { succeeded: false; error: string }\n\n/**\n * Validate a workspace-relative sandbox mention path. Rejects traversal (a\n * `..` path segment), absolute paths (leading `/`), backslashes, and null\n * bytes — the four ways a path picked in a client can escape the root the\n * index route scanned.\n *\n * Spaces and unicode are deliberately ALLOWED: in-box filenames are arbitrary,\n * and an ASCII-only charset would silently drop real files from a feature\n * whose whole job is naming them.\n */\nexport function validateSandboxMentionPath(path: unknown): SandboxMentionPathCheck {\n if (typeof path !== 'string' || path.length === 0) {\n return { succeeded: false, error: 'mention path must be a non-empty string' }\n }\n if (path.length > MAX_MENTION_PATH_LENGTH) {\n return { succeeded: false, error: `mention path must not exceed ${MAX_MENTION_PATH_LENGTH} characters` }\n }\n if (path.includes('\\0')) {\n return { succeeded: false, error: 'mention path must not contain null bytes' }\n }\n if (path.includes('\\\\')) {\n return { succeeded: false, error: 'mention path must not contain backslashes' }\n }\n if (path.startsWith('/')) {\n return { succeeded: false, error: 'mention path must be workspace-relative, not absolute' }\n }\n if (path.split('/').some((segment) => segment === '..')) {\n return { succeeded: false, error: 'mention path must not contain \"..\" segments' }\n }\n return { succeeded: true }\n}\n\nfunction parseFileMention(value: unknown, index: number): FileMention {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new ChatTurnInputError(`mentions[${index}] must be an object`)\n }\n const record = value as Record<string, unknown>\n\n const pathCheck = validateSandboxMentionPath(record.path)\n if (!pathCheck.succeeded) throw new ChatTurnInputError(`mentions[${index}]: ${pathCheck.error}`)\n\n const name = record.name\n if (typeof name !== 'string' || !name.trim()) {\n throw new ChatTurnInputError(`mentions[${index}].name must be a non-empty string`)\n }\n if (name.length > MAX_MENTION_NAME_LENGTH) {\n throw new ChatTurnInputError(`mentions[${index}].name must not exceed ${MAX_MENTION_NAME_LENGTH} characters`)\n }\n\n const size = record.size\n if (size !== undefined) {\n if (typeof size !== 'number' || !Number.isFinite(size)) {\n throw new ChatTurnInputError(`mentions[${index}].size must be a finite number`)\n }\n if (size < 0) {\n throw new ChatTurnInputError(`mentions[${index}].size must not be negative`)\n }\n }\n\n return { path: record.path as string, name, ...(typeof size === 'number' ? { size } : {}) }\n}\n\n/**\n * Validates the untyped `mentions` array off the wire, mirroring\n * {@link parseChatTurnParts}: the typed list, or `ChatTurnInputError` (400)\n * naming the offending entry. Never sanitizes-and-continues — a traversal path\n * is a rejected request, not a trimmed one.\n *\n * A path repeated within one turn is deduped to its first occurrence rather\n * than rejected: mentioning the same file twice is plausible user input, not\n * an attack.\n */\nexport function parseFileMentions(raw: unknown): FileMention[] {\n if (raw === undefined || raw === null) return []\n if (!Array.isArray(raw)) throw new ChatTurnInputError('mentions must be an array')\n if (raw.length > MENTION_MAX_COUNT) {\n throw new ChatTurnInputError(`mentions must not exceed ${MENTION_MAX_COUNT} entries`)\n }\n\n const mentions: FileMention[] = []\n const seenPaths = new Set<string>()\n for (let index = 0; index < raw.length; index += 1) {\n const mention = parseFileMention(raw[index], index)\n if (seenPaths.has(mention.path)) continue\n seenPaths.add(mention.path)\n mentions.push(mention)\n }\n return mentions\n}\n\n/** Validates the untyped `parts` array off the wire. Returns the typed parts\n * or throws `ChatTurnInputError` (400) naming the offending entry. */\nexport function parseChatTurnParts(raw: unknown): ChatTurnFilePartInput[] {\n if (raw === undefined || raw === null) return []\n if (!Array.isArray(raw)) throw new ChatTurnInputError('parts must be an array')\n return raw.map((entry, index) => {\n const part = entry as Record<string, unknown> | null\n if (!part || typeof part !== 'object') {\n throw new ChatTurnInputError(`parts[${index}] must be an object`)\n }\n if (part.type !== 'image' && part.type !== 'file') {\n throw new ChatTurnInputError(`parts[${index}].type must be 'image' or 'file'`)\n }\n for (const key of ['filename', 'mediaType', 'url', 'path'] as const) {\n if (part[key] !== undefined && typeof part[key] !== 'string') {\n throw new ChatTurnInputError(`parts[${index}].${key} must be a string`)\n }\n }\n if (part.content !== undefined) {\n throw new ChatTurnInputError(`parts[${index}].content is not supported; provide a url or path`)\n }\n\n const url = typeof part.url === 'string' && part.url.length > 0 ? part.url : undefined\n const path = typeof part.path === 'string' && part.path.length > 0 ? part.path : undefined\n if (Boolean(url) === Boolean(path)) {\n throw new ChatTurnInputError(`parts[${index}] requires exactly one url or path`)\n }\n if (path && !path.startsWith('/')) {\n throw new ChatTurnInputError(`parts[${index}].path must be absolute`)\n }\n\n const filename = typeof part.filename === 'string' ? part.filename.trim() : undefined\n if (part.type === 'file' && !filename) {\n throw new ChatTurnInputError(`parts[${index}].filename is required for file parts`)\n }\n return {\n type: part.type,\n ...(filename ? { filename } : {}),\n ...(part.mediaType !== undefined ? { mediaType: part.mediaType as string } : {}),\n ...(url ? { url } : { path: path! }),\n }\n })\n}\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-ZVEEWGDK.js";
4
4
  import {
5
5
  mentionKindForPath
6
- } from "./chunk-QYAQGCHF.js";
6
+ } from "./chunk-KWXUBMXU.js";
7
7
  import {
8
8
  persistedPartToInteraction
9
9
  } from "./chunk-3ZK5IJSW.js";
@@ -155,4 +155,4 @@ export {
155
155
  buildAttachmentPromptBlock,
156
156
  historyContentWithAttachments
157
157
  };
158
- //# sourceMappingURL=chunk-4PUMUTLU.js.map
158
+ //# sourceMappingURL=chunk-UXMIPX3Z.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  formatBytes
3
- } from "./chunk-QYAQGCHF.js";
3
+ } from "./chunk-KWXUBMXU.js";
4
4
 
5
5
  // src/chat-routes/binary-sniff.ts
6
6
  function bytesStartWith(bytes, offset, signature) {
@@ -155,4 +155,4 @@ export {
155
155
  attachmentSizeErrorMessage,
156
156
  attachmentTotalSizeErrorMessage
157
157
  };
158
- //# sourceMappingURL=chunk-IYLJS6VW.js.map
158
+ //# sourceMappingURL=chunk-WBHPN5DY.js.map
@@ -2,7 +2,7 @@ import { Part } from '@tangle-network/agent-interface';
2
2
  import { C as ChatInteractionField, a as ChatInteractionStatus, I as InteractionAnswers, N as NoticeKind } from './contract-CQNvv5th.js';
3
3
  import { W as WorkProductPersistedPart } from './types-CCeYywdS.js';
4
4
  import { ChatPlanPersistedPart } from './plans/index.js';
5
- import { C as ChatMentionKind, a as ChatAttachmentKind, b as ChatAttachmentInput, F as FileMention } from './wire-DSp4LzEE.js';
5
+ import { C as ChatMentionKind, a as ChatAttachmentKind, b as ChatAttachmentInput, F as FileMention } from './wire-DOZ-O6hD.js';
6
6
 
7
7
  /**
8
8
  * The stored shape of `message.parts` — one typed vocabulary for every part a
@@ -1,6 +1,6 @@
1
1
  import { SandboxInstance, ProvisionEvent, StorageConfig, ScopedTokenScope, TurnDriveResult, Sandbox, MintScopedTokenOptions } from '@tangle-network/sandbox';
2
2
  export { StorageConfig } from '@tangle-network/sandbox';
3
- import { AgentProfileMcpServer, AgentProfileFileMount, AgentProfile } from '@tangle-network/agent-interface';
3
+ import { ReasoningEffort, AgentProfileMcpServer, AgentProfileFileMount, AgentProfile } from '@tangle-network/agent-interface';
4
4
  import { T as ToolHeaderNames } from '../auth-DJs6lfAs.js';
5
5
  import { a as AppToolName, A as AppToolContext } from '../types-DbU-oO5h.js';
6
6
  import { Harness } from '../harness/index.js';
@@ -1168,7 +1168,7 @@ declare function mergeHistoryIntoParts(parts: PromptInputPart[], history?: Array
1168
1168
  /** Resolve conflicts and merge extra MCP profiles into the app tool MCP without overwriting existing keys */
1169
1169
  declare function mergeExtraMcp(appToolMcp: Record<string, AgentProfileMcpServer>, baseProfileMcp: Record<string, AgentProfileMcpServer>, extra: Record<string, AgentProfileMcpServer> | undefined): Record<string, AgentProfileMcpServer>;
1170
1170
  /** Attach a specified reasoning effort level to an agent profile for a given harness */
1171
- declare function attachReasoningEffort(profile: AgentProfile, harness: Harness, effort: 'auto' | 'low' | 'medium' | 'high' | undefined): AgentProfile;
1171
+ declare function attachReasoningEffort(profile: AgentProfile, harness: Harness, effort: 'auto' | ReasoningEffort | undefined): AgentProfile;
1172
1172
  /** Define options for configuring and controlling a streaming sandbox prompt session */
1173
1173
  interface StreamSandboxPromptOptions {
1174
1174
  sessionId?: string;
@@ -1185,7 +1185,7 @@ interface StreamSandboxPromptOptions {
1185
1185
  content: string;
1186
1186
  }>;
1187
1187
  harness?: Harness;
1188
- effort?: 'auto' | 'low' | 'medium' | 'high';
1188
+ effort?: 'auto' | ReasoningEffort;
1189
1189
  appToolMcp?: Record<string, AgentProfileMcpServer>;
1190
1190
  baseProfileMcp?: Record<string, AgentProfileMcpServer>;
1191
1191
  extraMcp?: Record<string, AgentProfileMcpServer>;
@@ -70,7 +70,7 @@ import {
70
70
  verifySandboxTerminalToken,
71
71
  verifyTerminalProxyToken,
72
72
  writeProfileFilesToBox
73
- } from "../chunk-7WXG4ZFP.js";
73
+ } from "../chunk-JYHMNFFU.js";
74
74
  import "../chunk-LWSJK546.js";
75
75
  import "../chunk-CQZSAR77.js";
76
76
  import "../chunk-ICOHEZK6.js";
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { F as FileMention } from './wire-DSp4LzEE.js';
2
+ import { F as FileMention } from './wire-DOZ-O6hD.js';
3
3
 
4
4
  /**
5
5
  * `useFileMentions` — the glue a host passes straight into `AgentComposer`'s
@@ -5,12 +5,12 @@ export { f as ChatFreeTextField, g as ComposerAnswerDelivery, h as INTERACTION_C
5
5
  import { ChatPlan } from '../plans/index.js';
6
6
  import { InteractionData } from '@tangle-network/agent-interface';
7
7
  export { InteractionData, InteractionOutcome, InteractionRequest } from '@tangle-network/agent-interface';
8
- import { e as ChatMentionPart, a as ChatAttachmentPart } from '../parts-7fbe2rj8.js';
9
- export { s as attachmentInputToPart, t as attachmentKindForMime, u as attachmentPartsFromMessageParts, x as isChatAttachmentPart, H as mentionInputToPart, I as mentionPartsFromMessageParts } from '../parts-7fbe2rj8.js';
8
+ import { e as ChatMentionPart, a as ChatAttachmentPart } from '../parts-F8W3-iry.js';
9
+ export { s as attachmentInputToPart, t as attachmentKindForMime, u as attachmentPartsFromMessageParts, x as isChatAttachmentPart, H as mentionInputToPart, I as mentionPartsFromMessageParts } from '../parts-F8W3-iry.js';
10
10
  import { E as EvidenceEntry, g as ExceptionEntry, b as WorkProductProvenance, P as ProfileBacktestSummary, Q as QualityCheck, W as WorkProductPersistedPart, h as WorkProductStatus } from '../types-CCeYywdS.js';
11
- export { C as ComposerMentionProp, D as DEFAULT_MENTION_EMPTY_TEXT, a as DEFAULT_MENTION_LIMIT, I as INDEX_REFRESH_AFTER_MS, M as MentionItem, b as UseFileMentionsOptions, U as UseFileMentionsResult, r as rankFileMentions, u as useFileMentions } from '../use-file-mentions-E6a7_cbH.js';
12
- import { a as ChatAttachmentKind, b as ChatAttachmentInput } from '../wire-DSp4LzEE.js';
13
- export { C as ChatMentionKind, e as ChatTurnFilePartInput, d as ChatTurnPartInput, c as ChatTurnRequestPayload, D as DISPATCH_MAX_MEDIA_PARTS, h as DISPATCH_MAX_PARTS, i as DISPATCH_REQUEST_MAX_BYTES, j as DISPATCH_STRUCTURAL_RESERVE_BYTES, F as FileMention, P as ProducerErrorEvent, l as ProducerNoticeEvent, m as ProducerPassthroughEvent, n as ProducerPassthroughEventType, o as ProducerReasoningEvent, p as ProducerTextEvent, q as ProducerToolCallEvent, r as ProducerToolResultEvent, s as ProducerUsageEvent, t as ProducerWireEvent, v as base64WireLen, w as buildMentionPromptBlock, x as chatTurnRequestInit, y as fileMentionsToParts, A as mediaTypeForMentionPath, B as mentionKindForPath } from '../wire-DSp4LzEE.js';
11
+ export { C as ComposerMentionProp, D as DEFAULT_MENTION_EMPTY_TEXT, a as DEFAULT_MENTION_LIMIT, I as INDEX_REFRESH_AFTER_MS, M as MentionItem, b as UseFileMentionsOptions, U as UseFileMentionsResult, r as rankFileMentions, u as useFileMentions } from '../use-file-mentions-CZ-Ua_sb.js';
12
+ import { a as ChatAttachmentKind, b as ChatAttachmentInput } from '../wire-DOZ-O6hD.js';
13
+ export { C as ChatMentionKind, e as ChatTurnFilePartInput, d as ChatTurnPartInput, c as ChatTurnRequestPayload, D as DISPATCH_MAX_MEDIA_PARTS, i as DISPATCH_MAX_PARTS, j as DISPATCH_REQUEST_MAX_BYTES, k as DISPATCH_STRUCTURAL_RESERVE_BYTES, F as FileMention, P as ProducerErrorEvent, m as ProducerNoticeEvent, n as ProducerPassthroughEvent, o as ProducerPassthroughEventType, p as ProducerReasoningEvent, q as ProducerTextEvent, r as ProducerToolCallEvent, s as ProducerToolResultEvent, t as ProducerUsageEvent, u as ProducerWireEvent, w as base64WireLen, x as buildMentionPromptBlock, y as chatTurnRequestInit, z as fileMentionsToParts, B as mediaTypeForMentionPath, E as mentionKindForPath } from '../wire-DOZ-O6hD.js';
14
14
  import { S as StepAgentActivity } from '../agent-activity-C8ZG0F0M.js';
15
15
  import { F as FlowTrace } from '../flow-types-CJxEmaRy.js';
16
16
  import { a as ReviewQueueItem, c as ReviewQueueState } from '../queue-VTBA5ONX.js';
@@ -19,7 +19,7 @@ export { S as SandboxTerminalConnection, a as SandboxTerminalConnectionResponse,
19
19
  import { i as ProductSeatOffer } from '../billing-BibxgALe.js';
20
20
  import { CatalogModel } from '../catalog/index.js';
21
21
  import { Harness } from '../harness/index.js';
22
- export { a as ATTACHMENT_ACCEPT, e as FileIndexReadyResponse, f as FileIndexResponse, g as FileIndexWarmingResponse } from '../attachment-validation-CNkH91Gs.js';
22
+ export { a as ATTACHMENT_ACCEPT, e as FileIndexReadyResponse, f as FileIndexResponse, g as FileIndexWarmingResponse } from '../attachment-validation-CsmUzuI3.js';
23
23
  export { a as attachmentPartKey } from '../stream-normalizer-CnPnMaTp.js';
24
24
  import '../billing/index.js';
25
25
 
@@ -73,7 +73,7 @@ import {
73
73
  useSmoothText,
74
74
  useThinkingSeconds,
75
75
  waterfallLayout
76
- } from "../chunk-NDVTYHLN.js";
76
+ } from "../chunk-AX63276Q.js";
77
77
  import "../chunk-FBVLEGEG.js";
78
78
  import {
79
79
  EvidenceLineageTable,
@@ -92,7 +92,7 @@ import {
92
92
  } from "../chunk-GEYACSFW.js";
93
93
  import {
94
94
  useComposerAttachments
95
- } from "../chunk-4OO7P3ZF.js";
95
+ } from "../chunk-6MUJROBT.js";
96
96
  import {
97
97
  tabTerminalConnectionId,
98
98
  useSandboxTerminalConnection
@@ -100,7 +100,7 @@ import {
100
100
  import "../chunk-QY4BRKRJ.js";
101
101
  import {
102
102
  ATTACHMENT_ACCEPT
103
- } from "../chunk-IYLJS6VW.js";
103
+ } from "../chunk-WBHPN5DY.js";
104
104
  import {
105
105
  attachmentInputToPart,
106
106
  attachmentKindForMime,
@@ -108,7 +108,7 @@ import {
108
108
  isChatAttachmentPart,
109
109
  mentionInputToPart,
110
110
  mentionPartsFromMessageParts
111
- } from "../chunk-4PUMUTLU.js";
111
+ } from "../chunk-UXMIPX3Z.js";
112
112
  import "../chunk-ZVEEWGDK.js";
113
113
  import {
114
114
  DISPATCH_MAX_MEDIA_PARTS,
@@ -121,7 +121,7 @@ import {
121
121
  fileMentionsToParts,
122
122
  mediaTypeForMentionPath,
123
123
  mentionKindForPath
124
- } from "../chunk-QYAQGCHF.js";
124
+ } from "../chunk-KWXUBMXU.js";
125
125
  import {
126
126
  attachmentPartKey
127
127
  } from "../chunk-5EPIPT4V.js";
@@ -1,8 +1,10 @@
1
+ import { ReasoningEffort } from '@tangle-network/agent-interface';
2
+
1
3
  /**
2
4
  * Wire contract between the chat client (composer + `streamChatTurn`) and the
3
- * assembled server vertical (`createChatTurnRoutes`). Import-free on purpose:
4
- * `/web-react` re-exports these types into browser bundles, so nothing here may
5
- * reach a Node builtin or an engine package.
5
+ * assembled server vertical (`createChatTurnRoutes`). Runtime-import-free on
6
+ * purpose: `/web-react` re-exports these types into browser bundles, so nothing
7
+ * here may reach a Node builtin or an engine package.
6
8
  *
7
9
  * The client part shape permits an absolute file path until the server converts
8
10
  * it to the URL required by the sandbox SDK. It is derived here, not imported,
@@ -25,6 +27,8 @@ interface ChatTurnFilePartInput {
25
27
  }
26
28
  /** Resolve input as either a text part or a file part of a chat turn */
27
29
  type ChatTurnPartInput = ChatTurnTextPartInput | ChatTurnFilePartInput;
30
+ /** A chat turn's automatic sentinel plus the canonical agent reasoning levels. */
31
+ type ChatReasoningEffort = 'auto' | ReasoningEffort;
28
32
  /** Represent a text event produced by a source with a fixed type and associated text content */
29
33
  interface ProducerTextEvent {
30
34
  type: 'text';
@@ -135,7 +139,7 @@ interface ChatTurnRequestPayload {
135
139
  * uploads still sends store-backed files here. */
136
140
  attachments?: ChatAttachmentInput[];
137
141
  model?: string;
138
- effort?: 'auto' | 'low' | 'medium' | 'high';
142
+ effort?: ChatReasoningEffort;
139
143
  harness?: string;
140
144
  /** Client-generated idempotency key for the logical turn (retry-safe). */
141
145
  turnId?: string;
@@ -269,4 +273,4 @@ declare function parseFileMentions(raw: unknown): FileMention[];
269
273
  * or throws `ChatTurnInputError` (400) naming the offending entry. */
270
274
  declare function parseChatTurnParts(raw: unknown): ChatTurnFilePartInput[];
271
275
 
272
- export { mediaTypeForMentionPath as A, mentionKindForPath as B, type ChatMentionKind as C, DISPATCH_MAX_MEDIA_PARTS as D, parseChatTurnParts as E, type FileMention as F, parseFileMentions as G, promptPartsByteSize as H, INLINE_PARTS_MAX_BYTES as I, validateSandboxMentionPath as J, MENTION_MAX_COUNT as M, type ProducerErrorEvent as P, type SandboxMentionPathCheck as S, type ChatAttachmentKind as a, type ChatAttachmentInput as b, type ChatTurnRequestPayload as c, type ChatTurnPartInput as d, type ChatTurnFilePartInput as e, ChatTurnInputError as f, type ChatTurnTextPartInput as g, DISPATCH_MAX_PARTS as h, DISPATCH_REQUEST_MAX_BYTES as i, DISPATCH_STRUCTURAL_RESERVE_BYTES as j, type FileMentionsToPartsOptions as k, type ProducerNoticeEvent as l, type ProducerPassthroughEvent as m, type ProducerPassthroughEventType as n, type ProducerReasoningEvent as o, type ProducerTextEvent as p, type ProducerToolCallEvent as q, type ProducerToolResultEvent as r, type ProducerUsageEvent as s, type ProducerWireEvent as t, assertPromptPartsWithinCap as u, base64WireLen as v, buildMentionPromptBlock as w, chatTurnRequestInit as x, fileMentionsToParts as y, formatBytes as z };
276
+ export { formatBytes as A, mediaTypeForMentionPath as B, type ChatMentionKind as C, DISPATCH_MAX_MEDIA_PARTS as D, mentionKindForPath as E, type FileMention as F, parseChatTurnParts as G, parseFileMentions as H, INLINE_PARTS_MAX_BYTES as I, promptPartsByteSize as J, validateSandboxMentionPath as K, MENTION_MAX_COUNT as M, type ProducerErrorEvent as P, type SandboxMentionPathCheck as S, type ChatAttachmentKind as a, type ChatAttachmentInput as b, type ChatTurnRequestPayload as c, type ChatTurnPartInput as d, type ChatTurnFilePartInput as e, type ChatReasoningEffort as f, ChatTurnInputError as g, type ChatTurnTextPartInput as h, DISPATCH_MAX_PARTS as i, DISPATCH_REQUEST_MAX_BYTES as j, DISPATCH_STRUCTURAL_RESERVE_BYTES as k, type FileMentionsToPartsOptions as l, type ProducerNoticeEvent as m, type ProducerPassthroughEvent as n, type ProducerPassthroughEventType as o, type ProducerReasoningEvent as p, type ProducerTextEvent as q, type ProducerToolCallEvent as r, type ProducerToolResultEvent as s, type ProducerUsageEvent as t, type ProducerWireEvent as u, assertPromptPartsWithinCap as v, base64WireLen as w, buildMentionPromptBlock as x, chatTurnRequestInit as y, fileMentionsToParts as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.44.33",
3
+ "version": "0.44.35",
4
4
  "packageManager": "pnpm@10.33.4",
5
5
  "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
6
6
  "keywords": [
@@ -425,12 +425,12 @@
425
425
  "@cloudflare/workers-types": "^4.20250620.0",
426
426
  "@radix-ui/react-dialog": "^1.1.15",
427
427
  "@tangle-network/agent-docs": "0.2.0",
428
- "@tangle-network/agent-eval": "0.134.1",
428
+ "@tangle-network/agent-eval": "0.135.1",
429
429
  "@tangle-network/agent-integrations": "^0.44.0",
430
430
  "@tangle-network/agent-interface": "0.36.0",
431
- "@tangle-network/agent-knowledge": "6.1.7",
431
+ "@tangle-network/agent-knowledge": "6.1.10",
432
432
  "@tangle-network/agent-profile-materialize": "0.9.2",
433
- "@tangle-network/agent-runtime": "0.108.0",
433
+ "@tangle-network/agent-runtime": "0.109.0",
434
434
  "@tangle-network/brand": "1.1.0",
435
435
  "@tangle-network/sandbox": "0.15.2",
436
436
  "@tangle-network/sandbox-ui": "0.90.3",
@@ -469,12 +469,12 @@
469
469
  "peerDependencies": {
470
470
  "@huggingface/transformers": ">=3",
471
471
  "@radix-ui/react-dialog": ">=1.1",
472
- "@tangle-network/agent-eval": ">=0.134.1",
472
+ "@tangle-network/agent-eval": ">=0.135.1",
473
473
  "@tangle-network/agent-integrations": ">=0.44.0",
474
474
  "@tangle-network/agent-interface": ">=0.36.0",
475
- "@tangle-network/agent-knowledge": ">=6.1.7",
475
+ "@tangle-network/agent-knowledge": ">=6.1.10",
476
476
  "@tangle-network/agent-profile-materialize": ">=0.9.2",
477
- "@tangle-network/agent-runtime": ">=0.108.0",
477
+ "@tangle-network/agent-runtime": ">=0.109.0",
478
478
  "@tangle-network/brand": ">=1.1.0",
479
479
  "@tangle-network/sandbox": ">=0.15.2",
480
480
  "@tangle-network/sandbox-ui": ">=0.90.3",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/chat-routes/wire.ts"],"sourcesContent":["/**\n * Wire contract between the chat client (composer + `streamChatTurn`) and the\n * assembled server vertical (`createChatTurnRoutes`). Import-free on purpose:\n * `/web-react` re-exports these types into browser bundles, so nothing here may\n * reach a Node builtin or an engine package.\n *\n * The client part shape permits an absolute file path until the server converts\n * it to the URL required by the sandbox SDK. It is derived here, not imported,\n * so the client bundle never touches the SDK.\n */\n\nexport interface ChatTurnTextPartInput {\n type: 'text'\n text: string\n}\n\n/** A non-text prompt part the upload route hands back and the client echoes\n * on send. `url` carries an inline `data:` URI for small files; `path` is a\n * sandbox workspace reference for large ones (the >1 MiB gateway body cap\n * makes the two-step upload mandatory). */\nexport interface ChatTurnFilePartInput {\n type: 'image' | 'file'\n filename?: string\n mediaType?: string\n url?: string\n path?: string\n}\n\n/** Resolve input as either a text part or a file part of a chat turn */\nexport type ChatTurnPartInput = ChatTurnTextPartInput | ChatTurnFilePartInput\n\n// ── producer stream vocabulary ───────────────────────────────────────────────\n\n/** Represent a text event produced by a source with a fixed type and associated text content */\nexport interface ProducerTextEvent {\n type: 'text'\n text: string\n}\n\n/** Define an event representing reasoning output with a fixed type and associated text */\nexport interface ProducerReasoningEvent {\n type: 'reasoning'\n text: string\n}\n\n/** Represent an event triggered by a producer tool call with its identifier, name, and arguments */\nexport interface ProducerToolCallEvent {\n type: 'tool_call'\n call: {\n toolCallId: string\n toolName: string\n args: Record<string, unknown>\n }\n}\n\n/** Describe the structure of an event representing the result of a producer tool call */\nexport interface ProducerToolResultEvent {\n type: 'tool_result'\n toolCallId: string\n toolName: string\n outcome: {\n ok: boolean\n result?: unknown\n message?: string\n }\n}\n\n/** Describe usage event with prompt and completion token counts for a producer */\nexport interface ProducerUsageEvent {\n type: 'usage'\n usage: {\n promptTokens: number\n completionTokens: number\n }\n}\n\n/** Define the structure for a producer notice event with type, id, kind, and text fields */\nexport interface ProducerNoticeEvent {\n type: 'notice'\n id: string\n /** Kept inline with `/interactions`' `NoticeKind` so this file stays import-free. */\n noticeKind: 'warning' | 'auto-declined'\n text: string\n}\n\n/** Represent an error event emitted by a producer containing message, code, and optional details */\nexport interface ProducerErrorEvent {\n type: 'error'\n data: {\n message: string\n code?: string\n details?: Record<string, unknown>\n }\n}\n\n/** Stable raw lifecycle/interaction/plan/route events forwarded unchanged. */\nexport type ProducerPassthroughEventType =\n | 'turn'\n | 'metadata'\n | 'interaction'\n | 'interaction.cancel'\n | 'plan.submitted'\n | 'done'\n | 'warning'\n | 'session.run.started'\n | 'session.run.completed'\n | 'session.run.failed'\n | 'turn_status'\n\n/** Define an event carrying passthrough data with flexible properties for producer communication */\nexport interface ProducerPassthroughEvent {\n type: ProducerPassthroughEventType\n data?: Record<string, unknown>\n /** Route markers and raw passthroughs may carry `turnId`, `status`, `seq`, etc. */\n [key: string]: unknown\n}\n\n/** Represent events emitted by a producer during its operation for processing and handling */\nexport type ProducerWireEvent =\n | ProducerTextEvent\n | ProducerReasoningEvent\n | ProducerToolCallEvent\n | ProducerToolResultEvent\n | ProducerUsageEvent\n | ProducerNoticeEvent\n | ProducerErrorEvent\n | ProducerPassthroughEvent\n\n/** The image/file split an attachment is rendered and persisted under — the\n * same discriminant as {@link ChatMentionKind}, but a distinct name because an\n * attachment carries content the product uploaded (`ChatAttachmentInput`)\n * while a mention points at a file the box already has. Defined HERE (the\n * import-free layer) so `ChatAttachmentInput` can reference it and the client\n * composer imports it without pulling the persisted-part vocabulary;\n * `/chat-store`'s parts module re-exports it alongside the attachment helpers. */\nexport type ChatAttachmentKind = 'image' | 'file'\n\n/** `POST` turn-body entry describing a file already uploaded to the product's\n * store (vault/object-store) — distinct from an inline {@link\n * ChatTurnFilePartInput} (which carries bytes) and from a {@link FileMention}\n * (a sandbox path the box already holds). The route resolves this field with\n * {@link resolveChatAttachments}: every path is re-validated and every size is\n * re-derived from the stored body, so nothing here is trusted as sent. */\nexport interface ChatAttachmentInput {\n path: string\n name: string\n size: number\n mediaType: string\n kind: ChatAttachmentKind\n}\n\n/** POST body for the turn route. `content` may be empty when `parts` carry the\n * message (an image-only send). Product routing fields (workspaceId etc.) ride\n * alongside and are read by the product's `authorize` seam. */\nexport interface ChatTurnRequestPayload {\n threadId: string\n content?: string\n /** Non-text parts from the upload route, echoed back verbatim. */\n parts?: ChatTurnFilePartInput[]\n /** `@`-picked file mentions for this turn — path references into the\n * workspace sandbox, NOT uploads, so they travel in their own field rather\n * than as `parts` entries. A product whose `parts` field is already spoken\n * for (an attachment sentinel) can still send mentions, and mentions\n * persist as their own `ChatMentionPart`s so a retry rebuilds them. The\n * route validates this field with {@link parseFileMentions} and replaces it\n * on the payload with the validated, deduped list. */\n mentions?: FileMention[]\n /** Files uploaded to the product's store ahead of the turn — path\n * references, NOT inline bytes (those ride `parts`). Validated and\n * size-re-derived by {@link resolveChatAttachments} into persistable\n * attachment parts; a product whose `parts` field is spoken for by inline\n * uploads still sends store-backed files here. */\n attachments?: ChatAttachmentInput[]\n model?: string\n effort?: 'auto' | 'low' | 'medium' | 'high'\n harness?: string\n /** Client-generated idempotency key for the logical turn (retry-safe). */\n turnId?: string\n [key: string]: unknown\n}\n\n/** `fetch` init for the turn route — the one place the client wire shape is\n * serialized, so composer glue and products never drift from the server's\n * parser. */\nexport function chatTurnRequestInit(payload: ChatTurnRequestPayload): RequestInit {\n return {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }\n}\n\n// ── inline-part byte budget ─────────────────────────────────────────────────\n//\n// The sandbox gateway caps request bodies at 1 MiB; a turn body whose inline\n// `data:` parts exceed it dies at the gateway with an opaque 413. Enforce the\n// budget at the route boundary instead, with headroom for the JSON envelope\n// (same fail-loud-at-the-choke-point style as /sandbox's provision-payload and\n// env-size gates).\n\n/** Define the maximum byte size allowed for inline parts in data processing */\nexport const INLINE_PARTS_MAX_BYTES = 950_000\n\n// ── dispatch (parts[]) budget vocabulary ────────────────────────────────────\n//\n// The default caps `buildDispatchParts` sizes an attachment/mention dispatch\n// against — the sidecar/proxy limits an assembled `parts` array crosses, one\n// step past `INLINE_PARTS_MAX_BYTES` (which gates the raw turn BODY). Grouped\n// here, in the import-free layer, so the numbers are one overridable\n// vocabulary the client can read and a product can tune per call rather than\n// constants buried in the server module. NOTE: these model sidecar/proxy caps,\n// not a product's MIME accept-list or vault bucketing — those are DOMAIN\n// values the product supplies, never defaulted here.\n\n/** Hard cap on the whole `/prompt` request body as it crosses the sandbox\n * proxy — smaller in practice than a raw-file write cap because a dispatch\n * carries several inline parts plus the flattened history in one request. */\nexport const DISPATCH_REQUEST_MAX_BYTES = 1024 * 1024\n\n/** Bytes reserved off the top of {@link DISPATCH_REQUEST_MAX_BYTES} for the\n * JSON structure around the parts array (keys, delimiters, per-part\n * `type`/`filename`/`mediaType` fields) that {@link base64WireLen} does not\n * account for — keeps the inline budget off the exact proxy cap where one\n * stray byte trips the 413. */\nexport const DISPATCH_STRUCTURAL_RESERVE_BYTES = 64 * 1024\n\n/** Sidecar's hard cap on the `parts` array of one prompt request — a dispatch\n * must never assemble more parts than this or the whole turn 400s. */\nexport const DISPATCH_MAX_PARTS = 64\n\n/** Product-side cap on media parts per dispatch (current turn + carried\n * history), well under {@link DISPATCH_MAX_PARTS}. History trimming that keeps\n * a transcript's native media under this is a PRODUCT concern (the pointer\n * block keeps trimmed media reachable); `buildDispatchParts` enforces only the\n * total {@link DISPATCH_MAX_PARTS} cap. */\nexport const DISPATCH_MAX_MEDIA_PARTS = 24\n\n/** Size a base64-encoded string occupies on the wire given the raw\n * (pre-encoding) byte length: base64 packs 3 raw bytes into 4 output\n * characters, rounded up to the next multiple of 4. */\nexport function base64WireLen(byteLen: number): number {\n return Math.ceil(byteLen / 3) * 4\n}\n\n/**\n * Render a raw byte count as a human-readable size (`512B`, `3KB`, `12MB\n * 500KB`). Ported EXACTLY from gtm-agent's `attachment-limits.ts` — byte-\n * identical implementation, not a reinterpretation — so `resolve-attachments`'s\n * and `promote-file-part`'s error strings match gtm's wording verbatim. Lives\n * in the import-free wire layer (not `resolve-attachments.ts` alone) because\n * BOTH the aggregate-cap message here and the per-file oversize message in\n * `promote-file-part.ts` need it; a browser composer wanting the same\n * formatting for a client-side pre-check can also import it with no engine\n * pulled in.\n */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes}B`\n if (bytes >= 1024 * 1024) {\n const megabytes = Math.floor(bytes / (1024 * 1024))\n const remainder = bytes % (1024 * 1024)\n return remainder === 0 ? `${megabytes}MB` : `${megabytes}MB ${formatBytes(remainder)}`\n }\n return `${Math.round(bytes / 1024)}KB`\n}\n\n/** Represent errors for invalid chat turn inputs with status and code properties */\nexport class ChatTurnInputError extends Error {\n constructor(message: string, readonly status = 400, readonly code = 'INVALID_CHAT_TURN') {\n super(message)\n this.name = 'ChatTurnInputError'\n }\n}\n\nfunction partByteSize(part: ChatTurnPartInput): number {\n let bytes = 0\n if (part.type === 'text') return part.text.length\n if (part.url) bytes += part.url.length\n if (part.path) bytes += part.path.length\n return bytes\n}\n\n/** Calculate the total byte size of an array of chat turn parts */\nexport function promptPartsByteSize(parts: ChatTurnPartInput[]): number {\n return parts.reduce((total, part) => total + partByteSize(part), 0)\n}\n\n/** Throws `ChatTurnInputError` (413) when the parts' inline payload would blow\n * the gateway cap. Path-ref parts are tiny by construction and always pass. */\nexport function assertPromptPartsWithinCap(\n parts: ChatTurnPartInput[],\n maxBytes = INLINE_PARTS_MAX_BYTES,\n): void {\n const total = promptPartsByteSize(parts)\n if (total <= maxBytes) return\n const largest = [...parts].sort((a, b) => partByteSize(b) - partByteSize(a))[0]\n const largestName = largest && largest.type !== 'text' ? largest.filename ?? largest.path ?? largest.type : 'text'\n throw new ChatTurnInputError(\n `Inline prompt parts total ${total}B, over the ${maxBytes}B budget (largest: ${largestName}, ${largest ? partByteSize(largest) : 0}B). ` +\n 'Upload large files through the upload route so they travel as sandbox path references.',\n 413,\n 'PROMPT_PARTS_TOO_LARGE',\n )\n}\n\n// ── file mentions ────────────────────────────────────────────────────────\n//\n// A file mention (`@`-picked in the composer, sandbox-ui#184) is a path\n// reference into the workspace sandbox — no byte upload. These helpers turn\n// a resolved mention list into wire parts and the prompt pointer block that\n// tells the agent where to read them from.\n\n/** A file mention resolved from the composer's `@`-picker: the\n * workspace-relative path plus enough metadata to build a prompt part and\n * pointer text. `path` is the canonical identity — the mention pill's\n * `MentionItem.id` for the file kind (`/web-react`'s `useFileMentions`). */\nexport interface FileMention {\n path: string\n name: string\n size?: number\n}\n\nconst MENTION_IMAGE_MEDIA_TYPES: ReadonlyMap<string, string> = new Map([\n ['.png', 'image/png'],\n ['.jpg', 'image/jpeg'],\n ['.jpeg', 'image/jpeg'],\n ['.gif', 'image/gif'],\n ['.webp', 'image/webp'],\n ['.svg', 'image/svg+xml'],\n ['.bmp', 'image/bmp'],\n ['.heic', 'image/heic'],\n ['.heif', 'image/heif'],\n ['.avif', 'image/avif'],\n])\n\nfunction extensionOf(path: string): string {\n const base = path.split('/').filter(Boolean).pop() ?? path\n const dot = base.lastIndexOf('.')\n return dot > 0 ? base.slice(dot).toLowerCase() : ''\n}\n\n/** The `image/*` mime for a mention path by extension, or `undefined` for\n * anything not in the known image set (dispatched as `type: 'file'`). */\nexport function mediaTypeForMentionPath(path: string): string | undefined {\n return MENTION_IMAGE_MEDIA_TYPES.get(extensionOf(path))\n}\n\n/** The image/file split a mention is rendered and persisted under — the\n * composer pill's icon, the dispatched part's `type`, and\n * `ChatMentionPart.mentionKind` are all this one value. */\nexport type ChatMentionKind = 'image' | 'file'\n\n/** `image` when the path's extension is in the known image set (the same table\n * {@link mediaTypeForMentionPath} reads), `file` otherwise. Exported so a\n * client that needs only the discriminant — a pill icon, a persisted part's\n * `mentionKind` — never re-declares the extension table; two frozen copies of\n * one mime table is how one gains a format and the other doesn't. */\nexport function mentionKindForPath(path: string): ChatMentionKind {\n return mediaTypeForMentionPath(path) ? 'image' : 'file'\n}\n\n/** Define options to resolve mention paths when converting file mentions to parts */\nexport interface FileMentionsToPartsOptions {\n /** Resolve a mention's workspace-relative path to the absolute path the\n * dispatched part should carry (e.g. a host prefixing the in-box vault\n * root). Default: identity — the path travels unchanged. */\n resolvePath?: (path: string) => string\n}\n\n/** Maps resolved file mentions to path-only `ChatTurnFilePartInput`s —\n * `image` vs `file` by extension, and always a `path`, never a `url` (the\n * url/path XOR invariant: a mention is a sandbox path reference, never\n * inline bytes). */\nexport function fileMentionsToParts(\n mentions: readonly FileMention[],\n opts: FileMentionsToPartsOptions = {},\n): ChatTurnFilePartInput[] {\n const resolvePath = opts.resolvePath ?? ((path: string) => path)\n return mentions.map((mention) => {\n const mediaType = mediaTypeForMentionPath(mention.path)\n const part: ChatTurnFilePartInput = {\n type: mediaType ? 'image' : 'file',\n filename: mention.name,\n path: resolvePath(mention.path),\n }\n if (mediaType) part.mediaType = mediaType\n return part\n })\n}\n\n/** The agent-facing pointer block appended to the dispatched prompt — never\n * persisted in message `content`. Empty array → `''` so callers can append\n * unconditionally. This is the sole producer of that text: the current\n * turn's dispatch and any history projection built from the same mention\n * list both route through here, so the two can't drift apart. */\nexport function buildMentionPromptBlock(\n mentions: readonly Pick<FileMention, 'name' | 'path'>[],\n): string {\n if (mentions.length === 0) return ''\n const lines = mentions.map((m) => `- ${m.name} (${m.path})`)\n return `\\n\\nMentioned files — read them from these paths:\\n${lines.join('\\n')}`\n}\n\n// ── mention validation ───────────────────────────────────────────────────\n//\n// This package owns BOTH ends of the mention path contract — it emits paths\n// from `createSandboxFileIndexRoute` and consumes them in `fileMentionsToParts`\n// / `buildMentionPromptBlock` — so the validation belongs here rather than in\n// each app that wires the pair up. A mention names a file that already exists\n// in the sandbox, so validation is a pure path/charset/count check with no\n// I/O: existence is proven later, when the agent reads the path and the turn\n// fails loudly if it is gone.\n\n/** Hard cap on mentions per turn. Bounds the prompt pointer block, the\n * persisted parts, and whatever media budget a dispatch draws from them. */\nexport const MENTION_MAX_COUNT = 16\n\n/** Longest mention display name accepted — bounds the pointer-block text and\n * the transcript pill label. */\nconst MAX_MENTION_NAME_LENGTH = 256\n/** Longest mention path accepted. */\nconst MAX_MENTION_PATH_LENGTH = 1024\n\n/** Represent the result of a sandbox mention path check indicating success or failure with an error message */\nexport type SandboxMentionPathCheck =\n | { succeeded: true }\n | { succeeded: false; error: string }\n\n/**\n * Validate a workspace-relative sandbox mention path. Rejects traversal (a\n * `..` path segment), absolute paths (leading `/`), backslashes, and null\n * bytes — the four ways a path picked in a client can escape the root the\n * index route scanned.\n *\n * Spaces and unicode are deliberately ALLOWED: in-box filenames are arbitrary,\n * and an ASCII-only charset would silently drop real files from a feature\n * whose whole job is naming them.\n */\nexport function validateSandboxMentionPath(path: unknown): SandboxMentionPathCheck {\n if (typeof path !== 'string' || path.length === 0) {\n return { succeeded: false, error: 'mention path must be a non-empty string' }\n }\n if (path.length > MAX_MENTION_PATH_LENGTH) {\n return { succeeded: false, error: `mention path must not exceed ${MAX_MENTION_PATH_LENGTH} characters` }\n }\n if (path.includes('\\0')) {\n return { succeeded: false, error: 'mention path must not contain null bytes' }\n }\n if (path.includes('\\\\')) {\n return { succeeded: false, error: 'mention path must not contain backslashes' }\n }\n if (path.startsWith('/')) {\n return { succeeded: false, error: 'mention path must be workspace-relative, not absolute' }\n }\n if (path.split('/').some((segment) => segment === '..')) {\n return { succeeded: false, error: 'mention path must not contain \"..\" segments' }\n }\n return { succeeded: true }\n}\n\nfunction parseFileMention(value: unknown, index: number): FileMention {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new ChatTurnInputError(`mentions[${index}] must be an object`)\n }\n const record = value as Record<string, unknown>\n\n const pathCheck = validateSandboxMentionPath(record.path)\n if (!pathCheck.succeeded) throw new ChatTurnInputError(`mentions[${index}]: ${pathCheck.error}`)\n\n const name = record.name\n if (typeof name !== 'string' || !name.trim()) {\n throw new ChatTurnInputError(`mentions[${index}].name must be a non-empty string`)\n }\n if (name.length > MAX_MENTION_NAME_LENGTH) {\n throw new ChatTurnInputError(`mentions[${index}].name must not exceed ${MAX_MENTION_NAME_LENGTH} characters`)\n }\n\n const size = record.size\n if (size !== undefined) {\n if (typeof size !== 'number' || !Number.isFinite(size)) {\n throw new ChatTurnInputError(`mentions[${index}].size must be a finite number`)\n }\n if (size < 0) {\n throw new ChatTurnInputError(`mentions[${index}].size must not be negative`)\n }\n }\n\n return { path: record.path as string, name, ...(typeof size === 'number' ? { size } : {}) }\n}\n\n/**\n * Validates the untyped `mentions` array off the wire, mirroring\n * {@link parseChatTurnParts}: the typed list, or `ChatTurnInputError` (400)\n * naming the offending entry. Never sanitizes-and-continues — a traversal path\n * is a rejected request, not a trimmed one.\n *\n * A path repeated within one turn is deduped to its first occurrence rather\n * than rejected: mentioning the same file twice is plausible user input, not\n * an attack.\n */\nexport function parseFileMentions(raw: unknown): FileMention[] {\n if (raw === undefined || raw === null) return []\n if (!Array.isArray(raw)) throw new ChatTurnInputError('mentions must be an array')\n if (raw.length > MENTION_MAX_COUNT) {\n throw new ChatTurnInputError(`mentions must not exceed ${MENTION_MAX_COUNT} entries`)\n }\n\n const mentions: FileMention[] = []\n const seenPaths = new Set<string>()\n for (let index = 0; index < raw.length; index += 1) {\n const mention = parseFileMention(raw[index], index)\n if (seenPaths.has(mention.path)) continue\n seenPaths.add(mention.path)\n mentions.push(mention)\n }\n return mentions\n}\n\n/** Validates the untyped `parts` array off the wire. Returns the typed parts\n * or throws `ChatTurnInputError` (400) naming the offending entry. */\nexport function parseChatTurnParts(raw: unknown): ChatTurnFilePartInput[] {\n if (raw === undefined || raw === null) return []\n if (!Array.isArray(raw)) throw new ChatTurnInputError('parts must be an array')\n return raw.map((entry, index) => {\n const part = entry as Record<string, unknown> | null\n if (!part || typeof part !== 'object') {\n throw new ChatTurnInputError(`parts[${index}] must be an object`)\n }\n if (part.type !== 'image' && part.type !== 'file') {\n throw new ChatTurnInputError(`parts[${index}].type must be 'image' or 'file'`)\n }\n for (const key of ['filename', 'mediaType', 'url', 'path'] as const) {\n if (part[key] !== undefined && typeof part[key] !== 'string') {\n throw new ChatTurnInputError(`parts[${index}].${key} must be a string`)\n }\n }\n if (part.content !== undefined) {\n throw new ChatTurnInputError(`parts[${index}].content is not supported; provide a url or path`)\n }\n\n const url = typeof part.url === 'string' && part.url.length > 0 ? part.url : undefined\n const path = typeof part.path === 'string' && part.path.length > 0 ? part.path : undefined\n if (Boolean(url) === Boolean(path)) {\n throw new ChatTurnInputError(`parts[${index}] requires exactly one url or path`)\n }\n if (path && !path.startsWith('/')) {\n throw new ChatTurnInputError(`parts[${index}].path must be absolute`)\n }\n\n const filename = typeof part.filename === 'string' ? part.filename.trim() : undefined\n if (part.type === 'file' && !filename) {\n throw new ChatTurnInputError(`parts[${index}].filename is required for file parts`)\n }\n return {\n type: part.type,\n ...(filename ? { filename } : {}),\n ...(part.mediaType !== undefined ? { mediaType: part.mediaType as string } : {}),\n ...(url ? { url } : { path: path! }),\n }\n })\n}\n"],"mappings":"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","names":[]}