@retinue/agentkit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +310 -0
- package/dist/adapters/bullmq/consumer.d.ts +33 -0
- package/dist/adapters/bullmq/consumer.js +41 -0
- package/dist/adapters/bullmq/dispatcher.d.ts +74 -0
- package/dist/adapters/bullmq/dispatcher.js +160 -0
- package/dist/adapters/bullmq/export.d.ts +31 -0
- package/dist/adapters/bullmq/export.js +53 -0
- package/dist/adapters/bullmq/extraction.d.ts +42 -0
- package/dist/adapters/bullmq/extraction.js +63 -0
- package/dist/adapters/bullmq/index.d.ts +13 -0
- package/dist/adapters/bullmq/index.js +13 -0
- package/dist/adapters/bullmq/lock.d.ts +77 -0
- package/dist/adapters/bullmq/lock.js +126 -0
- package/dist/adapters/bullmq/queue.d.ts +50 -0
- package/dist/adapters/bullmq/queue.js +81 -0
- package/dist/adapters/memory/artifact-exports.d.ts +11 -0
- package/dist/adapters/memory/artifact-exports.js +102 -0
- package/dist/adapters/memory/artifacts.d.ts +15 -0
- package/dist/adapters/memory/artifacts.js +134 -0
- package/dist/adapters/memory/blobs.d.ts +7 -0
- package/dist/adapters/memory/blobs.js +27 -0
- package/dist/adapters/memory/evaluation.d.ts +18 -0
- package/dist/adapters/memory/evaluation.js +148 -0
- package/dist/adapters/memory/files.d.ts +27 -0
- package/dist/adapters/memory/files.js +0 -0
- package/dist/adapters/memory/flows.d.ts +16 -0
- package/dist/adapters/memory/flows.js +117 -0
- package/dist/adapters/memory/hitl.d.ts +9 -0
- package/dist/adapters/memory/hitl.js +130 -0
- package/dist/adapters/memory/idempotency.d.ts +13 -0
- package/dist/adapters/memory/idempotency.js +32 -0
- package/dist/adapters/memory/index.d.ts +39 -0
- package/dist/adapters/memory/index.js +107 -0
- package/dist/adapters/memory/knowledge.d.ts +43 -0
- package/dist/adapters/memory/knowledge.js +248 -0
- package/dist/adapters/memory/mcp.d.ts +9 -0
- package/dist/adapters/memory/mcp.js +37 -0
- package/dist/adapters/memory/message-store.d.ts +17 -0
- package/dist/adapters/memory/message-store.js +70 -0
- package/dist/adapters/memory/principal-memory.d.ts +7 -0
- package/dist/adapters/memory/principal-memory.js +83 -0
- package/dist/adapters/memory/runtime.d.ts +29 -0
- package/dist/adapters/memory/runtime.js +0 -0
- package/dist/adapters/memory/sessions.d.ts +29 -0
- package/dist/adapters/memory/sessions.js +0 -0
- package/dist/adapters/memory/skills.d.ts +10 -0
- package/dist/adapters/memory/skills.js +41 -0
- package/dist/adapters/memory/thread-summary.d.ts +7 -0
- package/dist/adapters/memory/thread-summary.js +29 -0
- package/dist/adapters/memory/usage-limits.d.ts +13 -0
- package/dist/adapters/memory/usage-limits.js +72 -0
- package/dist/adapters/memory/usage.d.ts +16 -0
- package/dist/adapters/memory/usage.js +279 -0
- package/dist/adapters/otel/index.d.ts +111 -0
- package/dist/adapters/otel/index.js +133 -0
- package/dist/adapters/postgres/artifact-exports.d.ts +12 -0
- package/dist/adapters/postgres/artifact-exports.js +117 -0
- package/dist/adapters/postgres/artifacts.d.ts +16 -0
- package/dist/adapters/postgres/artifacts.js +172 -0
- package/dist/adapters/postgres/checkpoint-store.d.ts +16 -0
- package/dist/adapters/postgres/checkpoint-store.js +34 -0
- package/dist/adapters/postgres/config.d.ts +15 -0
- package/dist/adapters/postgres/config.js +187 -0
- package/dist/adapters/postgres/conversation-store.d.ts +4 -0
- package/dist/adapters/postgres/conversation-store.js +82 -0
- package/dist/adapters/postgres/evaluation.d.ts +17 -0
- package/dist/adapters/postgres/evaluation.js +193 -0
- package/dist/adapters/postgres/file-content.d.ts +30 -0
- package/dist/adapters/postgres/file-content.js +111 -0
- package/dist/adapters/postgres/files.d.ts +19 -0
- package/dist/adapters/postgres/files.js +209 -0
- package/dist/adapters/postgres/flows.d.ts +20 -0
- package/dist/adapters/postgres/flows.js +206 -0
- package/dist/adapters/postgres/hitl.d.ts +5 -0
- package/dist/adapters/postgres/hitl.js +247 -0
- package/dist/adapters/postgres/index.d.ts +35 -0
- package/dist/adapters/postgres/index.js +35 -0
- package/dist/adapters/postgres/knowledge.d.ts +48 -0
- package/dist/adapters/postgres/knowledge.js +255 -0
- package/dist/adapters/postgres/memory.d.ts +14 -0
- package/dist/adapters/postgres/memory.js +194 -0
- package/dist/adapters/postgres/message-store.d.ts +11 -0
- package/dist/adapters/postgres/message-store.js +145 -0
- package/dist/adapters/postgres/migrations.d.ts +69 -0
- package/dist/adapters/postgres/migrations.js +1594 -0
- package/dist/adapters/postgres/pg-executor.d.ts +19 -0
- package/dist/adapters/postgres/pg-executor.js +32 -0
- package/dist/adapters/postgres/retention.d.ts +26 -0
- package/dist/adapters/postgres/retention.js +59 -0
- package/dist/adapters/postgres/rollups.d.ts +17 -0
- package/dist/adapters/postgres/rollups.js +267 -0
- package/dist/adapters/postgres/run-coordinator.d.ts +5 -0
- package/dist/adapters/postgres/run-coordinator.js +98 -0
- package/dist/adapters/postgres/run-event-log.d.ts +26 -0
- package/dist/adapters/postgres/run-event-log.js +30 -0
- package/dist/adapters/postgres/run-store.d.ts +4 -0
- package/dist/adapters/postgres/run-store.js +199 -0
- package/dist/adapters/postgres/schema.d.ts +39 -0
- package/dist/adapters/postgres/schema.js +70 -0
- package/dist/adapters/postgres/session-state.d.ts +7 -0
- package/dist/adapters/postgres/session-state.js +99 -0
- package/dist/adapters/postgres/sql.d.ts +8 -0
- package/dist/adapters/postgres/sql.js +2 -0
- package/dist/adapters/postgres/transaction.d.ts +37 -0
- package/dist/adapters/postgres/transaction.js +93 -0
- package/dist/adapters/postgres/unit-of-work.d.ts +18 -0
- package/dist/adapters/postgres/unit-of-work.js +8 -0
- package/dist/adapters/postgres/usage-limits.d.ts +15 -0
- package/dist/adapters/postgres/usage-limits.js +136 -0
- package/dist/adapters/postgres/usage.d.ts +15 -0
- package/dist/adapters/postgres/usage.js +226 -0
- package/dist/adapters/redis/index.d.ts +9 -0
- package/dist/adapters/redis/index.js +9 -0
- package/dist/adapters/redis/realtime.d.ts +74 -0
- package/dist/adapters/redis/realtime.js +112 -0
- package/dist/adapters/supabase/index.d.ts +88 -0
- package/dist/adapters/supabase/index.js +84 -0
- package/dist/adapters/supabase/realtime.d.ts +30 -0
- package/dist/adapters/supabase/realtime.js +53 -0
- package/dist/adapters/supabase/rls.d.ts +99 -0
- package/dist/adapters/supabase/rls.js +216 -0
- package/dist/adapters/supabase/storage.d.ts +50 -0
- package/dist/adapters/supabase/storage.js +207 -0
- package/dist/agents/agent.d.ts +66 -0
- package/dist/agents/agent.js +209 -0
- package/dist/agents/define.d.ts +21 -0
- package/dist/agents/define.js +63 -0
- package/dist/agents/engine.d.ts +98 -0
- package/dist/agents/engine.js +462 -0
- package/dist/agents/index.d.ts +50 -0
- package/dist/agents/index.js +17 -0
- package/dist/artifacts/index.d.ts +114 -0
- package/dist/artifacts/index.js +219 -0
- package/dist/authorization/index.d.ts +76 -0
- package/dist/authorization/index.js +76 -0
- package/dist/capabilities/index.d.ts +120 -0
- package/dist/capabilities/index.js +167 -0
- package/dist/capabilities/runtime.d.ts +89 -0
- package/dist/capabilities/runtime.js +84 -0
- package/dist/citations/index.d.ts +161 -0
- package/dist/citations/index.js +182 -0
- package/dist/context/assembler.d.ts +82 -0
- package/dist/context/assembler.js +129 -0
- package/dist/context/compaction.d.ts +45 -0
- package/dist/context/compaction.js +55 -0
- package/dist/context/index.d.ts +75 -0
- package/dist/context/index.js +17 -0
- package/dist/core/content-parts.d.ts +194 -0
- package/dist/core/content-parts.js +23 -0
- package/dist/core/context.d.ts +51 -0
- package/dist/core/context.js +9 -0
- package/dist/core/env.d.ts +25 -0
- package/dist/core/env.js +41 -0
- package/dist/core/errors.d.ts +30 -0
- package/dist/core/errors.js +65 -0
- package/dist/core/events.d.ts +139 -0
- package/dist/core/events.js +99 -0
- package/dist/core/ids.d.ts +52 -0
- package/dist/core/ids.js +13 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/tokens.d.ts +22 -0
- package/dist/core/tokens.js +22 -0
- package/dist/core/validation.d.ts +34 -0
- package/dist/core/validation.js +176 -0
- package/dist/documents/extraction.d.ts +121 -0
- package/dist/documents/extraction.js +293 -0
- package/dist/documents/index.d.ts +199 -0
- package/dist/documents/index.js +65 -0
- package/dist/documents/parsers/pdf.d.ts +47 -0
- package/dist/documents/parsers/pdf.js +508 -0
- package/dist/documents/parsers/text.d.ts +59 -0
- package/dist/documents/parsers/text.js +325 -0
- package/dist/documents/read-tool.d.ts +52 -0
- package/dist/documents/read-tool.js +109 -0
- package/dist/documents/render.d.ts +29 -0
- package/dist/documents/render.js +59 -0
- package/dist/documents/vision.d.ts +159 -0
- package/dist/documents/vision.js +214 -0
- package/dist/entries/adapters-bullmq.d.ts +8 -0
- package/dist/entries/adapters-bullmq.js +8 -0
- package/dist/entries/adapters-otel.d.ts +13 -0
- package/dist/entries/adapters-otel.js +13 -0
- package/dist/entries/adapters-postgres.d.ts +10 -0
- package/dist/entries/adapters-postgres.js +10 -0
- package/dist/entries/adapters-redis.d.ts +3 -0
- package/dist/entries/adapters-redis.js +3 -0
- package/dist/entries/context.d.ts +20 -0
- package/dist/entries/context.js +20 -0
- package/dist/entries/flows.d.ts +15 -0
- package/dist/entries/flows.js +15 -0
- package/dist/entries/hitl.d.ts +10 -0
- package/dist/entries/hitl.js +10 -0
- package/dist/entries/knowledge.d.ts +18 -0
- package/dist/entries/knowledge.js +19 -0
- package/dist/entries/mcp.d.ts +10 -0
- package/dist/entries/mcp.js +10 -0
- package/dist/entries/observability.d.ts +14 -0
- package/dist/entries/observability.js +16 -0
- package/dist/entries/persistence.d.ts +11 -0
- package/dist/entries/persistence.js +11 -0
- package/dist/entries/providers.d.ts +14 -0
- package/dist/entries/providers.js +14 -0
- package/dist/entries/runtime.d.ts +13 -0
- package/dist/entries/runtime.js +13 -0
- package/dist/entries/server.d.ts +24 -0
- package/dist/entries/server.js +24 -0
- package/dist/entries/tools.d.ts +21 -0
- package/dist/entries/tools.js +21 -0
- package/dist/entries/usage.d.ts +10 -0
- package/dist/entries/usage.js +10 -0
- package/dist/evaluation/gate.d.ts +168 -0
- package/dist/evaluation/gate.js +180 -0
- package/dist/evaluation/graders.d.ts +125 -0
- package/dist/evaluation/graders.js +203 -0
- package/dist/evaluation/index.d.ts +120 -0
- package/dist/evaluation/index.js +183 -0
- package/dist/evaluation/judge.d.ts +75 -0
- package/dist/evaluation/judge.js +111 -0
- package/dist/export/index.d.ts +162 -0
- package/dist/export/index.js +363 -0
- package/dist/export/markdown.d.ts +19 -0
- package/dist/export/markdown.js +29 -0
- package/dist/export/pdf.d.ts +73 -0
- package/dist/export/pdf.js +407 -0
- package/dist/files/context.d.ts +97 -0
- package/dist/files/context.js +185 -0
- package/dist/files/index.d.ts +210 -0
- package/dist/files/index.js +338 -0
- package/dist/files/read-tool.d.ts +81 -0
- package/dist/files/read-tool.js +163 -0
- package/dist/files/turn-parts.d.ts +96 -0
- package/dist/files/turn-parts.js +171 -0
- package/dist/flows/index.d.ts +270 -0
- package/dist/flows/index.js +62 -0
- package/dist/flows/interpreter.d.ts +146 -0
- package/dist/flows/interpreter.js +426 -0
- package/dist/flows/runner.d.ts +145 -0
- package/dist/flows/runner.js +270 -0
- package/dist/graphql/index.d.ts +8 -0
- package/dist/graphql/index.js +8 -0
- package/dist/graphql/resolvers.d.ts +237 -0
- package/dist/graphql/resolvers.js +253 -0
- package/dist/graphql/schema.d.ts +11 -0
- package/dist/graphql/schema.js +258 -0
- package/dist/graphql/sse.d.ts +77 -0
- package/dist/graphql/sse.js +100 -0
- package/dist/hitl/approved-execution.d.ts +127 -0
- package/dist/hitl/approved-execution.js +177 -0
- package/dist/hitl/index.d.ts +79 -0
- package/dist/hitl/index.js +12 -0
- package/dist/hitl/service.d.ts +221 -0
- package/dist/hitl/service.js +268 -0
- package/dist/idempotency/index.d.ts +70 -0
- package/dist/idempotency/index.js +59 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +60 -0
- package/dist/knowledge/chunking.d.ts +57 -0
- package/dist/knowledge/chunking.js +158 -0
- package/dist/knowledge/index.d.ts +119 -0
- package/dist/knowledge/index.js +166 -0
- package/dist/knowledge/retrieval.d.ts +146 -0
- package/dist/knowledge/retrieval.js +170 -0
- package/dist/loadtest/harness.d.ts +168 -0
- package/dist/loadtest/harness.js +507 -0
- package/dist/loadtest/index.d.ts +13 -0
- package/dist/loadtest/index.js +13 -0
- package/dist/loadtest/injection.d.ts +89 -0
- package/dist/loadtest/injection.js +147 -0
- package/dist/loadtest/metrics.d.ts +197 -0
- package/dist/loadtest/metrics.js +160 -0
- package/dist/loadtest/runbooks.d.ts +28 -0
- package/dist/loadtest/runbooks.js +159 -0
- package/dist/loadtest/scenario.d.ts +104 -0
- package/dist/loadtest/scenario.js +208 -0
- package/dist/mcp/egress.d.ts +53 -0
- package/dist/mcp/egress.js +115 -0
- package/dist/mcp/index.d.ts +93 -0
- package/dist/mcp/index.js +33 -0
- package/dist/mcp/provider.d.ts +62 -0
- package/dist/mcp/provider.js +0 -0
- package/dist/models/index.d.ts +98 -0
- package/dist/models/index.js +74 -0
- package/dist/models/pricing.d.ts +24 -0
- package/dist/models/pricing.js +37 -0
- package/dist/models/provider-factory.d.ts +31 -0
- package/dist/models/provider-factory.js +67 -0
- package/dist/models/streaming.d.ts +145 -0
- package/dist/models/streaming.js +272 -0
- package/dist/models/vision.d.ts +38 -0
- package/dist/models/vision.js +62 -0
- package/dist/persistence/index.d.ts +1654 -0
- package/dist/persistence/index.js +226 -0
- package/dist/principal-memory/index.d.ts +106 -0
- package/dist/principal-memory/index.js +89 -0
- package/dist/retention/index.d.ts +89 -0
- package/dist/retention/index.js +70 -0
- package/dist/runtime/checkpoint.d.ts +37 -0
- package/dist/runtime/checkpoint.js +22 -0
- package/dist/runtime/index.d.ts +118 -0
- package/dist/runtime/index.js +69 -0
- package/dist/runtime/retry.d.ts +95 -0
- package/dist/runtime/retry.js +126 -0
- package/dist/runtime/serialization.d.ts +85 -0
- package/dist/runtime/serialization.js +95 -0
- package/dist/runtime/streaming.d.ts +54 -0
- package/dist/runtime/streaming.js +115 -0
- package/dist/runtime/worker.d.ts +130 -0
- package/dist/runtime/worker.js +405 -0
- package/dist/security/checklist.d.ts +53 -0
- package/dist/security/checklist.js +204 -0
- package/dist/security/findings.d.ts +56 -0
- package/dist/security/findings.js +168 -0
- package/dist/security/index.d.ts +14 -0
- package/dist/security/index.js +14 -0
- package/dist/security/prompt-safety.d.ts +100 -0
- package/dist/security/prompt-safety.js +133 -0
- package/dist/server/boot.d.ts +32 -0
- package/dist/server/boot.js +36 -0
- package/dist/server/cli-worker.d.ts +37 -0
- package/dist/server/cli-worker.js +151 -0
- package/dist/server/cli.d.ts +27 -0
- package/dist/server/cli.js +74 -0
- package/dist/server/config.d.ts +42 -0
- package/dist/server/config.js +127 -0
- package/dist/server/health.d.ts +59 -0
- package/dist/server/health.js +90 -0
- package/dist/server/host.d.ts +39 -0
- package/dist/server/host.js +124 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.js +15 -0
- package/dist/server/main.d.ts +16 -0
- package/dist/server/main.js +31 -0
- package/dist/server/sse-route.d.ts +21 -0
- package/dist/server/sse-route.js +282 -0
- package/dist/skills/index.d.ts +67 -0
- package/dist/skills/index.js +31 -0
- package/dist/skills/resolver.d.ts +54 -0
- package/dist/skills/resolver.js +121 -0
- package/dist/teams/index.d.ts +93 -0
- package/dist/teams/index.js +207 -0
- package/dist/telemetry/index.d.ts +157 -0
- package/dist/telemetry/index.js +71 -0
- package/dist/telemetry/instrument.d.ts +108 -0
- package/dist/telemetry/instrument.js +232 -0
- package/dist/telemetry/log-events.d.ts +17 -0
- package/dist/telemetry/log-events.js +58 -0
- package/dist/telemetry/metrics.d.ts +123 -0
- package/dist/telemetry/metrics.js +135 -0
- package/dist/telemetry/noop.d.ts +39 -0
- package/dist/telemetry/noop.js +143 -0
- package/dist/telemetry/redaction.d.ts +64 -0
- package/dist/telemetry/redaction.js +153 -0
- package/dist/telemetry/spans.d.ts +56 -0
- package/dist/telemetry/spans.js +78 -0
- package/dist/telemetry/trace-context.d.ts +55 -0
- package/dist/telemetry/trace-context.js +60 -0
- package/dist/toolkit/compute.d.ts +53 -0
- package/dist/toolkit/compute.js +152 -0
- package/dist/toolkit/data.d.ts +98 -0
- package/dist/toolkit/data.js +235 -0
- package/dist/toolkit/http.d.ts +113 -0
- package/dist/toolkit/http.js +205 -0
- package/dist/toolkit/index.d.ts +21 -0
- package/dist/toolkit/index.js +17 -0
- package/dist/toolkit/web.d.ts +107 -0
- package/dist/toolkit/web.js +147 -0
- package/dist/tools/define.d.ts +25 -0
- package/dist/tools/define.js +45 -0
- package/dist/tools/delegating.d.ts +132 -0
- package/dist/tools/delegating.js +211 -0
- package/dist/tools/index.d.ts +129 -0
- package/dist/tools/index.js +33 -0
- package/dist/tools/library/compute.d.ts +11 -0
- package/dist/tools/library/compute.js +46 -0
- package/dist/tools/library/data.d.ts +16 -0
- package/dist/tools/library/data.js +92 -0
- package/dist/tools/library/http.d.ts +28 -0
- package/dist/tools/library/http.js +71 -0
- package/dist/tools/library/index.d.ts +97 -0
- package/dist/tools/library/index.js +134 -0
- package/dist/tools/library/knowledge.d.ts +39 -0
- package/dist/tools/library/knowledge.js +58 -0
- package/dist/tools/library/web.d.ts +19 -0
- package/dist/tools/library/web.js +65 -0
- package/dist/tools/meta-tools.d.ts +19 -0
- package/dist/tools/meta-tools.js +36 -0
- package/dist/tools/registry.d.ts +146 -0
- package/dist/tools/registry.js +291 -0
- package/dist/usage/index.d.ts +105 -0
- package/dist/usage/index.js +20 -0
- package/dist/usage/quota.d.ts +258 -0
- package/dist/usage/quota.js +510 -0
- package/dist/usage/recorder.d.ts +29 -0
- package/dist/usage/recorder.js +96 -0
- package/dist/usage/rollups.d.ts +121 -0
- package/dist/usage/rollups.js +157 -0
- package/dist/worker/export.d.ts +57 -0
- package/dist/worker/export.js +81 -0
- package/dist/worker/extraction.d.ts +57 -0
- package/dist/worker/extraction.js +84 -0
- package/dist/worker/main.d.ts +103 -0
- package/dist/worker/main.js +159 -0
- package/package.json +187 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neutral model-turn streaming — `docs/03-intelligence-runtime.md`.
|
|
3
|
+
*
|
|
4
|
+
* The one place the Vercel AI SDK's `streamText` tool loop is used (boundary rule R3 keeps the SDK
|
|
5
|
+
* inside `models/`). It runs a multi-step turn — the model calls tools, their results feed back, it
|
|
6
|
+
* continues — and yields provider-neutral chunks. The agent engine consumes these chunks without
|
|
7
|
+
* ever importing the SDK, so switching providers changes nothing above this layer.
|
|
8
|
+
*/
|
|
9
|
+
import { type LanguageModel } from "ai";
|
|
10
|
+
import type { InputModality } from "./index.js";
|
|
11
|
+
/** Opaque handle to a provider model. Aliased here so layers above `models/` never import the SDK. */
|
|
12
|
+
export type ResolvedModel = LanguageModel;
|
|
13
|
+
/** A conversation turn message the engine hands down (text-only for v1; multimodal is additive). */
|
|
14
|
+
/**
|
|
15
|
+
* One piece of a turn's content — #185.
|
|
16
|
+
*
|
|
17
|
+
* `image` and `file` exist because the platform has always accepted them: `ImagePart` and `FilePart` are in the
|
|
18
|
+
* message contract, `InputModality` covers image, audio, video and pdf, and `resolveModel` filters on
|
|
19
|
+
* `requiredModalities`. What was missing was the last hop — the bridge to the provider took a string, so an
|
|
20
|
+
* attachment was stored, authorized, rendered and billed for, and then not mentioned to the model.
|
|
21
|
+
*
|
|
22
|
+
* A URL or a data payload rather than a platform file id, deliberately: this layer is the SDK boundary and knows
|
|
23
|
+
* nothing about the file store. The caller resolves a `FilePart` into bytes or a URL **through the mediated read
|
|
24
|
+
* path**, which is what keeps the modality bridge from becoming a way around file authorization.
|
|
25
|
+
*/
|
|
26
|
+
export type TurnContentPart = {
|
|
27
|
+
readonly kind: "text";
|
|
28
|
+
readonly text: string;
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: "image";
|
|
31
|
+
readonly image: string | URL;
|
|
32
|
+
readonly mediaType?: string;
|
|
33
|
+
} | {
|
|
34
|
+
readonly kind: "file";
|
|
35
|
+
readonly data: string | URL;
|
|
36
|
+
readonly mediaType: string;
|
|
37
|
+
readonly filename?: string;
|
|
38
|
+
};
|
|
39
|
+
export type TurnMessage = {
|
|
40
|
+
readonly role: "system" | "user" | "assistant";
|
|
41
|
+
/**
|
|
42
|
+
* A string for a text turn, parts when the turn carries an attachment.
|
|
43
|
+
*
|
|
44
|
+
* A union rather than `text` plus an optional `parts`, which was the other option and is worse: two fields
|
|
45
|
+
* that can both carry text leave the relationship between them undefined, and somebody eventually sets one and
|
|
46
|
+
* reads the other.
|
|
47
|
+
*/
|
|
48
|
+
readonly content: string | readonly TurnContentPart[];
|
|
49
|
+
};
|
|
50
|
+
/** The text of a turn, for callers that count tokens or log. Non-text parts contribute nothing. */
|
|
51
|
+
export declare const turnText: (message: TurnMessage) => string;
|
|
52
|
+
/** The modalities a turn actually needs, so a model can be checked against it rather than assumed. */
|
|
53
|
+
/**
|
|
54
|
+
* What non-text input a turn is carrying — REQ-036 (#185), AC-4.
|
|
55
|
+
*
|
|
56
|
+
* Counted, not inferred: one image part is one image. Audio is not counted yet and the field is deliberately
|
|
57
|
+
* absent rather than zero — a `TurnContentPart` has no duration, so the only honest answer is "this layer does
|
|
58
|
+
* not know", and a zero would be indistinguishable from a silent audio turn.
|
|
59
|
+
*/
|
|
60
|
+
export declare const nonTextCounts: (messages: readonly TurnMessage[]) => {
|
|
61
|
+
readonly imageCount?: number;
|
|
62
|
+
};
|
|
63
|
+
export declare const modalitiesOf: (messages: readonly TurnMessage[]) => readonly InputModality[];
|
|
64
|
+
/** A tool the model may call this turn. `execute` is the platform's guarded execution path. */
|
|
65
|
+
export type ModelTurnTool = {
|
|
66
|
+
readonly name: string;
|
|
67
|
+
readonly description?: string;
|
|
68
|
+
/** Zod schema or JSON-schema object; a permissive object schema is used when absent. */
|
|
69
|
+
readonly inputSchema?: unknown;
|
|
70
|
+
execute(input: unknown): Promise<unknown>;
|
|
71
|
+
};
|
|
72
|
+
export type ModelTurnRequest = {
|
|
73
|
+
readonly model: ResolvedModel;
|
|
74
|
+
readonly system?: string;
|
|
75
|
+
readonly messages: readonly TurnMessage[];
|
|
76
|
+
/**
|
|
77
|
+
* What the resolved model accepts — #185. Optional, and its absence means "do not check".
|
|
78
|
+
*
|
|
79
|
+
* Absent rather than defaulting to `["text"]`, because a caller that has not said what the model takes has not
|
|
80
|
+
* said the model takes text only. Defaulting would refuse every image turn from every caller that has not been
|
|
81
|
+
* updated, which is an outage dressed as a safety check.
|
|
82
|
+
*/
|
|
83
|
+
readonly modelModalities?: readonly InputModality[];
|
|
84
|
+
readonly tools?: readonly ModelTurnTool[];
|
|
85
|
+
readonly maxSteps?: number;
|
|
86
|
+
readonly abortSignal?: AbortSignal;
|
|
87
|
+
/**
|
|
88
|
+
* Generation parameters — #160.
|
|
89
|
+
*
|
|
90
|
+
* None of these existed, and none was sent: `streamText` was called with model, system, messages, tools and
|
|
91
|
+
* `stopWhen` only. So `ModelDefinition.limits.maxOutputTokens` was **decorative** in the text path — a
|
|
92
|
+
* definition declaring a 4,096-token cap capped nothing, and a run got whatever the provider's default
|
|
93
|
+
* happened to be. `models/vision.ts` was the only place that applied it.
|
|
94
|
+
*
|
|
95
|
+
* `temperature` matters for a second reason: the evaluation harness (#141) rests its reproducibility argument
|
|
96
|
+
* partly on temperature zero, and there was no way to ask for it on a real run.
|
|
97
|
+
*/
|
|
98
|
+
readonly maxOutputTokens?: number;
|
|
99
|
+
readonly temperature?: number;
|
|
100
|
+
readonly topP?: number;
|
|
101
|
+
readonly stopSequences?: readonly string[];
|
|
102
|
+
};
|
|
103
|
+
export type NeutralUsage = {
|
|
104
|
+
readonly inputTokens: number;
|
|
105
|
+
readonly outputTokens: number;
|
|
106
|
+
readonly cachedInputTokens: number;
|
|
107
|
+
readonly reasoningTokens?: number;
|
|
108
|
+
/**
|
|
109
|
+
* Non-text input, counted from what **we sent** rather than from what the provider reported — #185 AC-4.
|
|
110
|
+
*
|
|
111
|
+
* Deliberately not read out of the provider's usage object. The AI SDK's neutral usage has no modality
|
|
112
|
+
* breakdown, and the providers that expose one put it in provider-specific metadata under provider-specific
|
|
113
|
+
* names — so a platform reading it would work for one vendor and silently report zero for the rest, which is
|
|
114
|
+
* worse than not reporting at all because it looks like data.
|
|
115
|
+
*
|
|
116
|
+
* We know exactly what went out: the turn's parts. Counting there is provider-independent, always available,
|
|
117
|
+
* and auditable against the transcript — and it is the number a per-image price is charged against anyway.
|
|
118
|
+
*/
|
|
119
|
+
readonly imageCount?: number;
|
|
120
|
+
readonly audioSeconds?: number;
|
|
121
|
+
};
|
|
122
|
+
/** Provider-neutral stream chunk — the engine maps these to `RunEvent`s. */
|
|
123
|
+
export type NeutralStreamChunk = {
|
|
124
|
+
readonly type: "text-delta";
|
|
125
|
+
readonly id: string;
|
|
126
|
+
readonly text: string;
|
|
127
|
+
} | {
|
|
128
|
+
readonly type: "tool-call";
|
|
129
|
+
readonly toolCallId: string;
|
|
130
|
+
readonly toolName: string;
|
|
131
|
+
readonly input: unknown;
|
|
132
|
+
} | {
|
|
133
|
+
readonly type: "tool-result";
|
|
134
|
+
readonly toolCallId: string;
|
|
135
|
+
readonly toolName: string;
|
|
136
|
+
readonly output: unknown;
|
|
137
|
+
} | {
|
|
138
|
+
readonly type: "finish";
|
|
139
|
+
readonly usage: NeutralUsage;
|
|
140
|
+
} | {
|
|
141
|
+
readonly type: "error";
|
|
142
|
+
readonly error: unknown;
|
|
143
|
+
};
|
|
144
|
+
export declare function streamModelTurn(req: ModelTurnRequest): AsyncIterable<NeutralStreamChunk>;
|
|
145
|
+
//# sourceMappingURL=streaming.d.ts.map
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neutral model-turn streaming — `docs/03-intelligence-runtime.md`.
|
|
3
|
+
*
|
|
4
|
+
* The one place the Vercel AI SDK's `streamText` tool loop is used (boundary rule R3 keeps the SDK
|
|
5
|
+
* inside `models/`). It runs a multi-step turn — the model calls tools, their results feed back, it
|
|
6
|
+
* continues — and yields provider-neutral chunks. The agent engine consumes these chunks without
|
|
7
|
+
* ever importing the SDK, so switching providers changes nothing above this layer.
|
|
8
|
+
*/
|
|
9
|
+
import { jsonSchema, stepCountIs, streamText, tool as aiTool } from "ai";
|
|
10
|
+
import { AgentPlatformError } from "../core/errors.js";
|
|
11
|
+
/** The text of a turn, for callers that count tokens or log. Non-text parts contribute nothing. */
|
|
12
|
+
export const turnText = (message) => typeof message.content === "string"
|
|
13
|
+
? message.content
|
|
14
|
+
: message.content
|
|
15
|
+
.filter((part) => part.kind === "text")
|
|
16
|
+
.map((part) => part.text)
|
|
17
|
+
.join("\n");
|
|
18
|
+
/** The modalities a turn actually needs, so a model can be checked against it rather than assumed. */
|
|
19
|
+
/**
|
|
20
|
+
* What non-text input a turn is carrying — REQ-036 (#185), AC-4.
|
|
21
|
+
*
|
|
22
|
+
* Counted, not inferred: one image part is one image. Audio is not counted yet and the field is deliberately
|
|
23
|
+
* absent rather than zero — a `TurnContentPart` has no duration, so the only honest answer is "this layer does
|
|
24
|
+
* not know", and a zero would be indistinguishable from a silent audio turn.
|
|
25
|
+
*/
|
|
26
|
+
export const nonTextCounts = (messages) => {
|
|
27
|
+
let images = 0;
|
|
28
|
+
for (const message of messages) {
|
|
29
|
+
if (typeof message.content === "string")
|
|
30
|
+
continue;
|
|
31
|
+
for (const part of message.content)
|
|
32
|
+
if (part.kind === "image")
|
|
33
|
+
images += 1;
|
|
34
|
+
}
|
|
35
|
+
return images === 0 ? {} : { imageCount: images };
|
|
36
|
+
};
|
|
37
|
+
export const modalitiesOf = (messages) => {
|
|
38
|
+
const found = new Set();
|
|
39
|
+
for (const message of messages) {
|
|
40
|
+
if (typeof message.content === "string")
|
|
41
|
+
continue;
|
|
42
|
+
for (const part of message.content) {
|
|
43
|
+
if (part.kind === "image")
|
|
44
|
+
found.add("image");
|
|
45
|
+
// A file's modality is its media type: a PDF needs `pdf`, an audio file needs `audio`. Anything else is
|
|
46
|
+
// sent as a file and left to the provider — guessing a modality we cannot name would defeat the check.
|
|
47
|
+
if (part.kind === "file") {
|
|
48
|
+
if (part.mediaType === "application/pdf")
|
|
49
|
+
found.add("pdf");
|
|
50
|
+
else if (part.mediaType.startsWith("audio/"))
|
|
51
|
+
found.add("audio");
|
|
52
|
+
else if (part.mediaType.startsWith("video/"))
|
|
53
|
+
found.add("video");
|
|
54
|
+
else if (part.mediaType.startsWith("image/"))
|
|
55
|
+
found.add("image");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return [...found];
|
|
60
|
+
};
|
|
61
|
+
const isZodSchema = (s) => typeof s === "object" && s !== null && typeof s.safeParse === "function";
|
|
62
|
+
/** A JSON-schema-shaped object: `{ type: "object", … }`. Enough to tell one from a Zod schema or from absence. */
|
|
63
|
+
const isJsonSchema = (s) => typeof s === "object" && s !== null && typeof s.type === "string";
|
|
64
|
+
const toToolSet = (tools) => {
|
|
65
|
+
const set = {};
|
|
66
|
+
for (const t of tools) {
|
|
67
|
+
set[t.name] = aiTool({
|
|
68
|
+
...(t.description ? { description: t.description } : {}),
|
|
69
|
+
/**
|
|
70
|
+
* A **JSON schema is honoured**, not discarded — #159.
|
|
71
|
+
*
|
|
72
|
+
* `ModelTurnTool.inputSchema` documents itself as "Zod schema or JSON-schema object", and every tool the
|
|
73
|
+
* platform builds carries a JSON schema: that is what `defineDelegatingTool` takes and what
|
|
74
|
+
* `ToolDescriptor.inputSchema` holds. The previous version kept only a Zod schema and replaced everything
|
|
75
|
+
* else with a permissive `{ type: "object", additionalProperties: true }`.
|
|
76
|
+
*
|
|
77
|
+
* A permissive schema tells the model the tool takes *any* object — so it has no parameter names to fill in
|
|
78
|
+
* and emits calls with **empty arguments**. Every JSON-schema tool in the platform was reaching the model
|
|
79
|
+
* effectively undocumented, and the work it was asked to do silently did not happen: the assistant was
|
|
80
|
+
* asked to remember a fact, called `remember` with `{}`, and two turns later said it knew nothing.
|
|
81
|
+
*
|
|
82
|
+
* Wrapping with `jsonSchema()` is all that was missing. The permissive fallback stays for a tool that
|
|
83
|
+
* genuinely declares nothing, which is the only case it was meant for.
|
|
84
|
+
*/
|
|
85
|
+
inputSchema: isZodSchema(t.inputSchema)
|
|
86
|
+
? t.inputSchema
|
|
87
|
+
: isJsonSchema(t.inputSchema)
|
|
88
|
+
? jsonSchema(t.inputSchema)
|
|
89
|
+
: jsonSchema({ type: "object", additionalProperties: true }),
|
|
90
|
+
execute: (input) => t.execute(input),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return set;
|
|
94
|
+
};
|
|
95
|
+
const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
96
|
+
/**
|
|
97
|
+
* Run one turn and yield neutral chunks. `streamText` drives the model↔tool loop up to `maxSteps`;
|
|
98
|
+
* this reads its `fullStream` and normalizes the pieces the engine cares about (text deltas, tool
|
|
99
|
+
* calls/results, final usage, errors).
|
|
100
|
+
*/
|
|
101
|
+
/**
|
|
102
|
+
* A short, safe message for a thrown tool error.
|
|
103
|
+
*
|
|
104
|
+
* Bounded and message-only. The model needs enough to decide whether to retry or explain, and a validation
|
|
105
|
+
* message ("expected string, received undefined") is exactly that — but the value lands in the durable event log,
|
|
106
|
+
* so it is truncated and the object itself never travels.
|
|
107
|
+
*/
|
|
108
|
+
const errorCodeOf = (error) => {
|
|
109
|
+
if (typeof error === "object" && error !== null) {
|
|
110
|
+
const code = error.code;
|
|
111
|
+
if (typeof code === "string" && code.length > 0 && code.length <= 64)
|
|
112
|
+
return code;
|
|
113
|
+
const name = error.constructor?.name;
|
|
114
|
+
if (typeof name === "string" && name.length > 0)
|
|
115
|
+
return name;
|
|
116
|
+
}
|
|
117
|
+
return "tool-failed";
|
|
118
|
+
};
|
|
119
|
+
const errorMessageOf = (error) => {
|
|
120
|
+
const message = typeof error === "object" && error !== null && typeof error.message === "string"
|
|
121
|
+
? error.message
|
|
122
|
+
: String(error);
|
|
123
|
+
return message.slice(0, 200);
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* A turn's parts, in the SDK's shape.
|
|
127
|
+
*
|
|
128
|
+
* A message whose content is a plain string stays a plain string rather than being wrapped in a single text
|
|
129
|
+
* part. Providers treat the two identically, but the wire form differs, and a change that rewrites every
|
|
130
|
+
* existing text turn is a change whose blast radius is every conversation rather than the ones with an
|
|
131
|
+
* attachment in them.
|
|
132
|
+
*/
|
|
133
|
+
const toModelContent = (content) => {
|
|
134
|
+
if (typeof content === "string")
|
|
135
|
+
return content;
|
|
136
|
+
return content.map((part) => part.kind === "text"
|
|
137
|
+
? { type: "text", text: part.text }
|
|
138
|
+
: part.kind === "image"
|
|
139
|
+
? { type: "image", image: part.image, ...(part.mediaType === undefined ? {} : { mediaType: part.mediaType }) }
|
|
140
|
+
: {
|
|
141
|
+
type: "file",
|
|
142
|
+
data: part.data,
|
|
143
|
+
mediaType: part.mediaType,
|
|
144
|
+
...(part.filename === undefined ? {} : { filename: part.filename }),
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
export async function* streamModelTurn(req) {
|
|
148
|
+
/**
|
|
149
|
+
* Refuse a modality the model cannot take — #185.
|
|
150
|
+
*
|
|
151
|
+
* Fail closed, and loudly. The alternatives are both worse: dropping the attachment sends the model a turn
|
|
152
|
+
* that reads as if the user attached nothing, and it answers confidently about a message it never saw; and
|
|
153
|
+
* substituting a text description silently makes the transcript a record of something that did not happen.
|
|
154
|
+
*
|
|
155
|
+
* `resolveModel` already refuses to *hand out* a model that lacks a required modality, so in the normal path
|
|
156
|
+
* this never fires. It fires when a caller resolved a model for a text turn and an attachment arrived later in
|
|
157
|
+
* the conversation — which is exactly the case the resolution-time check cannot see.
|
|
158
|
+
*/
|
|
159
|
+
if (req.modelModalities !== undefined) {
|
|
160
|
+
const needed = modalitiesOf(req.messages);
|
|
161
|
+
const missing = needed.filter((m) => !req.modelModalities.includes(m));
|
|
162
|
+
if (missing.length > 0)
|
|
163
|
+
throw new AgentPlatformError({
|
|
164
|
+
code: "capability_unavailable",
|
|
165
|
+
message: `this turn carries ${missing.join(", ")} and the resolved model accepts only ` +
|
|
166
|
+
`${req.modelModalities.join(", ")}. Resolve a model with the required modalities, or remove the ` +
|
|
167
|
+
`attachment from the turn.`,
|
|
168
|
+
retryable: false,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const messages = req.messages.map((m) => ({ role: m.role, content: toModelContent(m.content) }));
|
|
172
|
+
const result = streamText({
|
|
173
|
+
model: req.model,
|
|
174
|
+
...(req.system ? { system: req.system } : {}),
|
|
175
|
+
messages,
|
|
176
|
+
...(req.tools && req.tools.length > 0 ? { tools: toToolSet(req.tools) } : {}),
|
|
177
|
+
stopWhen: stepCountIs(req.maxSteps ?? 8),
|
|
178
|
+
...(req.abortSignal ? { abortSignal: req.abortSignal } : {}),
|
|
179
|
+
// Spread conditionally so an unset parameter leaves the provider's own default alone, rather than pinning it
|
|
180
|
+
// to a value this layer invented. #160: none of these was sent at all before.
|
|
181
|
+
...(req.maxOutputTokens === undefined ? {} : { maxOutputTokens: req.maxOutputTokens }),
|
|
182
|
+
...(req.temperature === undefined ? {} : { temperature: req.temperature }),
|
|
183
|
+
...(req.topP === undefined ? {} : { topP: req.topP }),
|
|
184
|
+
...(req.stopSequences === undefined ? {} : { stopSequences: [...req.stopSequences] }),
|
|
185
|
+
});
|
|
186
|
+
for await (const chunk of result.fullStream) {
|
|
187
|
+
switch (chunk.type) {
|
|
188
|
+
case "text-delta":
|
|
189
|
+
yield { type: "text-delta", id: chunk.id ?? "text", text: chunk.text ?? "" };
|
|
190
|
+
break;
|
|
191
|
+
case "tool-call":
|
|
192
|
+
yield {
|
|
193
|
+
type: "tool-call",
|
|
194
|
+
toolCallId: chunk.toolCallId,
|
|
195
|
+
toolName: chunk.toolName,
|
|
196
|
+
input: chunk.input,
|
|
197
|
+
};
|
|
198
|
+
break;
|
|
199
|
+
case "tool-result":
|
|
200
|
+
yield {
|
|
201
|
+
type: "tool-result",
|
|
202
|
+
toolCallId: chunk.toolCallId,
|
|
203
|
+
toolName: chunk.toolName,
|
|
204
|
+
output: chunk.output,
|
|
205
|
+
};
|
|
206
|
+
break;
|
|
207
|
+
/**
|
|
208
|
+
* A tool whose `execute` threw — #159.
|
|
209
|
+
*
|
|
210
|
+
* The SDK runs the tools itself and emits `tool-error` when one throws. This case did not exist, so the
|
|
211
|
+
* chunk fell into `default: break` and the failure produced **no neutral chunk at all**: the model never
|
|
212
|
+
* learned the call failed, the projection was left with a `tool-call` and no matching `tool-result`, and
|
|
213
|
+
* the dangling call was only finalised as `tool.failed` at the end of the run — after the answer had
|
|
214
|
+
* already been written. A user asked the assistant to remember something and was told, two turns later,
|
|
215
|
+
* that it knew nothing about them.
|
|
216
|
+
*
|
|
217
|
+
* Mapped to a `tool-result` carrying an error payload rather than to a bare `error`, and that choice is the
|
|
218
|
+
* point: a bare error is a *run* failure, but a tool failing is a normal thing the model should see and
|
|
219
|
+
* respond to. Emitting it as the call's result both resolves the dangling call and puts the failure in the
|
|
220
|
+
* model's own history, so it can retry or say what went wrong.
|
|
221
|
+
*/
|
|
222
|
+
case "tool-error":
|
|
223
|
+
yield {
|
|
224
|
+
type: "tool-result",
|
|
225
|
+
toolCallId: chunk.toolCallId,
|
|
226
|
+
toolName: chunk.toolName,
|
|
227
|
+
output: {
|
|
228
|
+
ok: false,
|
|
229
|
+
// The message only, never the thrown object: a stack or a cause chain routinely carries a URL with a
|
|
230
|
+
// token in it, and this value goes into the run's durable event log. Same rule as #143's `recordError`.
|
|
231
|
+
error: {
|
|
232
|
+
code: errorCodeOf(chunk.error),
|
|
233
|
+
message: errorMessageOf(chunk.error),
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
break;
|
|
238
|
+
case "error":
|
|
239
|
+
yield { type: "error", error: chunk.error };
|
|
240
|
+
break;
|
|
241
|
+
case "finish": {
|
|
242
|
+
const usage = chunk.totalUsage ?? {};
|
|
243
|
+
yield {
|
|
244
|
+
type: "finish",
|
|
245
|
+
usage: {
|
|
246
|
+
inputTokens: num(usage.inputTokens),
|
|
247
|
+
outputTokens: num(usage.outputTokens),
|
|
248
|
+
cachedInputTokens: num(usage.cachedInputTokens),
|
|
249
|
+
...(usage.reasoningTokens !== undefined ? { reasoningTokens: num(usage.reasoningTokens) } : {}),
|
|
250
|
+
/**
|
|
251
|
+
* Non-text input, counted from the request rather than read from the response — #185 AC-4.
|
|
252
|
+
*
|
|
253
|
+
* This is the only place that knows both things at once: what went out, and that the turn has
|
|
254
|
+
* finished. The provider's usage object has no modality breakdown in the SDK's neutral shape, and
|
|
255
|
+
* the vendors that expose one bury it under vendor-specific metadata — so reading it there would
|
|
256
|
+
* work for one provider and silently report zero for the others, which is worse than reporting
|
|
257
|
+
* nothing because it looks like data.
|
|
258
|
+
*
|
|
259
|
+
* Only emitted when there is something to say. A text-only turn carries no `imageCount: 0`, so a
|
|
260
|
+
* pricing record that charges per image cannot be handed a zero it might treat as "unknown".
|
|
261
|
+
*/
|
|
262
|
+
...nonTextCounts(req.messages),
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
default:
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=streaming.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image description through a vision-capable model (#132).
|
|
3
|
+
*
|
|
4
|
+
* Lives in `models/` because boundary rule R3 keeps the Vercel AI SDK here: the layers above call
|
|
5
|
+
* `describeImage` and never see the SDK, exactly as they call `streamModelTurn` and never see `streamText`.
|
|
6
|
+
*
|
|
7
|
+
* **The capability check is not in this file, and that is the design.** `ModelRegistry.resolve` already
|
|
8
|
+
* refuses when no model satisfies `requiredModalities: ["image"]` — it throws `capability_unavailable`. So
|
|
9
|
+
* AC-3 ("a model without vision capability is never used for an image request") holds because the caller
|
|
10
|
+
* cannot *obtain* a model to pass here, not because this function checks one. A check here would be a second
|
|
11
|
+
* gate to keep in step with the first, and the weaker of the two would be the one that mattered.
|
|
12
|
+
*/
|
|
13
|
+
import { type LanguageModel } from "ai";
|
|
14
|
+
import type { NeutralUsage } from "./streaming.js";
|
|
15
|
+
/** The prompt a description is produced against. Injectable, because what matters differs by caller. */
|
|
16
|
+
export declare const DEFAULT_VISION_PROMPT: string;
|
|
17
|
+
export type DescribeImageRequest = {
|
|
18
|
+
readonly model: LanguageModel;
|
|
19
|
+
readonly bytes: Uint8Array;
|
|
20
|
+
readonly mediaType: string;
|
|
21
|
+
readonly prompt?: string;
|
|
22
|
+
readonly maxOutputTokens?: number;
|
|
23
|
+
readonly abortSignal?: AbortSignal;
|
|
24
|
+
};
|
|
25
|
+
export type DescribeImageResult = {
|
|
26
|
+
readonly text: string;
|
|
27
|
+
readonly usage: NeutralUsage;
|
|
28
|
+
readonly modelId: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* One vision call, returning the description and its token usage.
|
|
32
|
+
*
|
|
33
|
+
* The usage comes back rather than being recorded here: this module has no `ExecutionContext` and no
|
|
34
|
+
* `UsageRecorder`, and reaching for either would put a billing decision inside a provider call. The caller
|
|
35
|
+
* records it, which is also what makes the record idempotent on the *extraction*, not on the HTTP request.
|
|
36
|
+
*/
|
|
37
|
+
export declare const describeImage: (req: DescribeImageRequest) => Promise<DescribeImageResult>;
|
|
38
|
+
//# sourceMappingURL=vision.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image description through a vision-capable model (#132).
|
|
3
|
+
*
|
|
4
|
+
* Lives in `models/` because boundary rule R3 keeps the Vercel AI SDK here: the layers above call
|
|
5
|
+
* `describeImage` and never see the SDK, exactly as they call `streamModelTurn` and never see `streamText`.
|
|
6
|
+
*
|
|
7
|
+
* **The capability check is not in this file, and that is the design.** `ModelRegistry.resolve` already
|
|
8
|
+
* refuses when no model satisfies `requiredModalities: ["image"]` — it throws `capability_unavailable`. So
|
|
9
|
+
* AC-3 ("a model without vision capability is never used for an image request") holds because the caller
|
|
10
|
+
* cannot *obtain* a model to pass here, not because this function checks one. A check here would be a second
|
|
11
|
+
* gate to keep in step with the first, and the weaker of the two would be the one that mattered.
|
|
12
|
+
*/
|
|
13
|
+
import { generateText } from "ai";
|
|
14
|
+
import { AgentPlatformError } from "../core/errors.js";
|
|
15
|
+
/** The prompt a description is produced against. Injectable, because what matters differs by caller. */
|
|
16
|
+
export const DEFAULT_VISION_PROMPT = "Describe this image for someone who cannot see it. State what it shows, transcribe any text exactly as " +
|
|
17
|
+
"it appears, and describe any chart's axes and values. Do not speculate about anything not visible.";
|
|
18
|
+
const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
19
|
+
/**
|
|
20
|
+
* One vision call, returning the description and its token usage.
|
|
21
|
+
*
|
|
22
|
+
* The usage comes back rather than being recorded here: this module has no `ExecutionContext` and no
|
|
23
|
+
* `UsageRecorder`, and reaching for either would put a billing decision inside a provider call. The caller
|
|
24
|
+
* records it, which is also what makes the record idempotent on the *extraction*, not on the HTTP request.
|
|
25
|
+
*/
|
|
26
|
+
export const describeImage = async (req) => {
|
|
27
|
+
const content = [
|
|
28
|
+
{ type: "text", text: req.prompt ?? DEFAULT_VISION_PROMPT },
|
|
29
|
+
// The SDK takes the bytes and the media type; it decides per provider whether that becomes a base64
|
|
30
|
+
// data part or an upload. Passing a URL instead would mean a signed URL in a provider request, which is
|
|
31
|
+
// the thing #129 clamps to fifteen minutes precisely to avoid.
|
|
32
|
+
{ type: "image", image: req.bytes, mediaType: req.mediaType },
|
|
33
|
+
];
|
|
34
|
+
try {
|
|
35
|
+
const result = await generateText({
|
|
36
|
+
model: req.model,
|
|
37
|
+
messages: [{ role: "user", content }],
|
|
38
|
+
...(req.maxOutputTokens === undefined ? {} : { maxOutputTokens: req.maxOutputTokens }),
|
|
39
|
+
...(req.abortSignal ? { abortSignal: req.abortSignal } : {}),
|
|
40
|
+
});
|
|
41
|
+
const usage = result.usage;
|
|
42
|
+
return {
|
|
43
|
+
text: result.text,
|
|
44
|
+
usage: {
|
|
45
|
+
inputTokens: num(usage?.inputTokens),
|
|
46
|
+
outputTokens: num(usage?.outputTokens),
|
|
47
|
+
cachedInputTokens: num(usage?.cachedInputTokens),
|
|
48
|
+
},
|
|
49
|
+
modelId: typeof req.model === "string" ? req.model : (req.model.modelId ?? "unknown"),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// Wrapped so a provider's error shape does not reach the extraction pipeline, which would then have to
|
|
54
|
+
// know about three SDKs to decide whether a failure is retryable.
|
|
55
|
+
throw new AgentPlatformError({
|
|
56
|
+
code: "provider_unavailable",
|
|
57
|
+
message: "The vision model could not describe that image.",
|
|
58
|
+
retryable: true,
|
|
59
|
+
}, { cause: error });
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=vision.js.map
|