@tanstack/ai 0.42.0 → 0.43.1
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/README.md +15 -1
- package/dist/esm/activities/chat/adapter.js +23 -16
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/agent-loop-strategies.d.ts +5 -36
- package/dist/esm/activities/chat/agent-loop-strategies.js +75 -21
- package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -1
- package/dist/esm/activities/chat/cancel.d.ts +40 -0
- package/dist/esm/activities/chat/cancel.js +54 -0
- package/dist/esm/activities/chat/cancel.js.map +1 -0
- package/dist/esm/activities/chat/index.d.ts +28 -21
- package/dist/esm/activities/chat/index.js +2100 -1813
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/mcp/manager.d.ts +2 -2
- package/dist/esm/activities/chat/mcp/manager.js +90 -77
- package/dist/esm/activities/chat/mcp/manager.js.map +1 -1
- package/dist/esm/activities/chat/mcp/types.d.ts +2 -2
- package/dist/esm/activities/chat/messages.js +397 -346
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.js +17 -15
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
- package/dist/esm/activities/chat/middleware/capabilities.js +78 -43
- package/dist/esm/activities/chat/middleware/capabilities.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +94 -1
- package/dist/esm/activities/chat/middleware/compose.js +623 -531
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.js +12 -5
- package/dist/esm/activities/chat/middleware/define.js.map +1 -1
- package/dist/esm/activities/chat/middleware/index.d.ts +5 -1
- package/dist/esm/activities/chat/middleware/locks.d.ts +50 -0
- package/dist/esm/activities/chat/middleware/locks.js +71 -0
- package/dist/esm/activities/chat/middleware/locks.js.map +1 -0
- package/dist/esm/activities/chat/middleware/pending-turn.d.ts +15 -0
- package/dist/esm/activities/chat/middleware/pending-turn.js +35 -0
- package/dist/esm/activities/chat/middleware/pending-turn.js.map +1 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.d.ts +23 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.js +42 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.js.map +1 -0
- package/dist/esm/activities/chat/middleware/run-store.d.ts +283 -0
- package/dist/esm/activities/chat/middleware/run-store.js +176 -0
- package/dist/esm/activities/chat/middleware/run-store.js.map +1 -0
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js +14 -8
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js.map +1 -1
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +79 -70
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +59 -2
- package/dist/esm/activities/chat/middleware/validate.js +23 -28
- package/dist/esm/activities/chat/middleware/validate.js.map +1 -1
- package/dist/esm/activities/chat/stream/json-parser.js +39 -25
- package/dist/esm/activities/chat/stream/json-parser.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js +275 -234
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +24 -4
- package/dist/esm/activities/chat/stream/processor.js +1341 -1542
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/strategies.js +69 -53
- package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
- package/dist/esm/activities/chat/tools/approval-schema.d.ts +19 -0
- package/dist/esm/activities/chat/tools/approval-schema.js +117 -0
- package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js +164 -191
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
- package/dist/esm/activities/chat/tools/lazy-tools.js +24 -12
- package/dist/esm/activities/chat/tools/lazy-tools.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.js +293 -146
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +18 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +522 -531
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.d.ts +75 -16
- package/dist/esm/activities/chat/tools/tool-definition.js +95 -23
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/error-payload.js +85 -47
- package/dist/esm/activities/error-payload.js.map +1 -1
- package/dist/esm/activities/generateAudio/adapter.js +22 -15
- package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.d.ts +4 -0
- package/dist/esm/activities/generateAudio/index.js +141 -105
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateImage/adapter.js +22 -15
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.d.ts +4 -0
- package/dist/esm/activities/generateImage/index.js +155 -111
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/adapter.js +22 -15
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.d.ts +4 -0
- package/dist/esm/activities/generateSpeech/index.js +159 -110
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/adapter.js +22 -15
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.d.ts +4 -0
- package/dist/esm/activities/generateTranscription/index.js +159 -100
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.js +36 -29
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +143 -19
- package/dist/esm/activities/generateVideo/index.js +456 -279
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/snap.js +60 -48
- package/dist/esm/activities/generateVideo/snap.js.map +1 -1
- package/dist/esm/activities/index.js +8 -34
- package/dist/esm/activities/middleware/index.d.ts +1 -1
- package/dist/esm/activities/middleware/run.d.ts +10 -0
- package/dist/esm/activities/middleware/run.js +53 -29
- package/dist/esm/activities/middleware/run.js.map +1 -1
- package/dist/esm/activities/middleware/types.d.ts +44 -6
- package/dist/esm/activities/stream-generation-result.d.ts +4 -1
- package/dist/esm/activities/stream-generation-result.js +79 -44
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/adapter.js +22 -15
- package/dist/esm/activities/summarize/adapter.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +252 -202
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/activities/summarize/index.d.ts +27 -0
- package/dist/esm/activities/summarize/index.js +268 -102
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +2 -1
- package/dist/esm/adapter-internals.js +4 -11
- package/dist/esm/client.d.ts +25 -3
- package/dist/esm/client.js +131 -64
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/custom-events.d.ts +76 -0
- package/dist/esm/custom-events.js +37 -0
- package/dist/esm/custom-events.js.map +1 -0
- package/dist/esm/delivery-detach.d.ts +50 -0
- package/dist/esm/delivery-detach.js +71 -0
- package/dist/esm/delivery-detach.js.map +1 -0
- package/dist/esm/delivery-disconnect.d.ts +62 -0
- package/dist/esm/delivery-disconnect.js +81 -0
- package/dist/esm/delivery-disconnect.js.map +1 -0
- package/dist/esm/extend-adapter.js +19 -17
- package/dist/esm/extend-adapter.js.map +1 -1
- package/dist/esm/index.d.ts +24 -6
- package/dist/esm/index.js +30 -98
- package/dist/esm/interrupt-resume.d.ts +71 -0
- package/dist/esm/interrupt-resume.js +438 -0
- package/dist/esm/interrupt-resume.js.map +1 -0
- package/dist/esm/interrupt-serialization.d.ts +12 -0
- package/dist/esm/interrupt-serialization.js +178 -0
- package/dist/esm/interrupt-serialization.js.map +1 -0
- package/dist/esm/interrupts.d.ts +84 -0
- package/dist/esm/interrupts.js +31 -0
- package/dist/esm/interrupts.js.map +1 -0
- package/dist/esm/locks.d.ts +10 -0
- package/dist/esm/locks.js +2 -0
- package/dist/esm/logger/console-logger.js +101 -78
- package/dist/esm/logger/console-logger.js.map +1 -1
- package/dist/esm/logger/internal-logger.js +104 -89
- package/dist/esm/logger/internal-logger.js.map +1 -1
- package/dist/esm/logger/resolve.js +54 -49
- package/dist/esm/logger/resolve.js.map +1 -1
- package/dist/esm/logger/types.d.ts +1 -1
- package/dist/esm/middlewares/content-guard.js +142 -148
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/index.js +2 -6
- package/dist/esm/middlewares/otel.d.ts +3 -1
- package/dist/esm/middlewares/otel.js +599 -732
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/middlewares/usage-attributes.js +47 -40
- package/dist/esm/middlewares/usage-attributes.js.map +1 -1
- package/dist/esm/realtime/event-emitter.js +24 -25
- package/dist/esm/realtime/event-emitter.js.map +1 -1
- package/dist/esm/realtime/index.d.ts +5 -9
- package/dist/esm/realtime/index.js +29 -6
- package/dist/esm/realtime/index.js.map +1 -1
- package/dist/esm/scope.d.ts +47 -0
- package/dist/esm/stream-durability.d.ts +171 -0
- package/dist/esm/stream-durability.js +295 -0
- package/dist/esm/stream-durability.js.map +1 -0
- package/dist/esm/stream-to-response.d.ts +178 -13
- package/dist/esm/stream-to-response.js +663 -115
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.js +30 -16
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/system-prompts.js +27 -21
- package/dist/esm/system-prompts.js.map +1 -1
- package/dist/esm/tool-registry.js +72 -45
- package/dist/esm/tool-registry.js.map +1 -1
- package/dist/esm/tools/provider-tool.js +14 -5
- package/dist/esm/tools/provider-tool.js.map +1 -1
- package/dist/esm/types.d.ts +321 -42
- package/dist/esm/types.js +2 -0
- package/dist/esm/utilities/ag-ui-wire.js +79 -93
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.d.ts +26 -4
- package/dist/esm/utilities/chat-params.js +218 -92
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/dist/esm/utilities/errors.js +28 -18
- package/dist/esm/utilities/errors.js.map +1 -1
- package/dist/esm/utilities/media-prompt.js +46 -41
- package/dist/esm/utilities/media-prompt.js.map +1 -1
- package/dist/esm/utilities/numbers.js +13 -10
- package/dist/esm/utilities/numbers.js.map +1 -1
- package/dist/esm/utilities/provider-executed.js +20 -11
- package/dist/esm/utilities/provider-executed.js.map +1 -1
- package/dist/esm/utilities/sampling-keys.js +31 -19
- package/dist/esm/utilities/sampling-keys.js.map +1 -1
- package/dist/esm/utilities/tool-result.js +42 -30
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/dist/esm/utilities/usage.js +27 -9
- package/dist/esm/utilities/usage.js.map +1 -1
- package/dist/esm/utils.js +26 -18
- package/dist/esm/utils.js.map +1 -1
- package/package.json +10 -6
- package/skills/ai-core/SKILL.md +69 -18
- package/skills/ai-core/adapter-configuration/SKILL.md +44 -21
- package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +1 -3
- package/skills/ai-core/adapter-configuration/references/byteplus-adapter.md +148 -0
- package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +2 -6
- package/skills/ai-core/adapter-configuration/references/groq-adapter.md +2 -6
- package/skills/ai-core/adapter-configuration/references/openai-adapter.md +1 -3
- package/skills/ai-core/ag-ui-protocol/SKILL.md +1 -1
- package/skills/ai-core/chat-experience/SKILL.md +98 -11
- package/skills/ai-core/client-persistence/SKILL.md +277 -0
- package/skills/ai-core/custom-backend-integration/SKILL.md +1 -1
- package/skills/ai-core/debug-logging/SKILL.md +1 -1
- package/skills/ai-core/locks/SKILL.md +143 -0
- package/skills/ai-core/media-generation/SKILL.md +144 -12
- package/skills/ai-core/middleware/SKILL.md +258 -33
- package/skills/ai-core/structured-outputs/SKILL.md +1 -1
- package/skills/ai-core/tool-calling/SKILL.md +54 -61
- package/src/activities/chat/agent-loop-strategies.ts +5 -39
- package/src/activities/chat/cancel.ts +81 -0
- package/src/activities/chat/index.ts +1091 -200
- package/src/activities/chat/mcp/manager.ts +4 -4
- package/src/activities/chat/mcp/types.ts +2 -2
- package/src/activities/chat/messages.ts +5 -3
- package/src/activities/chat/middleware/builder.ts +1 -1
- package/src/activities/chat/middleware/compose.ts +186 -9
- package/src/activities/chat/middleware/index.ts +26 -0
- package/src/activities/chat/middleware/locks.ts +102 -0
- package/src/activities/chat/middleware/pending-turn.ts +47 -0
- package/src/activities/chat/middleware/run-disconnect.ts +62 -0
- package/src/activities/chat/middleware/run-store.ts +412 -0
- package/src/activities/chat/middleware/types.ts +62 -1
- package/src/activities/chat/stream/processor.ts +189 -5
- package/src/activities/chat/tools/approval-schema.ts +205 -0
- package/src/activities/chat/tools/tool-calls.ts +106 -13
- package/src/activities/chat/tools/tool-definition.ts +210 -39
- package/src/activities/generateAudio/index.ts +20 -3
- package/src/activities/generateImage/index.ts +20 -3
- package/src/activities/generateSpeech/index.ts +25 -3
- package/src/activities/generateTranscription/index.ts +26 -3
- package/src/activities/generateVideo/index.ts +345 -82
- package/src/activities/middleware/index.ts +2 -0
- package/src/activities/middleware/run.ts +31 -0
- package/src/activities/middleware/types.ts +49 -5
- package/src/activities/stream-generation-result.ts +30 -2
- package/src/activities/summarize/chat-stream-summarize.ts +5 -0
- package/src/activities/summarize/index.ts +200 -10
- package/src/adapter-internals.ts +10 -1
- package/src/client.ts +244 -0
- package/src/custom-events.ts +107 -0
- package/src/delivery-detach.ts +72 -0
- package/src/delivery-disconnect.ts +84 -0
- package/src/index.ts +138 -1
- package/src/interrupt-resume.ts +824 -0
- package/src/interrupt-serialization.ts +183 -0
- package/src/interrupts.ts +146 -0
- package/src/locks.ts +17 -0
- package/src/logger/types.ts +1 -1
- package/src/middlewares/otel.ts +23 -5
- package/src/realtime/index.ts +5 -9
- package/src/scope.ts +47 -0
- package/src/stream-durability.ts +598 -0
- package/src/stream-to-response.ts +1051 -95
- package/src/strip-to-spec-middleware.ts +3 -3
- package/src/types.ts +405 -45
- package/src/utilities/chat-params.ts +245 -55
- package/dist/esm/activities/index.js.map +0 -1
- package/dist/esm/adapter-internals.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/middlewares/index.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-params.js","sources":["../../../src/utilities/chat-params.ts"],"sourcesContent":["import { AGUIError, RunAgentInputSchema } from '@ag-ui/core'\nimport type { Context as AGUIContext } from '@ag-ui/core'\nimport type {\n JSONSchema,\n ModelMessage,\n SchemaInput,\n Tool,\n UIMessage,\n} from '../types'\n\nconst KNOWN_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n 'tool-call',\n 'tool-result',\n 'thinking',\n])\n\nfunction isValidParts(value: unknown): value is Array<{ type: string }> {\n if (!Array.isArray(value)) return false\n for (const p of value) {\n if (!p || typeof p !== 'object') return false\n const type = (p as { type?: unknown }).type\n if (typeof type !== 'string' || !KNOWN_PART_TYPES.has(type)) return false\n }\n return true\n}\n\n/**\n * Parse and validate an HTTP request body as an AG-UI `RunAgentInput`.\n *\n * Returns a spread-friendly object whose `messages` field is suitable for\n * passing directly to `chat({ messages })`. The existing\n * `convertMessagesToModelMessages` handles AG-UI fan-out dedup and\n * reasoning/activity/developer-role normalization internally.\n *\n * @throws An error with a migration-pointing message when the body does\n * not conform to AG-UI 0.0.52 `RunAgentInputSchema`. Surface this as a\n * 400 Bad Request to the client.\n */\nexport function chatParamsFromRequestBody(body: unknown): Promise<{\n messages: Array<UIMessage | ModelMessage>\n threadId: string\n runId: string\n parentRunId?: string\n tools: Array<{ name: string; description: string; parameters: JSONSchema }>\n forwardedProps: Record<string, unknown>\n state: unknown\n /**\n * @deprecated Use `aguiContext` instead. This alias will be removed in a\n * future release.\n */\n context: Array<AGUIContext>\n aguiContext: Array<AGUIContext>\n}> {\n const parseResult = RunAgentInputSchema.safeParse(body)\n if (!parseResult.success) {\n return Promise.reject(\n new AGUIError(\n `Request body is not a valid AG-UI RunAgentInput. ` +\n `If you're upgrading from a previous @tanstack/ai-client release, ` +\n `see docs/migration/ag-ui-compliance.md. ` +\n `Validation errors: ${parseResult.error.message}`,\n ),\n )\n }\n\n const parsed = parseResult.data\n const aguiContext = parsed.context\n\n // AG-UI Zod uses `.strip()` so extra fields like `parts` on messages are\n // dropped during parse. We re-attach them from the original body so the\n // existing UIMessage path inside `chat()` can use them directly.\n const rawMessages =\n (body as { messages?: Array<Record<string, unknown>> }).messages ?? []\n const messages = parsed.messages.map((m, i) => {\n const raw = rawMessages[i]\n if (\n raw &&\n typeof raw === 'object' &&\n 'parts' in raw &&\n isValidParts(raw.parts)\n ) {\n return { ...m, parts: raw.parts } as UIMessage | ModelMessage\n }\n return m as ModelMessage\n })\n\n return Promise.resolve({\n messages,\n threadId: parsed.threadId,\n runId: parsed.runId,\n parentRunId: parsed.parentRunId,\n tools: parsed.tools as Array<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n forwardedProps: (parsed.forwardedProps ?? {}) as Record<string, unknown>,\n state: parsed.state,\n context: aguiContext,\n aguiContext,\n })\n}\n\n/**\n * Read an HTTP `Request`, parse its JSON body, and validate it as an\n * AG-UI `RunAgentInput` — collapsing the standard `req.json()` +\n * `chatParamsFromRequestBody(...)` pair into a single call.\n *\n * On a malformed body or invalid AG-UI shape, this **throws a\n * `Response`** with status 400 and a migration-pointing message in the\n * body. Frameworks that natively handle thrown `Response` objects\n * (TanStack Start, SolidStart, Remix, React Router 7) will return the\n * 400 to the client automatically, so the handler reduces to:\n *\n * ```ts\n * export async function POST(req: Request) {\n * const params = await chatParamsFromRequest(req)\n * // ...use params\n * }\n * ```\n *\n * In frameworks that do not auto-handle thrown `Response` objects\n * (Next.js Route Handlers, SvelteKit, Hono, raw Node), wrap the call\n * with try/catch and return the caught Response yourself, or use\n * `chatParamsFromRequestBody` directly with your own JSON-parsing.\n *\n * @throws {Response} 400 on malformed JSON or invalid AG-UI shape.\n */\nexport async function chatParamsFromRequest(\n req: Request,\n): Promise<Awaited<ReturnType<typeof chatParamsFromRequestBody>>> {\n let body: unknown\n try {\n body = await req.json()\n } catch (cause) {\n // Preserve the underlying error on the thrown Response for\n // server-side observability without leaking it to the client.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n try {\n return await chatParamsFromRequestBody(body)\n } catch (cause) {\n // Generic public message — avoid echoing Zod paths (which can contain\n // user payload fragments) or internal validator strings to the client.\n // The original AGUIError is attached as `cause` so server logs can\n // surface it without exposing it to remote callers.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n}\n\n/**\n * Merge a server-side tool array with the AG-UI client-declared tools\n * received in the request body.\n *\n * Rules:\n * - Server tools win on name collision. The client's declaration is\n * ignored if the server already has a tool with that name. The client's\n * UI-side handler still fires when the streamed tool-result event comes\n * through (see `chat-client.ts` `onToolCall`), giving the\n * \"after server execution the client also handles\" semantic for free.\n * - Client-only tools (name not in `serverTools`) become no-execute\n * entries: the runtime's existing `ClientToolRequest` path handles\n * them — server emits a tool-call request, client executes via its\n * registered handler, client posts back the result.\n *\n * @param serverTools - The server's tool array (e.g. from\n * `[myToolDef.server(...)]`). Pass directly to `chat({ tools })`.\n * @param clientTools - The `tools` array received from\n * `chatParamsFromRequest(...)` / `chatParamsFromRequestBody(...)`.\n * @returns A merged array suitable for `chat({ tools })`.\n */\nexport function mergeAgentTools<TContext = unknown>(\n serverTools: ReadonlyArray<Tool<SchemaInput, SchemaInput, string, TContext>>,\n clientTools: ReadonlyArray<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n): Array<Tool<SchemaInput, SchemaInput, string, TContext>> {\n const seen = new Set(serverTools.map((t) => t.name))\n const merged: Array<Tool<SchemaInput, SchemaInput, string, TContext>> = [\n ...serverTools,\n ]\n for (const ct of clientTools) {\n if (seen.has(ct.name)) {\n // Server wins on name collision.\n continue\n }\n seen.add(ct.name)\n merged.push({\n name: ct.name,\n description: ct.description,\n inputSchema: ct.parameters,\n // No `execute` — runtime treats this as a client-side tool and\n // emits ClientToolRequest events.\n } as Tool<SchemaInput, SchemaInput, string, TContext>)\n }\n return merged\n}\n"],"names":[],"mappings":";AAUA,MAAM,uCAAuB,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,aAAa,OAAkD;AACtE,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAClC,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,UAAM,OAAQ,EAAyB;AACvC,QAAI,OAAO,SAAS,YAAY,CAAC,iBAAiB,IAAI,IAAI,EAAG,QAAO;AAAA,EACtE;AACA,SAAO;AACT;AAcO,SAAS,0BAA0B,MAcvC;AACD,QAAM,cAAc,oBAAoB,UAAU,IAAI;AACtD,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,QAAQ;AAAA,MACb,IAAI;AAAA,QACF,gLAGwB,YAAY,MAAM,OAAO;AAAA,MAAA;AAAA,IACnD;AAAA,EAEJ;AAEA,QAAM,SAAS,YAAY;AAC3B,QAAM,cAAc,OAAO;AAK3B,QAAM,cACH,KAAuD,YAAY,CAAA;AACtE,QAAM,WAAW,OAAO,SAAS,IAAI,CAAC,GAAG,MAAM;AAC7C,UAAM,MAAM,YAAY,CAAC;AACzB,QACE,OACA,OAAO,QAAQ,YACf,WAAW,OACX,aAAa,IAAI,KAAK,GACtB;AACA,aAAO,EAAE,GAAG,GAAG,OAAO,IAAI,MAAA;AAAA,IAC5B;AACA,WAAO;AAAA,EACT,CAAC;AAED,SAAO,QAAQ,QAAQ;AAAA,IACrB;AAAA,IACA,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO;AAAA,IACd,aAAa,OAAO;AAAA,IACpB,OAAO,OAAO;AAAA,IAKd,gBAAiB,OAAO,kBAAkB,CAAA;AAAA,IAC1C,OAAO,OAAO;AAAA,IACd,SAAS;AAAA,IACT;AAAA,EAAA,CACD;AACH;AA2BA,eAAsB,sBACpB,KACgE;AAChE,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAA;AAAA,EACnB,SAAS,OAAO;AAGd,UAAM,MAAM,IAAI;AAAA,MACd;AAAA,MACA,EAAE,QAAQ,IAAA;AAAA,IAAI;AAEd,QAA4B,QAAQ;AACtC,UAAM;AAAA,EACR;AACA,MAAI;AACF,WAAO,MAAM,0BAA0B,IAAI;AAAA,EAC7C,SAAS,OAAO;AAKd,UAAM,MAAM,IAAI;AAAA,MACd;AAAA,MACA,EAAE,QAAQ,IAAA;AAAA,IAAI;AAEd,QAA4B,QAAQ;AACtC,UAAM;AAAA,EACR;AACF;AAuBO,SAAS,gBACd,aACA,aAKyD;AACzD,QAAM,OAAO,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACnD,QAAM,SAAkE;AAAA,IACtE,GAAG;AAAA,EAAA;AAEL,aAAW,MAAM,aAAa;AAC5B,QAAI,KAAK,IAAI,GAAG,IAAI,GAAG;AAErB;AAAA,IACF;AACA,SAAK,IAAI,GAAG,IAAI;AAChB,WAAO,KAAK;AAAA,MACV,MAAM,GAAG;AAAA,MACT,aAAa,GAAG;AAAA,MAChB,aAAa,GAAG;AAAA;AAAA;AAAA,IAAA,CAGmC;AAAA,EACvD;AACA,SAAO;AACT;"}
|
|
1
|
+
{"version":3,"file":"chat-params.js","names":[],"sources":["../../../src/utilities/chat-params.ts"],"sourcesContent":["import { AGUIError } from '@ag-ui/core'\nimport type {\n Context as AGUIContext,\n Message as AGUIMessage,\n ResumeEntry as AGUIResumeEntry,\n Role as AGUIRole,\n} from '@ag-ui/core'\nimport type {\n AnyTool,\n JSONSchema,\n ModelMessage,\n RunAgentResumeItem,\n UIMessage,\n} from '../types'\n\nconst KNOWN_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n 'tool-call',\n 'tool-result',\n 'thinking',\n])\n\nfunction isValidParts(value: unknown): value is Array<{ type: string }> {\n if (!Array.isArray(value)) return false\n for (const p of value) {\n if (!p || typeof p !== 'object') return false\n const type = (p as { type?: unknown }).type\n if (typeof type !== 'string' || !KNOWN_PART_TYPES.has(type)) return false\n }\n return true\n}\n\n/**\n * Keyed by `AGUIRole` so a role added upstream fails to compile here until it\n * is handled, rather than silently falling through as an unknown role.\n */\nconst AGUI_ROLES: Record<AGUIRole, true> = {\n developer: true,\n system: true,\n assistant: true,\n user: true,\n tool: true,\n activity: true,\n reasoning: true,\n}\n\nfunction isAGUIRole(value: unknown): value is AGUIRole {\n return typeof value === 'string' && value in AGUI_ROLES\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Reject the request body, pointing at the migration guide. Mirrors the\n * message the previous `RunAgentInputSchema.safeParse` failure produced.\n */\nfunction invalidBody(reason: string): never {\n throw new AGUIError(\n `Request body is not a valid AG-UI RunAgentInput. ` +\n `If you're upgrading from a previous @tanstack/ai-client release, ` +\n `see docs/migration/ag-ui-compliance.md. ` +\n `Validation errors: ${reason}`,\n )\n}\n\nfunction requireString(value: unknown, at: string): string {\n if (typeof value !== 'string') invalidBody(`${at} must be a string`)\n return value\n}\n\nfunction requireArray(value: unknown, at: string): Array<unknown> {\n if (!Array.isArray(value)) invalidBody(`${at} must be an array`)\n return value\n}\n\n/**\n * Assert one AG-UI `Message`, discriminating on `role` exactly as the upstream\n * `MessageSchema` discriminated union does. The record view is retained on the\n * asserted type so callers can still inspect non-AG-UI extras like `parts`.\n */\nfunction assertAGUIMessage(\n value: Record<string, unknown>,\n at: string,\n): asserts value is Record<string, unknown> & AGUIMessage {\n requireString(value.id, `${at}.id`)\n\n const role = value.role\n if (!isAGUIRole(role)) {\n invalidBody(\n `${at}.role must be one of ${Object.keys(AGUI_ROLES).join(' | ')}`,\n )\n }\n\n switch (role) {\n case 'assistant':\n // Both optional: a tool-calling turn carries no text content.\n if (value.content !== undefined) {\n requireString(value.content, `${at}.content`)\n }\n if (value.toolCalls !== undefined) {\n requireArray(value.toolCalls, `${at}.toolCalls`)\n }\n break\n case 'user':\n if (typeof value.content !== 'string' && !Array.isArray(value.content)) {\n invalidBody(\n `${at}.content must be a string or an array of content parts`,\n )\n }\n break\n case 'tool':\n requireString(value.content, `${at}.content`)\n requireString(value.toolCallId, `${at}.toolCallId`)\n break\n case 'activity':\n requireString(value.activityType, `${at}.activityType`)\n if (!isRecord(value.content)) {\n invalidBody(`${at}.content must be an object`)\n }\n break\n case 'developer':\n case 'system':\n case 'reasoning':\n requireString(value.content, `${at}.content`)\n break\n }\n}\n\nfunction validateMessage(value: unknown, index: number): AGUIMessage {\n const at = `messages[${index}]`\n if (!isRecord(value)) invalidBody(`${at} must be an object`)\n assertAGUIMessage(value, at)\n\n // `parts` is TanStack's canonical extra, carried through so the UIMessage\n // path inside `chat()` can use it. Keep it only when it holds recognized\n // part types — the previous schema-based path dropped `parts` during parse\n // and re-attached it from the raw body behind this same check.\n if ('parts' in value && !isValidParts(value.parts)) {\n const withoutParts = { ...value }\n Reflect.deleteProperty(withoutParts, 'parts')\n return withoutParts\n }\n return value\n}\n\nfunction validateTool(\n value: unknown,\n index: number,\n): { name: string; description: string; parameters: JSONSchema } {\n const at = `tools[${index}]`\n if (!isRecord(value)) invalidBody(`${at} must be an object`)\n return {\n name: requireString(value.name, `${at}.name`),\n description: requireString(value.description, `${at}.description`),\n // Upstream `ToolSchema` types this as optional `any`; it reaches the\n // provider as a raw JSON Schema either way.\n parameters: value.parameters as JSONSchema,\n }\n}\n\nfunction validateContext(value: unknown, index: number): AGUIContext {\n const at = `context[${index}]`\n if (!isRecord(value)) invalidBody(`${at} must be an object`)\n return {\n description: requireString(value.description, `${at}.description`),\n value: requireString(value.value, `${at}.value`),\n }\n}\n\nfunction validateResumeEntry(value: unknown, index: number): AGUIResumeEntry {\n const at = `resume[${index}]`\n if (!isRecord(value)) invalidBody(`${at} must be an object`)\n const status = value.status\n if (status !== 'resolved' && status !== 'cancelled') {\n invalidBody(`${at}.status must be \"resolved\" or \"cancelled\"`)\n }\n const entry: AGUIResumeEntry = {\n interruptId: requireString(value.interruptId, `${at}.interruptId`),\n status,\n }\n // Omit the key entirely when absent, matching the optional-field shape the\n // schema produced.\n if (value.payload !== undefined) entry.payload = value.payload\n return entry\n}\n\n/**\n * Parse and validate an HTTP request body as an AG-UI `RunAgentInput`.\n *\n * Returns a spread-friendly object whose `messages` field is suitable for\n * passing directly to `chat({ messages })`. The existing\n * `convertMessagesToModelMessages` handles AG-UI fan-out dedup and\n * reasoning/activity/developer-role normalization internally.\n *\n * Validated structurally against the AG-UI `RunAgentInput` contract without a\n * schema library, so this package pulls in no validation runtime of its own.\n *\n * @throws An error with a migration-pointing message when the body does\n * not conform to AG-UI `RunAgentInput`. Surface this as a\n * 400 Bad Request to the client.\n */\nexport async function chatParamsFromRequestBody(body: unknown): Promise<{\n messages: Array<UIMessage | ModelMessage>\n threadId: string\n runId: string\n parentRunId?: string\n tools: Array<{ name: string; description: string; parameters: JSONSchema }>\n forwardedProps: Record<string, unknown>\n state: unknown\n resume?: Array<RunAgentResumeItem>\n /**\n * @deprecated Use `aguiContext` instead. This alias will be removed in a\n * future release.\n */\n context: Array<AGUIContext>\n aguiContext: Array<AGUIContext>\n}> {\n if (!isRecord(body)) invalidBody('body must be a JSON object')\n\n const threadId = requireString(body.threadId, 'threadId')\n const runId = requireString(body.runId, 'runId')\n const parentRunId =\n body.parentRunId === undefined\n ? undefined\n : requireString(body.parentRunId, 'parentRunId')\n\n const messages = requireArray(body.messages, 'messages').map(validateMessage)\n const tools = requireArray(body.tools, 'tools').map(validateTool)\n const aguiContext = requireArray(body.context, 'context').map(validateContext)\n const resume =\n body.resume === undefined\n ? undefined\n : requireArray(body.resume, 'resume').map(validateResumeEntry)\n\n if (body.forwardedProps !== undefined && !isRecord(body.forwardedProps)) {\n invalidBody('forwardedProps must be an object')\n }\n\n return {\n // Unknown top-level fields (e.g. a legacy `cursor`) are dropped by\n // construction: only the fields below are copied onto the result.\n messages: messages as Array<UIMessage | ModelMessage>,\n threadId,\n runId,\n parentRunId,\n tools,\n forwardedProps: (body.forwardedProps ?? {}) as Record<string, unknown>,\n state: body.state,\n resume: resume as Array<RunAgentResumeItem> | undefined,\n context: aguiContext,\n aguiContext,\n }\n}\n\n/**\n * Read an HTTP `Request`, parse its JSON body, and validate it as an\n * AG-UI `RunAgentInput` — collapsing the standard `req.json()` +\n * `chatParamsFromRequestBody(...)` pair into a single call.\n *\n * On a malformed body or invalid AG-UI shape, this **throws a\n * `Response`** with status 400 and a migration-pointing message in the\n * body. Frameworks that natively handle thrown `Response` objects\n * (TanStack Start, SolidStart, Remix, React Router 7) will return the\n * 400 to the client automatically, so the handler reduces to:\n *\n * ```ts\n * export async function POST(req: Request) {\n * const params = await chatParamsFromRequest(req)\n * // ...use params\n * }\n * ```\n *\n * In frameworks that do not auto-handle thrown `Response` objects\n * (Next.js Route Handlers, SvelteKit, Hono, raw Node), wrap the call\n * with try/catch and return the caught Response yourself, or use\n * `chatParamsFromRequestBody` directly with your own JSON-parsing.\n *\n * @throws {Response} 400 on malformed JSON or invalid AG-UI shape.\n */\nexport async function chatParamsFromRequest(\n req: Request,\n): Promise<Awaited<ReturnType<typeof chatParamsFromRequestBody>>> {\n let body: unknown\n try {\n body = await req.json()\n } catch (cause) {\n // Preserve the underlying error on the thrown Response for\n // server-side observability without leaking it to the client.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n try {\n return await chatParamsFromRequestBody(body)\n } catch (cause) {\n // Generic public message — avoid echoing Zod paths (which can contain\n // user payload fragments) or internal validator strings to the client.\n // The original AGUIError is attached as `cause` so server logs can\n // surface it without exposing it to remote callers.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n}\n\n/**\n * Client-declared tool stub (no execute). `name` is `string`, so arrays that\n * include these stubs intentionally widen `TypedStreamChunk` tool-name\n * discrimination — pass server tools alone when you need a closed name union.\n */\nexport type ClientToolDeclaration = {\n name: string\n description: string\n inputSchema: JSONSchema\n}\n\nexport type MergedAgentTools<TServerTools extends ReadonlyArray<AnyTool>> =\n ReadonlyArray<TServerTools[number] | ClientToolDeclaration>\n\n/**\n * Merge a server-side tool array with the AG-UI client-declared tools\n * received in the request body.\n *\n * Rules:\n * - Server tools win on name collision. The client's declaration is\n * ignored if the server already has a tool with that name. The client's\n * UI-side handler still fires when the streamed tool-result event comes\n * through (see `chat-client.ts` `onToolCall`), giving the\n * \"after server execution the client also handles\" semantic for free.\n * - Client-only tools (name not in `serverTools`) become no-execute\n * entries: the runtime's existing `ClientToolRequest` path handles\n * them — server emits a tool-call request, client executes via its\n * registered handler, client posts back the result.\n *\n * Typing:\n * - Empty `clientTools` preserves the server tuple (closed name union).\n * - Non-empty `clientTools` returns a widened array that honestly includes\n * client stubs, so `TypedStreamChunk` does not claim a closed server-only\n * name union.\n *\n * @param serverTools - The server's tool array (e.g. from\n * `[myToolDef.server(...)]`). Pass directly to `chat({ tools })`.\n * @param clientTools - The `tools` array received from\n * `chatParamsFromRequest(...)` / `chatParamsFromRequestBody(...)`.\n * @returns A merged array suitable for `chat({ tools })`.\n */\nexport function mergeAgentTools<\n const TServerTools extends ReadonlyArray<AnyTool>,\n>(serverTools: TServerTools, clientTools: readonly []): TServerTools\nexport function mergeAgentTools<\n const TServerTools extends ReadonlyArray<AnyTool>,\n>(\n serverTools: TServerTools,\n clientTools: ReadonlyArray<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n): MergedAgentTools<TServerTools>\nexport function mergeAgentTools<\n const TServerTools extends ReadonlyArray<AnyTool>,\n>(\n serverTools: TServerTools,\n clientTools: ReadonlyArray<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n): TServerTools | MergedAgentTools<TServerTools> {\n if (clientTools.length === 0) {\n return serverTools\n }\n const seen = new Set(serverTools.map((t) => t.name))\n const merged: Array<TServerTools[number] | ClientToolDeclaration> = [\n ...serverTools,\n ]\n for (const ct of clientTools) {\n if (seen.has(ct.name)) {\n // Server wins on name collision.\n continue\n }\n seen.add(ct.name)\n merged.push({\n name: ct.name,\n description: ct.description,\n inputSchema: ct.parameters,\n // No `execute` — runtime treats this as a client-side tool and\n // emits ClientToolRequest events.\n })\n }\n return merged\n}\n"],"mappings":"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"}
|
|
@@ -1,22 +1,32 @@
|
|
|
1
|
+
//#region src/utilities/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Best-effort extraction of a human-readable message from an unknown thrown
|
|
4
|
+
* value, returning `undefined` when none can be found.
|
|
5
|
+
*
|
|
6
|
+
* Used by `otelMiddleware` so error reporting stays identical across chat and
|
|
7
|
+
* media spans.
|
|
8
|
+
*/
|
|
1
9
|
function errorMessage(err) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return void 0;
|
|
10
|
+
if (err instanceof Error) return err.message;
|
|
11
|
+
if (typeof err === "string") return err;
|
|
12
|
+
if (err && typeof err === "object" && "message" in err) {
|
|
13
|
+
const m = err.message;
|
|
14
|
+
if (typeof m === "string") return m;
|
|
15
|
+
}
|
|
9
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Best-effort extraction of an error's type name (used for the `error.type`
|
|
19
|
+
* metric attribute), falling back to `'Error'` when no name is available.
|
|
20
|
+
*/
|
|
10
21
|
function errorTypeName(err) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
if (err instanceof Error) return err.name || "Error";
|
|
23
|
+
if (err && typeof err === "object" && "name" in err) {
|
|
24
|
+
const n = err.name;
|
|
25
|
+
if (typeof n === "string") return n;
|
|
26
|
+
}
|
|
27
|
+
return "Error";
|
|
17
28
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//# sourceMappingURL=errors.js.map
|
|
29
|
+
//#endregion
|
|
30
|
+
export { errorMessage, errorTypeName };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sources":["../../../src/utilities/errors.ts"],"sourcesContent":["/**\n * Best-effort extraction of a human-readable message from an unknown thrown\n * value, returning `undefined` when none can be found.\n *\n * Used by `otelMiddleware` so error reporting stays identical across chat and\n * media spans.\n */\nexport function errorMessage(err: unknown): string | undefined {\n if (err instanceof Error) return err.message\n if (typeof err === 'string') return err\n if (err && typeof err === 'object' && 'message' in err) {\n const m = (err as { message?: unknown }).message\n if (typeof m === 'string') return m\n }\n return undefined\n}\n\n/**\n * Best-effort extraction of an error's type name (used for the `error.type`\n * metric attribute), falling back to `'Error'` when no name is available.\n */\nexport function errorTypeName(err: unknown): string {\n if (err instanceof Error) return err.name || 'Error'\n if (err && typeof err === 'object' && 'name' in err) {\n const n = (err as { name?: unknown }).name\n if (typeof n === 'string') return n\n }\n return 'Error'\n}\n"],"
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../../../src/utilities/errors.ts"],"sourcesContent":["/**\n * Best-effort extraction of a human-readable message from an unknown thrown\n * value, returning `undefined` when none can be found.\n *\n * Used by `otelMiddleware` so error reporting stays identical across chat and\n * media spans.\n */\nexport function errorMessage(err: unknown): string | undefined {\n if (err instanceof Error) return err.message\n if (typeof err === 'string') return err\n if (err && typeof err === 'object' && 'message' in err) {\n const m = (err as { message?: unknown }).message\n if (typeof m === 'string') return m\n }\n return undefined\n}\n\n/**\n * Best-effort extraction of an error's type name (used for the `error.type`\n * metric attribute), falling back to `'Error'` when no name is available.\n */\nexport function errorTypeName(err: unknown): string {\n if (err instanceof Error) return err.name || 'Error'\n if (err && typeof err === 'object' && 'name' in err) {\n const n = (err as { name?: unknown }).name\n if (typeof n === 'string') return n\n }\n return 'Error'\n}\n"],"mappings":";;;;;;;;AAOA,SAAgB,aAAa,KAAkC;CAC7D,IAAI,eAAe,OAAO,OAAO,IAAI;CACrC,IAAI,OAAO,QAAQ,UAAU,OAAO;CACpC,IAAI,OAAO,OAAO,QAAQ,YAAY,aAAa,KAAK;EACtD,MAAM,IAAK,IAA8B;EACzC,IAAI,OAAO,MAAM,UAAU,OAAO;CACpC;AAEF;;;;;AAMA,SAAgB,cAAc,KAAsB;CAClD,IAAI,eAAe,OAAO,OAAO,IAAI,QAAQ;CAC7C,IAAI,OAAO,OAAO,QAAQ,YAAY,UAAU,KAAK;EACnD,MAAM,IAAK,IAA2B;EACtC,IAAI,OAAO,MAAM,UAAU,OAAO;CACpC;CACA,OAAO;AACT"}
|
|
@@ -1,43 +1,48 @@
|
|
|
1
|
+
//#region src/utilities/media-prompt.ts
|
|
2
|
+
/**
|
|
3
|
+
* Decompose a {@link MediaPrompt} into flattened text and per-modality part
|
|
4
|
+
* buckets, preserving prompt order everywhere. This is the single downrev
|
|
5
|
+
* point from the canonical interleaved prompt shape to the named-field
|
|
6
|
+
* request shapes most providers expose.
|
|
7
|
+
*/
|
|
1
8
|
function resolveMediaPrompt(prompt) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
audios
|
|
38
|
-
};
|
|
9
|
+
if (typeof prompt === "string") return {
|
|
10
|
+
text: prompt,
|
|
11
|
+
parts: [{
|
|
12
|
+
type: "text",
|
|
13
|
+
content: prompt
|
|
14
|
+
}],
|
|
15
|
+
images: [],
|
|
16
|
+
videos: [],
|
|
17
|
+
audios: []
|
|
18
|
+
};
|
|
19
|
+
const images = [];
|
|
20
|
+
const videos = [];
|
|
21
|
+
const audios = [];
|
|
22
|
+
const textSegments = [];
|
|
23
|
+
for (const part of prompt) switch (part.type) {
|
|
24
|
+
case "text":
|
|
25
|
+
if (part.content) textSegments.push(part.content);
|
|
26
|
+
break;
|
|
27
|
+
case "image":
|
|
28
|
+
images.push(part);
|
|
29
|
+
break;
|
|
30
|
+
case "video":
|
|
31
|
+
videos.push(part);
|
|
32
|
+
break;
|
|
33
|
+
case "audio":
|
|
34
|
+
audios.push(part);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
text: textSegments.join("\n\n"),
|
|
39
|
+
parts: prompt,
|
|
40
|
+
images,
|
|
41
|
+
videos,
|
|
42
|
+
audios
|
|
43
|
+
};
|
|
39
44
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
//# sourceMappingURL=media-prompt.js.map
|
|
45
|
+
//#endregion
|
|
46
|
+
export { resolveMediaPrompt };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=media-prompt.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-prompt.js","sources":["../../../src/utilities/media-prompt.ts"],"sourcesContent":["import type {\n AudioPart,\n ImagePart,\n MediaInputMetadata,\n MediaPrompt,\n MediaPromptPart,\n TextPart,\n VideoPart,\n} from '../types'\n\n/**\n * A {@link MediaPrompt} decomposed into the views adapters consume.\n *\n * Adapters with native multimodal prompts (Gemini `contents`, OpenRouter\n * chat content parts) consume `parts` to preserve interleaving; named-field\n * providers (fal, OpenAI) consume `text` plus the typed media buckets.\n *\n * Prompt text is **never rewritten**: text parts are concatenated verbatim.\n * Providers that support referencing inputs from the prompt (e.g. fal's\n * `@Image1`, OpenAI's \"image 1\" prose) expect the user to write that syntax\n * themselves — the SDK does not inject or substitute markers.\n */\nexport interface ResolvedMediaPrompt {\n /**\n * Text parts concatenated verbatim (paragraph-separated). Empty string\n * for media-only prompts.\n */\n text: string\n /** The prompt as ordered parts; a string prompt becomes one text part. */\n parts: Array<MediaPromptPart>\n /** Image parts in prompt order. */\n images: Array<ImagePart<MediaInputMetadata>>\n /** Video parts in prompt order. */\n videos: Array<VideoPart<MediaInputMetadata>>\n /** Audio parts in prompt order. */\n audios: Array<AudioPart<MediaInputMetadata>>\n}\n\n/**\n * Decompose a {@link MediaPrompt} into flattened text and per-modality part\n * buckets, preserving prompt order everywhere. This is the single downrev\n * point from the canonical interleaved prompt shape to the named-field\n * request shapes most providers expose.\n */\nexport function resolveMediaPrompt(prompt: MediaPrompt): ResolvedMediaPrompt {\n if (typeof prompt === 'string') {\n const textPart: TextPart = { type: 'text', content: prompt }\n return {\n text: prompt,\n parts: [textPart],\n images: [],\n videos: [],\n audios: [],\n }\n }\n\n const images: Array<ImagePart<MediaInputMetadata>> = []\n const videos: Array<VideoPart<MediaInputMetadata>> = []\n const audios: Array<AudioPart<MediaInputMetadata>> = []\n const textSegments: Array<string> = []\n\n for (const part of prompt) {\n switch (part.type) {\n case 'text':\n if (part.content) textSegments.push(part.content)\n break\n case 'image':\n images.push(part)\n break\n case 'video':\n videos.push(part)\n break\n case 'audio':\n audios.push(part)\n break\n }\n }\n\n return {\n text: textSegments.join('\\n\\n'),\n parts: prompt,\n images,\n videos,\n audios,\n }\n}\n"],"
|
|
1
|
+
{"version":3,"file":"media-prompt.js","names":[],"sources":["../../../src/utilities/media-prompt.ts"],"sourcesContent":["import type {\n AudioPart,\n ImagePart,\n MediaInputMetadata,\n MediaPrompt,\n MediaPromptPart,\n TextPart,\n VideoPart,\n} from '../types'\n\n/**\n * A {@link MediaPrompt} decomposed into the views adapters consume.\n *\n * Adapters with native multimodal prompts (Gemini `contents`, OpenRouter\n * chat content parts) consume `parts` to preserve interleaving; named-field\n * providers (fal, OpenAI) consume `text` plus the typed media buckets.\n *\n * Prompt text is **never rewritten**: text parts are concatenated verbatim.\n * Providers that support referencing inputs from the prompt (e.g. fal's\n * `@Image1`, OpenAI's \"image 1\" prose) expect the user to write that syntax\n * themselves — the SDK does not inject or substitute markers.\n */\nexport interface ResolvedMediaPrompt {\n /**\n * Text parts concatenated verbatim (paragraph-separated). Empty string\n * for media-only prompts.\n */\n text: string\n /** The prompt as ordered parts; a string prompt becomes one text part. */\n parts: Array<MediaPromptPart>\n /** Image parts in prompt order. */\n images: Array<ImagePart<MediaInputMetadata>>\n /** Video parts in prompt order. */\n videos: Array<VideoPart<MediaInputMetadata>>\n /** Audio parts in prompt order. */\n audios: Array<AudioPart<MediaInputMetadata>>\n}\n\n/**\n * Decompose a {@link MediaPrompt} into flattened text and per-modality part\n * buckets, preserving prompt order everywhere. This is the single downrev\n * point from the canonical interleaved prompt shape to the named-field\n * request shapes most providers expose.\n */\nexport function resolveMediaPrompt(prompt: MediaPrompt): ResolvedMediaPrompt {\n if (typeof prompt === 'string') {\n const textPart: TextPart = { type: 'text', content: prompt }\n return {\n text: prompt,\n parts: [textPart],\n images: [],\n videos: [],\n audios: [],\n }\n }\n\n const images: Array<ImagePart<MediaInputMetadata>> = []\n const videos: Array<VideoPart<MediaInputMetadata>> = []\n const audios: Array<AudioPart<MediaInputMetadata>> = []\n const textSegments: Array<string> = []\n\n for (const part of prompt) {\n switch (part.type) {\n case 'text':\n if (part.content) textSegments.push(part.content)\n break\n case 'image':\n images.push(part)\n break\n case 'video':\n videos.push(part)\n break\n case 'audio':\n audios.push(part)\n break\n }\n }\n\n return {\n text: textSegments.join('\\n\\n'),\n parts: prompt,\n images,\n videos,\n audios,\n }\n}\n"],"mappings":";;;;;;;AA4CA,SAAgB,mBAAmB,QAA0C;CAC3E,IAAI,OAAO,WAAW,UAEpB,OAAO;EACL,MAAM;EACN,OAAO,CAAC;GAHmB,MAAM;GAAQ,SAAS;EAG1C,CAAQ;EAChB,QAAQ,CAAC;EACT,QAAQ,CAAC;EACT,QAAQ,CAAC;CACX;CAGF,MAAM,SAA+C,CAAC;CACtD,MAAM,SAA+C,CAAC;CACtD,MAAM,SAA+C,CAAC;CACtD,MAAM,eAA8B,CAAC;CAErC,KAAK,MAAM,QAAQ,QACjB,QAAQ,KAAK,MAAb;EACE,KAAK;GACH,IAAI,KAAK,SAAS,aAAa,KAAK,KAAK,OAAO;GAChD;EACF,KAAK;GACH,OAAO,KAAK,IAAI;GAChB;EACF,KAAK;GACH,OAAO,KAAK,IAAI;GAChB;EACF,KAAK;GACH,OAAO,KAAK,IAAI;GAChB;CACJ;CAGF,OAAO;EACL,MAAM,aAAa,KAAK,MAAM;EAC9B,OAAO;EACP;EACA;EACA;CACF;AACF"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
//#region src/utilities/numbers.ts
|
|
2
|
+
/**
|
|
3
|
+
* Return the first candidate that is a finite `number`, or `undefined`.
|
|
4
|
+
*
|
|
5
|
+
* Handy for picking a value from among several possible spellings/sources where
|
|
6
|
+
* only some are populated — e.g. the provider-native sampling option names read
|
|
7
|
+
* by the OTel middleware, or the optional numeric fields on `TokenUsage`.
|
|
8
|
+
*/
|
|
1
9
|
function firstNumber(...candidates) {
|
|
2
|
-
|
|
3
|
-
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
4
|
-
return candidate;
|
|
5
|
-
}
|
|
6
|
-
}
|
|
7
|
-
return void 0;
|
|
10
|
+
for (const candidate of candidates) if (typeof candidate === "number" && Number.isFinite(candidate)) return candidate;
|
|
8
11
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//# sourceMappingURL=numbers.js.map
|
|
12
|
+
//#endregion
|
|
13
|
+
export { firstNumber };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=numbers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"numbers.js","sources":["../../../src/utilities/numbers.ts"],"sourcesContent":["/**\n * Return the first candidate that is a finite `number`, or `undefined`.\n *\n * Handy for picking a value from among several possible spellings/sources where\n * only some are populated — e.g. the provider-native sampling option names read\n * by the OTel middleware, or the optional numeric fields on `TokenUsage`.\n */\nexport function firstNumber(...candidates: Array<unknown>): number | undefined {\n for (const candidate of candidates) {\n if (typeof candidate === 'number' && Number.isFinite(candidate)) {\n return candidate\n }\n }\n return undefined\n}\n"],"
|
|
1
|
+
{"version":3,"file":"numbers.js","names":[],"sources":["../../../src/utilities/numbers.ts"],"sourcesContent":["/**\n * Return the first candidate that is a finite `number`, or `undefined`.\n *\n * Handy for picking a value from among several possible spellings/sources where\n * only some are populated — e.g. the provider-native sampling option names read\n * by the OTel middleware, or the optional numeric fields on `TokenUsage`.\n */\nexport function firstNumber(...candidates: Array<unknown>): number | undefined {\n for (const candidate of candidates) {\n if (typeof candidate === 'number' && Number.isFinite(candidate)) {\n return candidate\n }\n }\n return undefined\n}\n"],"mappings":";;;;;;;;AAOA,SAAgB,YAAY,GAAG,YAAgD;CAC7E,KAAK,MAAM,aAAa,YACtB,IAAI,OAAO,cAAc,YAAY,OAAO,SAAS,SAAS,GAC5D,OAAO;AAIb"}
|
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
//#region src/utilities/provider-executed.ts
|
|
2
|
+
/**
|
|
3
|
+
* Narrow a tool call's opaque `metadata` to the provider-executed convention.
|
|
4
|
+
* Returns the typed metadata when the call is provider-executed, else `null`.
|
|
5
|
+
*
|
|
6
|
+
* @see ProviderExecutedToolMetadata
|
|
7
|
+
*/
|
|
1
8
|
function getProviderExecutedMetadata(toolCall) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
return null;
|
|
9
|
+
const metadata = toolCall?.metadata;
|
|
10
|
+
if (typeof metadata === "object" && metadata !== null && metadata.providerExecuted === true) return metadata;
|
|
11
|
+
return null;
|
|
7
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* True when a tool call was executed by the provider (e.g. Anthropic
|
|
15
|
+
* `web_search` / `web_fetch` server tools) rather than the agent loop. Such
|
|
16
|
+
* calls must not be routed to client-side execution and are already "complete".
|
|
17
|
+
*/
|
|
8
18
|
function isProviderExecutedToolCall(toolCall) {
|
|
9
|
-
|
|
19
|
+
return getProviderExecutedMetadata(toolCall) !== null;
|
|
10
20
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
//# sourceMappingURL=provider-executed.js.map
|
|
21
|
+
//#endregion
|
|
22
|
+
export { getProviderExecutedMetadata, isProviderExecutedToolCall };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=provider-executed.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-executed.js","sources":["../../../src/utilities/provider-executed.ts"],"sourcesContent":["import type { ProviderExecutedToolMetadata } from '../types'\n\n/**\n * Narrow a tool call's opaque `metadata` to the provider-executed convention.\n * Returns the typed metadata when the call is provider-executed, else `null`.\n *\n * @see ProviderExecutedToolMetadata\n */\nexport function getProviderExecutedMetadata(\n toolCall: { metadata?: unknown } | null | undefined,\n): ProviderExecutedToolMetadata | null {\n const metadata = toolCall?.metadata\n if (\n typeof metadata === 'object' &&\n metadata !== null &&\n (metadata as ProviderExecutedToolMetadata).providerExecuted === true\n ) {\n return metadata as ProviderExecutedToolMetadata\n }\n return null\n}\n\n/**\n * True when a tool call was executed by the provider (e.g. Anthropic\n * `web_search` / `web_fetch` server tools) rather than the agent loop. Such\n * calls must not be routed to client-side execution and are already \"complete\".\n */\nexport function isProviderExecutedToolCall(\n toolCall: { metadata?: unknown } | null | undefined,\n): boolean {\n return getProviderExecutedMetadata(toolCall) !== null\n}\n"],"
|
|
1
|
+
{"version":3,"file":"provider-executed.js","names":[],"sources":["../../../src/utilities/provider-executed.ts"],"sourcesContent":["import type { ProviderExecutedToolMetadata } from '../types'\n\n/**\n * Narrow a tool call's opaque `metadata` to the provider-executed convention.\n * Returns the typed metadata when the call is provider-executed, else `null`.\n *\n * @see ProviderExecutedToolMetadata\n */\nexport function getProviderExecutedMetadata(\n toolCall: { metadata?: unknown } | null | undefined,\n): ProviderExecutedToolMetadata | null {\n const metadata = toolCall?.metadata\n if (\n typeof metadata === 'object' &&\n metadata !== null &&\n (metadata as ProviderExecutedToolMetadata).providerExecuted === true\n ) {\n return metadata as ProviderExecutedToolMetadata\n }\n return null\n}\n\n/**\n * True when a tool call was executed by the provider (e.g. Anthropic\n * `web_search` / `web_fetch` server tools) rather than the agent loop. Such\n * calls must not be routed to client-side execution and are already \"complete\".\n */\nexport function isProviderExecutedToolCall(\n toolCall: { metadata?: unknown } | null | undefined,\n): boolean {\n return getProviderExecutedMetadata(toolCall) !== null\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,4BACd,UACqC;CACrC,MAAM,WAAW,UAAU;CAC3B,IACE,OAAO,aAAa,YACpB,aAAa,QACZ,SAA0C,qBAAqB,MAEhE,OAAO;CAET,OAAO;AACT;;;;;;AAOA,SAAgB,2BACd,UACS;CACT,OAAO,4BAA4B,QAAQ,MAAM;AACnD"}
|
|
@@ -1,20 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
//#region src/utilities/sampling-keys.ts
|
|
2
|
+
/**
|
|
3
|
+
* Single source of truth for the provider-native key spellings that cap output
|
|
4
|
+
* tokens. Sampling options live in opaque, provider-native `modelOptions`, and
|
|
5
|
+
* every provider spells the token cap differently. Two call sites must agree on
|
|
6
|
+
* this set or they silently drift:
|
|
7
|
+
*
|
|
8
|
+
* - `activities/summarize/chat-stream-summarize.ts` — detects a caller-supplied
|
|
9
|
+
* token limit so the summarize default never overrides it.
|
|
10
|
+
* - `middlewares/otel.ts` — picks the first numeric spelling to populate the
|
|
11
|
+
* `gen_ai.request.max_tokens` attribute across providers.
|
|
12
|
+
*
|
|
13
|
+
* Keep this list in lockstep with `MAX_TOKENS_KEY_BY_ADAPTER` (the adapter →
|
|
14
|
+
* native-key map) in the summarize wrapper.
|
|
15
|
+
*/
|
|
16
|
+
var MAX_TOKENS_KEYS = [
|
|
17
|
+
"max_output_tokens",
|
|
18
|
+
"max_tokens",
|
|
19
|
+
"max_completion_tokens",
|
|
20
|
+
"maxOutputTokens",
|
|
21
|
+
"maxCompletionTokens",
|
|
22
|
+
"maxTokens"
|
|
14
23
|
];
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
//#
|
|
24
|
+
/**
|
|
25
|
+
* Ollama nests sampling under `options`; its token cap is `options.num_predict`
|
|
26
|
+
* rather than a flat key.
|
|
27
|
+
*/
|
|
28
|
+
var NESTED_MAX_TOKENS_KEY = "num_predict";
|
|
29
|
+
//#endregion
|
|
30
|
+
export { MAX_TOKENS_KEYS, NESTED_MAX_TOKENS_KEY };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=sampling-keys.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sampling-keys.js","sources":["../../../src/utilities/sampling-keys.ts"],"sourcesContent":["/**\n * Single source of truth for the provider-native key spellings that cap output\n * tokens. Sampling options live in opaque, provider-native `modelOptions`, and\n * every provider spells the token cap differently. Two call sites must agree on\n * this set or they silently drift:\n *\n * - `activities/summarize/chat-stream-summarize.ts` — detects a caller-supplied\n * token limit so the summarize default never overrides it.\n * - `middlewares/otel.ts` — picks the first numeric spelling to populate the\n * `gen_ai.request.max_tokens` attribute across providers.\n *\n * Keep this list in lockstep with `MAX_TOKENS_KEY_BY_ADAPTER` (the adapter →\n * native-key map) in the summarize wrapper.\n */\nexport const MAX_TOKENS_KEYS = [\n 'max_output_tokens', // OpenAI (Responses)\n 'max_tokens', // Anthropic / Grok\n 'max_completion_tokens', // Groq\n 'maxOutputTokens', // Gemini\n 'maxCompletionTokens', // OpenRouter\n 'maxTokens', // generic / migration leftover (no adapter reads it)\n] as const\n\n/**\n * Ollama nests sampling under `options`; its token cap is `options.num_predict`\n * rather than a flat key.\n */\nexport const NESTED_MAX_TOKENS_KEY = 'num_predict' as const\n"],"
|
|
1
|
+
{"version":3,"file":"sampling-keys.js","names":[],"sources":["../../../src/utilities/sampling-keys.ts"],"sourcesContent":["/**\n * Single source of truth for the provider-native key spellings that cap output\n * tokens. Sampling options live in opaque, provider-native `modelOptions`, and\n * every provider spells the token cap differently. Two call sites must agree on\n * this set or they silently drift:\n *\n * - `activities/summarize/chat-stream-summarize.ts` — detects a caller-supplied\n * token limit so the summarize default never overrides it.\n * - `middlewares/otel.ts` — picks the first numeric spelling to populate the\n * `gen_ai.request.max_tokens` attribute across providers.\n *\n * Keep this list in lockstep with `MAX_TOKENS_KEY_BY_ADAPTER` (the adapter →\n * native-key map) in the summarize wrapper.\n */\nexport const MAX_TOKENS_KEYS = [\n 'max_output_tokens', // OpenAI (Responses)\n 'max_tokens', // Anthropic / Grok\n 'max_completion_tokens', // Groq\n 'maxOutputTokens', // Gemini\n 'maxCompletionTokens', // OpenRouter\n 'maxTokens', // generic / migration leftover (no adapter reads it)\n] as const\n\n/**\n * Ollama nests sampling under `options`; its token cap is `options.num_predict`\n * rather than a flat key.\n */\nexport const NESTED_MAX_TOKENS_KEY = 'num_predict' as const\n"],"mappings":";;;;;;;;;;;;;;;AAcA,IAAa,kBAAkB;CAC7B;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AAMA,IAAa,wBAAwB"}
|
|
@@ -1,37 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
//#region src/utilities/tool-result.ts
|
|
2
|
+
var CONTENT_PART_TYPES = /* @__PURE__ */ new Set([
|
|
3
|
+
"text",
|
|
4
|
+
"image",
|
|
5
|
+
"audio",
|
|
6
|
+
"video",
|
|
7
|
+
"document"
|
|
7
8
|
]);
|
|
9
|
+
/**
|
|
10
|
+
* Structural check for a single `ContentPart`. A text part must carry a string
|
|
11
|
+
* `content`; every other modality must carry a `source` with `type` of
|
|
12
|
+
* `'url' | 'data'` and a string `value`.
|
|
13
|
+
*/
|
|
8
14
|
function isContentPart(value) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const src = source;
|
|
20
|
-
if (typeof src.value !== "string") return false;
|
|
21
|
-
if (src.type === "data") return typeof src.mimeType === "string";
|
|
22
|
-
return src.type === "url";
|
|
15
|
+
if (typeof value !== "object" || value === null) return false;
|
|
16
|
+
const part = value;
|
|
17
|
+
if (typeof part.type !== "string" || !CONTENT_PART_TYPES.has(part.type)) return false;
|
|
18
|
+
if (part.type === "text") return typeof part.content === "string";
|
|
19
|
+
const source = part.source;
|
|
20
|
+
if (typeof source !== "object" || source === null) return false;
|
|
21
|
+
const src = source;
|
|
22
|
+
if (typeof src.value !== "string") return false;
|
|
23
|
+
if (src.type === "data") return typeof src.mimeType === "string";
|
|
24
|
+
return src.type === "url";
|
|
23
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* True iff `value` is a NON-EMPTY array whose every element is a valid
|
|
28
|
+
* `ContentPart`. Empty arrays and mixed arrays return false so they continue
|
|
29
|
+
* to be treated as ordinary (stringified) data — this keeps the auto-detection
|
|
30
|
+
* footgun narrow.
|
|
31
|
+
*/
|
|
24
32
|
function isContentPartArray(value) {
|
|
25
|
-
|
|
33
|
+
return Array.isArray(value) && value.length > 0 && value.every(isContentPart);
|
|
26
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Normalize a tool's return value for transport:
|
|
37
|
+
* - string → unchanged
|
|
38
|
+
* - ContentPart array → unchanged (multimodal, passed through to the adapter)
|
|
39
|
+
* - anything else → `JSON.stringify`
|
|
40
|
+
*/
|
|
27
41
|
function normalizeToolResult(result) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
if (typeof result === "string") return result;
|
|
43
|
+
if (isContentPartArray(result)) return result;
|
|
44
|
+
return JSON.stringify(result);
|
|
31
45
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
};
|
|
37
|
-
//# sourceMappingURL=tool-result.js.map
|
|
46
|
+
//#endregion
|
|
47
|
+
export { isContentPart, isContentPartArray, normalizeToolResult };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=tool-result.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-result.js","sources":["../../../src/utilities/tool-result.ts"],"sourcesContent":["import type { ContentPart } from '../types'\n\nconst CONTENT_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n])\n\n/**\n * Structural check for a single `ContentPart`. A text part must carry a string\n * `content`; every other modality must carry a `source` with `type` of\n * `'url' | 'data'` and a string `value`.\n */\nexport function isContentPart(value: unknown): value is ContentPart {\n if (typeof value !== 'object' || value === null) return false\n const part = value as Record<string, unknown>\n if (typeof part.type !== 'string' || !CONTENT_PART_TYPES.has(part.type)) {\n return false\n }\n if (part.type === 'text') {\n return typeof part.content === 'string'\n }\n const source = part.source\n if (typeof source !== 'object' || source === null) return false\n const src = source as Record<string, unknown>\n if (typeof src.value !== 'string') return false\n // `data` sources require a mimeType (matches ContentPartDataSource); `url`\n // sources don't. Requiring it here keeps the runtime guard consistent with\n // the type and avoids emitting `data:undefined;base64,...` downstream.\n if (src.type === 'data') return typeof src.mimeType === 'string'\n return src.type === 'url'\n}\n\n/**\n * True iff `value` is a NON-EMPTY array whose every element is a valid\n * `ContentPart`. Empty arrays and mixed arrays return false so they continue\n * to be treated as ordinary (stringified) data — this keeps the auto-detection\n * footgun narrow.\n */\nexport function isContentPartArray(\n value: unknown,\n): value is Array<ContentPart> {\n return Array.isArray(value) && value.length > 0 && value.every(isContentPart)\n}\n\n/**\n * Normalize a tool's return value for transport:\n * - string → unchanged\n * - ContentPart array → unchanged (multimodal, passed through to the adapter)\n * - anything else → `JSON.stringify`\n */\nexport function normalizeToolResult(\n result: unknown,\n): string | Array<ContentPart> {\n if (typeof result === 'string') return result\n if (isContentPartArray(result)) return result\n return JSON.stringify(result)\n}\n"],"
|
|
1
|
+
{"version":3,"file":"tool-result.js","names":[],"sources":["../../../src/utilities/tool-result.ts"],"sourcesContent":["import type { ContentPart } from '../types'\n\nconst CONTENT_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n])\n\n/**\n * Structural check for a single `ContentPart`. A text part must carry a string\n * `content`; every other modality must carry a `source` with `type` of\n * `'url' | 'data'` and a string `value`.\n */\nexport function isContentPart(value: unknown): value is ContentPart {\n if (typeof value !== 'object' || value === null) return false\n const part = value as Record<string, unknown>\n if (typeof part.type !== 'string' || !CONTENT_PART_TYPES.has(part.type)) {\n return false\n }\n if (part.type === 'text') {\n return typeof part.content === 'string'\n }\n const source = part.source\n if (typeof source !== 'object' || source === null) return false\n const src = source as Record<string, unknown>\n if (typeof src.value !== 'string') return false\n // `data` sources require a mimeType (matches ContentPartDataSource); `url`\n // sources don't. Requiring it here keeps the runtime guard consistent with\n // the type and avoids emitting `data:undefined;base64,...` downstream.\n if (src.type === 'data') return typeof src.mimeType === 'string'\n return src.type === 'url'\n}\n\n/**\n * True iff `value` is a NON-EMPTY array whose every element is a valid\n * `ContentPart`. Empty arrays and mixed arrays return false so they continue\n * to be treated as ordinary (stringified) data — this keeps the auto-detection\n * footgun narrow.\n */\nexport function isContentPartArray(\n value: unknown,\n): value is Array<ContentPart> {\n return Array.isArray(value) && value.length > 0 && value.every(isContentPart)\n}\n\n/**\n * Normalize a tool's return value for transport:\n * - string → unchanged\n * - ContentPart array → unchanged (multimodal, passed through to the adapter)\n * - anything else → `JSON.stringify`\n */\nexport function normalizeToolResult(\n result: unknown,\n): string | Array<ContentPart> {\n if (typeof result === 'string') return result\n if (isContentPartArray(result)) return result\n return JSON.stringify(result)\n}\n"],"mappings":";AAEA,IAAM,qCAAqB,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;AAOD,SAAgB,cAAc,OAAsC;CAClE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,MAAM,OAAO;CACb,IAAI,OAAO,KAAK,SAAS,YAAY,CAAC,mBAAmB,IAAI,KAAK,IAAI,GACpE,OAAO;CAET,IAAI,KAAK,SAAS,QAChB,OAAO,OAAO,KAAK,YAAY;CAEjC,MAAM,SAAS,KAAK;CACpB,IAAI,OAAO,WAAW,YAAY,WAAW,MAAM,OAAO;CAC1D,MAAM,MAAM;CACZ,IAAI,OAAO,IAAI,UAAU,UAAU,OAAO;CAI1C,IAAI,IAAI,SAAS,QAAQ,OAAO,OAAO,IAAI,aAAa;CACxD,OAAO,IAAI,SAAS;AACtB;;;;;;;AAQA,SAAgB,mBACd,OAC6B;CAC7B,OAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,MAAM,MAAM,aAAa;AAC9E;;;;;;;AAQA,SAAgB,oBACd,QAC6B;CAC7B,IAAI,OAAO,WAAW,UAAU,OAAO;CACvC,IAAI,mBAAmB,MAAM,GAAG,OAAO;CACvC,OAAO,KAAK,UAAU,MAAM;AAC9B"}
|
|
@@ -1,11 +1,29 @@
|
|
|
1
|
+
//#region src/utilities/usage.ts
|
|
2
|
+
/**
|
|
3
|
+
* Builds the base TokenUsage object with core fields.
|
|
4
|
+
* Provider-specific functions should use this and then add their own details.
|
|
5
|
+
*
|
|
6
|
+
* @param input - The base token counts
|
|
7
|
+
* @returns A TokenUsage object with promptTokens, completionTokens, totalTokens
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const base = buildBaseUsage({
|
|
12
|
+
* promptTokens: 100,
|
|
13
|
+
* completionTokens: 50,
|
|
14
|
+
* totalTokens: 150
|
|
15
|
+
* });
|
|
16
|
+
* // Returns: { promptTokens: 100, completionTokens: 50, totalTokens: 150 }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
1
19
|
function buildBaseUsage(input) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
20
|
+
return {
|
|
21
|
+
promptTokens: input.promptTokens,
|
|
22
|
+
completionTokens: input.completionTokens,
|
|
23
|
+
totalTokens: input.totalTokens
|
|
24
|
+
};
|
|
7
25
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
//# sourceMappingURL=usage.js.map
|
|
26
|
+
//#endregion
|
|
27
|
+
export { buildBaseUsage };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.js","sources":["../../../src/utilities/usage.ts"],"sourcesContent":["import type { ProviderUsageDetails, TokenUsage } from '../types'\n\n/**\n * Input parameters for building base TokenUsage.\n * Provider functions should extract these from their SDK's response.\n */\nexport interface BaseUsageInput {\n /** Total input/prompt tokens */\n promptTokens: number\n /** Total output/completion tokens */\n completionTokens: number\n /** Total tokens (prompt + completion) */\n totalTokens: number\n}\n\n/**\n * Builds the base TokenUsage object with core fields.\n * Provider-specific functions should use this and then add their own details.\n *\n * @param input - The base token counts\n * @returns A TokenUsage object with promptTokens, completionTokens, totalTokens\n *\n * @example\n * ```typescript\n * const base = buildBaseUsage({\n * promptTokens: 100,\n * completionTokens: 50,\n * totalTokens: 150\n * });\n * // Returns: { promptTokens: 100, completionTokens: 50, totalTokens: 150 }\n * ```\n */\nexport function buildBaseUsage<TProviderDetails = ProviderUsageDetails>(\n input: BaseUsageInput,\n): TokenUsage<TProviderDetails> {\n return {\n promptTokens: input.promptTokens,\n completionTokens: input.completionTokens,\n totalTokens: input.totalTokens,\n }\n}\n"],"
|
|
1
|
+
{"version":3,"file":"usage.js","names":[],"sources":["../../../src/utilities/usage.ts"],"sourcesContent":["import type { ProviderUsageDetails, TokenUsage } from '../types'\n\n/**\n * Input parameters for building base TokenUsage.\n * Provider functions should extract these from their SDK's response.\n */\nexport interface BaseUsageInput {\n /** Total input/prompt tokens */\n promptTokens: number\n /** Total output/completion tokens */\n completionTokens: number\n /** Total tokens (prompt + completion) */\n totalTokens: number\n}\n\n/**\n * Builds the base TokenUsage object with core fields.\n * Provider-specific functions should use this and then add their own details.\n *\n * @param input - The base token counts\n * @returns A TokenUsage object with promptTokens, completionTokens, totalTokens\n *\n * @example\n * ```typescript\n * const base = buildBaseUsage({\n * promptTokens: 100,\n * completionTokens: 50,\n * totalTokens: 150\n * });\n * // Returns: { promptTokens: 100, completionTokens: 50, totalTokens: 150 }\n * ```\n */\nexport function buildBaseUsage<TProviderDetails = ProviderUsageDetails>(\n input: BaseUsageInput,\n): TokenUsage<TProviderDetails> {\n return {\n promptTokens: input.promptTokens,\n completionTokens: input.completionTokens,\n totalTokens: input.totalTokens,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAgCA,SAAgB,eACd,OAC8B;CAC9B,OAAO;EACL,cAAc,MAAM;EACpB,kBAAkB,MAAM;EACxB,aAAa,MAAM;CACrB;AACF"}
|
package/dist/esm/utils.js
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
|
+
//#region src/utils.ts
|
|
2
|
+
/**
|
|
3
|
+
* Detect image mime type from base64 data using magic bytes.
|
|
4
|
+
* Returns undefined if the format cannot be detected.
|
|
5
|
+
*
|
|
6
|
+
* This function analyzes the first few bytes of base64-encoded image data
|
|
7
|
+
* to determine the image format based on file signature (magic bytes).
|
|
8
|
+
*
|
|
9
|
+
* @param base64Data - The base64-encoded image data
|
|
10
|
+
* @returns The detected mime type, or undefined if unrecognized
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const mimeType = detectImageMimeType(imageBase64)
|
|
15
|
+
* // Returns 'image/jpeg', 'image/png', 'image/gif', 'image/webp', or undefined
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
1
18
|
function detectImageMimeType(base64Data) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return "image/png";
|
|
8
|
-
}
|
|
9
|
-
if (prefix.startsWith("R0lGOD")) {
|
|
10
|
-
return "image/gif";
|
|
11
|
-
}
|
|
12
|
-
if (prefix.startsWith("UklGR")) {
|
|
13
|
-
return "image/webp";
|
|
14
|
-
}
|
|
15
|
-
return void 0;
|
|
19
|
+
const prefix = base64Data.substring(0, 20);
|
|
20
|
+
if (prefix.startsWith("/9j/")) return "image/jpeg";
|
|
21
|
+
if (prefix.startsWith("iVBORw0KGgo")) return "image/png";
|
|
22
|
+
if (prefix.startsWith("R0lGOD")) return "image/gif";
|
|
23
|
+
if (prefix.startsWith("UklGR")) return "image/webp";
|
|
16
24
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
//# sourceMappingURL=utils.js.map
|
|
25
|
+
//#endregion
|
|
26
|
+
export { detectImageMimeType };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=utils.js.map
|
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Detect image mime type from base64 data using magic bytes.\n * Returns undefined if the format cannot be detected.\n *\n * This function analyzes the first few bytes of base64-encoded image data\n * to determine the image format based on file signature (magic bytes).\n *\n * @param base64Data - The base64-encoded image data\n * @returns The detected mime type, or undefined if unrecognized\n *\n * @example\n * ```ts\n * const mimeType = detectImageMimeType(imageBase64)\n * // Returns 'image/jpeg', 'image/png', 'image/gif', 'image/webp', or undefined\n * ```\n */\nexport function detectImageMimeType(\n base64Data: string,\n): 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | undefined {\n // Get first few bytes (base64 encoded)\n const prefix = base64Data.substring(0, 20)\n\n // JPEG: starts with /9j/ (FFD8FF in base64)\n if (prefix.startsWith('/9j/')) {\n return 'image/jpeg'\n }\n // PNG: starts with iVBORw0KGgo (89504E47 in base64)\n if (prefix.startsWith('iVBORw0KGgo')) {\n return 'image/png'\n }\n // GIF: starts with R0lGOD (474946 in base64)\n if (prefix.startsWith('R0lGOD')) {\n return 'image/gif'\n }\n // WebP: starts with UklGR (52494646 in base64, followed by WEBP)\n if (prefix.startsWith('UklGR')) {\n return 'image/webp'\n }\n\n return undefined\n}\n"],"
|
|
1
|
+
{"version":3,"file":"utils.js","names":[],"sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Detect image mime type from base64 data using magic bytes.\n * Returns undefined if the format cannot be detected.\n *\n * This function analyzes the first few bytes of base64-encoded image data\n * to determine the image format based on file signature (magic bytes).\n *\n * @param base64Data - The base64-encoded image data\n * @returns The detected mime type, or undefined if unrecognized\n *\n * @example\n * ```ts\n * const mimeType = detectImageMimeType(imageBase64)\n * // Returns 'image/jpeg', 'image/png', 'image/gif', 'image/webp', or undefined\n * ```\n */\nexport function detectImageMimeType(\n base64Data: string,\n): 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | undefined {\n // Get first few bytes (base64 encoded)\n const prefix = base64Data.substring(0, 20)\n\n // JPEG: starts with /9j/ (FFD8FF in base64)\n if (prefix.startsWith('/9j/')) {\n return 'image/jpeg'\n }\n // PNG: starts with iVBORw0KGgo (89504E47 in base64)\n if (prefix.startsWith('iVBORw0KGgo')) {\n return 'image/png'\n }\n // GIF: starts with R0lGOD (474946 in base64)\n if (prefix.startsWith('R0lGOD')) {\n return 'image/gif'\n }\n // WebP: starts with UklGR (52494646 in base64, followed by WEBP)\n if (prefix.startsWith('UklGR')) {\n return 'image/webp'\n }\n\n return undefined\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,SAAgB,oBACd,YACqE;CAErE,MAAM,SAAS,WAAW,UAAU,GAAG,EAAE;CAGzC,IAAI,OAAO,WAAW,MAAM,GAC1B,OAAO;CAGT,IAAI,OAAO,WAAW,aAAa,GACjC,OAAO;CAGT,IAAI,OAAO,WAAW,QAAQ,GAC5B,OAAO;CAGT,IAAI,OAAO,WAAW,OAAO,GAC3B,OAAO;AAIX"}
|