@amodalai/runtime 0.1.26 → 0.2.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/dist/src/__fixtures__/README.md +84 -0
- package/dist/src/__fixtures__/smoke-agent/amodal.json +11 -0
- package/dist/src/__fixtures__/smoke-agent/automations/test-auto.md +5 -0
- package/dist/src/__fixtures__/smoke-agent/connections/mock-api/access.json +11 -0
- package/dist/src/__fixtures__/smoke-agent/connections/mock-api/spec.json +4 -0
- package/dist/src/__fixtures__/smoke-agent/connections/mock-api/surface.md +9 -0
- package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/access.json +3 -0
- package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/spec.json +8 -0
- package/dist/src/__fixtures__/smoke-agent/evals/basic-eval.md +12 -0
- package/dist/src/__fixtures__/smoke-agent/knowledge/test-knowledge.md +3 -0
- package/dist/src/__fixtures__/smoke-agent/skills/test-skill/SKILL.md +11 -0
- package/dist/src/__fixtures__/smoke-agent/stores/test-items.json +11 -0
- package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.d.ts +18 -0
- package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.js +22 -0
- package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.js.map +1 -0
- package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/tool.json +17 -0
- package/dist/src/__fixtures__/smoke.test.js +718 -0
- package/dist/src/__fixtures__/smoke.test.js.map +1 -0
- package/dist/src/__tests__/test-providers.d.ts +40 -0
- package/dist/src/__tests__/test-providers.js +61 -0
- package/dist/src/__tests__/test-providers.js.map +1 -0
- package/dist/src/agent/local-server.d.ts +3 -3
- package/dist/src/agent/local-server.js +213 -122
- package/dist/src/agent/local-server.js.map +1 -1
- package/dist/src/agent/loop-types.d.ts +175 -0
- package/dist/src/agent/loop-types.js +20 -0
- package/dist/src/agent/loop-types.js.map +1 -0
- package/dist/src/agent/loop.d.ts +31 -0
- package/dist/src/agent/loop.js +139 -0
- package/dist/src/agent/loop.js.map +1 -0
- package/dist/src/agent/loop.test.js +1030 -0
- package/dist/src/agent/loop.test.js.map +1 -0
- package/dist/src/agent/mcp-config.d.ts +28 -0
- package/dist/src/agent/mcp-config.js +57 -0
- package/dist/src/agent/mcp-config.js.map +1 -0
- package/dist/src/agent/page-builder.js +6 -1
- package/dist/src/agent/page-builder.js.map +1 -1
- package/dist/src/agent/proactive/proactive-runner.d.ts +24 -8
- package/dist/src/agent/proactive/proactive-runner.js +30 -32
- package/dist/src/agent/proactive/proactive-runner.js.map +1 -1
- package/dist/src/agent/proactive/proactive-runner.test.d.ts +1 -1
- package/dist/src/agent/proactive/proactive-runner.test.js +75 -87
- package/dist/src/agent/proactive/proactive-runner.test.js.map +1 -1
- package/dist/src/agent/routes/admin-chat.d.ts +15 -3
- package/dist/src/agent/routes/admin-chat.js +63 -18
- package/dist/src/agent/routes/admin-chat.js.map +1 -1
- package/dist/src/agent/routes/automations.js +5 -6
- package/dist/src/agent/routes/automations.js.map +1 -1
- package/dist/src/agent/routes/evals.d.ts +3 -2
- package/dist/src/agent/routes/evals.js +25 -12
- package/dist/src/agent/routes/evals.js.map +1 -1
- package/dist/src/agent/routes/files.js +7 -9
- package/dist/src/agent/routes/files.js.map +1 -1
- package/dist/src/agent/routes/inspect.d.ts +6 -2
- package/dist/src/agent/routes/inspect.js +31 -17
- package/dist/src/agent/routes/inspect.js.map +1 -1
- package/dist/src/agent/routes/inspect.test.js +18 -42
- package/dist/src/agent/routes/inspect.test.js.map +1 -1
- package/dist/src/agent/routes/stores.js +9 -12
- package/dist/src/agent/routes/stores.js.map +1 -1
- package/dist/src/agent/routes/task.d.ts +15 -3
- package/dist/src/agent/routes/task.js +16 -7
- package/dist/src/agent/routes/task.js.map +1 -1
- package/dist/src/agent/routes/task.test.d.ts +1 -1
- package/dist/src/agent/routes/task.test.js +70 -53
- package/dist/src/agent/routes/task.test.js.map +1 -1
- package/dist/src/agent/routes/webhooks.js +12 -3
- package/dist/src/agent/routes/webhooks.js.map +1 -1
- package/dist/src/agent/session-store.d.ts +11 -2
- package/dist/src/agent/session-store.js +1 -1
- package/dist/src/agent/session-store.js.map +1 -1
- package/dist/src/agent/snapshot-server.d.ts +2 -22
- package/dist/src/agent/snapshot-server.js +50 -27
- package/dist/src/agent/snapshot-server.js.map +1 -1
- package/dist/src/agent/states/compacting.d.ts +14 -0
- package/dist/src/agent/states/compacting.js +258 -0
- package/dist/src/agent/states/compacting.js.map +1 -0
- package/dist/src/agent/states/confirming.d.ts +10 -0
- package/dist/src/agent/states/confirming.js +76 -0
- package/dist/src/agent/states/confirming.js.map +1 -0
- package/dist/src/agent/states/dispatching.d.ts +18 -0
- package/dist/src/agent/states/dispatching.js +241 -0
- package/dist/src/agent/states/dispatching.js.map +1 -0
- package/dist/src/agent/states/executing.d.ts +21 -0
- package/dist/src/agent/states/executing.js +308 -0
- package/dist/src/agent/states/executing.js.map +1 -0
- package/dist/src/agent/states/streaming.d.ts +10 -0
- package/dist/src/agent/states/streaming.js +155 -0
- package/dist/src/agent/states/streaming.js.map +1 -0
- package/dist/src/agent/states/thinking.d.ts +13 -0
- package/dist/src/agent/states/thinking.js +233 -0
- package/dist/src/agent/states/thinking.js.map +1 -0
- package/dist/src/agent/token-estimate.d.ts +17 -0
- package/dist/src/agent/token-estimate.js +13 -0
- package/dist/src/agent/token-estimate.js.map +1 -0
- package/dist/src/agent/tool-executor-local.js +9 -18
- package/dist/src/agent/tool-executor-local.js.map +1 -1
- package/dist/src/agent/tool-executor-local.test.js +3 -5
- package/dist/src/agent/tool-executor-local.test.js.map +1 -1
- package/dist/src/api/create-agent.d.ts +15 -0
- package/dist/src/api/create-agent.js +137 -0
- package/dist/src/api/create-agent.js.map +1 -0
- package/dist/src/api/types.d.ts +68 -0
- package/dist/src/api/types.js +7 -0
- package/dist/src/api/types.js.map +1 -0
- package/dist/src/context/compiler.d.ts +13 -0
- package/dist/src/context/compiler.js +358 -0
- package/dist/src/context/compiler.js.map +1 -0
- package/dist/src/context/compiler.test.js +532 -0
- package/dist/src/context/compiler.test.js.map +1 -0
- package/dist/src/context/types.d.ts +110 -0
- package/dist/src/context/types.js +7 -0
- package/dist/src/context/types.js.map +1 -0
- package/dist/src/index.d.ts +33 -6
- package/dist/src/index.js +35 -21
- package/dist/src/index.js.map +1 -1
- package/dist/src/providers/create-provider.d.ts +23 -0
- package/dist/src/providers/create-provider.js +185 -0
- package/dist/src/providers/create-provider.js.map +1 -0
- package/dist/src/{agent/stores-e2e.test.d.ts → providers/create-provider.test.d.ts} +1 -1
- package/dist/src/providers/create-provider.test.js +95 -0
- package/dist/src/providers/create-provider.test.js.map +1 -0
- package/dist/src/providers/failover.d.ts +38 -0
- package/dist/src/providers/failover.js +147 -0
- package/dist/src/providers/failover.js.map +1 -0
- package/dist/src/providers/failover.test.d.ts +6 -0
- package/dist/src/providers/failover.test.js +169 -0
- package/dist/src/providers/failover.test.js.map +1 -0
- package/dist/src/providers/types.d.ts +110 -0
- package/dist/src/providers/types.js +7 -0
- package/dist/src/providers/types.js.map +1 -0
- package/dist/src/routes/ai-stream.d.ts +13 -10
- package/dist/src/routes/ai-stream.js +76 -41
- package/dist/src/routes/ai-stream.js.map +1 -1
- package/dist/src/routes/chat-new.test.d.ts +6 -0
- package/dist/src/routes/chat-new.test.js +107 -0
- package/dist/src/routes/chat-new.test.js.map +1 -0
- package/dist/src/routes/chat-stream-new.test.d.ts +6 -0
- package/dist/src/routes/chat-stream-new.test.js +135 -0
- package/dist/src/routes/chat-stream-new.test.js.map +1 -0
- package/dist/src/routes/chat-stream.d.ts +14 -4
- package/dist/src/routes/chat-stream.js +47 -29
- package/dist/src/routes/chat-stream.js.map +1 -1
- package/dist/src/routes/chat.d.ts +13 -4
- package/dist/src/routes/chat.js +60 -23
- package/dist/src/routes/chat.js.map +1 -1
- package/dist/src/routes/health.d.ts +3 -2
- package/dist/src/routes/health.js.map +1 -1
- package/dist/src/routes/route-helpers.d.ts +50 -0
- package/dist/src/routes/route-helpers.js +80 -0
- package/dist/src/routes/route-helpers.js.map +1 -0
- package/dist/src/routes/session-resolver.d.ts +72 -0
- package/dist/src/routes/session-resolver.js +123 -0
- package/dist/src/routes/session-resolver.js.map +1 -0
- package/dist/src/routes/session-resolver.test.d.ts +6 -0
- package/dist/src/routes/session-resolver.test.js +206 -0
- package/dist/src/routes/session-resolver.test.js.map +1 -0
- package/dist/src/routes/webhooks.d.ts +3 -1
- package/dist/src/routes/webhooks.js +12 -4
- package/dist/src/routes/webhooks.js.map +1 -1
- package/dist/src/security/permission-checker.d.ts +80 -0
- package/dist/src/security/permission-checker.js +75 -0
- package/dist/src/security/permission-checker.js.map +1 -0
- package/dist/src/security/permission-checker.test.d.ts +6 -0
- package/dist/src/security/permission-checker.test.js +208 -0
- package/dist/src/security/permission-checker.test.js.map +1 -0
- package/dist/src/server.d.ts +12 -11
- package/dist/src/server.js +44 -46
- package/dist/src/server.js.map +1 -1
- package/dist/src/server.test.d.ts +1 -1
- package/dist/src/server.test.js +6 -144
- package/dist/src/server.test.js.map +1 -1
- package/dist/src/session/manager.d.ts +98 -0
- package/dist/src/session/manager.js +364 -0
- package/dist/src/session/manager.js.map +1 -0
- package/dist/src/session/manager.test.d.ts +6 -0
- package/dist/src/session/manager.test.js +315 -0
- package/dist/src/session/manager.test.js.map +1 -0
- package/dist/src/session/session-builder.d.ts +71 -0
- package/dist/src/session/session-builder.js +364 -0
- package/dist/src/session/session-builder.js.map +1 -0
- package/dist/src/session/session-builder.test.d.ts +6 -0
- package/dist/src/session/session-builder.test.js +352 -0
- package/dist/src/session/session-builder.test.js.map +1 -0
- package/dist/src/session/store.d.ts +57 -0
- package/dist/src/session/store.js +167 -0
- package/dist/src/session/store.js.map +1 -0
- package/dist/src/session/store.test.d.ts +6 -0
- package/dist/src/session/store.test.js +145 -0
- package/dist/src/session/store.test.js.map +1 -0
- package/dist/src/session/stream-hooks.d.ts +39 -0
- package/dist/src/session/stream-hooks.js +7 -0
- package/dist/src/session/stream-hooks.js.map +1 -0
- package/dist/src/session/tool-context-factory.d.ts +60 -0
- package/dist/src/session/tool-context-factory.js +190 -0
- package/dist/src/session/tool-context-factory.js.map +1 -0
- package/dist/src/session/tool-context-factory.test.d.ts +6 -0
- package/dist/src/session/tool-context-factory.test.js +287 -0
- package/dist/src/session/tool-context-factory.test.js.map +1 -0
- package/dist/src/session/types.d.ts +188 -0
- package/dist/src/session/types.js +7 -0
- package/dist/src/session/types.js.map +1 -0
- package/dist/src/stores/drizzle-store-backend.d.ts +49 -0
- package/dist/src/stores/drizzle-store-backend.js +306 -0
- package/dist/src/stores/drizzle-store-backend.js.map +1 -0
- package/dist/src/stores/drizzle-store-backend.test.d.ts +6 -0
- package/dist/src/stores/drizzle-store-backend.test.js +215 -0
- package/dist/src/stores/drizzle-store-backend.test.js.map +1 -0
- package/dist/src/stores/index.d.ts +4 -0
- package/dist/src/stores/index.js +2 -0
- package/dist/src/stores/index.js.map +1 -1
- package/dist/src/stores/pglite-store-backend.d.ts +16 -19
- package/dist/src/stores/pglite-store-backend.js +85 -239
- package/dist/src/stores/pglite-store-backend.js.map +1 -1
- package/dist/src/stores/postgres-store-backend.d.ts +30 -0
- package/dist/src/stores/postgres-store-backend.js +100 -0
- package/dist/src/stores/postgres-store-backend.js.map +1 -0
- package/dist/src/stores/schema.d.ts +491 -0
- package/dist/src/stores/schema.js +57 -0
- package/dist/src/stores/schema.js.map +1 -0
- package/dist/src/tools/admin-file-tools.d.ts +13 -0
- package/dist/src/tools/admin-file-tools.js +200 -0
- package/dist/src/tools/admin-file-tools.js.map +1 -0
- package/dist/src/tools/admin-file-tools.test.d.ts +6 -0
- package/dist/src/tools/admin-file-tools.test.js +152 -0
- package/dist/src/tools/admin-file-tools.test.js.map +1 -0
- package/dist/src/tools/custom-tool-adapter.d.ts +41 -0
- package/dist/src/tools/custom-tool-adapter.js +190 -0
- package/dist/src/tools/custom-tool-adapter.js.map +1 -0
- package/dist/src/tools/custom-tool-adapter.test.d.ts +6 -0
- package/dist/src/tools/custom-tool-adapter.test.js +244 -0
- package/dist/src/tools/custom-tool-adapter.test.js.map +1 -0
- package/dist/src/tools/dispatch-tool.d.ts +52 -0
- package/dist/src/tools/dispatch-tool.js +71 -0
- package/dist/src/tools/dispatch-tool.js.map +1 -0
- package/dist/src/tools/dispatch-tool.test.d.ts +6 -0
- package/dist/src/tools/dispatch-tool.test.js +75 -0
- package/dist/src/tools/dispatch-tool.test.js.map +1 -0
- package/dist/src/tools/mcp-tool-adapter.d.ts +18 -0
- package/dist/src/tools/mcp-tool-adapter.js +135 -0
- package/dist/src/tools/mcp-tool-adapter.js.map +1 -0
- package/dist/src/tools/mcp-tool-adapter.test.d.ts +6 -0
- package/dist/src/tools/mcp-tool-adapter.test.js +227 -0
- package/dist/src/tools/mcp-tool-adapter.test.js.map +1 -0
- package/dist/src/tools/registry.d.ts +25 -0
- package/dist/src/tools/registry.js +72 -0
- package/dist/src/tools/registry.js.map +1 -0
- package/dist/src/tools/registry.test.d.ts +6 -0
- package/dist/src/tools/registry.test.js +121 -0
- package/dist/src/tools/registry.test.js.map +1 -0
- package/dist/src/tools/request-tool.d.ts +42 -0
- package/dist/src/tools/request-tool.js +190 -0
- package/dist/src/tools/request-tool.js.map +1 -0
- package/dist/src/tools/request-tool.test.d.ts +6 -0
- package/dist/src/tools/request-tool.test.js +254 -0
- package/dist/src/tools/request-tool.test.js.map +1 -0
- package/dist/src/tools/store-tools.d.ts +29 -0
- package/dist/src/tools/store-tools.js +224 -0
- package/dist/src/tools/store-tools.js.map +1 -0
- package/dist/src/tools/store-tools.test.d.ts +6 -0
- package/dist/src/tools/store-tools.test.js +216 -0
- package/dist/src/tools/store-tools.test.js.map +1 -0
- package/dist/src/tools/types.d.ts +111 -0
- package/dist/src/tools/types.js +7 -0
- package/dist/src/tools/types.js.map +1 -0
- package/dist/src/types.d.ts +20 -12
- package/dist/src/types.js +3 -2
- package/dist/src/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -4
- package/dist/src/__tests__/sse-contract.test.js +0 -464
- package/dist/src/__tests__/sse-contract.test.js.map +0 -1
- package/dist/src/__tests__/tools.test.js +0 -583
- package/dist/src/__tests__/tools.test.js.map +0 -1
- package/dist/src/agent/agent-runner.d.ts +0 -33
- package/dist/src/agent/agent-runner.js +0 -1040
- package/dist/src/agent/agent-runner.js.map +0 -1
- package/dist/src/agent/custom-tools-e2e.test.d.ts +0 -6
- package/dist/src/agent/custom-tools-e2e.test.js +0 -566
- package/dist/src/agent/custom-tools-e2e.test.js.map +0 -1
- package/dist/src/agent/request-helper.d.ts +0 -16
- package/dist/src/agent/request-helper.js +0 -96
- package/dist/src/agent/request-helper.js.map +0 -1
- package/dist/src/agent/stores-e2e.test.js +0 -433
- package/dist/src/agent/stores-e2e.test.js.map +0 -1
- package/dist/src/agent/tool-context-builder.d.ts +0 -11
- package/dist/src/agent/tool-context-builder.js +0 -102
- package/dist/src/agent/tool-context-builder.js.map +0 -1
- package/dist/src/agent/tool-context-builder.test.d.ts +0 -6
- package/dist/src/agent/tool-context-builder.test.js +0 -152
- package/dist/src/agent/tool-context-builder.test.js.map +0 -1
- package/dist/src/agent/write-repo-file.test.js +0 -270
- package/dist/src/agent/write-repo-file.test.js.map +0 -1
- package/dist/src/cron/heartbeat-runner.d.ts +0 -21
- package/dist/src/cron/heartbeat-runner.js +0 -79
- package/dist/src/cron/heartbeat-runner.js.map +0 -1
- package/dist/src/cron/heartbeat-runner.test.d.ts +0 -6
- package/dist/src/cron/heartbeat-runner.test.js +0 -120
- package/dist/src/cron/heartbeat-runner.test.js.map +0 -1
- package/dist/src/cron/heartbeat-scheduler.d.ts +0 -26
- package/dist/src/cron/heartbeat-scheduler.js +0 -55
- package/dist/src/cron/heartbeat-scheduler.js.map +0 -1
- package/dist/src/cron/heartbeat-scheduler.test.d.ts +0 -6
- package/dist/src/cron/heartbeat-scheduler.test.js +0 -61
- package/dist/src/cron/heartbeat-scheduler.test.js.map +0 -1
- package/dist/src/routes/ai-stream.test.d.ts +0 -6
- package/dist/src/routes/ai-stream.test.js +0 -586
- package/dist/src/routes/ai-stream.test.js.map +0 -1
- package/dist/src/routes/ask-user-response.d.ts +0 -30
- package/dist/src/routes/ask-user-response.js +0 -61
- package/dist/src/routes/ask-user-response.js.map +0 -1
- package/dist/src/routes/ask-user-response.test.d.ts +0 -6
- package/dist/src/routes/ask-user-response.test.js +0 -88
- package/dist/src/routes/ask-user-response.test.js.map +0 -1
- package/dist/src/routes/chat-stream.test.d.ts +0 -6
- package/dist/src/routes/chat-stream.test.js +0 -155
- package/dist/src/routes/chat-stream.test.js.map +0 -1
- package/dist/src/routes/chat.test.d.ts +0 -6
- package/dist/src/routes/chat.test.js +0 -99
- package/dist/src/routes/chat.test.js.map +0 -1
- package/dist/src/routes/widget-actions.d.ts +0 -49
- package/dist/src/routes/widget-actions.js +0 -78
- package/dist/src/routes/widget-actions.js.map +0 -1
- package/dist/src/session/custom-tool-adapter.d.ts +0 -74
- package/dist/src/session/custom-tool-adapter.js +0 -180
- package/dist/src/session/custom-tool-adapter.js.map +0 -1
- package/dist/src/session/history-converter.d.ts +0 -21
- package/dist/src/session/history-converter.js +0 -59
- package/dist/src/session/history-converter.js.map +0 -1
- package/dist/src/session/history-converter.test.d.ts +0 -6
- package/dist/src/session/history-converter.test.js +0 -130
- package/dist/src/session/history-converter.test.js.map +0 -1
- package/dist/src/session/session-manager.d.ts +0 -219
- package/dist/src/session/session-manager.js +0 -915
- package/dist/src/session/session-manager.js.map +0 -1
- package/dist/src/session/session-manager.test.d.ts +0 -6
- package/dist/src/session/session-manager.test.js +0 -455
- package/dist/src/session/session-manager.test.js.map +0 -1
- package/dist/src/session/session-runner.d.ts +0 -45
- package/dist/src/session/session-runner.js +0 -719
- package/dist/src/session/session-runner.js.map +0 -1
- package/dist/src/session/session-runner.test.d.ts +0 -6
- package/dist/src/session/session-runner.test.js +0 -834
- package/dist/src/session/session-runner.test.js.map +0 -1
- /package/dist/src/{__tests__/sse-contract.test.d.ts → __fixtures__/smoke.test.d.ts} +0 -0
- /package/dist/src/{__tests__/tools.test.d.ts → agent/loop.test.d.ts} +0 -0
- /package/dist/src/{agent/write-repo-file.test.d.ts → context/compiler.test.d.ts} +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Provider failover chain.
|
|
8
|
+
*
|
|
9
|
+
* Tries each provider in sequence. On failure, logs the error and moves
|
|
10
|
+
* to the next provider. No retries or backoff within a single provider —
|
|
11
|
+
* the chain itself IS the retry strategy. If you want retries on the
|
|
12
|
+
* primary, put the same provider config twice in the chain.
|
|
13
|
+
*
|
|
14
|
+
* All providers exhausted → throws ProviderError with context about
|
|
15
|
+
* every attempt.
|
|
16
|
+
*/
|
|
17
|
+
import type { Logger } from '@amodalai/core';
|
|
18
|
+
import type { LLMProvider, ProviderConfig } from './types.js';
|
|
19
|
+
export interface FailoverChainConfig {
|
|
20
|
+
/** Primary provider (always first in the chain) */
|
|
21
|
+
primary: ProviderConfig;
|
|
22
|
+
/** Fallback providers, tried in order after primary fails */
|
|
23
|
+
fallbacks?: ProviderConfig[];
|
|
24
|
+
/** Logger for structured events */
|
|
25
|
+
logger: Logger;
|
|
26
|
+
/** Session ID for log context */
|
|
27
|
+
sessionId?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create an LLMProvider that tries a chain of providers in order.
|
|
31
|
+
*
|
|
32
|
+
* For generateText: tries each provider sequentially until one succeeds.
|
|
33
|
+
* For streamText: wraps the fullStream so that if a provider's stream
|
|
34
|
+
* fails before yielding any events, the next provider is tried. Once
|
|
35
|
+
* events have been yielded, mid-stream errors propagate (can't restart
|
|
36
|
+
* a partial stream).
|
|
37
|
+
*/
|
|
38
|
+
export declare function createFailoverProvider(config: FailoverChainConfig): LLMProvider;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { createProvider } from './create-provider.js';
|
|
7
|
+
import { ProviderError } from '../errors.js';
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// createFailoverProvider
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Create an LLMProvider that tries a chain of providers in order.
|
|
13
|
+
*
|
|
14
|
+
* For generateText: tries each provider sequentially until one succeeds.
|
|
15
|
+
* For streamText: wraps the fullStream so that if a provider's stream
|
|
16
|
+
* fails before yielding any events, the next provider is tried. Once
|
|
17
|
+
* events have been yielded, mid-stream errors propagate (can't restart
|
|
18
|
+
* a partial stream).
|
|
19
|
+
*/
|
|
20
|
+
export function createFailoverProvider(config) {
|
|
21
|
+
const chain = [config.primary, ...(config.fallbacks ?? [])];
|
|
22
|
+
const providers = chain.map((c) => createProvider(c));
|
|
23
|
+
const primary = providers[0];
|
|
24
|
+
function logFailure(idx, errorMsg, failedAttempts) {
|
|
25
|
+
failedAttempts.push({
|
|
26
|
+
provider: chain[idx].provider,
|
|
27
|
+
model: chain[idx].model,
|
|
28
|
+
error: errorMsg,
|
|
29
|
+
});
|
|
30
|
+
config.logger.error('provider_call_failed', {
|
|
31
|
+
session: config.sessionId,
|
|
32
|
+
provider: `${chain[idx].provider}/${chain[idx].model}`,
|
|
33
|
+
error: errorMsg,
|
|
34
|
+
willRetry: idx < providers.length - 1,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function logSuccess(idx, failedAttempts) {
|
|
38
|
+
if (idx > 0) {
|
|
39
|
+
config.logger.warn('provider_failover_used', {
|
|
40
|
+
session: config.sessionId,
|
|
41
|
+
failed: failedAttempts.map((a) => `${a.provider}/${a.model}`),
|
|
42
|
+
succeeded: `${chain[idx].provider}/${chain[idx].model}`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function throwAllFailed(failedAttempts, cause) {
|
|
47
|
+
throw new ProviderError('All providers failed', {
|
|
48
|
+
provider: 'failover',
|
|
49
|
+
context: { attempts: failedAttempts },
|
|
50
|
+
cause,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
model: primary.model,
|
|
55
|
+
provider: primary.provider,
|
|
56
|
+
languageModel: primary.languageModel,
|
|
57
|
+
streamText(opts) {
|
|
58
|
+
// Deferred promises for usage and text — resolved when stream completes.
|
|
59
|
+
let resolveUsage;
|
|
60
|
+
let rejectUsage;
|
|
61
|
+
const usagePromise = new Promise((res, rej) => {
|
|
62
|
+
resolveUsage = res;
|
|
63
|
+
rejectUsage = rej;
|
|
64
|
+
});
|
|
65
|
+
let resolveText;
|
|
66
|
+
let rejectText;
|
|
67
|
+
const textPromise = new Promise((res, rej) => {
|
|
68
|
+
resolveText = res;
|
|
69
|
+
rejectText = rej;
|
|
70
|
+
});
|
|
71
|
+
const failedAttempts = [];
|
|
72
|
+
const fullStream = (async function* () {
|
|
73
|
+
let lastUsage;
|
|
74
|
+
const textChunks = [];
|
|
75
|
+
for (let i = 0; i < providers.length; i++) {
|
|
76
|
+
let yieldedAny = false;
|
|
77
|
+
try {
|
|
78
|
+
const result = providers[i].streamText(opts);
|
|
79
|
+
for await (const event of result.fullStream) {
|
|
80
|
+
yieldedAny = true;
|
|
81
|
+
if (event.type === 'finish')
|
|
82
|
+
lastUsage = event.usage;
|
|
83
|
+
if (event.type === 'text-delta')
|
|
84
|
+
textChunks.push(event.textDelta);
|
|
85
|
+
yield event;
|
|
86
|
+
}
|
|
87
|
+
// Stream completed successfully
|
|
88
|
+
logSuccess(i, failedAttempts);
|
|
89
|
+
resolveUsage(lastUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 });
|
|
90
|
+
resolveText(textChunks.join(''));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
95
|
+
// If we already yielded events, we can't retry — log and propagate
|
|
96
|
+
if (yieldedAny) {
|
|
97
|
+
logFailure(i, errorMsg, failedAttempts);
|
|
98
|
+
rejectUsage(err);
|
|
99
|
+
rejectText(err);
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
logFailure(i, errorMsg, failedAttempts);
|
|
103
|
+
if (i === providers.length - 1) {
|
|
104
|
+
rejectUsage(err);
|
|
105
|
+
rejectText(err);
|
|
106
|
+
throwAllFailed(failedAttempts, err);
|
|
107
|
+
}
|
|
108
|
+
// Try next provider
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
})();
|
|
112
|
+
return {
|
|
113
|
+
fullStream,
|
|
114
|
+
// textStream derived from fullStream — consumers should use one or the other
|
|
115
|
+
textStream: (async function* () {
|
|
116
|
+
for await (const event of fullStream) {
|
|
117
|
+
if (event.type === 'text-delta') {
|
|
118
|
+
yield event.textDelta;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
})(),
|
|
122
|
+
usage: usagePromise,
|
|
123
|
+
text: textPromise,
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
async generateText(opts) {
|
|
127
|
+
const failedAttempts = [];
|
|
128
|
+
for (let i = 0; i < providers.length; i++) {
|
|
129
|
+
try {
|
|
130
|
+
const result = await providers[i].generateText(opts);
|
|
131
|
+
logSuccess(i, failedAttempts);
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
136
|
+
logFailure(i, errorMsg, failedAttempts);
|
|
137
|
+
if (i === providers.length - 1) {
|
|
138
|
+
throwAllFailed(failedAttempts, err);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Unreachable — TypeScript needs this
|
|
143
|
+
throw new ProviderError('No providers configured', { provider: 'failover' });
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=failover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failover.js","sourceRoot":"","sources":["../../../src/providers/failover.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Provider Failover Tests
|
|
8
|
+
*
|
|
9
|
+
* Integration tests against real providers (skipped when API keys are
|
|
10
|
+
* not available). No mocks — calls the real AI SDK through the real
|
|
11
|
+
* createProvider → createFailoverProvider chain.
|
|
12
|
+
*
|
|
13
|
+
* Scenarios:
|
|
14
|
+
* 1. Primary with invalid key + fallback with valid key → response from fallback
|
|
15
|
+
* 2. Valid primary → uses primary, no fallback
|
|
16
|
+
* 3. All providers fail → throws ProviderError with context about all attempts
|
|
17
|
+
*/
|
|
18
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
19
|
+
import { createFailoverProvider } from './failover.js';
|
|
20
|
+
import { ProviderError } from '../errors.js';
|
|
21
|
+
import { testProviders, hasAnyProvider } from '../__tests__/test-providers.js';
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
function makeMockLogger() {
|
|
26
|
+
return {
|
|
27
|
+
trace: vi.fn(),
|
|
28
|
+
debug: vi.fn(),
|
|
29
|
+
info: vi.fn(),
|
|
30
|
+
warn: vi.fn(),
|
|
31
|
+
error: vi.fn(),
|
|
32
|
+
fatal: vi.fn(),
|
|
33
|
+
child: vi.fn().mockReturnThis(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Unit tests (no API calls)
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
describe('createFailoverProvider (unit)', () => {
|
|
40
|
+
let logger;
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
logger = makeMockLogger();
|
|
43
|
+
});
|
|
44
|
+
it('exposes primary model and provider on the returned object', () => {
|
|
45
|
+
const provider = createFailoverProvider({
|
|
46
|
+
primary: { provider: 'anthropic', model: 'claude-sonnet-4-20250514', apiKey: 'fake' },
|
|
47
|
+
fallbacks: [{ provider: 'openai', model: 'gpt-4o', apiKey: 'fake' }],
|
|
48
|
+
logger,
|
|
49
|
+
});
|
|
50
|
+
expect(provider.model).toBe('claude-sonnet-4-20250514');
|
|
51
|
+
expect(provider.provider).toBe('anthropic');
|
|
52
|
+
expect(provider.languageModel).toBeDefined();
|
|
53
|
+
expect(typeof provider.streamText).toBe('function');
|
|
54
|
+
expect(typeof provider.generateText).toBe('function');
|
|
55
|
+
});
|
|
56
|
+
it('works with a single provider (no fallbacks)', () => {
|
|
57
|
+
const provider = createFailoverProvider({
|
|
58
|
+
primary: { provider: 'anthropic', model: 'claude-sonnet-4-20250514', apiKey: 'fake' },
|
|
59
|
+
logger,
|
|
60
|
+
});
|
|
61
|
+
expect(provider.model).toBe('claude-sonnet-4-20250514');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Integration tests (real API calls — skipped without keys)
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
describe.skipIf(!hasAnyProvider)('createFailoverProvider (integration)', () => {
|
|
68
|
+
let logger;
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
logger = makeMockLogger();
|
|
71
|
+
});
|
|
72
|
+
// ── 1. Primary fails (invalid key) + fallback succeeds ────────────────
|
|
73
|
+
it('uses fallback when primary has invalid API key', async () => {
|
|
74
|
+
const fallback = testProviders.cheapest();
|
|
75
|
+
const invalid = testProviders.invalid(fallback.provider);
|
|
76
|
+
const provider = createFailoverProvider({
|
|
77
|
+
primary: invalid,
|
|
78
|
+
fallbacks: [fallback],
|
|
79
|
+
logger,
|
|
80
|
+
sessionId: 'test-failover',
|
|
81
|
+
});
|
|
82
|
+
const result = await provider.generateText({
|
|
83
|
+
messages: [{ role: 'user', content: [{ type: 'text', text: 'Reply with exactly: pong' }] }],
|
|
84
|
+
maxOutputTokens: 10,
|
|
85
|
+
});
|
|
86
|
+
expect(result.text.toLowerCase()).toContain('pong');
|
|
87
|
+
expect(result.usage.inputTokens).toBeGreaterThan(0);
|
|
88
|
+
expect(result.usage.outputTokens).toBeGreaterThan(0);
|
|
89
|
+
expect(logger.error).toHaveBeenCalledWith('provider_call_failed', expect.objectContaining({ session: 'test-failover', willRetry: true }));
|
|
90
|
+
expect(logger.warn).toHaveBeenCalledWith('provider_failover_used', expect.objectContaining({
|
|
91
|
+
succeeded: `${fallback.provider}/${fallback.model}`,
|
|
92
|
+
}));
|
|
93
|
+
}, 30000);
|
|
94
|
+
// ── 2. Valid primary → uses primary, no fallback ──────────────────────
|
|
95
|
+
it('uses primary when it succeeds — fallback not touched', async () => {
|
|
96
|
+
const primary = testProviders.cheapest();
|
|
97
|
+
const provider = createFailoverProvider({
|
|
98
|
+
primary,
|
|
99
|
+
// Fallback has invalid key — would fail if reached
|
|
100
|
+
fallbacks: [testProviders.invalid(primary.provider)],
|
|
101
|
+
logger,
|
|
102
|
+
});
|
|
103
|
+
const result = await provider.generateText({
|
|
104
|
+
messages: [{ role: 'user', content: [{ type: 'text', text: 'Reply with exactly: ping' }] }],
|
|
105
|
+
maxOutputTokens: 10,
|
|
106
|
+
});
|
|
107
|
+
expect(result.text.toLowerCase()).toContain('ping');
|
|
108
|
+
expect(logger.error).not.toHaveBeenCalled();
|
|
109
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
110
|
+
}, 30000);
|
|
111
|
+
// ── 3. All providers fail → ProviderError with all attempts ───────────
|
|
112
|
+
it('throws ProviderError with context about all failed attempts', async () => {
|
|
113
|
+
const provider = createFailoverProvider({
|
|
114
|
+
primary: testProviders.invalid('anthropic'),
|
|
115
|
+
fallbacks: [testProviders.invalid('openai')],
|
|
116
|
+
logger,
|
|
117
|
+
sessionId: 'test-all-fail',
|
|
118
|
+
});
|
|
119
|
+
try {
|
|
120
|
+
await provider.generateText({
|
|
121
|
+
messages: [{ role: 'user', content: [{ type: 'text', text: 'hello' }] }],
|
|
122
|
+
maxOutputTokens: 10,
|
|
123
|
+
});
|
|
124
|
+
expect.unreachable('should have thrown');
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
expect(err).toBeInstanceOf(ProviderError);
|
|
128
|
+
const pe = err;
|
|
129
|
+
expect(pe.message).toBe('All providers failed');
|
|
130
|
+
expect(pe.provider).toBe('failover');
|
|
131
|
+
const attempts = pe.context['attempts'];
|
|
132
|
+
expect(attempts).toHaveLength(2);
|
|
133
|
+
expect(attempts[0].provider).toBe('anthropic');
|
|
134
|
+
expect(attempts[1].provider).toBe('openai');
|
|
135
|
+
}
|
|
136
|
+
expect(logger.error).toHaveBeenCalledTimes(2);
|
|
137
|
+
expect(logger.error).toHaveBeenCalledWith('provider_call_failed', expect.objectContaining({ session: 'test-all-fail', willRetry: true }));
|
|
138
|
+
expect(logger.error).toHaveBeenCalledWith('provider_call_failed', expect.objectContaining({ willRetry: false }));
|
|
139
|
+
}, 30000);
|
|
140
|
+
// ── 4. Streaming failover ─────────────────────────────────────────────
|
|
141
|
+
it('streaming: uses fallback when primary stream fails', async () => {
|
|
142
|
+
const fallback = testProviders.cheapest();
|
|
143
|
+
const invalid = testProviders.invalid(fallback.provider);
|
|
144
|
+
const provider = createFailoverProvider({
|
|
145
|
+
primary: invalid,
|
|
146
|
+
fallbacks: [fallback],
|
|
147
|
+
logger,
|
|
148
|
+
});
|
|
149
|
+
const result = provider.streamText({
|
|
150
|
+
messages: [{ role: 'user', content: [{ type: 'text', text: 'Reply with exactly: stream-ok' }] }],
|
|
151
|
+
maxOutputTokens: 10,
|
|
152
|
+
});
|
|
153
|
+
const events = [];
|
|
154
|
+
for await (const event of result.fullStream) {
|
|
155
|
+
events.push(event);
|
|
156
|
+
}
|
|
157
|
+
const textDeltas = events.filter((e) => e.type === 'text-delta');
|
|
158
|
+
expect(textDeltas.length).toBeGreaterThan(0);
|
|
159
|
+
const text = await result.text;
|
|
160
|
+
expect(text.length).toBeGreaterThan(0);
|
|
161
|
+
const usage = await result.usage;
|
|
162
|
+
expect(usage.inputTokens).toBeGreaterThan(0);
|
|
163
|
+
expect(logger.error).toHaveBeenCalledWith('provider_call_failed', expect.objectContaining({ willRetry: true }));
|
|
164
|
+
expect(logger.warn).toHaveBeenCalledWith('provider_failover_used', expect.objectContaining({
|
|
165
|
+
succeeded: `${fallback.provider}/${fallback.model}`,
|
|
166
|
+
}));
|
|
167
|
+
}, 30000);
|
|
168
|
+
});
|
|
169
|
+
//# sourceMappingURL=failover.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failover.test.js","sourceRoot":"","sources":["../../../src/providers/failover.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Provider abstraction wrapping the Vercel AI SDK.
|
|
8
|
+
*
|
|
9
|
+
* This is the boundary between Amodal code and the AI SDK. The rest of
|
|
10
|
+
* the runtime talks to LLMProvider, never to the SDK directly. This lets
|
|
11
|
+
* us swap SDK versions, add new providers, or intercept calls (logging,
|
|
12
|
+
* retries, cost tracking) in one place.
|
|
13
|
+
*/
|
|
14
|
+
import type { LanguageModel, ModelMessage, Tool, ToolChoice, ToolSet } from 'ai';
|
|
15
|
+
export interface StreamTextOptions {
|
|
16
|
+
messages: ModelMessage[];
|
|
17
|
+
system?: string;
|
|
18
|
+
tools?: Record<string, Tool>;
|
|
19
|
+
toolChoice?: ToolChoice<ToolSet>;
|
|
20
|
+
maxOutputTokens?: number;
|
|
21
|
+
temperature?: number;
|
|
22
|
+
abortSignal?: AbortSignal;
|
|
23
|
+
}
|
|
24
|
+
export interface GenerateTextOptions {
|
|
25
|
+
messages: ModelMessage[];
|
|
26
|
+
system?: string;
|
|
27
|
+
tools?: Record<string, Tool>;
|
|
28
|
+
toolChoice?: ToolChoice<ToolSet>;
|
|
29
|
+
maxOutputTokens?: number;
|
|
30
|
+
temperature?: number;
|
|
31
|
+
abortSignal?: AbortSignal;
|
|
32
|
+
}
|
|
33
|
+
export interface TokenUsage {
|
|
34
|
+
inputTokens: number;
|
|
35
|
+
outputTokens: number;
|
|
36
|
+
cachedInputTokens?: number;
|
|
37
|
+
cacheCreationInputTokens?: number;
|
|
38
|
+
totalTokens: number;
|
|
39
|
+
}
|
|
40
|
+
export interface StreamTextResult {
|
|
41
|
+
/** The full text stream — yields string chunks */
|
|
42
|
+
textStream: AsyncIterable<string>;
|
|
43
|
+
/** The full event stream — yields structured events including tool calls */
|
|
44
|
+
fullStream: AsyncIterable<StreamEvent>;
|
|
45
|
+
/** Resolves when the stream is complete with final usage */
|
|
46
|
+
usage: PromiseLike<TokenUsage>;
|
|
47
|
+
/** Resolves with the complete response text */
|
|
48
|
+
text: PromiseLike<string>;
|
|
49
|
+
}
|
|
50
|
+
export type StreamEvent = {
|
|
51
|
+
type: 'text-delta';
|
|
52
|
+
textDelta: string;
|
|
53
|
+
} | {
|
|
54
|
+
type: 'tool-call';
|
|
55
|
+
toolCallId: string;
|
|
56
|
+
toolName: string;
|
|
57
|
+
args: Record<string, unknown>;
|
|
58
|
+
} | {
|
|
59
|
+
type: 'tool-result';
|
|
60
|
+
toolCallId: string;
|
|
61
|
+
toolName: string;
|
|
62
|
+
result: unknown;
|
|
63
|
+
} | {
|
|
64
|
+
type: 'finish';
|
|
65
|
+
usage: TokenUsage;
|
|
66
|
+
} | {
|
|
67
|
+
type: 'error';
|
|
68
|
+
error: unknown;
|
|
69
|
+
};
|
|
70
|
+
export interface GenerateTextResult {
|
|
71
|
+
text: string;
|
|
72
|
+
toolCalls: Array<{
|
|
73
|
+
toolCallId: string;
|
|
74
|
+
toolName: string;
|
|
75
|
+
args: Record<string, unknown>;
|
|
76
|
+
}>;
|
|
77
|
+
usage: TokenUsage;
|
|
78
|
+
finishReason: 'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other';
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Unified LLM provider interface.
|
|
82
|
+
*
|
|
83
|
+
* Wraps the Vercel AI SDK so the rest of the runtime never imports `ai`
|
|
84
|
+
* directly. Implementations exist for Anthropic, OpenAI, Google, and
|
|
85
|
+
* any OpenAI-compatible provider (DeepSeek, Groq, Mistral, etc.).
|
|
86
|
+
*/
|
|
87
|
+
export interface LLMProvider {
|
|
88
|
+
/** Stream a text/tool response from the LLM */
|
|
89
|
+
streamText(opts: StreamTextOptions): StreamTextResult;
|
|
90
|
+
/** Generate a complete text response (non-streaming) */
|
|
91
|
+
generateText(opts: GenerateTextOptions): Promise<GenerateTextResult>;
|
|
92
|
+
/** The model identifier (e.g., 'claude-sonnet-4-20250514') */
|
|
93
|
+
readonly model: string;
|
|
94
|
+
/** The provider name (e.g., 'anthropic', 'openai', 'google') */
|
|
95
|
+
readonly provider: string;
|
|
96
|
+
/** The underlying AI SDK LanguageModel instance (for advanced use) */
|
|
97
|
+
readonly languageModel: LanguageModel;
|
|
98
|
+
}
|
|
99
|
+
export interface ProviderConfig {
|
|
100
|
+
/** Provider name: 'anthropic', 'openai', 'google', 'deepseek', etc. */
|
|
101
|
+
provider: string;
|
|
102
|
+
/** Model name: 'claude-sonnet-4-20250514', 'gpt-4o', etc. */
|
|
103
|
+
model: string;
|
|
104
|
+
/** API key (resolved from env or credentials) */
|
|
105
|
+
apiKey?: string;
|
|
106
|
+
/** Custom base URL (for self-hosted or proxy endpoints) */
|
|
107
|
+
baseUrl?: string;
|
|
108
|
+
/** AWS region (for Bedrock) */
|
|
109
|
+
region?: string;
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/providers/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
7
|
* Vercel AI SDK UI Message Stream Protocol adapter.
|
|
8
8
|
*
|
|
9
9
|
* Accepts requests in the Vercel AI SDK format, feeds the user message into
|
|
10
|
-
* the
|
|
11
|
-
* `SSEEvent` into the UI Message Stream Protocol that `@ai-sdk/react`'s
|
|
10
|
+
* the standalone session manager's `runMessage()` generator, and translates
|
|
11
|
+
* every `SSEEvent` into the UI Message Stream Protocol that `@ai-sdk/react`'s
|
|
12
12
|
* `useChat` hook expects.
|
|
13
13
|
*
|
|
14
14
|
* Protocol spec: events are newline-delimited JSON objects prefixed with
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
import { Router } from 'express';
|
|
19
19
|
import { z } from 'zod';
|
|
20
20
|
import type { AuthContext } from '../middleware/auth.js';
|
|
21
|
-
import type {
|
|
22
|
-
import {
|
|
21
|
+
import type { StandaloneSessionManager } from '../session/manager.js';
|
|
22
|
+
import type { StreamHooks } from '../session/stream-hooks.js';
|
|
23
23
|
import { type SSEEvent } from '../types.js';
|
|
24
|
+
import type { BundleResolver, SharedResources } from './session-resolver.js';
|
|
24
25
|
export declare const AIStreamRequestSchema: z.ZodObject<{
|
|
25
26
|
messages: z.ZodArray<z.ZodObject<{
|
|
26
27
|
role: z.ZodEnum<["user", "assistant", "system"]>;
|
|
@@ -36,14 +37,14 @@ export declare const AIStreamRequestSchema: z.ZodObject<{
|
|
|
36
37
|
}>, "many">>;
|
|
37
38
|
content: z.ZodOptional<z.ZodString>;
|
|
38
39
|
}, "strip", z.ZodTypeAny, {
|
|
39
|
-
role: "
|
|
40
|
+
role: "system" | "user" | "assistant";
|
|
40
41
|
content?: string | undefined;
|
|
41
42
|
parts?: {
|
|
42
43
|
type: string;
|
|
43
44
|
text?: string | undefined;
|
|
44
45
|
}[] | undefined;
|
|
45
46
|
}, {
|
|
46
|
-
role: "
|
|
47
|
+
role: "system" | "user" | "assistant";
|
|
47
48
|
content?: string | undefined;
|
|
48
49
|
parts?: {
|
|
49
50
|
type: string;
|
|
@@ -55,7 +56,7 @@ export declare const AIStreamRequestSchema: z.ZodObject<{
|
|
|
55
56
|
deploy_id: z.ZodOptional<z.ZodString>;
|
|
56
57
|
}, "strip", z.ZodTypeAny, {
|
|
57
58
|
messages: {
|
|
58
|
-
role: "
|
|
59
|
+
role: "system" | "user" | "assistant";
|
|
59
60
|
content?: string | undefined;
|
|
60
61
|
parts?: {
|
|
61
62
|
type: string;
|
|
@@ -67,7 +68,7 @@ export declare const AIStreamRequestSchema: z.ZodObject<{
|
|
|
67
68
|
deploy_id?: string | undefined;
|
|
68
69
|
}, {
|
|
69
70
|
messages: {
|
|
70
|
-
role: "
|
|
71
|
+
role: "system" | "user" | "assistant";
|
|
71
72
|
content?: string | undefined;
|
|
72
73
|
parts?: {
|
|
73
74
|
type: string;
|
|
@@ -152,7 +153,9 @@ interface AdapterState {
|
|
|
152
153
|
export declare function translateEvent(event: SSEEvent, state: AdapterState): UIStreamEvent[];
|
|
153
154
|
export declare function extractUserMessage(messages: AIStreamRequest['messages']): string;
|
|
154
155
|
export interface AIStreamRouterOptions {
|
|
155
|
-
sessionManager:
|
|
156
|
+
sessionManager: StandaloneSessionManager;
|
|
157
|
+
bundleResolver: BundleResolver;
|
|
158
|
+
shared: SharedResources;
|
|
156
159
|
/** Factory that builds per-request stream hooks from the auth context */
|
|
157
160
|
createStreamHooks?: (auth?: AuthContext) => StreamHooks;
|
|
158
161
|
}
|