indusagi 0.13.7 → 0.13.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.js +28713 -0
- package/dist/ai.js +19964 -0
- package/dist/capabilities.js +2730 -0
- package/dist/cli.js +9 -0
- package/dist/connectors-saas.js +3355 -0
- package/dist/index.js +18211 -0
- package/dist/interop.js +3247 -0
- package/dist/llmgateway.js +3343 -0
- package/dist/mcp.js +2448 -0
- package/dist/memory.js +0 -0
- package/dist/react-host/index.js +101 -0
- package/dist/react-host/ink.js +97 -0
- package/dist/react-host/jsx-runtime.js +95 -0
- package/dist/react-ink.js +3834 -0
- package/dist/runtime.js +4521 -0
- package/dist/sarvam-mcp.js +960 -0
- package/dist/shell-app.js +12435 -0
- package/dist/smithy.js +8133 -0
- package/dist/swarm.js +8918 -0
- package/dist/tracing.js +761 -0
- package/dist/tui.js +2213 -0
- package/dist/types/capabilities/backends/index.d.ts +10 -0
- package/dist/types/capabilities/backends/node-backends.d.ts +30 -0
- package/dist/types/capabilities/capabilities.test.d.ts +14 -0
- package/dist/types/capabilities/files/diff.d.ts +49 -0
- package/dist/types/capabilities/files/edit.d.ts +21 -0
- package/dist/types/capabilities/files/ls.d.ts +16 -0
- package/dist/types/capabilities/files/read-state-gate.d.ts +69 -0
- package/dist/types/capabilities/files/read-state-gate.test.d.ts +14 -0
- package/dist/types/capabilities/files/read.d.ts +15 -0
- package/dist/types/capabilities/files/write.d.ts +16 -0
- package/dist/types/capabilities/index.d.ts +29 -0
- package/dist/types/capabilities/kernel/backends.d.ts +119 -0
- package/dist/types/capabilities/kernel/context.d.ts +52 -0
- package/dist/types/capabilities/kernel/index.d.ts +24 -0
- package/dist/types/capabilities/kernel/output.d.ts +40 -0
- package/dist/types/capabilities/kernel/registry.d.ts +61 -0
- package/dist/types/capabilities/kernel/spec.d.ts +168 -0
- package/dist/types/capabilities/planning/todo.d.ts +62 -0
- package/dist/types/capabilities/registry.d.ts +44 -0
- package/dist/types/capabilities/search/find.d.ts +33 -0
- package/dist/types/capabilities/search/grep.d.ts +30 -0
- package/dist/types/capabilities/shell/bash.d.ts +18 -0
- package/dist/types/capabilities/shell/process.d.ts +33 -0
- package/dist/types/capabilities/web/webfetch.d.ts +25 -0
- package/dist/types/capabilities/web/websearch.d.ts +23 -0
- package/dist/types/connectors-saas/connectors-saas.test.d.ts +12 -0
- package/dist/types/connectors-saas/index.d.ts +16 -0
- package/dist/types/connectors-saas/saas/adapter/composio-backend.d.ts +60 -0
- package/dist/types/connectors-saas/saas/control/connect.d.ts +114 -0
- package/dist/types/connectors-saas/saas/control/tools.d.ts +67 -0
- package/dist/types/connectors-saas/saas/core/builder.d.ts +86 -0
- package/dist/types/connectors-saas/saas/core/cache.d.ts +53 -0
- package/dist/types/connectors-saas/saas/core/index.d.ts +14 -0
- package/dist/types/connectors-saas/saas/core/port.d.ts +157 -0
- package/dist/types/connectors-saas/saas/core/scope-planner.d.ts +74 -0
- package/dist/types/connectors-saas/saas/gateway.d.ts +133 -0
- package/dist/types/connectors-saas/saas/render/format.d.ts +54 -0
- package/dist/types/connectors-saas/saas/render/index.d.ts +19 -0
- package/dist/types/connectors-saas/saas/render/summarizers.d.ts +58 -0
- package/dist/types/connectors-sarvam/connect.d.ts +14 -0
- package/dist/types/connectors-sarvam/connectors-sarvam.test.d.ts +14 -0
- package/dist/types/connectors-sarvam/index.d.ts +14 -0
- package/dist/types/connectors-sarvam/toolbox.d.ts +19 -0
- package/dist/types/connectors-sarvam/types.d.ts +49 -0
- package/dist/types/connectors-zoho/connect.d.ts +22 -0
- package/dist/types/connectors-zoho/connectors-zoho.test.d.ts +10 -0
- package/dist/types/connectors-zoho/index.d.ts +13 -0
- package/dist/types/connectors-zoho/toolbox.d.ts +19 -0
- package/dist/types/connectors-zoho/types.d.ts +56 -0
- package/dist/types/facade/agent.d.ts +1 -0
- package/dist/types/facade/ai.d.ts +1 -0
- package/dist/types/facade/bot/actions/bash.d.ts +71 -0
- package/dist/types/facade/bot/actions/bash.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/checkpoint.d.ts +49 -0
- package/dist/types/facade/bot/actions/checkpoint.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/composio/accounts.d.ts +22 -0
- package/dist/types/facade/bot/actions/composio/client.d.ts +39 -0
- package/dist/types/facade/bot/actions/composio/connect.d.ts +19 -0
- package/dist/types/facade/bot/actions/composio/enable.d.ts +25 -0
- package/dist/types/facade/bot/actions/composio/execute.d.ts +26 -0
- package/dist/types/facade/bot/actions/composio/helpers.d.ts +16 -0
- package/dist/types/facade/bot/actions/composio/index.d.ts +9 -0
- package/dist/types/facade/bot/actions/composio/provider.d.ts +8 -0
- package/dist/types/facade/bot/actions/composio/toolkits.d.ts +22 -0
- package/dist/types/facade/bot/actions/composio/tools.d.ts +28 -0
- package/dist/types/facade/bot/actions/composio/types.d.ts +137 -0
- package/dist/types/facade/bot/actions/crew/activity-tracker.d.ts +65 -0
- package/dist/types/facade/bot/actions/crew/cleanup.d.ts +50 -0
- package/dist/types/facade/bot/actions/crew/fs-lock.d.ts +11 -0
- package/dist/types/facade/bot/actions/crew/index.d.ts +11 -0
- package/dist/types/facade/bot/actions/crew/mailbox.d.ts +20 -0
- package/dist/types/facade/bot/actions/crew/model-policy.d.ts +22 -0
- package/dist/types/facade/bot/actions/crew/names.d.ts +27 -0
- package/dist/types/facade/bot/actions/crew/protocol.d.ts +74 -0
- package/dist/types/facade/bot/actions/crew/task-store.d.ts +88 -0
- package/dist/types/facade/bot/actions/crew/team-attach-claim.d.ts +35 -0
- package/dist/types/facade/bot/actions/crew/team-config.d.ts +54 -0
- package/dist/types/facade/bot/actions/crew/worktree.d.ts +39 -0
- package/dist/types/facade/bot/actions/edit-diff.d.ts +66 -0
- package/dist/types/facade/bot/actions/edit-utils.d.ts +86 -0
- package/dist/types/facade/bot/actions/edit.d.ts +59 -0
- package/dist/types/facade/bot/actions/edit.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/find.d.ts +27 -0
- package/dist/types/facade/bot/actions/find.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/grep.d.ts +53 -0
- package/dist/types/facade/bot/actions/grep.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/index.d.ts +316 -0
- package/dist/types/facade/bot/actions/kit/hook-runner.d.ts +12 -0
- package/dist/types/facade/bot/actions/kit/image-resize.d.ts +19 -0
- package/dist/types/facade/bot/actions/kit/mime.d.ts +1 -0
- package/dist/types/facade/bot/actions/kit/shell.d.ts +12 -0
- package/dist/types/facade/bot/actions/ls.d.ts +41 -0
- package/dist/types/facade/bot/actions/path-utils.d.ts +8 -0
- package/dist/types/facade/bot/actions/process-controller.d.ts +14 -0
- package/dist/types/facade/bot/actions/process-manager.d.ts +59 -0
- package/dist/types/facade/bot/actions/process-types.d.ts +73 -0
- package/dist/types/facade/bot/actions/process.d.ts +37 -0
- package/dist/types/facade/bot/actions/read-state.d.ts +83 -0
- package/dist/types/facade/bot/actions/read-state.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/read.d.ts +43 -0
- package/dist/types/facade/bot/actions/read.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/registry.d.ts +20 -0
- package/dist/types/facade/bot/actions/sandbox-backend.d.ts +99 -0
- package/dist/types/facade/bot/actions/sandbox-backend.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/todo-store.d.ts +76 -0
- package/dist/types/facade/bot/actions/todo-types.d.ts +72 -0
- package/dist/types/facade/bot/actions/todo.d.ts +76 -0
- package/dist/types/facade/bot/actions/truncate.d.ts +78 -0
- package/dist/types/facade/bot/actions/webfetch.d.ts +35 -0
- package/dist/types/facade/bot/actions/websearch.d.ts +37 -0
- package/dist/types/facade/bot/actions/websearch.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/write.d.ts +45 -0
- package/dist/types/facade/bot/agent-loop.d.ts +30 -0
- package/dist/types/facade/bot/agent-loop.test.d.ts +1 -0
- package/dist/types/facade/bot/agent.d.ts +140 -0
- package/dist/types/facade/bot/error-handler.d.ts +7 -0
- package/dist/types/facade/bot/event-bus.d.ts +8 -0
- package/dist/types/facade/bot/index.d.ts +7 -0
- package/dist/types/facade/bot/messages.d.ts +83 -0
- package/dist/types/facade/bot/permission-gate.test.d.ts +1 -0
- package/dist/types/facade/bot/proxy.d.ts +70 -0
- package/dist/types/facade/bot/session-manager.d.ts +409 -0
- package/dist/types/facade/bot/state-manager.d.ts +21 -0
- package/dist/types/facade/bot/telemetry.d.ts +6 -0
- package/dist/types/facade/bot/types.d.ts +267 -0
- package/dist/types/facade/mcp-core/client-pool.d.ts +105 -0
- package/dist/types/facade/mcp-core/client.d.ts +245 -0
- package/dist/types/facade/mcp-core/client.test.d.ts +18 -0
- package/dist/types/facade/mcp-core/config.d.ts +60 -0
- package/dist/types/facade/mcp-core/errors.d.ts +103 -0
- package/dist/types/facade/mcp-core/index.d.ts +59 -0
- package/dist/types/facade/mcp-core/oauth/flow.d.ts +72 -0
- package/dist/types/facade/mcp-core/oauth/index.d.ts +18 -0
- package/dist/types/facade/mcp-core/oauth/oauth.test.d.ts +8 -0
- package/dist/types/facade/mcp-core/oauth/provider.d.ts +51 -0
- package/dist/types/facade/mcp-core/oauth/token-store.d.ts +31 -0
- package/dist/types/facade/mcp-core/oauth/types.d.ts +96 -0
- package/dist/types/facade/mcp-core/schema-converter.d.ts +67 -0
- package/dist/types/facade/mcp-core/server.d.ts +110 -0
- package/dist/types/facade/mcp-core/tool-factory.d.ts +62 -0
- package/dist/types/facade/mcp-core/toolbox-bridge.d.ts +46 -0
- package/dist/types/facade/mcp-core/toolbox-bridge.test.d.ts +8 -0
- package/dist/types/facade/mcp-core/types.d.ts +298 -0
- package/dist/types/facade/mcp.d.ts +1 -0
- package/dist/types/facade/memory.d.ts +1 -0
- package/dist/types/facade/ml/adapters/amazon-bedrock.d.ts +32 -0
- package/dist/types/facade/ml/adapters/anthropic-retry.test.d.ts +1 -0
- package/dist/types/facade/ml/adapters/anthropic.d.ts +78 -0
- package/dist/types/facade/ml/adapters/azure-openai-responses.d.ts +14 -0
- package/dist/types/facade/ml/adapters/google-shared.d.ts +99 -0
- package/dist/types/facade/ml/adapters/google-vertex.d.ts +14 -0
- package/dist/types/facade/ml/adapters/google.d.ts +15 -0
- package/dist/types/facade/ml/adapters/kimi.d.ts +21 -0
- package/dist/types/facade/ml/adapters/mock.d.ts +9 -0
- package/dist/types/facade/ml/adapters/nvidia.d.ts +12 -0
- package/dist/types/facade/ml/adapters/openai-codex-responses.d.ts +8 -0
- package/dist/types/facade/ml/adapters/openai-completions.d.ts +24 -0
- package/dist/types/facade/ml/adapters/openai-responses-shared.d.ts +25 -0
- package/dist/types/facade/ml/adapters/openai-responses.d.ts +12 -0
- package/dist/types/facade/ml/adapters/openai-scaffold.d.ts +3 -0
- package/dist/types/facade/ml/adapters/register-builtins.d.ts +2 -0
- package/dist/types/facade/ml/adapters/simple-options.d.ts +39 -0
- package/dist/types/facade/ml/adapters/simple-options.test.d.ts +1 -0
- package/dist/types/facade/ml/adapters/transform-messages.d.ts +23 -0
- package/dist/types/facade/ml/api-registry.d.ts +49 -0
- package/dist/types/facade/ml/cli.d.ts +2 -0
- package/dist/types/facade/ml/env-api-keys.d.ts +19 -0
- package/dist/types/facade/ml/index.d.ts +27 -0
- package/dist/types/facade/ml/kit/auth/anthropic.d.ts +17 -0
- package/dist/types/facade/ml/kit/auth/github-copilot.d.ts +22 -0
- package/dist/types/facade/ml/kit/auth/index.d.ts +50 -0
- package/dist/types/facade/ml/kit/auth/kimi.d.ts +23 -0
- package/dist/types/facade/ml/kit/auth/oauth-page.d.ts +2 -0
- package/dist/types/facade/ml/kit/auth/openai-codex.d.ts +36 -0
- package/dist/types/facade/ml/kit/auth/pkce.d.ts +12 -0
- package/dist/types/facade/ml/kit/auth/types.d.ts +67 -0
- package/dist/types/facade/ml/kit/base-stream-handler.d.ts +30 -0
- package/dist/types/facade/ml/kit/event-stream.d.ts +63 -0
- package/dist/types/facade/ml/kit/index.d.ts +14 -0
- package/dist/types/facade/ml/kit/json-parse.d.ts +23 -0
- package/dist/types/facade/ml/kit/message-transform.d.ts +32 -0
- package/dist/types/facade/ml/kit/output-factory.d.ts +8 -0
- package/dist/types/facade/ml/kit/overflow.d.ts +9 -0
- package/dist/types/facade/ml/kit/provider-adapter.d.ts +56 -0
- package/dist/types/facade/ml/kit/provider-client-builder.d.ts +36 -0
- package/dist/types/facade/ml/kit/provider-consolidation.d.ts +3 -0
- package/dist/types/facade/ml/kit/provider-constants.d.ts +34 -0
- package/dist/types/facade/ml/kit/provider-errors.d.ts +52 -0
- package/dist/types/facade/ml/kit/sanitize-unicode.d.ts +2 -0
- package/dist/types/facade/ml/kit/stream-event-helper.d.ts +38 -0
- package/dist/types/facade/ml/kit/stream-handler-types.d.ts +46 -0
- package/dist/types/facade/ml/kit/streaming-state-manager.d.ts +42 -0
- package/dist/types/facade/ml/kit/tool-converter.d.ts +49 -0
- package/dist/types/facade/ml/kit/typebox-helpers.d.ts +24 -0
- package/dist/types/facade/ml/kit/validation.d.ts +26 -0
- package/dist/types/facade/ml/models.d.ts +41 -0
- package/dist/types/facade/ml/models.generated.d.ts +12405 -0
- package/dist/types/facade/ml/stream.d.ts +27 -0
- package/dist/types/facade/ml/types.d.ts +269 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/interop/index.d.ts +12 -0
- package/dist/types/interop/interop.test.d.ts +19 -0
- package/dist/types/interop/protocol-bridge/bridge.d.ts +52 -0
- package/dist/types/interop/protocol-bridge/contract.d.ts +249 -0
- package/dist/types/interop/protocol-bridge/endpoint.d.ts +124 -0
- package/dist/types/interop/protocol-bridge/fleet.d.ts +92 -0
- package/dist/types/interop/protocol-bridge/host.d.ts +61 -0
- package/dist/types/interop/protocol-bridge/index.d.ts +16 -0
- package/dist/types/interop/protocol-bridge/schema.d.ts +29 -0
- package/dist/types/llmgateway/catalog/cards.d.ts +18 -0
- package/dist/types/llmgateway/catalog/cost.d.ts +15 -0
- package/dist/types/llmgateway/catalog/index.d.ts +7 -0
- package/dist/types/llmgateway/catalog/query.d.ts +40 -0
- package/dist/types/llmgateway/connectors/anthropic.d.ts +35 -0
- package/dist/types/llmgateway/connectors/azure-openai.d.ts +33 -0
- package/dist/types/llmgateway/connectors/bedrock.d.ts +113 -0
- package/dist/types/llmgateway/connectors/google-vertex.d.ts +38 -0
- package/dist/types/llmgateway/connectors/google.d.ts +40 -0
- package/dist/types/llmgateway/connectors/index.d.ts +26 -0
- package/dist/types/llmgateway/connectors/kimi.d.ts +28 -0
- package/dist/types/llmgateway/connectors/mock.d.ts +37 -0
- package/dist/types/llmgateway/connectors/nvidia.d.ts +27 -0
- package/dist/types/llmgateway/connectors/ollama.d.ts +45 -0
- package/dist/types/llmgateway/connectors/openai-chat.d.ts +25 -0
- package/dist/types/llmgateway/connectors/openai-responses.d.ts +49 -0
- package/dist/types/llmgateway/contract/connector.d.ts +32 -0
- package/dist/types/llmgateway/contract/conversation.d.ts +86 -0
- package/dist/types/llmgateway/contract/emission.d.ts +55 -0
- package/dist/types/llmgateway/contract/errors.d.ts +27 -0
- package/dist/types/llmgateway/contract/index.d.ts +13 -0
- package/dist/types/llmgateway/contract/model-card.d.ts +42 -0
- package/dist/types/llmgateway/contract/options.d.ts +31 -0
- package/dist/types/llmgateway/contract/reply.d.ts +33 -0
- package/dist/types/llmgateway/conversion/index.d.ts +10 -0
- package/dist/types/llmgateway/conversion/mappers.d.ts +65 -0
- package/dist/types/llmgateway/conversion/openai-compatible.d.ts +46 -0
- package/dist/types/llmgateway/conversion/reduce.d.ts +20 -0
- package/dist/types/llmgateway/credentials/oauth.d.ts +73 -0
- package/dist/types/llmgateway/credentials/pkce.d.ts +52 -0
- package/dist/types/llmgateway/credentials/secrets.d.ts +76 -0
- package/dist/types/llmgateway/gateway.d.ts +34 -0
- package/dist/types/llmgateway/gateway.test.d.ts +14 -0
- package/dist/types/llmgateway/index.d.ts +11 -0
- package/dist/types/llmgateway/streaming/channel.d.ts +24 -0
- package/dist/types/llmgateway/streaming/index.d.ts +10 -0
- package/dist/types/llmgateway/streaming/ndjson.d.ts +17 -0
- package/dist/types/llmgateway/streaming/sse.d.ts +31 -0
- package/dist/types/react-host/index.d.ts +9 -0
- package/dist/types/react-host/ink.d.ts +5 -0
- package/dist/types/react-host/jsx-runtime.d.ts +4 -0
- package/dist/types/react-host/loader.d.ts +3 -0
- package/dist/types/react-ink/components/ChangelogBlock.d.ts +8 -0
- package/dist/types/react-ink/components/DisplayBlockView.d.ts +8 -0
- package/dist/types/react-ink/components/Footer.d.ts +11 -0
- package/dist/types/react-ink/components/MessageList.d.ts +14 -0
- package/dist/types/react-ink/components/MessageRow.d.ts +13 -0
- package/dist/types/react-ink/components/StatusLine.d.ts +18 -0
- package/dist/types/react-ink/components/TaskPanel.d.ts +11 -0
- package/dist/types/react-ink/components/ToolEventBlock.d.ts +48 -0
- package/dist/types/react-ink/components/ToolEventBlock.test.d.ts +1 -0
- package/dist/types/react-ink/components/dialogs/DialogFrame.d.ts +8 -0
- package/dist/types/react-ink/components/dialogs/LoginDialog.d.ts +14 -0
- package/dist/types/react-ink/components/dialogs/ModelDialog.d.ts +9 -0
- package/dist/types/react-ink/components/dialogs/OAuthDialog.d.ts +9 -0
- package/dist/types/react-ink/components/dialogs/ScopedModelsDialog.d.ts +9 -0
- package/dist/types/react-ink/components/dialogs/SelectableDialog.d.ts +22 -0
- package/dist/types/react-ink/components/dialogs/SessionDialog.d.ts +13 -0
- package/dist/types/react-ink/components/dialogs/SettingsDialog.d.ts +14 -0
- package/dist/types/react-ink/components/dialogs/StartupSessionPicker.d.ts +9 -0
- package/dist/types/react-ink/components/dialogs/ThemeDialog.d.ts +26 -0
- package/dist/types/react-ink/components/dialogs/TreeDialog.d.ts +8 -0
- package/dist/types/react-ink/components/dialogs/UserMessageDialog.d.ts +8 -0
- package/dist/types/react-ink/components/messages/AssistantMessage.d.ts +12 -0
- package/dist/types/react-ink/components/messages/BashMessage.d.ts +8 -0
- package/dist/types/react-ink/components/messages/BranchSummaryMessage.d.ts +8 -0
- package/dist/types/react-ink/components/messages/CompactionMessage.d.ts +8 -0
- package/dist/types/react-ink/components/messages/CustomMessage.d.ts +9 -0
- package/dist/types/react-ink/components/messages/SkillInvocationMessage.d.ts +7 -0
- package/dist/types/react-ink/components/messages/ToolCallMessage.d.ts +8 -0
- package/dist/types/react-ink/components/messages/ToolResultBlock.d.ts +12 -0
- package/dist/types/react-ink/components/messages/UserMessage.d.ts +9 -0
- package/dist/types/react-ink/diff/Diff.d.ts +22 -0
- package/dist/types/react-ink/diff/diff.test.d.ts +1 -0
- package/dist/types/react-ink/diff/structured.d.ts +41 -0
- package/dist/types/react-ink/diff/word-diff.d.ts +27 -0
- package/dist/types/react-ink/index.d.ts +42 -0
- package/dist/types/react-ink/markdown/Markdown.d.ts +23 -0
- package/dist/types/react-ink/markdown/MarkdownTable.d.ts +19 -0
- package/dist/types/react-ink/markdown/StreamingMarkdown.d.ts +34 -0
- package/dist/types/react-ink/markdown/format-token.d.ts +39 -0
- package/dist/types/react-ink/markdown/highlight.d.ts +31 -0
- package/dist/types/react-ink/theme-adapter.d.ts +66 -0
- package/dist/types/react-ink/types.d.ts +133 -0
- package/dist/types/react-ink/utils/message-groups.d.ts +25 -0
- package/dist/types/react-ink/utils/selection-dialog.d.ts +2 -0
- package/dist/types/react-ink/utils/session-browser.d.ts +27 -0
- package/dist/types/react-ink/utils/tool-display.d.ts +37 -0
- package/dist/types/runtime/cadence/cadence.test.d.ts +6 -0
- package/dist/types/runtime/cadence/fold.d.ts +48 -0
- package/dist/types/runtime/cadence/index.d.ts +12 -0
- package/dist/types/runtime/cadence/reducer.d.ts +44 -0
- package/dist/types/runtime/conductor/agent.d.ts +71 -0
- package/dist/types/runtime/conductor/index.d.ts +11 -0
- package/dist/types/runtime/contract/config.d.ts +44 -0
- package/dist/types/runtime/contract/effect.d.ts +41 -0
- package/dist/types/runtime/contract/errors.d.ts +22 -0
- package/dist/types/runtime/contract/events.d.ts +63 -0
- package/dist/types/runtime/contract/index.d.ts +18 -0
- package/dist/types/runtime/contract/run-state.d.ts +57 -0
- package/dist/types/runtime/contract/session.d.ts +42 -0
- package/dist/types/runtime/contract/signal.d.ts +49 -0
- package/dist/types/runtime/contract/tools.d.ts +45 -0
- package/dist/types/runtime/dispatch/index.d.ts +9 -0
- package/dist/types/runtime/dispatch/scheduler.d.ts +53 -0
- package/dist/types/runtime/index.d.ts +22 -0
- package/dist/types/runtime/ledger/accumulator.d.ts +44 -0
- package/dist/types/runtime/ledger/bus.d.ts +50 -0
- package/dist/types/runtime/ledger/index.d.ts +11 -0
- package/dist/types/runtime/memory/compactor.d.ts +56 -0
- package/dist/types/runtime/memory/estimate.d.ts +21 -0
- package/dist/types/runtime/memory/index.d.ts +8 -0
- package/dist/types/runtime/runtime.test.d.ts +15 -0
- package/dist/types/runtime/store/dag.d.ts +72 -0
- package/dist/types/runtime/store/hash.d.ts +18 -0
- package/dist/types/runtime/store/index.d.ts +12 -0
- package/dist/types/runtime/store/persist.d.ts +49 -0
- package/dist/types/runtime/turn/driver.d.ts +32 -0
- package/dist/types/runtime/turn/index.d.ts +8 -0
- package/dist/types/runtime/wire/index.d.ts +10 -0
- package/dist/types/runtime/wire/projectors.d.ts +53 -0
- package/dist/types/shell-app/auth-cli/index.d.ts +9 -0
- package/dist/types/shell-app/auth-cli/oauth-cli.d.ts +73 -0
- package/dist/types/shell-app/boot/context.d.ts +108 -0
- package/dist/types/shell-app/boot/index.d.ts +13 -0
- package/dist/types/shell-app/boot/pipeline.d.ts +66 -0
- package/dist/types/shell-app/boot/stages.d.ts +77 -0
- package/dist/types/shell-app/cli.d.ts +75 -0
- package/dist/types/shell-app/config/index.d.ts +11 -0
- package/dist/types/shell-app/config/locator.d.ts +85 -0
- package/dist/types/shell-app/config/settings.d.ts +91 -0
- package/dist/types/shell-app/index.d.ts +26 -0
- package/dist/types/shell-app/invocation/flags.d.ts +89 -0
- package/dist/types/shell-app/invocation/parse.d.ts +89 -0
- package/dist/types/shell-app/locate/brand.d.ts +71 -0
- package/dist/types/shell-app/locate/index.d.ts +12 -0
- package/dist/types/shell-app/locate/locator.d.ts +122 -0
- package/dist/types/shell-app/runners/contract.d.ts +116 -0
- package/dist/types/shell-app/runners/index.d.ts +15 -0
- package/dist/types/shell-app/runners/one-shot.d.ts +21 -0
- package/dist/types/shell-app/runners/registry.d.ts +34 -0
- package/dist/types/shell-app/runners/repl.d.ts +51 -0
- package/dist/types/shell-app/runners/wire.d.ts +34 -0
- package/dist/types/shell-app/shell-app.test.d.ts +14 -0
- package/dist/types/shell-app/upgrade/index.d.ts +9 -0
- package/dist/types/shell-app/upgrade/upgrades.d.ts +62 -0
- package/dist/types/smithy/config/flag-reader.d.ts +128 -0
- package/dist/types/smithy/config/index.d.ts +9 -0
- package/dist/types/smithy/forge.d.ts +135 -0
- package/dist/types/smithy/index.d.ts +36 -0
- package/dist/types/smithy/knowledge/index.d.ts +10 -0
- package/dist/types/smithy/knowledge/loader.d.ts +64 -0
- package/dist/types/smithy/persona/blueprint.d.ts +75 -0
- package/dist/types/smithy/persona/define-agent.d.ts +51 -0
- package/dist/types/smithy/persona/index.d.ts +14 -0
- package/dist/types/smithy/persona/profiles.d.ts +44 -0
- package/dist/types/smithy/runtime/index.d.ts +9 -0
- package/dist/types/smithy/runtime/tool-ledger.d.ts +130 -0
- package/dist/types/smithy/smithy.test.d.ts +20 -0
- package/dist/types/smithy/ui/index.d.ts +11 -0
- package/dist/types/smithy/ui/transcript.d.ts +183 -0
- package/dist/types/swarm/coordinator.d.ts +192 -0
- package/dist/types/swarm/index.d.ts +33 -0
- package/dist/types/swarm/isolation/index.d.ts +14 -0
- package/dist/types/swarm/isolation/runner.d.ts +61 -0
- package/dist/types/swarm/isolation/worktree.d.ts +142 -0
- package/dist/types/swarm/kernel/faults.d.ts +52 -0
- package/dist/types/swarm/kernel/ids.d.ts +25 -0
- package/dist/types/swarm/kernel/index.d.ts +26 -0
- package/dist/types/swarm/kernel/json-cell.d.ts +113 -0
- package/dist/types/swarm/kernel/jsonl-log.d.ts +81 -0
- package/dist/types/swarm/postbox/channel.d.ts +88 -0
- package/dist/types/swarm/postbox/codecs.d.ts +253 -0
- package/dist/types/swarm/postbox/index.d.ts +16 -0
- package/dist/types/swarm/roster/index.d.ts +16 -0
- package/dist/types/swarm/roster/manifest.d.ts +185 -0
- package/dist/types/swarm/swarm.test.d.ts +24 -0
- package/dist/types/swarm/telemetry/activity.d.ts +143 -0
- package/dist/types/swarm/workboard/board.d.ts +174 -0
- package/dist/types/swarm/workboard/dep-graph.d.ts +77 -0
- package/dist/types/swarm/workboard/index.d.ts +12 -0
- package/dist/types/tracing/adapter/index.d.ts +12 -0
- package/dist/types/tracing/adapter/runtime-trace.d.ts +54 -0
- package/dist/types/tracing/channel/index.d.ts +9 -0
- package/dist/types/tracing/channel/signal.d.ts +93 -0
- package/dist/types/tracing/index.d.ts +25 -0
- package/dist/types/tracing/recorder/index.d.ts +12 -0
- package/dist/types/tracing/recorder/recorder.d.ts +75 -0
- package/dist/types/tracing/recorder/sampling.d.ts +55 -0
- package/dist/types/tracing/redaction/index.d.ts +8 -0
- package/dist/types/tracing/redaction/secret-scrubber.d.ts +95 -0
- package/dist/types/tracing/registry/hub.d.ts +57 -0
- package/dist/types/tracing/registry/index.d.ts +7 -0
- package/dist/types/tracing/signal/handle.d.ts +73 -0
- package/dist/types/tracing/signal/index.d.ts +12 -0
- package/dist/types/tracing/signal/segment.d.ts +124 -0
- package/dist/types/tracing/sinks/base.d.ts +51 -0
- package/dist/types/tracing/sinks/console.d.ts +44 -0
- package/dist/types/tracing/sinks/file.d.ts +47 -0
- package/dist/types/tracing/sinks/index.d.ts +16 -0
- package/dist/types/tracing/sinks/stream.d.ts +46 -0
- package/dist/types/tracing/tracing.test.d.ts +7 -0
- package/dist/types/ui/autocomplete.d.ts +59 -0
- package/dist/types/ui/contracts.d.ts +38 -0
- package/dist/types/ui/editor-component.d.ts +100 -0
- package/dist/types/ui/fuzzy.d.ts +29 -0
- package/dist/types/ui/index.d.ts +8 -0
- package/dist/types/ui/keybindings.d.ts +62 -0
- package/dist/types/ui/keys.d.ts +171 -0
- package/dist/types/ui/theme-types.d.ts +36 -0
- package/dist/types/ui/utils.d.ts +101 -0
- package/dist/types/ui-bridge/InteractiveApp.d.ts +38 -0
- package/dist/types/ui-bridge/adapter.d.ts +90 -0
- package/dist/types/ui-bridge/index.d.ts +37 -0
- package/dist/zoho.js +1451 -0
- package/package.json +48 -48
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,4521 @@
|
|
|
1
|
+
// src/llmgateway/contract/errors.ts
|
|
2
|
+
var GatewayError = class _GatewayError extends Error {
|
|
3
|
+
kind;
|
|
4
|
+
status;
|
|
5
|
+
provider;
|
|
6
|
+
constructor(kind, message, extra) {
|
|
7
|
+
super(message, extra?.cause !== void 0 ? { cause: extra.cause } : void 0);
|
|
8
|
+
this.name = "GatewayError";
|
|
9
|
+
this.kind = kind;
|
|
10
|
+
this.status = extra?.status;
|
|
11
|
+
this.provider = extra?.provider;
|
|
12
|
+
Object.setPrototypeOf(this, _GatewayError.prototype);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function gatewayError(kind, message, extra) {
|
|
16
|
+
return new GatewayError(kind, message, extra);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/llmgateway/catalog/cards.ts
|
|
20
|
+
var MODEL_CARDS = [
|
|
21
|
+
// ── Anthropic (Claude) ──────────────────────────────────────────────────
|
|
22
|
+
{
|
|
23
|
+
id: "claude-opus-4",
|
|
24
|
+
provider: "anthropic",
|
|
25
|
+
api: "anthropic-messages",
|
|
26
|
+
displayName: "Claude Opus 4",
|
|
27
|
+
contextWindow: 2e5,
|
|
28
|
+
maxOutputTokens: 32e3,
|
|
29
|
+
modalities: ["text", "image"],
|
|
30
|
+
reasoning: true,
|
|
31
|
+
cost: {
|
|
32
|
+
// approximate — flagship tier
|
|
33
|
+
inputPerMTok: 15,
|
|
34
|
+
outputPerMTok: 75,
|
|
35
|
+
cacheReadPerMTok: 1.5,
|
|
36
|
+
cacheWritePerMTok: 18.75
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "claude-opus-4-7",
|
|
41
|
+
provider: "anthropic",
|
|
42
|
+
api: "anthropic-messages",
|
|
43
|
+
displayName: "Claude Opus 4.7",
|
|
44
|
+
contextWindow: 1e6,
|
|
45
|
+
maxOutputTokens: 128e3,
|
|
46
|
+
modalities: ["text", "image"],
|
|
47
|
+
reasoning: true,
|
|
48
|
+
cost: {
|
|
49
|
+
// approximate — flagship tier
|
|
50
|
+
inputPerMTok: 5,
|
|
51
|
+
outputPerMTok: 25,
|
|
52
|
+
cacheReadPerMTok: 0.5,
|
|
53
|
+
cacheWritePerMTok: 6.25
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "claude-opus-4-8",
|
|
58
|
+
provider: "anthropic",
|
|
59
|
+
api: "anthropic-messages",
|
|
60
|
+
displayName: "Claude Opus 4.8",
|
|
61
|
+
contextWindow: 1e6,
|
|
62
|
+
maxOutputTokens: 128e3,
|
|
63
|
+
modalities: ["text", "image"],
|
|
64
|
+
reasoning: true,
|
|
65
|
+
cost: {
|
|
66
|
+
// approximate — flagship tier
|
|
67
|
+
inputPerMTok: 5,
|
|
68
|
+
outputPerMTok: 25,
|
|
69
|
+
cacheReadPerMTok: 0.5,
|
|
70
|
+
cacheWritePerMTok: 6.25
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "claude-sonnet-4",
|
|
75
|
+
provider: "anthropic",
|
|
76
|
+
api: "anthropic-messages",
|
|
77
|
+
displayName: "Claude Sonnet 4",
|
|
78
|
+
contextWindow: 2e5,
|
|
79
|
+
maxOutputTokens: 64e3,
|
|
80
|
+
modalities: ["text", "image"],
|
|
81
|
+
reasoning: true,
|
|
82
|
+
cost: {
|
|
83
|
+
// approximate — mid tier
|
|
84
|
+
inputPerMTok: 3,
|
|
85
|
+
outputPerMTok: 15,
|
|
86
|
+
cacheReadPerMTok: 0.3,
|
|
87
|
+
cacheWritePerMTok: 3.75
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "claude-haiku-3-5",
|
|
92
|
+
provider: "anthropic",
|
|
93
|
+
api: "anthropic-messages",
|
|
94
|
+
displayName: "Claude Haiku 3.5",
|
|
95
|
+
contextWindow: 2e5,
|
|
96
|
+
maxOutputTokens: 8192,
|
|
97
|
+
modalities: ["text", "image"],
|
|
98
|
+
reasoning: false,
|
|
99
|
+
cost: {
|
|
100
|
+
// approximate — fast/cheap tier
|
|
101
|
+
inputPerMTok: 0.8,
|
|
102
|
+
outputPerMTok: 4,
|
|
103
|
+
cacheReadPerMTok: 0.08,
|
|
104
|
+
cacheWritePerMTok: 1
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
// ── OpenAI (GPT + o-series) ─────────────────────────────────────────────
|
|
108
|
+
{
|
|
109
|
+
id: "gpt-4o",
|
|
110
|
+
provider: "openai",
|
|
111
|
+
api: "openai-completions",
|
|
112
|
+
displayName: "GPT-4o",
|
|
113
|
+
contextWindow: 128e3,
|
|
114
|
+
maxOutputTokens: 16384,
|
|
115
|
+
modalities: ["text", "image", "audio"],
|
|
116
|
+
reasoning: false,
|
|
117
|
+
cost: {
|
|
118
|
+
// approximate — omni flagship
|
|
119
|
+
inputPerMTok: 2.5,
|
|
120
|
+
outputPerMTok: 10,
|
|
121
|
+
cacheReadPerMTok: 1.25
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "gpt-4o-mini",
|
|
126
|
+
provider: "openai",
|
|
127
|
+
api: "openai-completions",
|
|
128
|
+
displayName: "GPT-4o mini",
|
|
129
|
+
contextWindow: 128e3,
|
|
130
|
+
maxOutputTokens: 16384,
|
|
131
|
+
modalities: ["text", "image"],
|
|
132
|
+
reasoning: false,
|
|
133
|
+
cost: {
|
|
134
|
+
// approximate — small omni
|
|
135
|
+
inputPerMTok: 0.15,
|
|
136
|
+
outputPerMTok: 0.6,
|
|
137
|
+
cacheReadPerMTok: 0.075
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: "o3",
|
|
142
|
+
provider: "openai",
|
|
143
|
+
api: "openai-responses",
|
|
144
|
+
displayName: "OpenAI o3",
|
|
145
|
+
contextWindow: 2e5,
|
|
146
|
+
maxOutputTokens: 1e5,
|
|
147
|
+
modalities: ["text", "image"],
|
|
148
|
+
reasoning: true,
|
|
149
|
+
cost: {
|
|
150
|
+
// approximate — reasoning flagship
|
|
151
|
+
inputPerMTok: 10,
|
|
152
|
+
outputPerMTok: 40,
|
|
153
|
+
cacheReadPerMTok: 2.5
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: "o4-mini",
|
|
158
|
+
provider: "openai",
|
|
159
|
+
api: "openai-responses",
|
|
160
|
+
displayName: "OpenAI o4-mini",
|
|
161
|
+
contextWindow: 2e5,
|
|
162
|
+
maxOutputTokens: 1e5,
|
|
163
|
+
modalities: ["text", "image"],
|
|
164
|
+
reasoning: true,
|
|
165
|
+
cost: {
|
|
166
|
+
// approximate — small reasoning
|
|
167
|
+
inputPerMTok: 1.1,
|
|
168
|
+
outputPerMTok: 4.4,
|
|
169
|
+
cacheReadPerMTok: 0.275
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
// ── Google (Gemini) ─────────────────────────────────────────────────────
|
|
173
|
+
{
|
|
174
|
+
id: "gemini-2.5-pro",
|
|
175
|
+
provider: "google",
|
|
176
|
+
api: "google-generative",
|
|
177
|
+
displayName: "Gemini 2.5 Pro",
|
|
178
|
+
contextWindow: 1e6,
|
|
179
|
+
maxOutputTokens: 65536,
|
|
180
|
+
modalities: ["text", "image", "audio"],
|
|
181
|
+
reasoning: true,
|
|
182
|
+
cost: {
|
|
183
|
+
// approximate — long-context pro tier
|
|
184
|
+
inputPerMTok: 1.25,
|
|
185
|
+
outputPerMTok: 10,
|
|
186
|
+
cacheReadPerMTok: 0.31
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "gemini-2.5-flash",
|
|
191
|
+
provider: "google",
|
|
192
|
+
api: "google-generative",
|
|
193
|
+
displayName: "Gemini 2.5 Flash",
|
|
194
|
+
contextWindow: 1e6,
|
|
195
|
+
maxOutputTokens: 65536,
|
|
196
|
+
modalities: ["text", "image", "audio"],
|
|
197
|
+
reasoning: true,
|
|
198
|
+
cost: {
|
|
199
|
+
// approximate — fast flash tier
|
|
200
|
+
inputPerMTok: 0.3,
|
|
201
|
+
outputPerMTok: 2.5,
|
|
202
|
+
cacheReadPerMTok: 0.075
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
// ── NVIDIA (OpenAI-compatible NIM endpoint) ─────────────────────────────
|
|
206
|
+
{
|
|
207
|
+
id: "nvidia/llama-3.1-nemotron-70b-instruct",
|
|
208
|
+
provider: "nvidia",
|
|
209
|
+
api: "nvidia-openai-compatible",
|
|
210
|
+
displayName: "Nemotron 70B Instruct",
|
|
211
|
+
baseUrl: "https://integrate.api.nvidia.com/v1",
|
|
212
|
+
contextWindow: 128e3,
|
|
213
|
+
maxOutputTokens: 4096,
|
|
214
|
+
modalities: ["text"],
|
|
215
|
+
reasoning: false,
|
|
216
|
+
cost: {
|
|
217
|
+
// approximate — hosted NIM inference
|
|
218
|
+
inputPerMTok: 0.2,
|
|
219
|
+
outputPerMTok: 0.2
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
// ── Kimi / Moonshot (OpenAI-compatible) ─────────────────────────────────
|
|
223
|
+
{
|
|
224
|
+
id: "kimi-k2",
|
|
225
|
+
provider: "kimi",
|
|
226
|
+
api: "kimi-openai-compatible",
|
|
227
|
+
displayName: "Kimi K2",
|
|
228
|
+
baseUrl: "https://api.moonshot.ai/v1",
|
|
229
|
+
contextWindow: 256e3,
|
|
230
|
+
maxOutputTokens: 16384,
|
|
231
|
+
modalities: ["text"],
|
|
232
|
+
reasoning: true,
|
|
233
|
+
cost: {
|
|
234
|
+
// approximate — Moonshot hosted
|
|
235
|
+
inputPerMTok: 0.6,
|
|
236
|
+
outputPerMTok: 2.5,
|
|
237
|
+
cacheReadPerMTok: 0.15
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
// ── MiniMax (Anthropic-compatible Messages API) ─────────────────────────
|
|
241
|
+
// Wire: same dialect as Claude (`anthropic-messages`) at api.minimax.io.
|
|
242
|
+
// Auth: MINIMAX_API_KEY. Ids match facade/models.generated.ts.
|
|
243
|
+
{
|
|
244
|
+
id: "MiniMax-M2",
|
|
245
|
+
provider: "minimax",
|
|
246
|
+
api: "anthropic-messages",
|
|
247
|
+
displayName: "MiniMax M2",
|
|
248
|
+
baseUrl: "https://api.minimax.io/anthropic",
|
|
249
|
+
contextWindow: 204800,
|
|
250
|
+
maxOutputTokens: 131072,
|
|
251
|
+
modalities: ["text"],
|
|
252
|
+
reasoning: true,
|
|
253
|
+
cost: {
|
|
254
|
+
inputPerMTok: 0.3,
|
|
255
|
+
outputPerMTok: 1.2
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "MiniMax-M2.1",
|
|
260
|
+
provider: "minimax",
|
|
261
|
+
api: "anthropic-messages",
|
|
262
|
+
displayName: "MiniMax M2.1",
|
|
263
|
+
baseUrl: "https://api.minimax.io/anthropic",
|
|
264
|
+
contextWindow: 204800,
|
|
265
|
+
maxOutputTokens: 131072,
|
|
266
|
+
modalities: ["text"],
|
|
267
|
+
reasoning: true,
|
|
268
|
+
cost: {
|
|
269
|
+
inputPerMTok: 0.3,
|
|
270
|
+
outputPerMTok: 1.2
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: "MiniMax-M2.5",
|
|
275
|
+
provider: "minimax",
|
|
276
|
+
api: "anthropic-messages",
|
|
277
|
+
displayName: "MiniMax M2.5",
|
|
278
|
+
baseUrl: "https://api.minimax.io/anthropic",
|
|
279
|
+
contextWindow: 204800,
|
|
280
|
+
maxOutputTokens: 131072,
|
|
281
|
+
modalities: ["text"],
|
|
282
|
+
reasoning: true,
|
|
283
|
+
cost: {
|
|
284
|
+
inputPerMTok: 0.3,
|
|
285
|
+
outputPerMTok: 1.2
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: "MiniMax-M2.7",
|
|
290
|
+
provider: "minimax",
|
|
291
|
+
api: "anthropic-messages",
|
|
292
|
+
displayName: "MiniMax M2.7",
|
|
293
|
+
baseUrl: "https://api.minimax.io/anthropic",
|
|
294
|
+
contextWindow: 204800,
|
|
295
|
+
maxOutputTokens: 131072,
|
|
296
|
+
modalities: ["text"],
|
|
297
|
+
reasoning: true,
|
|
298
|
+
cost: {
|
|
299
|
+
inputPerMTok: 0.3,
|
|
300
|
+
outputPerMTok: 1.2
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: "MiniMax-M3",
|
|
305
|
+
provider: "minimax",
|
|
306
|
+
api: "anthropic-messages",
|
|
307
|
+
displayName: "MiniMax M3",
|
|
308
|
+
baseUrl: "https://api.minimax.io/anthropic",
|
|
309
|
+
contextWindow: 1048576,
|
|
310
|
+
maxOutputTokens: 512e3,
|
|
311
|
+
modalities: ["text", "image"],
|
|
312
|
+
reasoning: true,
|
|
313
|
+
cost: {
|
|
314
|
+
inputPerMTok: 0.6,
|
|
315
|
+
outputPerMTok: 2.4,
|
|
316
|
+
cacheReadPerMTok: 0.12
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
// ── Ollama (local; pricing is zero — your own hardware) ─────────────────
|
|
320
|
+
{
|
|
321
|
+
id: "llama3.1:8b",
|
|
322
|
+
provider: "ollama",
|
|
323
|
+
api: "ollama",
|
|
324
|
+
displayName: "Llama 3.1 8B (local)",
|
|
325
|
+
baseUrl: "http://localhost:11434",
|
|
326
|
+
contextWindow: 128e3,
|
|
327
|
+
maxOutputTokens: 8192,
|
|
328
|
+
modalities: ["text"],
|
|
329
|
+
reasoning: false,
|
|
330
|
+
cost: {
|
|
331
|
+
// local inference — no per-token cost
|
|
332
|
+
inputPerMTok: 0,
|
|
333
|
+
outputPerMTok: 0
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
// ── Mock (deterministic test fixture; free) ─────────────────────────────
|
|
337
|
+
{
|
|
338
|
+
id: "mock-1",
|
|
339
|
+
provider: "mock",
|
|
340
|
+
api: "mock",
|
|
341
|
+
displayName: "Mock Model",
|
|
342
|
+
contextWindow: 32e3,
|
|
343
|
+
maxOutputTokens: 4096,
|
|
344
|
+
modalities: ["text"],
|
|
345
|
+
reasoning: false,
|
|
346
|
+
cost: {
|
|
347
|
+
inputPerMTok: 0,
|
|
348
|
+
outputPerMTok: 0
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
// src/llmgateway/catalog/query.ts
|
|
354
|
+
function getCard(id) {
|
|
355
|
+
return MODEL_CARDS.find((card) => card.id === id);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/llmgateway/credentials/secrets.ts
|
|
359
|
+
var SECRET_TABLE = {
|
|
360
|
+
anthropic: {
|
|
361
|
+
provider: "anthropic",
|
|
362
|
+
scheme: "api-key",
|
|
363
|
+
envVars: ["ANTHROPIC_API_KEY"],
|
|
364
|
+
hint: "Anthropic Messages API bearer key."
|
|
365
|
+
},
|
|
366
|
+
openai: {
|
|
367
|
+
provider: "openai",
|
|
368
|
+
scheme: "api-key",
|
|
369
|
+
envVars: ["OPENAI_API_KEY"],
|
|
370
|
+
hint: "OpenAI platform API key."
|
|
371
|
+
},
|
|
372
|
+
google: {
|
|
373
|
+
provider: "google",
|
|
374
|
+
scheme: "api-key",
|
|
375
|
+
// Gemini Developer API: GEMINI_API_KEY is canonical; GOOGLE_API_KEY is the
|
|
376
|
+
// legacy/alias name still honored by the Generative Language API.
|
|
377
|
+
envVars: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
|
|
378
|
+
hint: "Google Generative Language (Gemini) API key."
|
|
379
|
+
},
|
|
380
|
+
"google-vertex": {
|
|
381
|
+
provider: "google-vertex",
|
|
382
|
+
scheme: "cloud-iam",
|
|
383
|
+
// Vertex authenticates via Application Default Credentials; these point at
|
|
384
|
+
// a service-account key file and the target project/region.
|
|
385
|
+
envVars: [
|
|
386
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
387
|
+
"GOOGLE_VERTEX_PROJECT",
|
|
388
|
+
"GOOGLE_CLOUD_PROJECT"
|
|
389
|
+
],
|
|
390
|
+
hint: "Vertex AI via Application Default Credentials (service account)."
|
|
391
|
+
},
|
|
392
|
+
amazon: {
|
|
393
|
+
provider: "amazon",
|
|
394
|
+
scheme: "cloud-iam",
|
|
395
|
+
// Bedrock supports a short-lived bearer token, otherwise the standard
|
|
396
|
+
// AWS SigV4 credential chain.
|
|
397
|
+
envVars: [
|
|
398
|
+
"AWS_BEARER_TOKEN_BEDROCK",
|
|
399
|
+
"AWS_ACCESS_KEY_ID",
|
|
400
|
+
"AWS_PROFILE"
|
|
401
|
+
],
|
|
402
|
+
hint: "Amazon Bedrock via bearer token or AWS credential chain."
|
|
403
|
+
},
|
|
404
|
+
azure: {
|
|
405
|
+
provider: "azure",
|
|
406
|
+
scheme: "api-key",
|
|
407
|
+
envVars: ["AZURE_OPENAI_API_KEY", "AZURE_API_KEY"],
|
|
408
|
+
hint: "Azure OpenAI resource key (paired with endpoint + deployment)."
|
|
409
|
+
},
|
|
410
|
+
nvidia: {
|
|
411
|
+
provider: "nvidia",
|
|
412
|
+
scheme: "api-key",
|
|
413
|
+
envVars: ["NVIDIA_API_KEY"],
|
|
414
|
+
hint: "NVIDIA NIM / build.nvidia.com API key."
|
|
415
|
+
},
|
|
416
|
+
kimi: {
|
|
417
|
+
provider: "kimi",
|
|
418
|
+
scheme: "api-key",
|
|
419
|
+
// Moonshot AI ships the Kimi models; MOONSHOT_API_KEY is the documented
|
|
420
|
+
// name, KIMI_API_KEY is a common community alias.
|
|
421
|
+
envVars: ["MOONSHOT_API_KEY", "KIMI_API_KEY"],
|
|
422
|
+
hint: "Moonshot AI (Kimi) API key."
|
|
423
|
+
},
|
|
424
|
+
minimax: {
|
|
425
|
+
provider: "minimax",
|
|
426
|
+
scheme: "api-key",
|
|
427
|
+
envVars: ["MINIMAX_API_KEY"],
|
|
428
|
+
hint: "MiniMax global API key (api.minimax.io Anthropic-compatible)."
|
|
429
|
+
},
|
|
430
|
+
"minimax-cn": {
|
|
431
|
+
provider: "minimax-cn",
|
|
432
|
+
scheme: "api-key",
|
|
433
|
+
// Same key often works for both; CN-specific override first when set.
|
|
434
|
+
envVars: ["MINIMAX_CN_API_KEY", "MINIMAX_API_KEY"],
|
|
435
|
+
hint: "MiniMax China API key (api.minimaxi.com Anthropic-compatible)."
|
|
436
|
+
},
|
|
437
|
+
ollama: {
|
|
438
|
+
provider: "ollama",
|
|
439
|
+
scheme: "none",
|
|
440
|
+
envVars: [],
|
|
441
|
+
hint: "Local Ollama server \u2014 no credential required."
|
|
442
|
+
},
|
|
443
|
+
mock: {
|
|
444
|
+
provider: "mock",
|
|
445
|
+
scheme: "none",
|
|
446
|
+
envVars: [],
|
|
447
|
+
hint: "In-process mock transport \u2014 no credential required."
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
function readEnv(name) {
|
|
451
|
+
const raw = process.env[name];
|
|
452
|
+
if (raw === void 0) return void 0;
|
|
453
|
+
const trimmed = raw.trim();
|
|
454
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
455
|
+
}
|
|
456
|
+
async function resolveSecret(provider, override) {
|
|
457
|
+
if (override !== void 0) {
|
|
458
|
+
const trimmed = override.trim();
|
|
459
|
+
if (trimmed.length > 0) return trimmed;
|
|
460
|
+
}
|
|
461
|
+
const descriptor = SECRET_TABLE[provider];
|
|
462
|
+
if (descriptor.scheme === "none") return void 0;
|
|
463
|
+
for (const name of descriptor.envVars) {
|
|
464
|
+
const value = readEnv(name);
|
|
465
|
+
if (value !== void 0) return value;
|
|
466
|
+
}
|
|
467
|
+
return void 0;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/llmgateway/streaming/sse.ts
|
|
471
|
+
var LF = "\n";
|
|
472
|
+
var CR = "\r";
|
|
473
|
+
async function* sseEvents(body, signal) {
|
|
474
|
+
if (signal?.aborted) {
|
|
475
|
+
throw gatewayError("aborted", "request aborted before reading the event stream");
|
|
476
|
+
}
|
|
477
|
+
const reader = body.getReader();
|
|
478
|
+
const decoder = new TextDecoder("utf-8");
|
|
479
|
+
let pending = "";
|
|
480
|
+
let eventType = "message";
|
|
481
|
+
let dataLines = [];
|
|
482
|
+
let sawData = false;
|
|
483
|
+
const onAbort = () => {
|
|
484
|
+
void reader.cancel().catch(() => {
|
|
485
|
+
});
|
|
486
|
+
};
|
|
487
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
488
|
+
const resetEvent = () => {
|
|
489
|
+
eventType = "message";
|
|
490
|
+
dataLines = [];
|
|
491
|
+
sawData = false;
|
|
492
|
+
};
|
|
493
|
+
const dispatch = () => {
|
|
494
|
+
if (!sawData) {
|
|
495
|
+
resetEvent();
|
|
496
|
+
return void 0;
|
|
497
|
+
}
|
|
498
|
+
const out = { event: eventType, data: dataLines.join(LF) };
|
|
499
|
+
resetEvent();
|
|
500
|
+
return out;
|
|
501
|
+
};
|
|
502
|
+
const consumeLine = (line) => {
|
|
503
|
+
if (line === "") {
|
|
504
|
+
return dispatch();
|
|
505
|
+
}
|
|
506
|
+
if (line.startsWith(":")) {
|
|
507
|
+
return void 0;
|
|
508
|
+
}
|
|
509
|
+
const colon = line.indexOf(":");
|
|
510
|
+
let field;
|
|
511
|
+
let value;
|
|
512
|
+
if (colon === -1) {
|
|
513
|
+
field = line;
|
|
514
|
+
value = "";
|
|
515
|
+
} else {
|
|
516
|
+
field = line.slice(0, colon);
|
|
517
|
+
value = line.slice(colon + 1);
|
|
518
|
+
if (value.startsWith(" ")) {
|
|
519
|
+
value = value.slice(1);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
switch (field) {
|
|
523
|
+
case "event":
|
|
524
|
+
eventType = value;
|
|
525
|
+
break;
|
|
526
|
+
case "data":
|
|
527
|
+
dataLines.push(value);
|
|
528
|
+
sawData = true;
|
|
529
|
+
break;
|
|
530
|
+
// "id" and "retry" are valid SSE fields but irrelevant to LLM streams.
|
|
531
|
+
default:
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
return void 0;
|
|
535
|
+
};
|
|
536
|
+
function* drainLines(atEnd) {
|
|
537
|
+
let start = 0;
|
|
538
|
+
let i = 0;
|
|
539
|
+
while (i < pending.length) {
|
|
540
|
+
const ch = pending[i];
|
|
541
|
+
if (ch === LF) {
|
|
542
|
+
yield pending.slice(start, i);
|
|
543
|
+
i += 1;
|
|
544
|
+
start = i;
|
|
545
|
+
} else if (ch === CR) {
|
|
546
|
+
if (i === pending.length - 1 && !atEnd) {
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
yield pending.slice(start, i);
|
|
550
|
+
if (pending[i + 1] === LF) {
|
|
551
|
+
i += 2;
|
|
552
|
+
} else {
|
|
553
|
+
i += 1;
|
|
554
|
+
}
|
|
555
|
+
start = i;
|
|
556
|
+
} else {
|
|
557
|
+
i += 1;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
pending = pending.slice(start);
|
|
561
|
+
if (atEnd && pending.length > 0) {
|
|
562
|
+
const last = pending;
|
|
563
|
+
pending = "";
|
|
564
|
+
yield last;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
try {
|
|
568
|
+
for (; ; ) {
|
|
569
|
+
if (signal?.aborted) {
|
|
570
|
+
throw gatewayError("aborted", "request aborted while reading the event stream");
|
|
571
|
+
}
|
|
572
|
+
const { value, done } = await reader.read();
|
|
573
|
+
if (done) {
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
pending += decoder.decode(value, { stream: true });
|
|
577
|
+
for (const line of drainLines(false)) {
|
|
578
|
+
const ev = consumeLine(line);
|
|
579
|
+
if (ev !== void 0) {
|
|
580
|
+
yield ev;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
pending += decoder.decode();
|
|
585
|
+
for (const line of drainLines(true)) {
|
|
586
|
+
const ev = consumeLine(line);
|
|
587
|
+
if (ev !== void 0) {
|
|
588
|
+
yield ev;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
} catch (cause) {
|
|
592
|
+
if (signal?.aborted) {
|
|
593
|
+
throw gatewayError("aborted", "request aborted while reading the event stream", { cause });
|
|
594
|
+
}
|
|
595
|
+
if (cause instanceof Error && cause.name === "GatewayError") {
|
|
596
|
+
throw cause;
|
|
597
|
+
}
|
|
598
|
+
throw gatewayError("transport", "failed reading the server-sent event stream", { cause });
|
|
599
|
+
} finally {
|
|
600
|
+
signal?.removeEventListener("abort", onAbort);
|
|
601
|
+
reader.releaseLock();
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// src/llmgateway/conversion/reduce.ts
|
|
606
|
+
var EMPTY_USAGE = { inputTokens: 0, outputTokens: 0 };
|
|
607
|
+
function initial() {
|
|
608
|
+
return {
|
|
609
|
+
thinking: "",
|
|
610
|
+
text: "",
|
|
611
|
+
order: [],
|
|
612
|
+
calls: /* @__PURE__ */ new Map(),
|
|
613
|
+
usage: EMPTY_USAGE,
|
|
614
|
+
stop: "complete"
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
function ensureCall(state, id, name) {
|
|
618
|
+
const existing = state.calls.get(id);
|
|
619
|
+
if (existing === void 0) {
|
|
620
|
+
state.order.push(id);
|
|
621
|
+
state.calls.set(id, { name, args: "" });
|
|
622
|
+
} else if (name !== "" && existing.name === "") {
|
|
623
|
+
existing.name = name;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function step(state, emission) {
|
|
627
|
+
switch (emission.kind) {
|
|
628
|
+
case "text":
|
|
629
|
+
state.text += emission.delta;
|
|
630
|
+
break;
|
|
631
|
+
case "thinking":
|
|
632
|
+
state.thinking += emission.delta;
|
|
633
|
+
break;
|
|
634
|
+
case "tool_call_start":
|
|
635
|
+
ensureCall(state, emission.id, emission.name);
|
|
636
|
+
break;
|
|
637
|
+
case "tool_call_delta": {
|
|
638
|
+
ensureCall(state, emission.id, "");
|
|
639
|
+
const slot = state.calls.get(emission.id);
|
|
640
|
+
if (slot !== void 0) {
|
|
641
|
+
slot.args += emission.argsDelta;
|
|
642
|
+
}
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
case "usage":
|
|
646
|
+
state.usage = emission.usage;
|
|
647
|
+
break;
|
|
648
|
+
case "stop":
|
|
649
|
+
state.stop = emission.stop;
|
|
650
|
+
break;
|
|
651
|
+
// `done` and `error` are terminal envelopes, not deltas to fold; a caller
|
|
652
|
+
// that hands them to the reducer simply contributes nothing.
|
|
653
|
+
case "done":
|
|
654
|
+
case "error":
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
function parseArgs(raw) {
|
|
659
|
+
const trimmed = raw.trim();
|
|
660
|
+
if (trimmed === "") {
|
|
661
|
+
return {};
|
|
662
|
+
}
|
|
663
|
+
try {
|
|
664
|
+
return JSON.parse(trimmed);
|
|
665
|
+
} catch {
|
|
666
|
+
return raw;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function toBlocks(state) {
|
|
670
|
+
const blocks = [];
|
|
671
|
+
if (state.thinking !== "") {
|
|
672
|
+
const block = { kind: "thinking", text: state.thinking };
|
|
673
|
+
blocks.push(block);
|
|
674
|
+
}
|
|
675
|
+
if (state.text !== "") {
|
|
676
|
+
const block = { kind: "text", text: state.text };
|
|
677
|
+
blocks.push(block);
|
|
678
|
+
}
|
|
679
|
+
for (const id of state.order) {
|
|
680
|
+
const call = state.calls.get(id);
|
|
681
|
+
if (call === void 0) {
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
const block = {
|
|
685
|
+
kind: "tool_call",
|
|
686
|
+
id,
|
|
687
|
+
name: call.name,
|
|
688
|
+
input: parseArgs(call.args)
|
|
689
|
+
};
|
|
690
|
+
blocks.push(block);
|
|
691
|
+
}
|
|
692
|
+
return blocks;
|
|
693
|
+
}
|
|
694
|
+
function foldReply(model, emissions) {
|
|
695
|
+
const state = initial();
|
|
696
|
+
for (const emission of emissions) {
|
|
697
|
+
step(state, emission);
|
|
698
|
+
}
|
|
699
|
+
const blocks = toBlocks(state);
|
|
700
|
+
let stop = state.stop;
|
|
701
|
+
if (stop === "complete" && state.order.length > 0) {
|
|
702
|
+
stop = "tool_calls";
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
role: "assistant",
|
|
706
|
+
model,
|
|
707
|
+
blocks,
|
|
708
|
+
usage: state.usage,
|
|
709
|
+
stop
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/llmgateway/streaming/channel.ts
|
|
714
|
+
function channelOf(gen) {
|
|
715
|
+
return {
|
|
716
|
+
[Symbol.asyncIterator]() {
|
|
717
|
+
return gen();
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
async function collectReply(channel) {
|
|
722
|
+
const buffer = [];
|
|
723
|
+
let model = "unknown";
|
|
724
|
+
for await (const emission of channel) {
|
|
725
|
+
if (emission.kind === "done") {
|
|
726
|
+
return emission.reply;
|
|
727
|
+
}
|
|
728
|
+
if (emission.kind === "error") {
|
|
729
|
+
throw emission.error;
|
|
730
|
+
}
|
|
731
|
+
buffer.push(emission);
|
|
732
|
+
}
|
|
733
|
+
if (buffer.length === 0) {
|
|
734
|
+
throw gatewayError("transport", "channel closed without emitting any data");
|
|
735
|
+
}
|
|
736
|
+
return foldReply(model, buffer);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// src/llmgateway/conversion/mappers.ts
|
|
740
|
+
function stringifyToolOutput(output) {
|
|
741
|
+
if (typeof output === "string") {
|
|
742
|
+
return output;
|
|
743
|
+
}
|
|
744
|
+
try {
|
|
745
|
+
return JSON.stringify(output);
|
|
746
|
+
} catch {
|
|
747
|
+
return String(output);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
function toArgumentString(input) {
|
|
751
|
+
if (typeof input === "string") {
|
|
752
|
+
return input;
|
|
753
|
+
}
|
|
754
|
+
try {
|
|
755
|
+
return JSON.stringify(input ?? {});
|
|
756
|
+
} catch {
|
|
757
|
+
return "{}";
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function toOpenAIChatMessages(c) {
|
|
761
|
+
const messages = [];
|
|
762
|
+
if (c.system !== void 0 && c.system !== "") {
|
|
763
|
+
messages.push({ role: "system", content: c.system });
|
|
764
|
+
}
|
|
765
|
+
for (const turn of c.turns) {
|
|
766
|
+
switch (turn.role) {
|
|
767
|
+
case "user":
|
|
768
|
+
messages.push(openAiUserMessage(turn.blocks));
|
|
769
|
+
break;
|
|
770
|
+
case "assistant":
|
|
771
|
+
messages.push(openAiAssistantMessage(turn.blocks));
|
|
772
|
+
break;
|
|
773
|
+
case "tool":
|
|
774
|
+
for (const message of openAiToolMessages(turn.blocks)) {
|
|
775
|
+
messages.push(message);
|
|
776
|
+
}
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return messages;
|
|
781
|
+
}
|
|
782
|
+
function openAiUserMessage(blocks) {
|
|
783
|
+
const parts = [];
|
|
784
|
+
let hasImage = false;
|
|
785
|
+
for (const block of blocks) {
|
|
786
|
+
if (block.kind === "text") {
|
|
787
|
+
parts.push({ type: "text", text: block.text });
|
|
788
|
+
} else if (block.kind === "image") {
|
|
789
|
+
hasImage = true;
|
|
790
|
+
parts.push({
|
|
791
|
+
type: "image_url",
|
|
792
|
+
image_url: { url: `data:${block.mediaType};base64,${block.dataBase64}` }
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (!hasImage) {
|
|
797
|
+
const text = parts.map((p) => p.text ?? "").join("");
|
|
798
|
+
return { role: "user", content: text };
|
|
799
|
+
}
|
|
800
|
+
return { role: "user", content: parts };
|
|
801
|
+
}
|
|
802
|
+
function openAiAssistantMessage(blocks) {
|
|
803
|
+
let text = "";
|
|
804
|
+
const toolCalls = [];
|
|
805
|
+
for (const block of blocks) {
|
|
806
|
+
if (block.kind === "text") {
|
|
807
|
+
text += block.text;
|
|
808
|
+
} else if (block.kind === "tool_call") {
|
|
809
|
+
toolCalls.push({
|
|
810
|
+
id: block.id,
|
|
811
|
+
type: "function",
|
|
812
|
+
function: { name: block.name, arguments: toArgumentString(block.input) }
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
const message = { role: "assistant", content: text === "" ? null : text };
|
|
817
|
+
if (toolCalls.length > 0) {
|
|
818
|
+
message.tool_calls = toolCalls;
|
|
819
|
+
}
|
|
820
|
+
return message;
|
|
821
|
+
}
|
|
822
|
+
function openAiToolMessages(blocks) {
|
|
823
|
+
const out = [];
|
|
824
|
+
for (const block of blocks) {
|
|
825
|
+
if (block.kind === "tool_result") {
|
|
826
|
+
out.push({
|
|
827
|
+
role: "tool",
|
|
828
|
+
tool_call_id: block.callId,
|
|
829
|
+
content: stringifyToolOutput(block.output)
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return out;
|
|
834
|
+
}
|
|
835
|
+
function toOpenAITools(tools) {
|
|
836
|
+
if (tools === void 0 || tools.length === 0) {
|
|
837
|
+
return void 0;
|
|
838
|
+
}
|
|
839
|
+
return tools.map((tool) => ({
|
|
840
|
+
type: "function",
|
|
841
|
+
function: {
|
|
842
|
+
name: tool.name,
|
|
843
|
+
description: tool.description,
|
|
844
|
+
parameters: tool.parameters
|
|
845
|
+
}
|
|
846
|
+
}));
|
|
847
|
+
}
|
|
848
|
+
function toOpenAIToolChoice(choice) {
|
|
849
|
+
if (choice === void 0) {
|
|
850
|
+
return void 0;
|
|
851
|
+
}
|
|
852
|
+
return choice;
|
|
853
|
+
}
|
|
854
|
+
function anthropicThinkingBudget(level, maxOutput) {
|
|
855
|
+
const fraction = {
|
|
856
|
+
low: 0.2,
|
|
857
|
+
medium: 0.4,
|
|
858
|
+
high: 0.6,
|
|
859
|
+
max: 0.8
|
|
860
|
+
};
|
|
861
|
+
if (level === "off") {
|
|
862
|
+
return 0;
|
|
863
|
+
}
|
|
864
|
+
const budget = Math.floor(maxOutput * fraction[level]);
|
|
865
|
+
return Math.max(1024, budget);
|
|
866
|
+
}
|
|
867
|
+
function toAnthropicRequest(c, model, opts) {
|
|
868
|
+
const body = {
|
|
869
|
+
model: model.id,
|
|
870
|
+
max_tokens: opts.maxOutputTokens ?? model.maxOutputTokens,
|
|
871
|
+
messages: toAnthropicMessages(c)
|
|
872
|
+
};
|
|
873
|
+
if (c.system !== void 0 && c.system !== "") {
|
|
874
|
+
body.system = c.system;
|
|
875
|
+
}
|
|
876
|
+
const tools = toAnthropicTools(c.tools);
|
|
877
|
+
if (tools !== void 0) {
|
|
878
|
+
body.tools = tools;
|
|
879
|
+
}
|
|
880
|
+
const toolChoice = toAnthropicToolChoice(opts.toolChoice);
|
|
881
|
+
if (toolChoice !== void 0) {
|
|
882
|
+
body.tool_choice = toolChoice;
|
|
883
|
+
}
|
|
884
|
+
if (opts.temperature !== void 0) {
|
|
885
|
+
body.temperature = opts.temperature;
|
|
886
|
+
}
|
|
887
|
+
if (opts.topP !== void 0) {
|
|
888
|
+
body.top_p = opts.topP;
|
|
889
|
+
}
|
|
890
|
+
if (model.reasoning && opts.thinking !== void 0 && opts.thinking !== "off") {
|
|
891
|
+
const max = opts.maxOutputTokens ?? model.maxOutputTokens;
|
|
892
|
+
body.thinking = { type: "enabled", budget_tokens: anthropicThinkingBudget(opts.thinking, max) };
|
|
893
|
+
}
|
|
894
|
+
return body;
|
|
895
|
+
}
|
|
896
|
+
function toAnthropicMessages(c) {
|
|
897
|
+
const messages = [];
|
|
898
|
+
for (const turn of c.turns) {
|
|
899
|
+
if (turn.role === "tool") {
|
|
900
|
+
messages.push({ role: "user", content: anthropicContent(turn) });
|
|
901
|
+
} else {
|
|
902
|
+
messages.push({ role: turn.role, content: anthropicContent(turn) });
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return messages;
|
|
906
|
+
}
|
|
907
|
+
function anthropicContent(turn) {
|
|
908
|
+
const content = [];
|
|
909
|
+
for (const block of turn.blocks) {
|
|
910
|
+
switch (block.kind) {
|
|
911
|
+
case "text":
|
|
912
|
+
content.push({ type: "text", text: block.text });
|
|
913
|
+
break;
|
|
914
|
+
case "thinking":
|
|
915
|
+
content.push(
|
|
916
|
+
block.signature !== void 0 ? { type: "thinking", thinking: block.text, signature: block.signature } : { type: "thinking", thinking: block.text }
|
|
917
|
+
);
|
|
918
|
+
break;
|
|
919
|
+
case "tool_call":
|
|
920
|
+
content.push({
|
|
921
|
+
type: "tool_use",
|
|
922
|
+
id: block.id,
|
|
923
|
+
name: block.name,
|
|
924
|
+
input: typeof block.input === "string" ? safeParse(block.input) : block.input ?? {}
|
|
925
|
+
});
|
|
926
|
+
break;
|
|
927
|
+
case "tool_result":
|
|
928
|
+
content.push({
|
|
929
|
+
type: "tool_result",
|
|
930
|
+
tool_use_id: block.callId,
|
|
931
|
+
content: stringifyToolOutput(block.output),
|
|
932
|
+
...block.isError === true ? { is_error: true } : {}
|
|
933
|
+
});
|
|
934
|
+
break;
|
|
935
|
+
case "image":
|
|
936
|
+
content.push({
|
|
937
|
+
type: "image",
|
|
938
|
+
source: { type: "base64", media_type: block.mediaType, data: block.dataBase64 }
|
|
939
|
+
});
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return content;
|
|
944
|
+
}
|
|
945
|
+
function toAnthropicTools(tools) {
|
|
946
|
+
if (tools === void 0 || tools.length === 0) {
|
|
947
|
+
return void 0;
|
|
948
|
+
}
|
|
949
|
+
return tools.map((tool) => ({
|
|
950
|
+
name: tool.name,
|
|
951
|
+
description: tool.description,
|
|
952
|
+
input_schema: tool.parameters
|
|
953
|
+
}));
|
|
954
|
+
}
|
|
955
|
+
function toAnthropicToolChoice(choice) {
|
|
956
|
+
switch (choice) {
|
|
957
|
+
case "auto":
|
|
958
|
+
return { type: "auto" };
|
|
959
|
+
case "required":
|
|
960
|
+
return { type: "any" };
|
|
961
|
+
case "none":
|
|
962
|
+
return { type: "none" };
|
|
963
|
+
default:
|
|
964
|
+
return void 0;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
function toGoogleContents(c) {
|
|
968
|
+
const contents = [];
|
|
969
|
+
for (const turn of c.turns) {
|
|
970
|
+
const role = turn.role === "assistant" ? "model" : "user";
|
|
971
|
+
contents.push({ role, parts: googleParts(turn) });
|
|
972
|
+
}
|
|
973
|
+
return contents;
|
|
974
|
+
}
|
|
975
|
+
function googleParts(turn) {
|
|
976
|
+
const parts = [];
|
|
977
|
+
for (const block of turn.blocks) {
|
|
978
|
+
switch (block.kind) {
|
|
979
|
+
case "text":
|
|
980
|
+
parts.push({ text: block.text });
|
|
981
|
+
break;
|
|
982
|
+
case "thinking":
|
|
983
|
+
parts.push({ text: block.text, thought: true });
|
|
984
|
+
break;
|
|
985
|
+
case "tool_call":
|
|
986
|
+
parts.push({
|
|
987
|
+
functionCall: {
|
|
988
|
+
name: block.name,
|
|
989
|
+
args: typeof block.input === "string" ? safeParse(block.input) : block.input ?? {}
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
break;
|
|
993
|
+
case "tool_result":
|
|
994
|
+
parts.push({
|
|
995
|
+
functionResponse: {
|
|
996
|
+
name: block.callId,
|
|
997
|
+
response: { result: stringifyToolOutput(block.output) }
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
break;
|
|
1001
|
+
case "image":
|
|
1002
|
+
parts.push({ inlineData: { mimeType: block.mediaType, data: block.dataBase64 } });
|
|
1003
|
+
break;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return parts;
|
|
1007
|
+
}
|
|
1008
|
+
function toGoogleSystemInstruction(c) {
|
|
1009
|
+
if (c.system === void 0 || c.system === "") {
|
|
1010
|
+
return void 0;
|
|
1011
|
+
}
|
|
1012
|
+
return { role: "system", parts: [{ text: c.system }] };
|
|
1013
|
+
}
|
|
1014
|
+
function toGoogleTools(tools) {
|
|
1015
|
+
if (tools === void 0 || tools.length === 0) {
|
|
1016
|
+
return void 0;
|
|
1017
|
+
}
|
|
1018
|
+
return [
|
|
1019
|
+
{
|
|
1020
|
+
functionDeclarations: tools.map((tool) => ({
|
|
1021
|
+
name: tool.name,
|
|
1022
|
+
description: tool.description,
|
|
1023
|
+
parameters: tool.parameters
|
|
1024
|
+
}))
|
|
1025
|
+
}
|
|
1026
|
+
];
|
|
1027
|
+
}
|
|
1028
|
+
function toResponsesInput(c) {
|
|
1029
|
+
const input = [];
|
|
1030
|
+
for (const turn of c.turns) {
|
|
1031
|
+
switch (turn.role) {
|
|
1032
|
+
case "user":
|
|
1033
|
+
input.push({ type: "message", role: "user", content: responsesUserContent(turn.blocks) });
|
|
1034
|
+
break;
|
|
1035
|
+
case "assistant":
|
|
1036
|
+
for (const item of responsesAssistantItems(turn.blocks)) {
|
|
1037
|
+
input.push(item);
|
|
1038
|
+
}
|
|
1039
|
+
break;
|
|
1040
|
+
case "tool":
|
|
1041
|
+
for (const block of turn.blocks) {
|
|
1042
|
+
if (block.kind === "tool_result") {
|
|
1043
|
+
input.push({
|
|
1044
|
+
type: "function_call_output",
|
|
1045
|
+
call_id: block.callId,
|
|
1046
|
+
output: stringifyToolOutput(block.output)
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return input;
|
|
1054
|
+
}
|
|
1055
|
+
function responsesUserContent(blocks) {
|
|
1056
|
+
const parts = [];
|
|
1057
|
+
for (const block of blocks) {
|
|
1058
|
+
if (block.kind === "text") {
|
|
1059
|
+
parts.push({ type: "input_text", text: block.text });
|
|
1060
|
+
} else if (block.kind === "image") {
|
|
1061
|
+
parts.push({
|
|
1062
|
+
type: "input_image",
|
|
1063
|
+
image_url: `data:${block.mediaType};base64,${block.dataBase64}`
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return parts;
|
|
1068
|
+
}
|
|
1069
|
+
function responsesAssistantItems(blocks) {
|
|
1070
|
+
const items = [];
|
|
1071
|
+
const content = [];
|
|
1072
|
+
for (const block of blocks) {
|
|
1073
|
+
if (block.kind === "text") {
|
|
1074
|
+
content.push({ type: "output_text", text: block.text });
|
|
1075
|
+
} else if (block.kind === "tool_call") {
|
|
1076
|
+
items.push({
|
|
1077
|
+
type: "function_call",
|
|
1078
|
+
call_id: block.id,
|
|
1079
|
+
name: block.name,
|
|
1080
|
+
arguments: toArgumentString(block.input)
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
if (content.length > 0) {
|
|
1085
|
+
items.unshift({ type: "message", role: "assistant", content });
|
|
1086
|
+
}
|
|
1087
|
+
return items;
|
|
1088
|
+
}
|
|
1089
|
+
function toResponsesTools(tools) {
|
|
1090
|
+
if (tools === void 0 || tools.length === 0) {
|
|
1091
|
+
return void 0;
|
|
1092
|
+
}
|
|
1093
|
+
return tools.map((tool) => ({
|
|
1094
|
+
type: "function",
|
|
1095
|
+
name: tool.name,
|
|
1096
|
+
description: tool.description,
|
|
1097
|
+
parameters: tool.parameters
|
|
1098
|
+
}));
|
|
1099
|
+
}
|
|
1100
|
+
function safeParse(raw) {
|
|
1101
|
+
try {
|
|
1102
|
+
return JSON.parse(raw);
|
|
1103
|
+
} catch {
|
|
1104
|
+
return {};
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// src/llmgateway/connectors/anthropic.ts
|
|
1109
|
+
var DEFAULT_BASE_URL = "https://api.anthropic.com";
|
|
1110
|
+
var ANTHROPIC_VERSION = "2023-06-01";
|
|
1111
|
+
function isObject(v) {
|
|
1112
|
+
return typeof v === "object" && v !== null;
|
|
1113
|
+
}
|
|
1114
|
+
function readString(o, key) {
|
|
1115
|
+
const v = o[key];
|
|
1116
|
+
return typeof v === "string" ? v : void 0;
|
|
1117
|
+
}
|
|
1118
|
+
function readNumber(o, key) {
|
|
1119
|
+
const v = o[key];
|
|
1120
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
1121
|
+
}
|
|
1122
|
+
function mapStopReason(reason) {
|
|
1123
|
+
switch (reason) {
|
|
1124
|
+
case "end_turn":
|
|
1125
|
+
case "stop_sequence":
|
|
1126
|
+
return "complete";
|
|
1127
|
+
case "tool_use":
|
|
1128
|
+
return "tool_calls";
|
|
1129
|
+
case "max_tokens":
|
|
1130
|
+
return "max_output";
|
|
1131
|
+
case "refusal":
|
|
1132
|
+
return "refusal";
|
|
1133
|
+
case null:
|
|
1134
|
+
case void 0:
|
|
1135
|
+
case "":
|
|
1136
|
+
return void 0;
|
|
1137
|
+
default:
|
|
1138
|
+
return "complete";
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
function mergeUsage(current, raw) {
|
|
1142
|
+
if (!isObject(raw)) {
|
|
1143
|
+
return current;
|
|
1144
|
+
}
|
|
1145
|
+
const next = { ...current };
|
|
1146
|
+
const input = readNumber(raw, "input_tokens");
|
|
1147
|
+
if (input !== void 0) {
|
|
1148
|
+
next.inputTokens = input;
|
|
1149
|
+
}
|
|
1150
|
+
const output = readNumber(raw, "output_tokens");
|
|
1151
|
+
if (output !== void 0) {
|
|
1152
|
+
next.outputTokens = output;
|
|
1153
|
+
}
|
|
1154
|
+
const cacheRead = readNumber(raw, "cache_read_input_tokens");
|
|
1155
|
+
if (cacheRead !== void 0) {
|
|
1156
|
+
next.cacheReadTokens = cacheRead;
|
|
1157
|
+
}
|
|
1158
|
+
const cacheWrite = readNumber(raw, "cache_creation_input_tokens");
|
|
1159
|
+
if (cacheWrite !== void 0) {
|
|
1160
|
+
next.cacheWriteTokens = cacheWrite;
|
|
1161
|
+
}
|
|
1162
|
+
return next;
|
|
1163
|
+
}
|
|
1164
|
+
function parseEventData(event, provider) {
|
|
1165
|
+
if (event.data === "") {
|
|
1166
|
+
return void 0;
|
|
1167
|
+
}
|
|
1168
|
+
let parsed;
|
|
1169
|
+
try {
|
|
1170
|
+
parsed = JSON.parse(event.data);
|
|
1171
|
+
} catch (cause) {
|
|
1172
|
+
throw gatewayError("parse", "could not decode an Anthropic stream event", { provider, cause });
|
|
1173
|
+
}
|
|
1174
|
+
return isObject(parsed) ? parsed : void 0;
|
|
1175
|
+
}
|
|
1176
|
+
function* emitFromEvent(type, payload, state) {
|
|
1177
|
+
switch (type) {
|
|
1178
|
+
case "message_start": {
|
|
1179
|
+
const message = payload.message;
|
|
1180
|
+
if (isObject(message)) {
|
|
1181
|
+
state.usage = mergeUsage(state.usage, message.usage);
|
|
1182
|
+
yield { kind: "usage", usage: state.usage };
|
|
1183
|
+
}
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
case "content_block_start": {
|
|
1187
|
+
yield* emitBlockStart(payload, state);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
case "content_block_delta": {
|
|
1191
|
+
yield* emitBlockDelta(payload, state);
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
case "content_block_stop": {
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
case "message_delta": {
|
|
1198
|
+
const delta = payload.delta;
|
|
1199
|
+
if (isObject(delta)) {
|
|
1200
|
+
const stop = mapStopReason(readString(delta, "stop_reason"));
|
|
1201
|
+
if (stop !== void 0) {
|
|
1202
|
+
yield { kind: "stop", stop };
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
const usage = payload.usage;
|
|
1206
|
+
if (usage !== void 0) {
|
|
1207
|
+
state.usage = mergeUsage(state.usage, usage);
|
|
1208
|
+
yield { kind: "usage", usage: state.usage };
|
|
1209
|
+
}
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
case "message_stop":
|
|
1213
|
+
case "ping":
|
|
1214
|
+
return;
|
|
1215
|
+
case "error": {
|
|
1216
|
+
const error = payload.error;
|
|
1217
|
+
const detail = isObject(error) ? readString(error, "message") : void 0;
|
|
1218
|
+
throw gatewayError("http", `Anthropic reported a stream error${detail !== void 0 ? `: ${detail}` : ""}`);
|
|
1219
|
+
}
|
|
1220
|
+
default:
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
function* emitBlockStart(payload, state) {
|
|
1225
|
+
const index = readNumber(payload, "index") ?? 0;
|
|
1226
|
+
const block = payload.content_block;
|
|
1227
|
+
if (!isObject(block)) {
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
if (readString(block, "type") === "tool_use") {
|
|
1231
|
+
const id = readString(block, "id") ?? `tool_${index}`;
|
|
1232
|
+
const name = readString(block, "name") ?? "";
|
|
1233
|
+
state.toolIds.set(index, id);
|
|
1234
|
+
yield { kind: "tool_call_start", id, name };
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
function* emitBlockDelta(payload, state) {
|
|
1238
|
+
const index = readNumber(payload, "index") ?? 0;
|
|
1239
|
+
const delta = payload.delta;
|
|
1240
|
+
if (!isObject(delta)) {
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
switch (readString(delta, "type")) {
|
|
1244
|
+
case "text_delta": {
|
|
1245
|
+
const text = readString(delta, "text");
|
|
1246
|
+
if (text !== void 0 && text !== "") {
|
|
1247
|
+
yield { kind: "text", delta: text };
|
|
1248
|
+
}
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
case "thinking_delta": {
|
|
1252
|
+
const thinking = readString(delta, "thinking");
|
|
1253
|
+
if (thinking !== void 0 && thinking !== "") {
|
|
1254
|
+
yield { kind: "thinking", delta: thinking };
|
|
1255
|
+
}
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
case "input_json_delta": {
|
|
1259
|
+
const fragment = readString(delta, "partial_json");
|
|
1260
|
+
if (fragment !== void 0 && fragment !== "") {
|
|
1261
|
+
const id = state.toolIds.get(index);
|
|
1262
|
+
if (id !== void 0) {
|
|
1263
|
+
yield { kind: "tool_call_delta", id, argsDelta: fragment };
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
// `signature_delta` (thinking signatures) carries no user-visible content.
|
|
1269
|
+
default:
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
function endpointFor(model) {
|
|
1274
|
+
const base = (model.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1275
|
+
return `${base}/v1/messages`;
|
|
1276
|
+
}
|
|
1277
|
+
async function postMessages(url, apiKey, body, signal, provider = "anthropic") {
|
|
1278
|
+
let response;
|
|
1279
|
+
try {
|
|
1280
|
+
response = await fetch(url, {
|
|
1281
|
+
method: "POST",
|
|
1282
|
+
headers: {
|
|
1283
|
+
"content-type": "application/json",
|
|
1284
|
+
"x-api-key": apiKey,
|
|
1285
|
+
"anthropic-version": ANTHROPIC_VERSION
|
|
1286
|
+
},
|
|
1287
|
+
body: JSON.stringify(body),
|
|
1288
|
+
...signal !== void 0 ? { signal } : {}
|
|
1289
|
+
});
|
|
1290
|
+
} catch (cause) {
|
|
1291
|
+
if (signal?.aborted) {
|
|
1292
|
+
throw gatewayError("aborted", `request canceled before ${provider} responded`, {
|
|
1293
|
+
provider,
|
|
1294
|
+
cause
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
throw gatewayError("transport", `network call to ${provider} failed`, { provider, cause });
|
|
1298
|
+
}
|
|
1299
|
+
if (!response.ok) {
|
|
1300
|
+
const detail = await readErrorBody(response);
|
|
1301
|
+
const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
|
|
1302
|
+
throw gatewayError(kind, `${provider} returned HTTP ${response.status}${detail}`, {
|
|
1303
|
+
provider,
|
|
1304
|
+
status: response.status
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
if (response.body === null) {
|
|
1308
|
+
throw gatewayError("transport", `${provider} response arrived with no body`, {
|
|
1309
|
+
provider,
|
|
1310
|
+
status: response.status
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
return response;
|
|
1314
|
+
}
|
|
1315
|
+
async function readErrorBody(response) {
|
|
1316
|
+
try {
|
|
1317
|
+
const text = await response.text();
|
|
1318
|
+
return text === "" ? "" : `: ${text.slice(0, 500)}`;
|
|
1319
|
+
} catch {
|
|
1320
|
+
return "";
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
function asGatewayError(cause) {
|
|
1324
|
+
if (cause instanceof Error && cause.name === "GatewayError") {
|
|
1325
|
+
return cause;
|
|
1326
|
+
}
|
|
1327
|
+
return gatewayError("transport", "Anthropic stream failed", { provider: "anthropic", cause });
|
|
1328
|
+
}
|
|
1329
|
+
function createanthropicConnector(deps2) {
|
|
1330
|
+
const api = "anthropic-messages";
|
|
1331
|
+
async function requireKey(model, options) {
|
|
1332
|
+
const provider = model.provider;
|
|
1333
|
+
const key = await deps2.resolveSecret(provider, options.apiKey);
|
|
1334
|
+
if (key === void 0 || key === "") {
|
|
1335
|
+
const hint = provider === "minimax" || provider === "minimax-cn" ? "set MINIMAX_API_KEY (or MINIMAX_CN_API_KEY for CN) or pass options.apiKey" : "set ANTHROPIC_API_KEY or pass options.apiKey";
|
|
1336
|
+
throw gatewayError("auth", `no API key found for provider "${provider}" (${hint})`, {
|
|
1337
|
+
provider
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
return key;
|
|
1341
|
+
}
|
|
1342
|
+
function stream2(model, conversation, options) {
|
|
1343
|
+
return channelOf(async function* run() {
|
|
1344
|
+
const collected = [];
|
|
1345
|
+
try {
|
|
1346
|
+
const apiKey = await requireKey(model, options);
|
|
1347
|
+
const body = toAnthropicRequest(conversation, model, options);
|
|
1348
|
+
body.stream = true;
|
|
1349
|
+
const response = await postMessages(
|
|
1350
|
+
endpointFor(model),
|
|
1351
|
+
apiKey,
|
|
1352
|
+
body,
|
|
1353
|
+
options.signal,
|
|
1354
|
+
model.provider
|
|
1355
|
+
);
|
|
1356
|
+
const wire = response.body;
|
|
1357
|
+
const state = { toolIds: /* @__PURE__ */ new Map(), usage: { inputTokens: 0, outputTokens: 0 } };
|
|
1358
|
+
for await (const event of sseEvents(wire, options.signal)) {
|
|
1359
|
+
const payload = parseEventData(event, "anthropic");
|
|
1360
|
+
if (payload === void 0) {
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
const type = event.event !== "message" ? event.event : readString(payload, "type") ?? event.event;
|
|
1364
|
+
for (const emission of emitFromEvent(type, payload, state)) {
|
|
1365
|
+
collected.push(emission);
|
|
1366
|
+
yield emission;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
const reply = foldReply(model.id, collected);
|
|
1370
|
+
yield { kind: "done", reply };
|
|
1371
|
+
} catch (cause) {
|
|
1372
|
+
yield { kind: "error", error: asGatewayError(cause) };
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
function complete2(model, conversation, options) {
|
|
1377
|
+
return collectReply(stream2(model, conversation, options));
|
|
1378
|
+
}
|
|
1379
|
+
return {
|
|
1380
|
+
id: "anthropic",
|
|
1381
|
+
api,
|
|
1382
|
+
stream: stream2,
|
|
1383
|
+
complete: complete2
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// src/llmgateway/conversion/openai-compatible.ts
|
|
1388
|
+
function isObject2(v) {
|
|
1389
|
+
return typeof v === "object" && v !== null;
|
|
1390
|
+
}
|
|
1391
|
+
function str(o, key) {
|
|
1392
|
+
const v = o[key];
|
|
1393
|
+
return typeof v === "string" ? v : void 0;
|
|
1394
|
+
}
|
|
1395
|
+
function num(o, key) {
|
|
1396
|
+
const v = o[key];
|
|
1397
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
1398
|
+
}
|
|
1399
|
+
function mapFinishReason(reason) {
|
|
1400
|
+
switch (reason) {
|
|
1401
|
+
case "stop":
|
|
1402
|
+
return "complete";
|
|
1403
|
+
case "length":
|
|
1404
|
+
return "max_output";
|
|
1405
|
+
case "tool_calls":
|
|
1406
|
+
case "function_call":
|
|
1407
|
+
return "tool_calls";
|
|
1408
|
+
case "content_filter":
|
|
1409
|
+
return "refusal";
|
|
1410
|
+
case null:
|
|
1411
|
+
case void 0:
|
|
1412
|
+
case "":
|
|
1413
|
+
return void 0;
|
|
1414
|
+
default:
|
|
1415
|
+
return "complete";
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
function readUsage(chunk) {
|
|
1419
|
+
const usage = chunk.usage;
|
|
1420
|
+
if (!isObject2(usage)) {
|
|
1421
|
+
return void 0;
|
|
1422
|
+
}
|
|
1423
|
+
const inputTokens = num(usage, "prompt_tokens") ?? 0;
|
|
1424
|
+
const outputTokens = num(usage, "completion_tokens") ?? 0;
|
|
1425
|
+
const result = { inputTokens, outputTokens };
|
|
1426
|
+
const details = usage.prompt_tokens_details;
|
|
1427
|
+
if (isObject2(details)) {
|
|
1428
|
+
const cached = num(details, "cached_tokens");
|
|
1429
|
+
if (cached !== void 0) {
|
|
1430
|
+
result.cacheReadTokens = cached;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return result;
|
|
1434
|
+
}
|
|
1435
|
+
function buildBody(model, c, opts, cfg, stream2) {
|
|
1436
|
+
const body = {
|
|
1437
|
+
model: model.id,
|
|
1438
|
+
messages: toOpenAIChatMessages(c),
|
|
1439
|
+
stream: stream2
|
|
1440
|
+
};
|
|
1441
|
+
if (stream2) {
|
|
1442
|
+
body.stream_options = { include_usage: true };
|
|
1443
|
+
}
|
|
1444
|
+
const tools = toOpenAITools(c.tools);
|
|
1445
|
+
if (tools !== void 0) {
|
|
1446
|
+
body.tools = tools;
|
|
1447
|
+
const choice = toOpenAIToolChoice(opts.toolChoice);
|
|
1448
|
+
if (choice !== void 0) {
|
|
1449
|
+
body.tool_choice = choice;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
const maxTokens = opts.maxOutputTokens ?? model.maxOutputTokens;
|
|
1453
|
+
if (maxTokens > 0) {
|
|
1454
|
+
body.max_tokens = maxTokens;
|
|
1455
|
+
}
|
|
1456
|
+
if (opts.temperature !== void 0) {
|
|
1457
|
+
body.temperature = opts.temperature;
|
|
1458
|
+
}
|
|
1459
|
+
if (opts.topP !== void 0) {
|
|
1460
|
+
body.top_p = opts.topP;
|
|
1461
|
+
}
|
|
1462
|
+
if (cfg.bodyExtra !== void 0) {
|
|
1463
|
+
for (const [k, v] of Object.entries(cfg.bodyExtra)) {
|
|
1464
|
+
body[k] = v;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
return body;
|
|
1468
|
+
}
|
|
1469
|
+
async function postChat(cfg, body, provider, signal) {
|
|
1470
|
+
const headers = { "content-type": "application/json", ...cfg.headers };
|
|
1471
|
+
let response;
|
|
1472
|
+
try {
|
|
1473
|
+
response = await fetch(cfg.url, {
|
|
1474
|
+
method: "POST",
|
|
1475
|
+
headers,
|
|
1476
|
+
body: JSON.stringify(body),
|
|
1477
|
+
...signal !== void 0 ? { signal } : {}
|
|
1478
|
+
});
|
|
1479
|
+
} catch (cause) {
|
|
1480
|
+
if (signal?.aborted) {
|
|
1481
|
+
throw gatewayError("aborted", "request aborted before a response arrived", { provider, cause });
|
|
1482
|
+
}
|
|
1483
|
+
throw gatewayError("transport", "network request to the provider failed", { provider, cause });
|
|
1484
|
+
}
|
|
1485
|
+
if (!response.ok) {
|
|
1486
|
+
const detail = await readErrorBody2(response);
|
|
1487
|
+
const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
|
|
1488
|
+
throw gatewayError(kind, `provider returned HTTP ${response.status}${detail}`, {
|
|
1489
|
+
provider,
|
|
1490
|
+
status: response.status
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
if (response.body === null) {
|
|
1494
|
+
throw gatewayError("transport", "provider response had no body", { provider, status: response.status });
|
|
1495
|
+
}
|
|
1496
|
+
return response;
|
|
1497
|
+
}
|
|
1498
|
+
async function readErrorBody2(response) {
|
|
1499
|
+
try {
|
|
1500
|
+
const text = await response.text();
|
|
1501
|
+
return text === "" ? "" : `: ${text.slice(0, 500)}`;
|
|
1502
|
+
} catch {
|
|
1503
|
+
return "";
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
function* emitFromChunk(chunk, tracker) {
|
|
1507
|
+
const usage = readUsage(chunk);
|
|
1508
|
+
if (usage !== void 0) {
|
|
1509
|
+
yield { kind: "usage", usage };
|
|
1510
|
+
}
|
|
1511
|
+
const choices = chunk.choices;
|
|
1512
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
const choice = choices[0];
|
|
1516
|
+
if (!isObject2(choice)) {
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
const delta = choice.delta;
|
|
1520
|
+
if (isObject2(delta)) {
|
|
1521
|
+
const content = str(delta, "content");
|
|
1522
|
+
if (content !== void 0 && content !== "") {
|
|
1523
|
+
yield { kind: "text", delta: content };
|
|
1524
|
+
}
|
|
1525
|
+
const reasoning = str(delta, "reasoning_content") ?? str(delta, "reasoning");
|
|
1526
|
+
if (reasoning !== void 0 && reasoning !== "") {
|
|
1527
|
+
yield { kind: "thinking", delta: reasoning };
|
|
1528
|
+
}
|
|
1529
|
+
const toolCalls = delta.tool_calls;
|
|
1530
|
+
if (Array.isArray(toolCalls)) {
|
|
1531
|
+
for (const tc of toolCalls) {
|
|
1532
|
+
yield* emitToolCall(tc, tracker);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
const finish = mapFinishReason(str(choice, "finish_reason"));
|
|
1537
|
+
if (finish !== void 0) {
|
|
1538
|
+
yield { kind: "stop", stop: finish };
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
function* emitToolCall(tc, tracker) {
|
|
1542
|
+
if (!isObject2(tc)) {
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
const index = num(tc, "index") ?? 0;
|
|
1546
|
+
const explicitId = str(tc, "id");
|
|
1547
|
+
const fn = tc.function;
|
|
1548
|
+
const name = isObject2(fn) ? str(fn, "name") : void 0;
|
|
1549
|
+
const args = isObject2(fn) ? str(fn, "arguments") : void 0;
|
|
1550
|
+
let id = tracker.ids.get(index);
|
|
1551
|
+
if (id === void 0) {
|
|
1552
|
+
id = explicitId ?? `call_${index}`;
|
|
1553
|
+
tracker.ids.set(index, id);
|
|
1554
|
+
yield { kind: "tool_call_start", id, name: name ?? "" };
|
|
1555
|
+
} else if (explicitId !== void 0 && explicitId !== id) {
|
|
1556
|
+
}
|
|
1557
|
+
if (args !== void 0 && args !== "") {
|
|
1558
|
+
yield { kind: "tool_call_delta", id, argsDelta: args };
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
function streamOpenAICompatibleChat(cfg, model, c, opts) {
|
|
1562
|
+
return channelOf(async function* run() {
|
|
1563
|
+
const provider = model.provider;
|
|
1564
|
+
const collected = [];
|
|
1565
|
+
try {
|
|
1566
|
+
const body = buildBody(model, c, opts, cfg, true);
|
|
1567
|
+
const response = await postChat(cfg, body, provider, opts.signal);
|
|
1568
|
+
const stream2 = response.body;
|
|
1569
|
+
const tracker = { ids: /* @__PURE__ */ new Map() };
|
|
1570
|
+
for await (const sse of sseEvents(stream2, opts.signal)) {
|
|
1571
|
+
const payload = sse.data;
|
|
1572
|
+
if (payload === "[DONE]") {
|
|
1573
|
+
break;
|
|
1574
|
+
}
|
|
1575
|
+
let chunk;
|
|
1576
|
+
try {
|
|
1577
|
+
chunk = JSON.parse(payload);
|
|
1578
|
+
} catch (cause) {
|
|
1579
|
+
throw gatewayError("parse", "failed to parse an OpenAI stream chunk", { provider, cause });
|
|
1580
|
+
}
|
|
1581
|
+
if (!isObject2(chunk)) {
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
for (const emission of emitFromChunk(chunk, tracker)) {
|
|
1585
|
+
collected.push(emission);
|
|
1586
|
+
yield emission;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
const reply = foldReply(model.id, collected);
|
|
1590
|
+
yield { kind: "done", reply };
|
|
1591
|
+
} catch (cause) {
|
|
1592
|
+
const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("transport", "OpenAI-compatible stream failed", { provider, cause });
|
|
1593
|
+
yield { kind: "error", error };
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// src/llmgateway/connectors/openai-chat.ts
|
|
1599
|
+
var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
1600
|
+
function endpointFor2(model) {
|
|
1601
|
+
const base = (model.baseUrl ?? DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, "");
|
|
1602
|
+
return `${base}/chat/completions`;
|
|
1603
|
+
}
|
|
1604
|
+
var OpenAIChatConnector = class {
|
|
1605
|
+
constructor(deps2) {
|
|
1606
|
+
this.deps = deps2;
|
|
1607
|
+
}
|
|
1608
|
+
deps;
|
|
1609
|
+
id = "openai-chat";
|
|
1610
|
+
api = "openai-completions";
|
|
1611
|
+
stream(model, conversation, options) {
|
|
1612
|
+
const deps2 = this.deps;
|
|
1613
|
+
return channelOf(async function* run() {
|
|
1614
|
+
let cfg;
|
|
1615
|
+
try {
|
|
1616
|
+
cfg = await resolveConfig(deps2, model, options);
|
|
1617
|
+
} catch (cause) {
|
|
1618
|
+
const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("auth", "could not prepare the OpenAI request", {
|
|
1619
|
+
provider: model.provider,
|
|
1620
|
+
cause
|
|
1621
|
+
});
|
|
1622
|
+
yield { kind: "error", error };
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
yield* streamOpenAICompatibleChat(cfg, model, conversation, options);
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
async complete(model, conversation, options) {
|
|
1629
|
+
return collectReply(this.stream(model, conversation, options));
|
|
1630
|
+
}
|
|
1631
|
+
};
|
|
1632
|
+
async function resolveConfig(deps2, model, options) {
|
|
1633
|
+
const key = await deps2.resolveSecret(model.provider, options.apiKey);
|
|
1634
|
+
if (key === void 0 || key === "") {
|
|
1635
|
+
throw gatewayError(
|
|
1636
|
+
"auth",
|
|
1637
|
+
"no OpenAI credential found \u2014 set OPENAI_API_KEY or pass options.apiKey",
|
|
1638
|
+
{ provider: model.provider }
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
return {
|
|
1642
|
+
url: endpointFor2(model),
|
|
1643
|
+
headers: { authorization: `Bearer ${key}` }
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
function createopenaichatConnector(deps2) {
|
|
1647
|
+
return new OpenAIChatConnector(deps2);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// src/llmgateway/connectors/openai-responses.ts
|
|
1651
|
+
var DEFAULT_ORIGIN = "https://api.openai.com/v1";
|
|
1652
|
+
function isObject3(v) {
|
|
1653
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1654
|
+
}
|
|
1655
|
+
function readString2(o, key) {
|
|
1656
|
+
const v = o[key];
|
|
1657
|
+
return typeof v === "string" ? v : void 0;
|
|
1658
|
+
}
|
|
1659
|
+
function readNumber2(o, key) {
|
|
1660
|
+
const v = o[key];
|
|
1661
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
1662
|
+
}
|
|
1663
|
+
function reasoningEffort(level) {
|
|
1664
|
+
switch (level) {
|
|
1665
|
+
case "low":
|
|
1666
|
+
return "low";
|
|
1667
|
+
case "medium":
|
|
1668
|
+
return "medium";
|
|
1669
|
+
case "high":
|
|
1670
|
+
case "max":
|
|
1671
|
+
return "high";
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
function mapStatus(status, incompleteReason) {
|
|
1675
|
+
switch (status) {
|
|
1676
|
+
case "completed":
|
|
1677
|
+
return "complete";
|
|
1678
|
+
case "incomplete":
|
|
1679
|
+
return incompleteReason === "max_output_tokens" ? "max_output" : "complete";
|
|
1680
|
+
case "failed":
|
|
1681
|
+
case "cancelled":
|
|
1682
|
+
return "error";
|
|
1683
|
+
default:
|
|
1684
|
+
return "complete";
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
function joinUrl(origin, path) {
|
|
1688
|
+
return origin.endsWith("/") ? origin.slice(0, -1) + path : origin + path;
|
|
1689
|
+
}
|
|
1690
|
+
function buildBody2(model, c, opts) {
|
|
1691
|
+
const body = {
|
|
1692
|
+
model: model.id,
|
|
1693
|
+
input: toResponsesInput(c),
|
|
1694
|
+
stream: true
|
|
1695
|
+
};
|
|
1696
|
+
if (c.system !== void 0 && c.system !== "") {
|
|
1697
|
+
body.instructions = c.system;
|
|
1698
|
+
}
|
|
1699
|
+
const tools = toResponsesTools(c.tools);
|
|
1700
|
+
if (tools !== void 0) {
|
|
1701
|
+
body.tools = tools;
|
|
1702
|
+
if (opts.toolChoice !== void 0) {
|
|
1703
|
+
body.tool_choice = opts.toolChoice;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
const ceiling = opts.maxOutputTokens ?? model.maxOutputTokens;
|
|
1707
|
+
if (ceiling > 0) {
|
|
1708
|
+
body.max_output_tokens = ceiling;
|
|
1709
|
+
}
|
|
1710
|
+
if (opts.temperature !== void 0) {
|
|
1711
|
+
body.temperature = opts.temperature;
|
|
1712
|
+
}
|
|
1713
|
+
if (opts.topP !== void 0) {
|
|
1714
|
+
body.top_p = opts.topP;
|
|
1715
|
+
}
|
|
1716
|
+
if (model.reasoning && opts.thinking !== void 0 && opts.thinking !== "off") {
|
|
1717
|
+
body.reasoning = { effort: reasoningEffort(opts.thinking) };
|
|
1718
|
+
}
|
|
1719
|
+
return body;
|
|
1720
|
+
}
|
|
1721
|
+
async function postResponses(url, apiKey, body, provider, signal) {
|
|
1722
|
+
let response;
|
|
1723
|
+
try {
|
|
1724
|
+
response = await fetch(url, {
|
|
1725
|
+
method: "POST",
|
|
1726
|
+
headers: {
|
|
1727
|
+
"content-type": "application/json",
|
|
1728
|
+
authorization: `Bearer ${apiKey}`
|
|
1729
|
+
},
|
|
1730
|
+
body: JSON.stringify(body),
|
|
1731
|
+
...signal !== void 0 ? { signal } : {}
|
|
1732
|
+
});
|
|
1733
|
+
} catch (cause) {
|
|
1734
|
+
if (signal?.aborted) {
|
|
1735
|
+
throw gatewayError("aborted", "the Responses request was cancelled before a reply arrived", {
|
|
1736
|
+
provider,
|
|
1737
|
+
cause
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
throw gatewayError("transport", "could not reach the OpenAI Responses endpoint", { provider, cause });
|
|
1741
|
+
}
|
|
1742
|
+
if (!response.ok) {
|
|
1743
|
+
const detail = await describeFailure(response);
|
|
1744
|
+
const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
|
|
1745
|
+
throw gatewayError(kind, `OpenAI Responses replied with HTTP ${response.status}${detail}`, {
|
|
1746
|
+
provider,
|
|
1747
|
+
status: response.status
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
if (response.body === null) {
|
|
1751
|
+
throw gatewayError("transport", "OpenAI Responses returned an empty body", {
|
|
1752
|
+
provider,
|
|
1753
|
+
status: response.status
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
return response;
|
|
1757
|
+
}
|
|
1758
|
+
async function describeFailure(response) {
|
|
1759
|
+
try {
|
|
1760
|
+
const text = await response.text();
|
|
1761
|
+
return text === "" ? "" : ` \u2014 ${text.slice(0, 500)}`;
|
|
1762
|
+
} catch {
|
|
1763
|
+
return "";
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
function extractUsage(response) {
|
|
1767
|
+
const usage = response.usage;
|
|
1768
|
+
if (!isObject3(usage)) {
|
|
1769
|
+
return void 0;
|
|
1770
|
+
}
|
|
1771
|
+
const inputTokens = readNumber2(usage, "input_tokens") ?? 0;
|
|
1772
|
+
const outputTokens = readNumber2(usage, "output_tokens") ?? 0;
|
|
1773
|
+
const result = { inputTokens, outputTokens };
|
|
1774
|
+
const inputDetails = usage.input_tokens_details;
|
|
1775
|
+
if (isObject3(inputDetails)) {
|
|
1776
|
+
const cached = readNumber2(inputDetails, "cached_tokens");
|
|
1777
|
+
if (cached !== void 0) {
|
|
1778
|
+
result.cacheReadTokens = cached;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
return result;
|
|
1782
|
+
}
|
|
1783
|
+
function* translateEvent(payload, calls) {
|
|
1784
|
+
const type = readString2(payload, "type");
|
|
1785
|
+
switch (type) {
|
|
1786
|
+
case "response.output_text.delta": {
|
|
1787
|
+
const delta = readString2(payload, "delta");
|
|
1788
|
+
if (delta !== void 0 && delta !== "") {
|
|
1789
|
+
yield { kind: "text", delta };
|
|
1790
|
+
}
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
case "response.reasoning_summary_text.delta":
|
|
1794
|
+
case "response.reasoning_text.delta": {
|
|
1795
|
+
const delta = readString2(payload, "delta");
|
|
1796
|
+
if (delta !== void 0 && delta !== "") {
|
|
1797
|
+
yield { kind: "thinking", delta };
|
|
1798
|
+
}
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
case "response.output_item.added": {
|
|
1802
|
+
yield* announceItem(payload, calls);
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
case "response.function_call_arguments.delta": {
|
|
1806
|
+
const itemId = readString2(payload, "item_id");
|
|
1807
|
+
const delta = readString2(payload, "delta");
|
|
1808
|
+
if (itemId === void 0 || delta === void 0 || delta === "") {
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
const id = calls.byItem.get(itemId) ?? itemId;
|
|
1812
|
+
yield { kind: "tool_call_delta", id, argsDelta: delta };
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
case "response.completed":
|
|
1816
|
+
case "response.incomplete":
|
|
1817
|
+
case "response.failed": {
|
|
1818
|
+
yield* finishResponse(payload);
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
default:
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
function* announceItem(payload, calls) {
|
|
1826
|
+
const item = payload.item;
|
|
1827
|
+
if (!isObject3(item)) {
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
if (readString2(item, "type") !== "function_call") {
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
const name = readString2(item, "name") ?? "";
|
|
1834
|
+
const callId = readString2(item, "call_id") ?? readString2(item, "id") ?? `call_${calls.byItem.size}`;
|
|
1835
|
+
const itemId = readString2(item, "id");
|
|
1836
|
+
if (itemId !== void 0) {
|
|
1837
|
+
calls.byItem.set(itemId, callId);
|
|
1838
|
+
}
|
|
1839
|
+
yield { kind: "tool_call_start", id: callId, name };
|
|
1840
|
+
}
|
|
1841
|
+
function* finishResponse(payload) {
|
|
1842
|
+
const response = isObject3(payload.response) ? payload.response : payload;
|
|
1843
|
+
const usage = extractUsage(response);
|
|
1844
|
+
if (usage !== void 0) {
|
|
1845
|
+
yield { kind: "usage", usage };
|
|
1846
|
+
}
|
|
1847
|
+
const status = readString2(response, "status");
|
|
1848
|
+
const incomplete = isObject3(response.incomplete_details) ? readString2(response.incomplete_details, "reason") : void 0;
|
|
1849
|
+
yield { kind: "stop", stop: mapStatus(status, incomplete) };
|
|
1850
|
+
}
|
|
1851
|
+
function createopenairesponsesConnector(deps2) {
|
|
1852
|
+
const { resolveSecret: resolveSecret2 } = deps2;
|
|
1853
|
+
const stream2 = (model, conversation, options) => {
|
|
1854
|
+
return channelOf(async function* run() {
|
|
1855
|
+
const provider = model.provider;
|
|
1856
|
+
const collected = [];
|
|
1857
|
+
try {
|
|
1858
|
+
const apiKey = await resolveSecret2(provider, options.apiKey);
|
|
1859
|
+
if (apiKey === void 0 || apiKey === "") {
|
|
1860
|
+
throw gatewayError("auth", "no OpenAI API key was found for the Responses endpoint", { provider });
|
|
1861
|
+
}
|
|
1862
|
+
const origin = model.baseUrl ?? DEFAULT_ORIGIN;
|
|
1863
|
+
const url = joinUrl(origin, "/responses");
|
|
1864
|
+
const body = buildBody2(model, conversation, options);
|
|
1865
|
+
const response = await postResponses(url, apiKey, body, provider, options.signal);
|
|
1866
|
+
const wire = response.body;
|
|
1867
|
+
const calls = { byItem: /* @__PURE__ */ new Map() };
|
|
1868
|
+
for await (const sse of sseEvents(wire, options.signal)) {
|
|
1869
|
+
const raw = sse.data;
|
|
1870
|
+
if (raw === "" || raw === "[DONE]") {
|
|
1871
|
+
continue;
|
|
1872
|
+
}
|
|
1873
|
+
let payload;
|
|
1874
|
+
try {
|
|
1875
|
+
payload = JSON.parse(raw);
|
|
1876
|
+
} catch (cause) {
|
|
1877
|
+
throw gatewayError("parse", "could not parse an OpenAI Responses stream event", {
|
|
1878
|
+
provider,
|
|
1879
|
+
cause
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
if (!isObject3(payload)) {
|
|
1883
|
+
continue;
|
|
1884
|
+
}
|
|
1885
|
+
for (const emission of translateEvent(payload, calls)) {
|
|
1886
|
+
collected.push(emission);
|
|
1887
|
+
yield emission;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
yield { kind: "done", reply: foldReply(model.id, collected) };
|
|
1891
|
+
} catch (cause) {
|
|
1892
|
+
const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("transport", "the OpenAI Responses stream failed", { provider, cause });
|
|
1893
|
+
yield { kind: "error", error };
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
};
|
|
1897
|
+
const complete2 = (model, conversation, options) => {
|
|
1898
|
+
return collectReply(stream2(model, conversation, options));
|
|
1899
|
+
};
|
|
1900
|
+
return {
|
|
1901
|
+
id: "openai-responses",
|
|
1902
|
+
api: "openai-responses",
|
|
1903
|
+
stream: stream2,
|
|
1904
|
+
complete: complete2
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// src/llmgateway/streaming/ndjson.ts
|
|
1909
|
+
async function* ndjsonLines(body, signal) {
|
|
1910
|
+
if (signal?.aborted) {
|
|
1911
|
+
throw gatewayError("aborted", "request aborted before reading the ndjson stream");
|
|
1912
|
+
}
|
|
1913
|
+
const reader = body.getReader();
|
|
1914
|
+
const decoder = new TextDecoder("utf-8");
|
|
1915
|
+
let pending = "";
|
|
1916
|
+
const onAbort = () => {
|
|
1917
|
+
void reader.cancel().catch(() => {
|
|
1918
|
+
});
|
|
1919
|
+
};
|
|
1920
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1921
|
+
const parseLine = (raw) => {
|
|
1922
|
+
const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
|
|
1923
|
+
const trimmed = line.trim();
|
|
1924
|
+
if (trimmed === "") {
|
|
1925
|
+
return void 0;
|
|
1926
|
+
}
|
|
1927
|
+
try {
|
|
1928
|
+
return JSON.parse(trimmed);
|
|
1929
|
+
} catch (cause) {
|
|
1930
|
+
throw gatewayError("parse", "failed to parse an ndjson line as JSON", { cause });
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
try {
|
|
1934
|
+
for (; ; ) {
|
|
1935
|
+
if (signal?.aborted) {
|
|
1936
|
+
throw gatewayError("aborted", "request aborted while reading the ndjson stream");
|
|
1937
|
+
}
|
|
1938
|
+
const { value, done } = await reader.read();
|
|
1939
|
+
if (done) {
|
|
1940
|
+
break;
|
|
1941
|
+
}
|
|
1942
|
+
pending += decoder.decode(value, { stream: true });
|
|
1943
|
+
let nl = pending.indexOf("\n");
|
|
1944
|
+
while (nl !== -1) {
|
|
1945
|
+
const raw = pending.slice(0, nl);
|
|
1946
|
+
pending = pending.slice(nl + 1);
|
|
1947
|
+
const parsed = parseLine(raw);
|
|
1948
|
+
if (parsed !== void 0) {
|
|
1949
|
+
yield parsed;
|
|
1950
|
+
}
|
|
1951
|
+
nl = pending.indexOf("\n");
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
pending += decoder.decode();
|
|
1955
|
+
const tail = parseLine(pending);
|
|
1956
|
+
if (tail !== void 0) {
|
|
1957
|
+
yield tail;
|
|
1958
|
+
}
|
|
1959
|
+
} catch (cause) {
|
|
1960
|
+
if (signal?.aborted) {
|
|
1961
|
+
throw gatewayError("aborted", "request aborted while reading the ndjson stream", { cause });
|
|
1962
|
+
}
|
|
1963
|
+
if (cause instanceof Error && cause.name === "GatewayError") {
|
|
1964
|
+
throw cause;
|
|
1965
|
+
}
|
|
1966
|
+
throw gatewayError("transport", "failed reading the ndjson stream", { cause });
|
|
1967
|
+
} finally {
|
|
1968
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1969
|
+
reader.releaseLock();
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// src/llmgateway/connectors/google.ts
|
|
1974
|
+
var DEFAULT_BASE_URL2 = "https://generativelanguage.googleapis.com";
|
|
1975
|
+
var PROVIDER = "google";
|
|
1976
|
+
function isObject4(value) {
|
|
1977
|
+
return typeof value === "object" && value !== null;
|
|
1978
|
+
}
|
|
1979
|
+
function readString3(o, key) {
|
|
1980
|
+
const v = o[key];
|
|
1981
|
+
return typeof v === "string" ? v : void 0;
|
|
1982
|
+
}
|
|
1983
|
+
function readNumber3(o, key) {
|
|
1984
|
+
const v = o[key];
|
|
1985
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
1986
|
+
}
|
|
1987
|
+
function geminiThinkingBudget(level, maxOutput) {
|
|
1988
|
+
const fraction = {
|
|
1989
|
+
low: 0.2,
|
|
1990
|
+
medium: 0.4,
|
|
1991
|
+
high: 0.6,
|
|
1992
|
+
max: 0.8
|
|
1993
|
+
};
|
|
1994
|
+
const budget = Math.floor(maxOutput * fraction[level]);
|
|
1995
|
+
return Math.max(1024, budget);
|
|
1996
|
+
}
|
|
1997
|
+
function buildGenerationConfig(model, opts) {
|
|
1998
|
+
const maxOutputTokens = opts.maxOutputTokens ?? model.maxOutputTokens;
|
|
1999
|
+
const config = { maxOutputTokens };
|
|
2000
|
+
if (opts.temperature !== void 0) {
|
|
2001
|
+
config.temperature = opts.temperature;
|
|
2002
|
+
}
|
|
2003
|
+
if (opts.topP !== void 0) {
|
|
2004
|
+
config.topP = opts.topP;
|
|
2005
|
+
}
|
|
2006
|
+
if (model.reasoning && opts.thinking !== void 0 && opts.thinking !== "off") {
|
|
2007
|
+
config.thinkingConfig = {
|
|
2008
|
+
includeThoughts: true,
|
|
2009
|
+
thinkingBudget: geminiThinkingBudget(opts.thinking, maxOutputTokens)
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
return config;
|
|
2013
|
+
}
|
|
2014
|
+
function buildRequestBody(model, conversation, opts) {
|
|
2015
|
+
const body = {
|
|
2016
|
+
contents: toGoogleContents(conversation),
|
|
2017
|
+
generationConfig: buildGenerationConfig(model, opts)
|
|
2018
|
+
};
|
|
2019
|
+
const systemInstruction = toGoogleSystemInstruction(conversation);
|
|
2020
|
+
if (systemInstruction !== void 0) {
|
|
2021
|
+
body.systemInstruction = systemInstruction;
|
|
2022
|
+
}
|
|
2023
|
+
const tools = toGoogleTools(conversation.tools);
|
|
2024
|
+
if (tools !== void 0) {
|
|
2025
|
+
body.tools = tools;
|
|
2026
|
+
}
|
|
2027
|
+
return body;
|
|
2028
|
+
}
|
|
2029
|
+
function streamEndpoint(model) {
|
|
2030
|
+
const base = (model.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/u, "");
|
|
2031
|
+
return `${base}/v1beta/models/${model.id}:streamGenerateContent?alt=sse`;
|
|
2032
|
+
}
|
|
2033
|
+
async function openStream(url, apiKey, body, signal) {
|
|
2034
|
+
let response;
|
|
2035
|
+
try {
|
|
2036
|
+
response = await fetch(url, {
|
|
2037
|
+
method: "POST",
|
|
2038
|
+
headers: {
|
|
2039
|
+
"content-type": "application/json",
|
|
2040
|
+
"x-goog-api-key": apiKey
|
|
2041
|
+
},
|
|
2042
|
+
body: JSON.stringify(body),
|
|
2043
|
+
...signal !== void 0 ? { signal } : {}
|
|
2044
|
+
});
|
|
2045
|
+
} catch (cause) {
|
|
2046
|
+
if (signal?.aborted) {
|
|
2047
|
+
throw gatewayError("aborted", "the Gemini request was aborted before a response arrived", {
|
|
2048
|
+
provider: PROVIDER,
|
|
2049
|
+
cause
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
throw gatewayError("transport", "could not reach the Gemini endpoint", {
|
|
2053
|
+
provider: PROVIDER,
|
|
2054
|
+
cause
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
if (!response.ok) {
|
|
2058
|
+
const detail = await readErrorDetail(response);
|
|
2059
|
+
const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
|
|
2060
|
+
throw gatewayError(kind, `Gemini replied with HTTP ${response.status}${detail}`, {
|
|
2061
|
+
provider: PROVIDER,
|
|
2062
|
+
status: response.status
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
if (response.body === null) {
|
|
2066
|
+
throw gatewayError("transport", "the Gemini response carried no body to stream", {
|
|
2067
|
+
provider: PROVIDER,
|
|
2068
|
+
status: response.status
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
return response;
|
|
2072
|
+
}
|
|
2073
|
+
async function readErrorDetail(response) {
|
|
2074
|
+
try {
|
|
2075
|
+
const text = await response.text();
|
|
2076
|
+
return text === "" ? "" : `: ${text.slice(0, 500)}`;
|
|
2077
|
+
} catch {
|
|
2078
|
+
return "";
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
function mapFinishReason2(reason) {
|
|
2082
|
+
switch (reason) {
|
|
2083
|
+
case "STOP":
|
|
2084
|
+
return "complete";
|
|
2085
|
+
case "MAX_TOKENS":
|
|
2086
|
+
return "max_output";
|
|
2087
|
+
case "SAFETY":
|
|
2088
|
+
case "RECITATION":
|
|
2089
|
+
case "BLOCKLIST":
|
|
2090
|
+
case "PROHIBITED_CONTENT":
|
|
2091
|
+
case "SPII":
|
|
2092
|
+
return "refusal";
|
|
2093
|
+
case void 0:
|
|
2094
|
+
case "":
|
|
2095
|
+
case "FINISH_REASON_UNSPECIFIED":
|
|
2096
|
+
case "OTHER":
|
|
2097
|
+
return void 0;
|
|
2098
|
+
default:
|
|
2099
|
+
return "complete";
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
function readUsage2(frame) {
|
|
2103
|
+
const meta = frame.usageMetadata;
|
|
2104
|
+
if (!isObject4(meta)) {
|
|
2105
|
+
return void 0;
|
|
2106
|
+
}
|
|
2107
|
+
const inputTokens = readNumber3(meta, "promptTokenCount") ?? 0;
|
|
2108
|
+
const outputTokens = readNumber3(meta, "candidatesTokenCount") ?? 0;
|
|
2109
|
+
const usage = { inputTokens, outputTokens };
|
|
2110
|
+
const cachedRead = readNumber3(meta, "cachedContentTokenCount");
|
|
2111
|
+
if (cachedRead !== void 0) {
|
|
2112
|
+
usage.cacheReadTokens = cachedRead;
|
|
2113
|
+
}
|
|
2114
|
+
return usage;
|
|
2115
|
+
}
|
|
2116
|
+
function* emitFromParts(parts, tracker) {
|
|
2117
|
+
for (const part of parts) {
|
|
2118
|
+
if (!isObject4(part)) {
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
const text = readString3(part, "text");
|
|
2122
|
+
if (text !== void 0 && text !== "") {
|
|
2123
|
+
if (part.thought === true) {
|
|
2124
|
+
yield { kind: "thinking", delta: text };
|
|
2125
|
+
} else {
|
|
2126
|
+
yield { kind: "text", delta: text };
|
|
2127
|
+
}
|
|
2128
|
+
continue;
|
|
2129
|
+
}
|
|
2130
|
+
const functionCall = part.functionCall;
|
|
2131
|
+
if (isObject4(functionCall)) {
|
|
2132
|
+
const name = readString3(functionCall, "name") ?? "";
|
|
2133
|
+
const id = `gemini_call_${tracker.next}`;
|
|
2134
|
+
tracker.next += 1;
|
|
2135
|
+
yield { kind: "tool_call_start", id, name };
|
|
2136
|
+
const args = "args" in functionCall ? functionCall.args : {};
|
|
2137
|
+
yield { kind: "tool_call_delta", id, argsDelta: serializeArgs(args) };
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
function serializeArgs(args) {
|
|
2142
|
+
if (typeof args === "string") {
|
|
2143
|
+
return args;
|
|
2144
|
+
}
|
|
2145
|
+
try {
|
|
2146
|
+
return JSON.stringify(args ?? {});
|
|
2147
|
+
} catch {
|
|
2148
|
+
return "{}";
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
function* emitFromFrame(frame, tracker) {
|
|
2152
|
+
const usage = readUsage2(frame);
|
|
2153
|
+
if (usage !== void 0) {
|
|
2154
|
+
yield { kind: "usage", usage };
|
|
2155
|
+
}
|
|
2156
|
+
const candidates = frame.candidates;
|
|
2157
|
+
if (!Array.isArray(candidates) || candidates.length === 0) {
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
const candidate = candidates[0];
|
|
2161
|
+
if (!isObject4(candidate)) {
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
const content = candidate.content;
|
|
2165
|
+
if (isObject4(content) && Array.isArray(content.parts)) {
|
|
2166
|
+
yield* emitFromParts(content.parts, tracker);
|
|
2167
|
+
}
|
|
2168
|
+
const stop = mapFinishReason2(readString3(candidate, "finishReason"));
|
|
2169
|
+
if (stop !== void 0) {
|
|
2170
|
+
yield { kind: "stop", stop };
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
function creategoogleConnector(deps2) {
|
|
2174
|
+
const { resolveSecret: resolveSecret2 } = deps2;
|
|
2175
|
+
return {
|
|
2176
|
+
id: "google-generative",
|
|
2177
|
+
api: "google-generative",
|
|
2178
|
+
stream(model, conversation, options) {
|
|
2179
|
+
return channelOf(async function* run() {
|
|
2180
|
+
const collected = [];
|
|
2181
|
+
try {
|
|
2182
|
+
const apiKey = await resolveSecret2(PROVIDER, options.apiKey);
|
|
2183
|
+
if (apiKey === void 0 || apiKey === "") {
|
|
2184
|
+
throw gatewayError("auth", "no Gemini API key was found (set GEMINI_API_KEY or pass apiKey)", {
|
|
2185
|
+
provider: PROVIDER
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
const url = streamEndpoint(model);
|
|
2189
|
+
const body = buildRequestBody(model, conversation, options);
|
|
2190
|
+
const response = await openStream(url, apiKey, body, options.signal);
|
|
2191
|
+
const wire = response.body;
|
|
2192
|
+
const tracker = { next: 0 };
|
|
2193
|
+
for await (const event of sseEvents(wire, options.signal)) {
|
|
2194
|
+
const payload = event.data;
|
|
2195
|
+
if (payload === "" || payload === "[DONE]") {
|
|
2196
|
+
continue;
|
|
2197
|
+
}
|
|
2198
|
+
let frame;
|
|
2199
|
+
try {
|
|
2200
|
+
frame = JSON.parse(payload);
|
|
2201
|
+
} catch (cause) {
|
|
2202
|
+
throw gatewayError("parse", "could not parse a Gemini stream frame", {
|
|
2203
|
+
provider: PROVIDER,
|
|
2204
|
+
cause
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
if (!isObject4(frame)) {
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
for (const emission of emitFromFrame(frame, tracker)) {
|
|
2211
|
+
collected.push(emission);
|
|
2212
|
+
yield emission;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
const reply = foldReply(model.id, collected);
|
|
2216
|
+
yield { kind: "done", reply };
|
|
2217
|
+
} catch (cause) {
|
|
2218
|
+
const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("transport", "the Gemini stream failed", { provider: PROVIDER, cause });
|
|
2219
|
+
yield { kind: "error", error };
|
|
2220
|
+
}
|
|
2221
|
+
});
|
|
2222
|
+
},
|
|
2223
|
+
complete(model, conversation, options) {
|
|
2224
|
+
return collectReply(this.stream(model, conversation, options));
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
// src/llmgateway/connectors/google-vertex.ts
|
|
2230
|
+
var PROVIDER2 = "google-vertex";
|
|
2231
|
+
var API_KIND = "google-vertex";
|
|
2232
|
+
var DEFAULT_LOCATION = "us-central1";
|
|
2233
|
+
var defaultBaseUrl = (location) => `https://${location}-aiplatform.googleapis.com`;
|
|
2234
|
+
function envValue(name) {
|
|
2235
|
+
const raw = process.env[name];
|
|
2236
|
+
if (raw === void 0) {
|
|
2237
|
+
return void 0;
|
|
2238
|
+
}
|
|
2239
|
+
const trimmed = raw.trim();
|
|
2240
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
2241
|
+
}
|
|
2242
|
+
function firstEnv(names) {
|
|
2243
|
+
for (const name of names) {
|
|
2244
|
+
const value = envValue(name);
|
|
2245
|
+
if (value !== void 0) {
|
|
2246
|
+
return value;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
return void 0;
|
|
2250
|
+
}
|
|
2251
|
+
function resolveTarget(model) {
|
|
2252
|
+
const project = firstEnv(["GOOGLE_VERTEX_PROJECT", "GOOGLE_CLOUD_PROJECT"]);
|
|
2253
|
+
if (project === void 0) {
|
|
2254
|
+
throw gatewayError(
|
|
2255
|
+
"auth",
|
|
2256
|
+
"no Vertex project found; set GOOGLE_VERTEX_PROJECT or GOOGLE_CLOUD_PROJECT",
|
|
2257
|
+
{ provider: PROVIDER2 }
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2260
|
+
const location = firstEnv(["GOOGLE_VERTEX_LOCATION", "CLOUD_ML_REGION"]) ?? DEFAULT_LOCATION;
|
|
2261
|
+
const host = (model.baseUrl ?? defaultBaseUrl(location)).replace(/\/+$/, "");
|
|
2262
|
+
const url = `${host}/v1/projects/${encodeURIComponent(project)}/locations/${encodeURIComponent(location)}/publishers/google/models/${encodeURIComponent(model.id)}:streamGenerateContent?alt=sse`;
|
|
2263
|
+
return { project, location, url };
|
|
2264
|
+
}
|
|
2265
|
+
function toFunctionCallingConfig(choice) {
|
|
2266
|
+
switch (choice) {
|
|
2267
|
+
case "auto":
|
|
2268
|
+
return { functionCallingConfig: { mode: "AUTO" } };
|
|
2269
|
+
case "required":
|
|
2270
|
+
return { functionCallingConfig: { mode: "ANY" } };
|
|
2271
|
+
case "none":
|
|
2272
|
+
return { functionCallingConfig: { mode: "NONE" } };
|
|
2273
|
+
default:
|
|
2274
|
+
return void 0;
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
function thinkingBudget(level, maxOutput) {
|
|
2278
|
+
if (level === void 0 || level === "off") {
|
|
2279
|
+
return void 0;
|
|
2280
|
+
}
|
|
2281
|
+
const fraction = {
|
|
2282
|
+
low: 0.2,
|
|
2283
|
+
medium: 0.4,
|
|
2284
|
+
high: 0.6,
|
|
2285
|
+
max: 0.8
|
|
2286
|
+
};
|
|
2287
|
+
return Math.max(0, Math.floor(maxOutput * fraction[level]));
|
|
2288
|
+
}
|
|
2289
|
+
function buildBody3(model, c, opts) {
|
|
2290
|
+
const body = {
|
|
2291
|
+
contents: toGoogleContents(c)
|
|
2292
|
+
};
|
|
2293
|
+
const systemInstruction = toGoogleSystemInstruction(c);
|
|
2294
|
+
if (systemInstruction !== void 0) {
|
|
2295
|
+
body.systemInstruction = systemInstruction;
|
|
2296
|
+
}
|
|
2297
|
+
const tools = toGoogleTools(c.tools);
|
|
2298
|
+
if (tools !== void 0) {
|
|
2299
|
+
body.tools = tools;
|
|
2300
|
+
const toolConfig = toFunctionCallingConfig(opts.toolChoice);
|
|
2301
|
+
if (toolConfig !== void 0) {
|
|
2302
|
+
body.toolConfig = toolConfig;
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
const generationConfig = {};
|
|
2306
|
+
const maxTokens = opts.maxOutputTokens ?? model.maxOutputTokens;
|
|
2307
|
+
if (maxTokens > 0) {
|
|
2308
|
+
generationConfig.maxOutputTokens = maxTokens;
|
|
2309
|
+
}
|
|
2310
|
+
if (opts.temperature !== void 0) {
|
|
2311
|
+
generationConfig.temperature = opts.temperature;
|
|
2312
|
+
}
|
|
2313
|
+
if (opts.topP !== void 0) {
|
|
2314
|
+
generationConfig.topP = opts.topP;
|
|
2315
|
+
}
|
|
2316
|
+
if (model.reasoning) {
|
|
2317
|
+
const budget = thinkingBudget(opts.thinking, maxTokens);
|
|
2318
|
+
if (budget !== void 0) {
|
|
2319
|
+
generationConfig.thinkingConfig = { thinkingBudget: budget };
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
if (Object.keys(generationConfig).length > 0) {
|
|
2323
|
+
body.generationConfig = generationConfig;
|
|
2324
|
+
}
|
|
2325
|
+
return body;
|
|
2326
|
+
}
|
|
2327
|
+
function isObject5(v) {
|
|
2328
|
+
return typeof v === "object" && v !== null;
|
|
2329
|
+
}
|
|
2330
|
+
function readString4(o, key) {
|
|
2331
|
+
const v = o[key];
|
|
2332
|
+
return typeof v === "string" ? v : void 0;
|
|
2333
|
+
}
|
|
2334
|
+
function readNumber4(o, key) {
|
|
2335
|
+
const v = o[key];
|
|
2336
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
2337
|
+
}
|
|
2338
|
+
function readBool(o, key) {
|
|
2339
|
+
return o[key] === true;
|
|
2340
|
+
}
|
|
2341
|
+
function mapFinishReason3(reason) {
|
|
2342
|
+
switch (reason) {
|
|
2343
|
+
case void 0:
|
|
2344
|
+
case "":
|
|
2345
|
+
case "FINISH_REASON_UNSPECIFIED":
|
|
2346
|
+
return void 0;
|
|
2347
|
+
case "STOP":
|
|
2348
|
+
return "complete";
|
|
2349
|
+
case "MAX_TOKENS":
|
|
2350
|
+
return "max_output";
|
|
2351
|
+
case "SAFETY":
|
|
2352
|
+
case "RECITATION":
|
|
2353
|
+
case "BLOCKLIST":
|
|
2354
|
+
case "PROHIBITED_CONTENT":
|
|
2355
|
+
case "SPII":
|
|
2356
|
+
return "refusal";
|
|
2357
|
+
default:
|
|
2358
|
+
return "complete";
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
function readUsage3(chunk) {
|
|
2362
|
+
const meta = chunk.usageMetadata;
|
|
2363
|
+
if (!isObject5(meta)) {
|
|
2364
|
+
return void 0;
|
|
2365
|
+
}
|
|
2366
|
+
const inputTokens = readNumber4(meta, "promptTokenCount") ?? 0;
|
|
2367
|
+
const candidate = readNumber4(meta, "candidatesTokenCount") ?? 0;
|
|
2368
|
+
const thoughts = readNumber4(meta, "thoughtsTokenCount") ?? 0;
|
|
2369
|
+
const result = {
|
|
2370
|
+
inputTokens,
|
|
2371
|
+
outputTokens: candidate + thoughts
|
|
2372
|
+
};
|
|
2373
|
+
const cached = readNumber4(meta, "cachedContentTokenCount");
|
|
2374
|
+
if (cached !== void 0) {
|
|
2375
|
+
result.cacheReadTokens = cached;
|
|
2376
|
+
}
|
|
2377
|
+
return result;
|
|
2378
|
+
}
|
|
2379
|
+
function* emitFromChunk2(chunk, tracker) {
|
|
2380
|
+
const usage = readUsage3(chunk);
|
|
2381
|
+
if (usage !== void 0) {
|
|
2382
|
+
yield { kind: "usage", usage };
|
|
2383
|
+
}
|
|
2384
|
+
const candidates = chunk.candidates;
|
|
2385
|
+
if (!Array.isArray(candidates) || candidates.length === 0) {
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
const candidate = candidates[0];
|
|
2389
|
+
if (!isObject5(candidate)) {
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
const content = candidate.content;
|
|
2393
|
+
if (isObject5(content)) {
|
|
2394
|
+
const parts = content.parts;
|
|
2395
|
+
if (Array.isArray(parts)) {
|
|
2396
|
+
for (const part of parts) {
|
|
2397
|
+
yield* emitFromPart(part, tracker);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
const finish = mapFinishReason3(readString4(candidate, "finishReason"));
|
|
2402
|
+
if (finish !== void 0) {
|
|
2403
|
+
yield { kind: "stop", stop: finish };
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
function* emitFromPart(part, tracker) {
|
|
2407
|
+
if (!isObject5(part)) {
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
const functionCall = part.functionCall;
|
|
2411
|
+
if (isObject5(functionCall)) {
|
|
2412
|
+
const name = readString4(functionCall, "name") ?? "";
|
|
2413
|
+
const id = `vertex_call_${tracker.next}`;
|
|
2414
|
+
tracker.next += 1;
|
|
2415
|
+
yield { kind: "tool_call_start", id, name };
|
|
2416
|
+
const args = functionCall.args;
|
|
2417
|
+
const argsDelta = serializeArgs2(args);
|
|
2418
|
+
if (argsDelta !== "") {
|
|
2419
|
+
yield { kind: "tool_call_delta", id, argsDelta };
|
|
2420
|
+
}
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
const text = readString4(part, "text");
|
|
2424
|
+
if (text !== void 0 && text !== "") {
|
|
2425
|
+
if (readBool(part, "thought")) {
|
|
2426
|
+
yield { kind: "thinking", delta: text };
|
|
2427
|
+
} else {
|
|
2428
|
+
yield { kind: "text", delta: text };
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
function serializeArgs2(args) {
|
|
2433
|
+
if (args === void 0 || args === null) {
|
|
2434
|
+
return "";
|
|
2435
|
+
}
|
|
2436
|
+
if (typeof args === "string") {
|
|
2437
|
+
return args;
|
|
2438
|
+
}
|
|
2439
|
+
try {
|
|
2440
|
+
return JSON.stringify(args);
|
|
2441
|
+
} catch {
|
|
2442
|
+
return "";
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
async function postStream(url, token, body, signal) {
|
|
2446
|
+
let response;
|
|
2447
|
+
try {
|
|
2448
|
+
response = await fetch(url, {
|
|
2449
|
+
method: "POST",
|
|
2450
|
+
headers: {
|
|
2451
|
+
"content-type": "application/json",
|
|
2452
|
+
authorization: `Bearer ${token}`
|
|
2453
|
+
},
|
|
2454
|
+
body: JSON.stringify(body),
|
|
2455
|
+
...signal !== void 0 ? { signal } : {}
|
|
2456
|
+
});
|
|
2457
|
+
} catch (cause) {
|
|
2458
|
+
if (signal?.aborted) {
|
|
2459
|
+
throw gatewayError("aborted", "Vertex request was aborted before a response arrived", {
|
|
2460
|
+
provider: PROVIDER2,
|
|
2461
|
+
cause
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
throw gatewayError("transport", "network request to Vertex AI failed", {
|
|
2465
|
+
provider: PROVIDER2,
|
|
2466
|
+
cause
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
if (!response.ok) {
|
|
2470
|
+
const detail = await readErrorBody3(response);
|
|
2471
|
+
const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
|
|
2472
|
+
throw gatewayError(kind, `Vertex AI returned HTTP ${response.status}${detail}`, {
|
|
2473
|
+
provider: PROVIDER2,
|
|
2474
|
+
status: response.status
|
|
2475
|
+
});
|
|
2476
|
+
}
|
|
2477
|
+
if (response.body === null) {
|
|
2478
|
+
throw gatewayError("transport", "Vertex AI response carried no body", {
|
|
2479
|
+
provider: PROVIDER2,
|
|
2480
|
+
status: response.status
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
return response;
|
|
2484
|
+
}
|
|
2485
|
+
async function readErrorBody3(response) {
|
|
2486
|
+
try {
|
|
2487
|
+
const text = await response.text();
|
|
2488
|
+
return text === "" ? "" : `: ${text.slice(0, 500)}`;
|
|
2489
|
+
} catch {
|
|
2490
|
+
return "";
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
function creategooglevertexConnector(deps2) {
|
|
2494
|
+
const { resolveSecret: resolveSecret2 } = deps2;
|
|
2495
|
+
function stream2(model, conversation, options) {
|
|
2496
|
+
return channelOf(async function* run() {
|
|
2497
|
+
const collected = [];
|
|
2498
|
+
try {
|
|
2499
|
+
const token = await resolveSecret2(PROVIDER2, options.apiKey);
|
|
2500
|
+
if (token === void 0 || token === "") {
|
|
2501
|
+
throw gatewayError(
|
|
2502
|
+
"auth",
|
|
2503
|
+
"no Google access token available for Vertex AI; provide one via apiKey or the environment",
|
|
2504
|
+
{ provider: PROVIDER2 }
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
const target = resolveTarget(model);
|
|
2508
|
+
const body = buildBody3(model, conversation, options);
|
|
2509
|
+
const response = await postStream(target.url, token, body, options.signal);
|
|
2510
|
+
const sseBody = response.body;
|
|
2511
|
+
const tracker = { next: 0 };
|
|
2512
|
+
for await (const event of sseEvents(sseBody, options.signal)) {
|
|
2513
|
+
const payload = event.data;
|
|
2514
|
+
if (payload === "" || payload === "[DONE]") {
|
|
2515
|
+
continue;
|
|
2516
|
+
}
|
|
2517
|
+
let chunk;
|
|
2518
|
+
try {
|
|
2519
|
+
chunk = JSON.parse(payload);
|
|
2520
|
+
} catch (cause) {
|
|
2521
|
+
throw gatewayError("parse", "failed to parse a Vertex AI stream chunk", {
|
|
2522
|
+
provider: PROVIDER2,
|
|
2523
|
+
cause
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
if (!isObject5(chunk)) {
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
for (const emission of emitFromChunk2(chunk, tracker)) {
|
|
2530
|
+
collected.push(emission);
|
|
2531
|
+
yield emission;
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
yield { kind: "done", reply: foldReply(model.id, collected) };
|
|
2535
|
+
} catch (cause) {
|
|
2536
|
+
const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("transport", "Vertex AI stream failed", { provider: PROVIDER2, cause });
|
|
2537
|
+
yield { kind: "error", error };
|
|
2538
|
+
}
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
function complete2(model, conversation, options) {
|
|
2542
|
+
return collectReply(stream2(model, conversation, options));
|
|
2543
|
+
}
|
|
2544
|
+
return {
|
|
2545
|
+
id: "google-vertex",
|
|
2546
|
+
api: API_KIND,
|
|
2547
|
+
stream: stream2,
|
|
2548
|
+
complete: complete2
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
// src/llmgateway/connectors/bedrock.ts
|
|
2553
|
+
var PROVIDER3 = "amazon";
|
|
2554
|
+
var DIALECT = "amazon-bedrock";
|
|
2555
|
+
var FALLBACK_REGION = "us-east-1";
|
|
2556
|
+
function discoverRegion() {
|
|
2557
|
+
const env = (typeof process !== "undefined" ? process.env : void 0) ?? {};
|
|
2558
|
+
const region = env.AWS_REGION ?? env.AWS_DEFAULT_REGION ?? env.AWS_BEDROCK_REGION;
|
|
2559
|
+
const trimmed = region?.trim();
|
|
2560
|
+
return trimmed !== void 0 && trimmed !== "" ? trimmed : void 0;
|
|
2561
|
+
}
|
|
2562
|
+
function converseStreamUrl(model, region) {
|
|
2563
|
+
const base = model.baseUrl !== void 0 && model.baseUrl !== "" ? model.baseUrl.replace(/\/+$/, "") : `https://bedrock-runtime.${region}.amazonaws.com`;
|
|
2564
|
+
return `${base}/model/${encodeURIComponent(model.id)}/converse-stream`;
|
|
2565
|
+
}
|
|
2566
|
+
function stringifyOutput(output) {
|
|
2567
|
+
if (typeof output === "string") {
|
|
2568
|
+
return output;
|
|
2569
|
+
}
|
|
2570
|
+
try {
|
|
2571
|
+
return JSON.stringify(output);
|
|
2572
|
+
} catch {
|
|
2573
|
+
return String(output);
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
function toJsonInput(input) {
|
|
2577
|
+
if (input !== null && typeof input === "object" && !Array.isArray(input)) {
|
|
2578
|
+
return input;
|
|
2579
|
+
}
|
|
2580
|
+
if (typeof input === "string") {
|
|
2581
|
+
try {
|
|
2582
|
+
const parsed = JSON.parse(input);
|
|
2583
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2584
|
+
return parsed;
|
|
2585
|
+
}
|
|
2586
|
+
} catch {
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
return { value: input };
|
|
2590
|
+
}
|
|
2591
|
+
function toContentBlocks(block) {
|
|
2592
|
+
switch (block.kind) {
|
|
2593
|
+
case "text":
|
|
2594
|
+
return [{ text: block.text }];
|
|
2595
|
+
case "thinking":
|
|
2596
|
+
return [
|
|
2597
|
+
{
|
|
2598
|
+
reasoningContent: {
|
|
2599
|
+
reasoningText: block.signature !== void 0 ? { text: block.text, signature: block.signature } : { text: block.text }
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
];
|
|
2603
|
+
case "image": {
|
|
2604
|
+
const slash = block.mediaType.indexOf("/");
|
|
2605
|
+
const format = slash >= 0 ? block.mediaType.slice(slash + 1) : block.mediaType;
|
|
2606
|
+
return [
|
|
2607
|
+
{
|
|
2608
|
+
image: {
|
|
2609
|
+
format,
|
|
2610
|
+
source: { bytes: block.dataBase64 }
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
];
|
|
2614
|
+
}
|
|
2615
|
+
case "tool_call":
|
|
2616
|
+
return [
|
|
2617
|
+
{
|
|
2618
|
+
toolUse: {
|
|
2619
|
+
toolUseId: block.id,
|
|
2620
|
+
name: block.name,
|
|
2621
|
+
input: toJsonInput(block.input)
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
];
|
|
2625
|
+
case "tool_result":
|
|
2626
|
+
return [
|
|
2627
|
+
{
|
|
2628
|
+
toolResult: {
|
|
2629
|
+
toolUseId: block.callId,
|
|
2630
|
+
content: [{ text: stringifyOutput(block.output) }],
|
|
2631
|
+
...block.isError === true ? { status: "error" } : {}
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
];
|
|
2635
|
+
case "command": {
|
|
2636
|
+
const argv = block.args && block.args.length > 0 ? ` ${block.args.join(" ")}` : "";
|
|
2637
|
+
return [{ text: `$ ${block.command}${argv}` }];
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
function converseRole(turn) {
|
|
2642
|
+
return turn.role === "assistant" ? "assistant" : "user";
|
|
2643
|
+
}
|
|
2644
|
+
function toConverseMessages(conversation) {
|
|
2645
|
+
const messages = [];
|
|
2646
|
+
for (const turn of conversation.turns) {
|
|
2647
|
+
const content = [];
|
|
2648
|
+
for (const block of turn.blocks) {
|
|
2649
|
+
for (const wire of toContentBlocks(block)) {
|
|
2650
|
+
content.push(wire);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
if (content.length > 0) {
|
|
2654
|
+
messages.push({ role: converseRole(turn), content });
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
return messages;
|
|
2658
|
+
}
|
|
2659
|
+
function toConverseSystem(conversation) {
|
|
2660
|
+
if (conversation.system === void 0 || conversation.system === "") {
|
|
2661
|
+
return void 0;
|
|
2662
|
+
}
|
|
2663
|
+
return [{ text: conversation.system }];
|
|
2664
|
+
}
|
|
2665
|
+
function toConverseTools(tools) {
|
|
2666
|
+
if (tools === void 0 || tools.length === 0) {
|
|
2667
|
+
return void 0;
|
|
2668
|
+
}
|
|
2669
|
+
return tools.map((tool) => ({
|
|
2670
|
+
toolSpec: {
|
|
2671
|
+
name: tool.name,
|
|
2672
|
+
description: tool.description,
|
|
2673
|
+
inputSchema: { json: tool.parameters }
|
|
2674
|
+
}
|
|
2675
|
+
}));
|
|
2676
|
+
}
|
|
2677
|
+
function toConverseToolChoice(options) {
|
|
2678
|
+
switch (options.toolChoice) {
|
|
2679
|
+
case "auto":
|
|
2680
|
+
return { auto: {} };
|
|
2681
|
+
case "required":
|
|
2682
|
+
return { any: {} };
|
|
2683
|
+
case "none":
|
|
2684
|
+
case void 0:
|
|
2685
|
+
return void 0;
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
function toInferenceConfig(model, options) {
|
|
2689
|
+
const config = {
|
|
2690
|
+
maxTokens: options.maxOutputTokens ?? model.maxOutputTokens
|
|
2691
|
+
};
|
|
2692
|
+
if (options.temperature !== void 0) {
|
|
2693
|
+
config.temperature = options.temperature;
|
|
2694
|
+
}
|
|
2695
|
+
if (options.topP !== void 0) {
|
|
2696
|
+
config.topP = options.topP;
|
|
2697
|
+
}
|
|
2698
|
+
return config;
|
|
2699
|
+
}
|
|
2700
|
+
function buildConverseBody(model, conversation, options) {
|
|
2701
|
+
const body = {
|
|
2702
|
+
messages: toConverseMessages(conversation),
|
|
2703
|
+
inferenceConfig: toInferenceConfig(model, options)
|
|
2704
|
+
};
|
|
2705
|
+
const system = toConverseSystem(conversation);
|
|
2706
|
+
if (system !== void 0) {
|
|
2707
|
+
body.system = system;
|
|
2708
|
+
}
|
|
2709
|
+
const tools = toConverseTools(conversation.tools);
|
|
2710
|
+
if (tools !== void 0) {
|
|
2711
|
+
const toolConfig = { tools };
|
|
2712
|
+
const choice = toConverseToolChoice(options);
|
|
2713
|
+
if (choice !== void 0) {
|
|
2714
|
+
toolConfig.toolChoice = choice;
|
|
2715
|
+
}
|
|
2716
|
+
body.toolConfig = toolConfig;
|
|
2717
|
+
}
|
|
2718
|
+
return body;
|
|
2719
|
+
}
|
|
2720
|
+
async function requireSignedTransport(resolveSecret2, options) {
|
|
2721
|
+
const credential = await resolveSecret2(PROVIDER3, options.apiKey);
|
|
2722
|
+
const region = discoverRegion() ?? FALLBACK_REGION;
|
|
2723
|
+
void credential;
|
|
2724
|
+
void region;
|
|
2725
|
+
throw gatewayError(
|
|
2726
|
+
"unsupported",
|
|
2727
|
+
"bedrock streaming requires SigV4 (todo)",
|
|
2728
|
+
{ provider: PROVIDER3 }
|
|
2729
|
+
);
|
|
2730
|
+
}
|
|
2731
|
+
function createbedrockConnector(deps2) {
|
|
2732
|
+
const { resolveSecret: resolveSecret2 } = deps2;
|
|
2733
|
+
return {
|
|
2734
|
+
id: "bedrock",
|
|
2735
|
+
api: DIALECT,
|
|
2736
|
+
stream(model, conversation, options) {
|
|
2737
|
+
return channelOf(async function* () {
|
|
2738
|
+
const region = discoverRegion() ?? FALLBACK_REGION;
|
|
2739
|
+
const url = converseStreamUrl(model, region);
|
|
2740
|
+
const body = buildConverseBody(model, conversation, options);
|
|
2741
|
+
void url;
|
|
2742
|
+
void body;
|
|
2743
|
+
await requireSignedTransport(resolveSecret2, options);
|
|
2744
|
+
});
|
|
2745
|
+
},
|
|
2746
|
+
complete(model, conversation, options) {
|
|
2747
|
+
return collectReply(this.stream(model, conversation, options));
|
|
2748
|
+
}
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
// src/llmgateway/connectors/azure-openai.ts
|
|
2753
|
+
var AZURE_API_VERSION = "2024-08-01-preview";
|
|
2754
|
+
function deploymentName(model) {
|
|
2755
|
+
const id = model.id.trim();
|
|
2756
|
+
if (id === "") {
|
|
2757
|
+
throw gatewayError(
|
|
2758
|
+
"unsupported",
|
|
2759
|
+
"azure card is missing an id to use as the deployment name",
|
|
2760
|
+
{ provider: model.provider }
|
|
2761
|
+
);
|
|
2762
|
+
}
|
|
2763
|
+
return id;
|
|
2764
|
+
}
|
|
2765
|
+
function endpointFor3(model) {
|
|
2766
|
+
const base = model.baseUrl?.replace(/\/+$/, "");
|
|
2767
|
+
if (base === void 0 || base === "") {
|
|
2768
|
+
throw gatewayError(
|
|
2769
|
+
"unsupported",
|
|
2770
|
+
"azure card has no baseUrl; set it to the resource endpoint, e.g. https://<resource>.openai.azure.com",
|
|
2771
|
+
{ provider: model.provider }
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
2774
|
+
const deployment = encodeURIComponent(deploymentName(model));
|
|
2775
|
+
const query = new URLSearchParams({ "api-version": AZURE_API_VERSION });
|
|
2776
|
+
return `${base}/openai/deployments/${deployment}/chat/completions?${query.toString()}`;
|
|
2777
|
+
}
|
|
2778
|
+
var AzureOpenAIConnector = class {
|
|
2779
|
+
constructor(deps2) {
|
|
2780
|
+
this.deps = deps2;
|
|
2781
|
+
}
|
|
2782
|
+
deps;
|
|
2783
|
+
id = "azure-openai";
|
|
2784
|
+
api = "azure-openai";
|
|
2785
|
+
stream(model, conversation, options) {
|
|
2786
|
+
const deps2 = this.deps;
|
|
2787
|
+
return channelOf(async function* run() {
|
|
2788
|
+
let cfg;
|
|
2789
|
+
try {
|
|
2790
|
+
cfg = await resolveConfig2(deps2, model, options);
|
|
2791
|
+
} catch (cause) {
|
|
2792
|
+
const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("auth", "could not prepare the Azure OpenAI request", {
|
|
2793
|
+
provider: model.provider,
|
|
2794
|
+
cause
|
|
2795
|
+
});
|
|
2796
|
+
yield { kind: "error", error };
|
|
2797
|
+
return;
|
|
2798
|
+
}
|
|
2799
|
+
yield* streamOpenAICompatibleChat(cfg, model, conversation, options);
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
async complete(model, conversation, options) {
|
|
2803
|
+
return collectReply(this.stream(model, conversation, options));
|
|
2804
|
+
}
|
|
2805
|
+
};
|
|
2806
|
+
async function resolveConfig2(deps2, model, options) {
|
|
2807
|
+
const url = endpointFor3(model);
|
|
2808
|
+
const key = await deps2.resolveSecret(model.provider, options.apiKey);
|
|
2809
|
+
if (key === void 0 || key === "") {
|
|
2810
|
+
throw gatewayError(
|
|
2811
|
+
"auth",
|
|
2812
|
+
"no Azure OpenAI credential found \u2014 set AZURE_OPENAI_API_KEY (or AZURE_API_KEY), or pass options.apiKey",
|
|
2813
|
+
{ provider: model.provider }
|
|
2814
|
+
);
|
|
2815
|
+
}
|
|
2816
|
+
return {
|
|
2817
|
+
url,
|
|
2818
|
+
// Azure authenticates with an api-key header, not an OpenAI bearer token.
|
|
2819
|
+
headers: { "api-key": key }
|
|
2820
|
+
};
|
|
2821
|
+
}
|
|
2822
|
+
function createazureopenaiConnector(deps2) {
|
|
2823
|
+
return new AzureOpenAIConnector(deps2);
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
// src/llmgateway/connectors/nvidia.ts
|
|
2827
|
+
var DEFAULT_NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1";
|
|
2828
|
+
function endpointFor4(model) {
|
|
2829
|
+
const base = (model.baseUrl ?? DEFAULT_NVIDIA_BASE_URL).replace(/\/+$/, "");
|
|
2830
|
+
return `${base}/chat/completions`;
|
|
2831
|
+
}
|
|
2832
|
+
function bodyOverridesFor(model) {
|
|
2833
|
+
if (!model.reasoning) {
|
|
2834
|
+
return void 0;
|
|
2835
|
+
}
|
|
2836
|
+
return { chat_template_kwargs: { thinking: true } };
|
|
2837
|
+
}
|
|
2838
|
+
var NvidiaConnector = class {
|
|
2839
|
+
constructor(deps2) {
|
|
2840
|
+
this.deps = deps2;
|
|
2841
|
+
}
|
|
2842
|
+
deps;
|
|
2843
|
+
id = "nvidia";
|
|
2844
|
+
api = "nvidia-openai-compatible";
|
|
2845
|
+
stream(model, conversation, options) {
|
|
2846
|
+
const deps2 = this.deps;
|
|
2847
|
+
return channelOf(async function* run() {
|
|
2848
|
+
let cfg;
|
|
2849
|
+
try {
|
|
2850
|
+
cfg = await assembleConfig(deps2, model, options);
|
|
2851
|
+
} catch (cause) {
|
|
2852
|
+
const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("auth", "unable to prepare the NVIDIA request", {
|
|
2853
|
+
provider: model.provider,
|
|
2854
|
+
cause
|
|
2855
|
+
});
|
|
2856
|
+
yield { kind: "error", error };
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
yield* streamOpenAICompatibleChat(cfg, model, conversation, options);
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
async complete(model, conversation, options) {
|
|
2863
|
+
return collectReply(this.stream(model, conversation, options));
|
|
2864
|
+
}
|
|
2865
|
+
};
|
|
2866
|
+
async function assembleConfig(deps2, model, options) {
|
|
2867
|
+
const key = await deps2.resolveSecret(model.provider, options.apiKey);
|
|
2868
|
+
if (key === void 0 || key === "") {
|
|
2869
|
+
throw gatewayError(
|
|
2870
|
+
"auth",
|
|
2871
|
+
"missing NVIDIA credential \u2014 set NVIDIA_API_KEY or pass options.apiKey",
|
|
2872
|
+
{ provider: model.provider }
|
|
2873
|
+
);
|
|
2874
|
+
}
|
|
2875
|
+
const bodyExtra = bodyOverridesFor(model);
|
|
2876
|
+
return {
|
|
2877
|
+
url: endpointFor4(model),
|
|
2878
|
+
headers: { authorization: `Bearer ${key}` },
|
|
2879
|
+
...bodyExtra ? { bodyExtra } : {}
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
function createnvidiaConnector(deps2) {
|
|
2883
|
+
return new NvidiaConnector(deps2);
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
// src/llmgateway/connectors/kimi.ts
|
|
2887
|
+
var DEFAULT_MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1";
|
|
2888
|
+
function endpointFor5(model) {
|
|
2889
|
+
const base = (model.baseUrl ?? DEFAULT_MOONSHOT_BASE_URL).replace(/\/+$/, "");
|
|
2890
|
+
return `${base}/chat/completions`;
|
|
2891
|
+
}
|
|
2892
|
+
var KimiConnector = class {
|
|
2893
|
+
constructor(deps2) {
|
|
2894
|
+
this.deps = deps2;
|
|
2895
|
+
}
|
|
2896
|
+
deps;
|
|
2897
|
+
id = "kimi";
|
|
2898
|
+
api = "kimi-openai-compatible";
|
|
2899
|
+
stream(model, conversation, options) {
|
|
2900
|
+
const deps2 = this.deps;
|
|
2901
|
+
return channelOf(async function* run() {
|
|
2902
|
+
let cfg;
|
|
2903
|
+
try {
|
|
2904
|
+
cfg = await resolveConfig3(deps2, model, options);
|
|
2905
|
+
} catch (cause) {
|
|
2906
|
+
const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("auth", "could not prepare the Kimi request", {
|
|
2907
|
+
provider: model.provider,
|
|
2908
|
+
cause
|
|
2909
|
+
});
|
|
2910
|
+
yield { kind: "error", error };
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
yield* streamOpenAICompatibleChat(cfg, model, conversation, options);
|
|
2914
|
+
});
|
|
2915
|
+
}
|
|
2916
|
+
async complete(model, conversation, options) {
|
|
2917
|
+
return collectReply(this.stream(model, conversation, options));
|
|
2918
|
+
}
|
|
2919
|
+
};
|
|
2920
|
+
async function resolveConfig3(deps2, model, options) {
|
|
2921
|
+
const key = await deps2.resolveSecret(model.provider, options.apiKey);
|
|
2922
|
+
if (key === void 0 || key === "") {
|
|
2923
|
+
throw gatewayError(
|
|
2924
|
+
"auth",
|
|
2925
|
+
"no Kimi credential found \u2014 set MOONSHOT_API_KEY (or KIMI_API_KEY), or pass options.apiKey",
|
|
2926
|
+
{ provider: model.provider }
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
return {
|
|
2930
|
+
url: endpointFor5(model),
|
|
2931
|
+
headers: { authorization: `Bearer ${key}` }
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
function createkimiConnector(deps2) {
|
|
2935
|
+
return new KimiConnector(deps2);
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
// src/llmgateway/connectors/ollama.ts
|
|
2939
|
+
var DEFAULT_BASE_URL3 = "http://localhost:11434";
|
|
2940
|
+
var PROVIDER4 = "ollama";
|
|
2941
|
+
function isObject6(value) {
|
|
2942
|
+
return typeof value === "object" && value !== null;
|
|
2943
|
+
}
|
|
2944
|
+
function readString5(o, key) {
|
|
2945
|
+
const v = o[key];
|
|
2946
|
+
return typeof v === "string" ? v : void 0;
|
|
2947
|
+
}
|
|
2948
|
+
function readNumber5(o, key) {
|
|
2949
|
+
const v = o[key];
|
|
2950
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
2951
|
+
}
|
|
2952
|
+
function buildOptions(model, opts) {
|
|
2953
|
+
const options = {};
|
|
2954
|
+
const numPredict = opts.maxOutputTokens ?? model.maxOutputTokens;
|
|
2955
|
+
if (numPredict > 0) {
|
|
2956
|
+
options.num_predict = numPredict;
|
|
2957
|
+
}
|
|
2958
|
+
if (opts.temperature !== void 0) {
|
|
2959
|
+
options.temperature = opts.temperature;
|
|
2960
|
+
}
|
|
2961
|
+
if (opts.topP !== void 0) {
|
|
2962
|
+
options.top_p = opts.topP;
|
|
2963
|
+
}
|
|
2964
|
+
return Object.keys(options).length > 0 ? options : void 0;
|
|
2965
|
+
}
|
|
2966
|
+
function buildRequestBody2(model, conversation, opts) {
|
|
2967
|
+
const body = {
|
|
2968
|
+
model: model.id,
|
|
2969
|
+
messages: toOpenAIChatMessages(conversation),
|
|
2970
|
+
stream: true
|
|
2971
|
+
};
|
|
2972
|
+
const tools = toOpenAITools(conversation.tools);
|
|
2973
|
+
if (tools !== void 0) {
|
|
2974
|
+
body.tools = tools;
|
|
2975
|
+
}
|
|
2976
|
+
const options = buildOptions(model, opts);
|
|
2977
|
+
if (options !== void 0) {
|
|
2978
|
+
body.options = options;
|
|
2979
|
+
}
|
|
2980
|
+
return body;
|
|
2981
|
+
}
|
|
2982
|
+
function chatEndpoint(model) {
|
|
2983
|
+
const base = (model.baseUrl ?? DEFAULT_BASE_URL3).replace(/\/+$/u, "");
|
|
2984
|
+
return `${base}/api/chat`;
|
|
2985
|
+
}
|
|
2986
|
+
function buildHeaders(secret) {
|
|
2987
|
+
const headers = { "content-type": "application/json" };
|
|
2988
|
+
if (secret !== void 0 && secret !== "") {
|
|
2989
|
+
headers.authorization = `Bearer ${secret}`;
|
|
2990
|
+
}
|
|
2991
|
+
return headers;
|
|
2992
|
+
}
|
|
2993
|
+
async function openStream2(url, headers, body, signal) {
|
|
2994
|
+
let response;
|
|
2995
|
+
try {
|
|
2996
|
+
response = await fetch(url, {
|
|
2997
|
+
method: "POST",
|
|
2998
|
+
headers,
|
|
2999
|
+
body: JSON.stringify(body),
|
|
3000
|
+
...signal !== void 0 ? { signal } : {}
|
|
3001
|
+
});
|
|
3002
|
+
} catch (cause) {
|
|
3003
|
+
if (signal?.aborted) {
|
|
3004
|
+
throw gatewayError("aborted", "the Ollama request was aborted before a response arrived", {
|
|
3005
|
+
provider: PROVIDER4,
|
|
3006
|
+
cause
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
throw gatewayError("transport", "could not reach the Ollama daemon (is `ollama serve` running?)", {
|
|
3010
|
+
provider: PROVIDER4,
|
|
3011
|
+
cause
|
|
3012
|
+
});
|
|
3013
|
+
}
|
|
3014
|
+
if (!response.ok) {
|
|
3015
|
+
const detail = await readErrorDetail2(response);
|
|
3016
|
+
const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
|
|
3017
|
+
throw gatewayError(kind, `Ollama replied with HTTP ${response.status}${detail}`, {
|
|
3018
|
+
provider: PROVIDER4,
|
|
3019
|
+
status: response.status
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
if (response.body === null) {
|
|
3023
|
+
throw gatewayError("transport", "the Ollama response carried no body to stream", {
|
|
3024
|
+
provider: PROVIDER4,
|
|
3025
|
+
status: response.status
|
|
3026
|
+
});
|
|
3027
|
+
}
|
|
3028
|
+
return response;
|
|
3029
|
+
}
|
|
3030
|
+
async function readErrorDetail2(response) {
|
|
3031
|
+
try {
|
|
3032
|
+
const text = await response.text();
|
|
3033
|
+
return text === "" ? "" : `: ${text.slice(0, 500)}`;
|
|
3034
|
+
} catch {
|
|
3035
|
+
return "";
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
function mapDoneReason(reason) {
|
|
3039
|
+
switch (reason) {
|
|
3040
|
+
case "stop":
|
|
3041
|
+
return "complete";
|
|
3042
|
+
case "length":
|
|
3043
|
+
return "max_output";
|
|
3044
|
+
case void 0:
|
|
3045
|
+
case "":
|
|
3046
|
+
return void 0;
|
|
3047
|
+
default:
|
|
3048
|
+
return "complete";
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
function readUsage4(line) {
|
|
3052
|
+
const inputTokens = readNumber5(line, "prompt_eval_count");
|
|
3053
|
+
const outputTokens = readNumber5(line, "eval_count");
|
|
3054
|
+
if (inputTokens === void 0 && outputTokens === void 0) {
|
|
3055
|
+
return void 0;
|
|
3056
|
+
}
|
|
3057
|
+
return {
|
|
3058
|
+
inputTokens: inputTokens ?? 0,
|
|
3059
|
+
outputTokens: outputTokens ?? 0
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
function serializeArgs3(args) {
|
|
3063
|
+
if (typeof args === "string") {
|
|
3064
|
+
return args;
|
|
3065
|
+
}
|
|
3066
|
+
try {
|
|
3067
|
+
return JSON.stringify(args ?? {});
|
|
3068
|
+
} catch {
|
|
3069
|
+
return "{}";
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
function* emitToolCalls(toolCalls, tracker) {
|
|
3073
|
+
for (const entry of toolCalls) {
|
|
3074
|
+
if (!isObject6(entry)) {
|
|
3075
|
+
continue;
|
|
3076
|
+
}
|
|
3077
|
+
const fn = entry.function;
|
|
3078
|
+
if (!isObject6(fn)) {
|
|
3079
|
+
continue;
|
|
3080
|
+
}
|
|
3081
|
+
const name = readString5(fn, "name") ?? "";
|
|
3082
|
+
const id = `ollama_call_${tracker.next}`;
|
|
3083
|
+
tracker.next += 1;
|
|
3084
|
+
yield { kind: "tool_call_start", id, name };
|
|
3085
|
+
const args = "arguments" in fn ? fn.arguments : {};
|
|
3086
|
+
yield { kind: "tool_call_delta", id, argsDelta: serializeArgs3(args) };
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
function* emitFromLine(line, tracker) {
|
|
3090
|
+
const message = line.message;
|
|
3091
|
+
if (isObject6(message)) {
|
|
3092
|
+
const content = readString5(message, "content");
|
|
3093
|
+
if (content !== void 0 && content !== "") {
|
|
3094
|
+
yield { kind: "text", delta: content };
|
|
3095
|
+
}
|
|
3096
|
+
const toolCalls = message.tool_calls;
|
|
3097
|
+
if (Array.isArray(toolCalls)) {
|
|
3098
|
+
yield* emitToolCalls(toolCalls, tracker);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
if (line.done === true) {
|
|
3102
|
+
const usage = readUsage4(line);
|
|
3103
|
+
if (usage !== void 0) {
|
|
3104
|
+
yield { kind: "usage", usage };
|
|
3105
|
+
}
|
|
3106
|
+
const stop = mapDoneReason(readString5(line, "done_reason"));
|
|
3107
|
+
if (stop !== void 0) {
|
|
3108
|
+
yield { kind: "stop", stop };
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
function createollamaConnector(deps2) {
|
|
3113
|
+
const { resolveSecret: resolveSecret2 } = deps2;
|
|
3114
|
+
return {
|
|
3115
|
+
id: "ollama",
|
|
3116
|
+
api: "ollama",
|
|
3117
|
+
stream(model, conversation, options) {
|
|
3118
|
+
return channelOf(async function* run() {
|
|
3119
|
+
const collected = [];
|
|
3120
|
+
try {
|
|
3121
|
+
const secret = await resolveSecret2(PROVIDER4, options.apiKey);
|
|
3122
|
+
const url = chatEndpoint(model);
|
|
3123
|
+
const headers = buildHeaders(secret);
|
|
3124
|
+
const body = buildRequestBody2(model, conversation, options);
|
|
3125
|
+
const response = await openStream2(url, headers, body, options.signal);
|
|
3126
|
+
const wire = response.body;
|
|
3127
|
+
const tracker = { next: 0 };
|
|
3128
|
+
for await (const value of ndjsonLines(wire, options.signal)) {
|
|
3129
|
+
if (!isObject6(value)) {
|
|
3130
|
+
continue;
|
|
3131
|
+
}
|
|
3132
|
+
for (const emission of emitFromLine(value, tracker)) {
|
|
3133
|
+
collected.push(emission);
|
|
3134
|
+
yield emission;
|
|
3135
|
+
}
|
|
3136
|
+
if (value.done === true) {
|
|
3137
|
+
break;
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
const reply = foldReply(model.id, collected);
|
|
3141
|
+
yield { kind: "done", reply };
|
|
3142
|
+
} catch (cause) {
|
|
3143
|
+
const error = cause instanceof Error && cause.name === "GatewayError" ? cause : gatewayError("transport", "the Ollama stream failed", { provider: PROVIDER4, cause });
|
|
3144
|
+
yield { kind: "error", error };
|
|
3145
|
+
}
|
|
3146
|
+
});
|
|
3147
|
+
},
|
|
3148
|
+
complete(model, conversation, options) {
|
|
3149
|
+
return collectReply(this.stream(model, conversation, options));
|
|
3150
|
+
}
|
|
3151
|
+
};
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
// src/llmgateway/connectors/mock.ts
|
|
3155
|
+
var ECHO_CALL_ID = "mock_call_0";
|
|
3156
|
+
var SCRIPTED_USAGE = {
|
|
3157
|
+
inputTokens: 8,
|
|
3158
|
+
outputTokens: 5
|
|
3159
|
+
};
|
|
3160
|
+
function scriptedDeltas() {
|
|
3161
|
+
return [
|
|
3162
|
+
{ kind: "text", delta: "Hello" },
|
|
3163
|
+
{ kind: "text", delta: " world" },
|
|
3164
|
+
{ kind: "tool_call_start", id: ECHO_CALL_ID, name: "echo" },
|
|
3165
|
+
{ kind: "tool_call_delta", id: ECHO_CALL_ID, argsDelta: '{"msg":"hi"}' },
|
|
3166
|
+
{ kind: "usage", usage: { ...SCRIPTED_USAGE } },
|
|
3167
|
+
{ kind: "stop", stop: "tool_calls" }
|
|
3168
|
+
];
|
|
3169
|
+
}
|
|
3170
|
+
function finalDeltas() {
|
|
3171
|
+
return [
|
|
3172
|
+
{ kind: "text", delta: "Hello world" },
|
|
3173
|
+
{ kind: "usage", usage: { ...SCRIPTED_USAGE } },
|
|
3174
|
+
{ kind: "stop", stop: "complete" }
|
|
3175
|
+
];
|
|
3176
|
+
}
|
|
3177
|
+
function hasToolResult(conversation) {
|
|
3178
|
+
return conversation.turns.some((turn) => turn.blocks.some((block) => block.kind === "tool_result"));
|
|
3179
|
+
}
|
|
3180
|
+
function echoOffered(conversation) {
|
|
3181
|
+
return (conversation.tools ?? []).some((tool) => tool.name === "echo");
|
|
3182
|
+
}
|
|
3183
|
+
function pickDeltas(conversation) {
|
|
3184
|
+
return !hasToolResult(conversation) && echoOffered(conversation) ? scriptedDeltas() : finalDeltas();
|
|
3185
|
+
}
|
|
3186
|
+
var MockConnector = class {
|
|
3187
|
+
id = "mock";
|
|
3188
|
+
api = "mock";
|
|
3189
|
+
// The resolver is accepted for factory symmetry with the live connectors; the
|
|
3190
|
+
// mock dialect needs no credential, so it is intentionally not retained.
|
|
3191
|
+
constructor(_deps) {
|
|
3192
|
+
}
|
|
3193
|
+
stream(model, conversation, options) {
|
|
3194
|
+
const isAborted = () => options.signal?.aborted ?? false;
|
|
3195
|
+
const deltas = pickDeltas(conversation);
|
|
3196
|
+
return channelOf(async function* run() {
|
|
3197
|
+
if (isAborted()) {
|
|
3198
|
+
yield abortedDone(model.id);
|
|
3199
|
+
return;
|
|
3200
|
+
}
|
|
3201
|
+
for (const delta of deltas) {
|
|
3202
|
+
if (isAborted()) {
|
|
3203
|
+
yield abortedDone(model.id);
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
yield delta;
|
|
3207
|
+
}
|
|
3208
|
+
yield { kind: "done", reply: foldReply(model.id, deltas) };
|
|
3209
|
+
});
|
|
3210
|
+
}
|
|
3211
|
+
async complete(model, conversation, options) {
|
|
3212
|
+
return collectReply(this.stream(model, conversation, options));
|
|
3213
|
+
}
|
|
3214
|
+
};
|
|
3215
|
+
function abortedDone(modelId) {
|
|
3216
|
+
const reply = {
|
|
3217
|
+
role: "assistant",
|
|
3218
|
+
model: modelId,
|
|
3219
|
+
blocks: [],
|
|
3220
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
3221
|
+
stop: "aborted"
|
|
3222
|
+
};
|
|
3223
|
+
return { kind: "done", reply };
|
|
3224
|
+
}
|
|
3225
|
+
function createmockConnector(deps2) {
|
|
3226
|
+
return new MockConnector(deps2);
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
// src/llmgateway/connectors/index.ts
|
|
3230
|
+
var deps = { resolveSecret };
|
|
3231
|
+
var CONNECTOR_REGISTRY = {
|
|
3232
|
+
"anthropic-messages": createanthropicConnector(deps),
|
|
3233
|
+
"openai-completions": createopenaichatConnector(deps),
|
|
3234
|
+
"openai-responses": createopenairesponsesConnector(deps),
|
|
3235
|
+
"google-generative": creategoogleConnector(deps),
|
|
3236
|
+
"google-vertex": creategooglevertexConnector(deps),
|
|
3237
|
+
"amazon-bedrock": createbedrockConnector(deps),
|
|
3238
|
+
"azure-openai": createazureopenaiConnector(deps),
|
|
3239
|
+
"nvidia-openai-compatible": createnvidiaConnector(deps),
|
|
3240
|
+
"kimi-openai-compatible": createkimiConnector(deps),
|
|
3241
|
+
ollama: createollamaConnector(deps),
|
|
3242
|
+
mock: createmockConnector(deps)
|
|
3243
|
+
};
|
|
3244
|
+
function connectorForApi(api) {
|
|
3245
|
+
return CONNECTOR_REGISTRY[api];
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
// src/llmgateway/gateway.ts
|
|
3249
|
+
var NO_OPTIONS = {};
|
|
3250
|
+
function stream(modelId, conversation, options) {
|
|
3251
|
+
const card = getCard(modelId);
|
|
3252
|
+
if (card === void 0) {
|
|
3253
|
+
throw gatewayError("unsupported", `unknown model: ${modelId}`);
|
|
3254
|
+
}
|
|
3255
|
+
return streamWithCard(card, conversation, options);
|
|
3256
|
+
}
|
|
3257
|
+
function streamWithCard(card, conversation, options) {
|
|
3258
|
+
const connector = connectorForApi(card.api);
|
|
3259
|
+
return connector.stream(card, conversation, options ?? NO_OPTIONS);
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
// src/runtime/contract/errors.ts
|
|
3263
|
+
function runError(kind, message, cause) {
|
|
3264
|
+
return cause !== void 0 ? { kind, message, cause } : { kind, message };
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
// src/runtime/cadence/fold.ts
|
|
3268
|
+
var ZERO_USAGE = { inputTokens: 0, outputTokens: 0 };
|
|
3269
|
+
function addUsage(base, more) {
|
|
3270
|
+
const cacheRead = (base.cacheReadTokens ?? 0) + (more.cacheReadTokens ?? 0);
|
|
3271
|
+
const cacheWrite = (base.cacheWriteTokens ?? 0) + (more.cacheWriteTokens ?? 0);
|
|
3272
|
+
return {
|
|
3273
|
+
inputTokens: base.inputTokens + more.inputTokens,
|
|
3274
|
+
outputTokens: base.outputTokens + more.outputTokens,
|
|
3275
|
+
...cacheRead > 0 ? { cacheReadTokens: cacheRead } : {},
|
|
3276
|
+
...cacheWrite > 0 ? { cacheWriteTokens: cacheWrite } : {}
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
function affectsBlocks(emission) {
|
|
3280
|
+
switch (emission.kind) {
|
|
3281
|
+
case "text":
|
|
3282
|
+
case "thinking":
|
|
3283
|
+
case "tool_call_start":
|
|
3284
|
+
case "tool_call_delta":
|
|
3285
|
+
case "done":
|
|
3286
|
+
return true;
|
|
3287
|
+
case "usage":
|
|
3288
|
+
case "stop":
|
|
3289
|
+
case "error":
|
|
3290
|
+
return false;
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
function parseToolArgs(raw) {
|
|
3294
|
+
const trimmed = raw.trim();
|
|
3295
|
+
if (trimmed.length === 0) return {};
|
|
3296
|
+
try {
|
|
3297
|
+
return JSON.parse(trimmed);
|
|
3298
|
+
} catch {
|
|
3299
|
+
return { __unparsed: raw };
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
function foldBlocks(blocks, emission) {
|
|
3303
|
+
if (!affectsBlocks(emission)) return blocks;
|
|
3304
|
+
switch (emission.kind) {
|
|
3305
|
+
case "text": {
|
|
3306
|
+
const last = blocks[blocks.length - 1];
|
|
3307
|
+
if (last && last.kind === "text") {
|
|
3308
|
+
const merged = {
|
|
3309
|
+
kind: "text",
|
|
3310
|
+
text: last.text + emission.delta
|
|
3311
|
+
};
|
|
3312
|
+
return [...blocks.slice(0, -1), merged];
|
|
3313
|
+
}
|
|
3314
|
+
return [...blocks, { kind: "text", text: emission.delta }];
|
|
3315
|
+
}
|
|
3316
|
+
case "thinking": {
|
|
3317
|
+
const last = blocks[blocks.length - 1];
|
|
3318
|
+
if (last && last.kind === "thinking") {
|
|
3319
|
+
const merged = {
|
|
3320
|
+
kind: "thinking",
|
|
3321
|
+
text: last.text + emission.delta,
|
|
3322
|
+
...last.signature !== void 0 ? { signature: last.signature } : {}
|
|
3323
|
+
};
|
|
3324
|
+
return [...blocks.slice(0, -1), merged];
|
|
3325
|
+
}
|
|
3326
|
+
return [...blocks, { kind: "thinking", text: emission.delta }];
|
|
3327
|
+
}
|
|
3328
|
+
case "tool_call_start": {
|
|
3329
|
+
const opened = {
|
|
3330
|
+
kind: "tool_call",
|
|
3331
|
+
id: emission.id,
|
|
3332
|
+
name: emission.name,
|
|
3333
|
+
input: ""
|
|
3334
|
+
};
|
|
3335
|
+
return [...blocks, opened];
|
|
3336
|
+
}
|
|
3337
|
+
case "tool_call_delta": {
|
|
3338
|
+
const idx = blocks.findIndex(
|
|
3339
|
+
(b) => b.kind === "tool_call" && b.id === emission.id
|
|
3340
|
+
);
|
|
3341
|
+
if (idx === -1) {
|
|
3342
|
+
const opened = {
|
|
3343
|
+
kind: "tool_call",
|
|
3344
|
+
id: emission.id,
|
|
3345
|
+
name: "",
|
|
3346
|
+
input: emission.argsDelta
|
|
3347
|
+
};
|
|
3348
|
+
return [...blocks, opened];
|
|
3349
|
+
}
|
|
3350
|
+
const prior = blocks[idx];
|
|
3351
|
+
const priorRaw = typeof prior.input === "string" ? prior.input : "";
|
|
3352
|
+
const grown = {
|
|
3353
|
+
kind: "tool_call",
|
|
3354
|
+
id: prior.id,
|
|
3355
|
+
name: prior.name,
|
|
3356
|
+
input: priorRaw + emission.argsDelta
|
|
3357
|
+
};
|
|
3358
|
+
const copy = blocks.slice();
|
|
3359
|
+
copy[idx] = grown;
|
|
3360
|
+
return copy;
|
|
3361
|
+
}
|
|
3362
|
+
case "done": {
|
|
3363
|
+
return emission.reply.blocks;
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
return blocks;
|
|
3367
|
+
}
|
|
3368
|
+
function sealToolArgs(turn) {
|
|
3369
|
+
let changed = false;
|
|
3370
|
+
const blocks = turn.blocks.map((b) => {
|
|
3371
|
+
if (b.kind === "tool_call" && typeof b.input === "string") {
|
|
3372
|
+
changed = true;
|
|
3373
|
+
const sealed = {
|
|
3374
|
+
kind: "tool_call",
|
|
3375
|
+
id: b.id,
|
|
3376
|
+
name: b.name,
|
|
3377
|
+
input: parseToolArgs(b.input)
|
|
3378
|
+
};
|
|
3379
|
+
return sealed;
|
|
3380
|
+
}
|
|
3381
|
+
return b;
|
|
3382
|
+
});
|
|
3383
|
+
if (!changed) return turn;
|
|
3384
|
+
return { role: "assistant", blocks };
|
|
3385
|
+
}
|
|
3386
|
+
function foldTurn(turn, emission) {
|
|
3387
|
+
const blocks = foldBlocks(turn.blocks, emission);
|
|
3388
|
+
if (blocks === turn.blocks) return turn;
|
|
3389
|
+
return { role: "assistant", blocks };
|
|
3390
|
+
}
|
|
3391
|
+
function toolCallsOf(turn) {
|
|
3392
|
+
return turn.blocks.filter((b) => b.kind === "tool_call");
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
// src/runtime/cadence/reducer.ts
|
|
3396
|
+
function freshRunId() {
|
|
3397
|
+
return crypto.randomUUID();
|
|
3398
|
+
}
|
|
3399
|
+
function initialSnapshot(sessionId, model, runId = freshRunId()) {
|
|
3400
|
+
return {
|
|
3401
|
+
runId,
|
|
3402
|
+
sessionId,
|
|
3403
|
+
phase: "idle",
|
|
3404
|
+
messages: [],
|
|
3405
|
+
pending: [],
|
|
3406
|
+
usageTotal: ZERO_USAGE,
|
|
3407
|
+
model
|
|
3408
|
+
};
|
|
3409
|
+
}
|
|
3410
|
+
function publish(event) {
|
|
3411
|
+
return { kind: "publish", event };
|
|
3412
|
+
}
|
|
3413
|
+
function announce(snapshot) {
|
|
3414
|
+
return publish({ kind: "snapshot", snapshot });
|
|
3415
|
+
}
|
|
3416
|
+
function fault(state, error) {
|
|
3417
|
+
const next = { ...state, phase: "faulted", error };
|
|
3418
|
+
return {
|
|
3419
|
+
state: next,
|
|
3420
|
+
effects: [
|
|
3421
|
+
{ kind: "persist", snapshot: next },
|
|
3422
|
+
publish({ kind: "faulted", error, snapshot: next })
|
|
3423
|
+
]
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
function lastAssistant(state) {
|
|
3427
|
+
const last = state.messages[state.messages.length - 1];
|
|
3428
|
+
return last && last.role === "assistant" ? last : void 0;
|
|
3429
|
+
}
|
|
3430
|
+
function withLastAssistant(state, turn) {
|
|
3431
|
+
return [...state.messages.slice(0, -1), turn];
|
|
3432
|
+
}
|
|
3433
|
+
function cadence(config) {
|
|
3434
|
+
const descriptors = config.tools?.descriptors() ?? [];
|
|
3435
|
+
function conversationOf(messages) {
|
|
3436
|
+
return {
|
|
3437
|
+
...config.system !== void 0 ? { system: config.system } : {},
|
|
3438
|
+
turns: messages,
|
|
3439
|
+
...descriptors.length > 0 ? { tools: descriptors } : {}
|
|
3440
|
+
};
|
|
3441
|
+
}
|
|
3442
|
+
function streamOptions() {
|
|
3443
|
+
return {
|
|
3444
|
+
...config.thinking !== void 0 ? { thinking: config.thinking } : {},
|
|
3445
|
+
...config.maxOutputTokens !== void 0 ? { maxOutputTokens: config.maxOutputTokens } : {}
|
|
3446
|
+
};
|
|
3447
|
+
}
|
|
3448
|
+
function invoke(state, messages) {
|
|
3449
|
+
const { error: _cleared, ...rest } = state;
|
|
3450
|
+
void _cleared;
|
|
3451
|
+
const next = {
|
|
3452
|
+
...rest,
|
|
3453
|
+
messages,
|
|
3454
|
+
phase: "invoking",
|
|
3455
|
+
pending: []
|
|
3456
|
+
};
|
|
3457
|
+
return {
|
|
3458
|
+
state: next,
|
|
3459
|
+
effects: [
|
|
3460
|
+
{
|
|
3461
|
+
kind: "invoke_model",
|
|
3462
|
+
conversation: conversationOf(messages),
|
|
3463
|
+
options: streamOptions()
|
|
3464
|
+
},
|
|
3465
|
+
announce(next)
|
|
3466
|
+
]
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
function onSubmit(state, input) {
|
|
3470
|
+
const blank = { role: "assistant", blocks: [] };
|
|
3471
|
+
const messages = [...state.messages, ...input, blank];
|
|
3472
|
+
return invoke(state, messages);
|
|
3473
|
+
}
|
|
3474
|
+
function onEmission(state, signal) {
|
|
3475
|
+
if (signal.kind !== "emission") return invalid(state, signal);
|
|
3476
|
+
const emission = signal.emission;
|
|
3477
|
+
if (emission.kind === "error") {
|
|
3478
|
+
return fault(
|
|
3479
|
+
state,
|
|
3480
|
+
runError("model_failed", emission.error.message, emission.error)
|
|
3481
|
+
);
|
|
3482
|
+
}
|
|
3483
|
+
if (emission.kind === "usage") {
|
|
3484
|
+
const next2 = {
|
|
3485
|
+
...state,
|
|
3486
|
+
phase: "streaming",
|
|
3487
|
+
usageTotal: addUsage(state.usageTotal, emission.usage)
|
|
3488
|
+
};
|
|
3489
|
+
return { state: next2, effects: [] };
|
|
3490
|
+
}
|
|
3491
|
+
if (emission.kind === "stop") {
|
|
3492
|
+
const next2 = { ...state, phase: "streaming" };
|
|
3493
|
+
return { state: next2, effects: [] };
|
|
3494
|
+
}
|
|
3495
|
+
const open = lastAssistant(state);
|
|
3496
|
+
const target = open ?? { role: "assistant", blocks: [] };
|
|
3497
|
+
const folded = foldTurn(target, emission);
|
|
3498
|
+
const usageTotal = emission.kind === "done" ? addUsage(state.usageTotal, emission.reply.usage) : state.usageTotal;
|
|
3499
|
+
const messages = open ? withLastAssistant(state, folded) : [...state.messages, folded];
|
|
3500
|
+
const next = {
|
|
3501
|
+
...state,
|
|
3502
|
+
phase: "streaming",
|
|
3503
|
+
usageTotal,
|
|
3504
|
+
messages
|
|
3505
|
+
};
|
|
3506
|
+
const effects = [];
|
|
3507
|
+
switch (emission.kind) {
|
|
3508
|
+
case "text":
|
|
3509
|
+
effects.push(publish({ kind: "text_delta", delta: emission.delta }));
|
|
3510
|
+
break;
|
|
3511
|
+
case "thinking":
|
|
3512
|
+
effects.push(publish({ kind: "thinking_delta", delta: emission.delta }));
|
|
3513
|
+
break;
|
|
3514
|
+
case "tool_call_start":
|
|
3515
|
+
effects.push(
|
|
3516
|
+
publish({
|
|
3517
|
+
kind: "tool_started",
|
|
3518
|
+
id: emission.id,
|
|
3519
|
+
name: emission.name
|
|
3520
|
+
})
|
|
3521
|
+
);
|
|
3522
|
+
break;
|
|
3523
|
+
case "tool_call_delta":
|
|
3524
|
+
case "done":
|
|
3525
|
+
break;
|
|
3526
|
+
}
|
|
3527
|
+
return { state: next, effects };
|
|
3528
|
+
}
|
|
3529
|
+
function onStreamEnd(state) {
|
|
3530
|
+
const turn = lastAssistant(state);
|
|
3531
|
+
if (!turn) {
|
|
3532
|
+
return fault(
|
|
3533
|
+
state,
|
|
3534
|
+
runError("invalid_state", "stream_end with no open assistant turn")
|
|
3535
|
+
);
|
|
3536
|
+
}
|
|
3537
|
+
const sealed = sealToolArgs(turn);
|
|
3538
|
+
const messages = withLastAssistant(state, sealed);
|
|
3539
|
+
const calls = toolCallsOf(sealed);
|
|
3540
|
+
if (calls.length === 0) {
|
|
3541
|
+
const settled2 = {
|
|
3542
|
+
...state,
|
|
3543
|
+
phase: "settled",
|
|
3544
|
+
messages,
|
|
3545
|
+
pending: []
|
|
3546
|
+
};
|
|
3547
|
+
return {
|
|
3548
|
+
state: settled2,
|
|
3549
|
+
effects: [
|
|
3550
|
+
{ kind: "persist", snapshot: settled2 },
|
|
3551
|
+
publish({ kind: "settled", snapshot: settled2 })
|
|
3552
|
+
]
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3555
|
+
const pending = calls.map((c) => ({
|
|
3556
|
+
id: c.id,
|
|
3557
|
+
name: c.name,
|
|
3558
|
+
stage: "running"
|
|
3559
|
+
}));
|
|
3560
|
+
const dispatching = {
|
|
3561
|
+
...state,
|
|
3562
|
+
phase: "dispatching",
|
|
3563
|
+
messages,
|
|
3564
|
+
pending
|
|
3565
|
+
};
|
|
3566
|
+
const effects = [announce(dispatching)];
|
|
3567
|
+
for (const c of calls) {
|
|
3568
|
+
const call = { id: c.id, name: c.name, input: c.input };
|
|
3569
|
+
effects.push({ kind: "run_tool", call });
|
|
3570
|
+
}
|
|
3571
|
+
return { state: dispatching, effects };
|
|
3572
|
+
}
|
|
3573
|
+
function onToolSettled(state, id, result) {
|
|
3574
|
+
const known = state.pending.find((p) => p.id === id);
|
|
3575
|
+
if (!known) {
|
|
3576
|
+
return fault(
|
|
3577
|
+
state,
|
|
3578
|
+
runError("invalid_state", `tool_settled for unknown call ${id}`)
|
|
3579
|
+
);
|
|
3580
|
+
}
|
|
3581
|
+
const pending = state.pending.map(
|
|
3582
|
+
(p) => p.id === id ? { ...p, stage: "done" } : p
|
|
3583
|
+
);
|
|
3584
|
+
const finished = publish({
|
|
3585
|
+
kind: "tool_finished",
|
|
3586
|
+
id,
|
|
3587
|
+
name: known.name,
|
|
3588
|
+
outcome: result
|
|
3589
|
+
});
|
|
3590
|
+
const block = {
|
|
3591
|
+
kind: "tool_result",
|
|
3592
|
+
callId: result.id,
|
|
3593
|
+
output: result.output,
|
|
3594
|
+
isError: result.isError
|
|
3595
|
+
};
|
|
3596
|
+
const messages = appendToolResult(state.messages, block);
|
|
3597
|
+
const allDone = pending.every((p) => p.stage === "done");
|
|
3598
|
+
if (!allDone) {
|
|
3599
|
+
const waiting = { ...state, pending, messages };
|
|
3600
|
+
return { state: waiting, effects: [finished, announce(waiting)] };
|
|
3601
|
+
}
|
|
3602
|
+
const cleared = { ...state, messages, pending: [] };
|
|
3603
|
+
const invocation = invoke(cleared, messages);
|
|
3604
|
+
return {
|
|
3605
|
+
state: invocation.state,
|
|
3606
|
+
effects: [finished, ...invocation.effects]
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
3609
|
+
function onCompacted(state, summary) {
|
|
3610
|
+
const keep = config.compaction?.keepRecent ?? 0;
|
|
3611
|
+
const recent = keep > 0 ? state.messages.slice(-keep) : [];
|
|
3612
|
+
const messages = [summary, ...recent];
|
|
3613
|
+
return invoke(state, messages);
|
|
3614
|
+
}
|
|
3615
|
+
function invalid(state, signal) {
|
|
3616
|
+
return fault(
|
|
3617
|
+
state,
|
|
3618
|
+
runError(
|
|
3619
|
+
"invalid_state",
|
|
3620
|
+
`signal '${signal.kind}' is not valid in phase '${state.phase}'`
|
|
3621
|
+
)
|
|
3622
|
+
);
|
|
3623
|
+
}
|
|
3624
|
+
return function step2(state, signal) {
|
|
3625
|
+
if (state.phase === "faulted" || state.phase === "settled") {
|
|
3626
|
+
if (signal.kind === "submit") return onSubmit(state, signal.input);
|
|
3627
|
+
if (signal.kind === "compacted") return onCompacted(state, signal.summary);
|
|
3628
|
+
if (signal.kind === "fault") return fault(state, signal.error);
|
|
3629
|
+
return { state, effects: [] };
|
|
3630
|
+
}
|
|
3631
|
+
switch (signal.kind) {
|
|
3632
|
+
case "abort":
|
|
3633
|
+
return fault(state, runError("aborted", "run aborted by caller"));
|
|
3634
|
+
case "fault":
|
|
3635
|
+
return fault(state, signal.error);
|
|
3636
|
+
case "submit":
|
|
3637
|
+
return onSubmit(state, signal.input);
|
|
3638
|
+
case "emission":
|
|
3639
|
+
return onEmission(state, signal);
|
|
3640
|
+
case "stream_end":
|
|
3641
|
+
return onStreamEnd(state);
|
|
3642
|
+
case "tool_settled":
|
|
3643
|
+
return onToolSettled(state, signal.id, signal.result);
|
|
3644
|
+
case "compacted":
|
|
3645
|
+
return onCompacted(state, signal.summary);
|
|
3646
|
+
}
|
|
3647
|
+
};
|
|
3648
|
+
}
|
|
3649
|
+
function appendToolResult(messages, block) {
|
|
3650
|
+
const last = messages[messages.length - 1];
|
|
3651
|
+
if (last && last.role === "tool") {
|
|
3652
|
+
const grown = { role: "tool", blocks: [...last.blocks, block] };
|
|
3653
|
+
return [...messages.slice(0, -1), grown];
|
|
3654
|
+
}
|
|
3655
|
+
const opened = { role: "tool", blocks: [block] };
|
|
3656
|
+
return [...messages, opened];
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
// src/runtime/turn/driver.ts
|
|
3660
|
+
function toRunError(thrown) {
|
|
3661
|
+
if (thrown instanceof GatewayError) {
|
|
3662
|
+
return runError("model_failed", thrown.message, thrown);
|
|
3663
|
+
}
|
|
3664
|
+
if (thrown instanceof Error) {
|
|
3665
|
+
return runError("model_failed", thrown.message, thrown);
|
|
3666
|
+
}
|
|
3667
|
+
return runError("model_failed", "the model stream failed", thrown);
|
|
3668
|
+
}
|
|
3669
|
+
async function* driveTurn(channel) {
|
|
3670
|
+
try {
|
|
3671
|
+
for await (const emission of channel) {
|
|
3672
|
+
yield { kind: "emission", emission };
|
|
3673
|
+
}
|
|
3674
|
+
} catch (thrown) {
|
|
3675
|
+
yield { kind: "fault", error: toRunError(thrown) };
|
|
3676
|
+
return;
|
|
3677
|
+
}
|
|
3678
|
+
yield { kind: "stream_end" };
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3681
|
+
// src/runtime/dispatch/scheduler.ts
|
|
3682
|
+
var DEFAULT_CONCURRENCY = 8;
|
|
3683
|
+
var SignalMailbox = class {
|
|
3684
|
+
buffer = [];
|
|
3685
|
+
wake = null;
|
|
3686
|
+
closed = false;
|
|
3687
|
+
/** Hand a finished signal to the consumer. */
|
|
3688
|
+
deliver(signal) {
|
|
3689
|
+
this.buffer.push(signal);
|
|
3690
|
+
this.flush();
|
|
3691
|
+
}
|
|
3692
|
+
/** Mark the round complete so a parked consumer can terminate. */
|
|
3693
|
+
close() {
|
|
3694
|
+
this.closed = true;
|
|
3695
|
+
this.flush();
|
|
3696
|
+
}
|
|
3697
|
+
/**
|
|
3698
|
+
* Pull the next buffered signal, or `undefined` once the round is finished
|
|
3699
|
+
* and the buffer is drained. Parks on a fresh promise while empty and open.
|
|
3700
|
+
*/
|
|
3701
|
+
async next() {
|
|
3702
|
+
for (; ; ) {
|
|
3703
|
+
const ready = this.buffer.shift();
|
|
3704
|
+
if (ready !== void 0) return ready;
|
|
3705
|
+
if (this.closed) return void 0;
|
|
3706
|
+
await new Promise((resolve) => {
|
|
3707
|
+
this.wake = resolve;
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
/** Wake a parked consumer, if any. */
|
|
3712
|
+
flush() {
|
|
3713
|
+
const wake = this.wake;
|
|
3714
|
+
if (wake) {
|
|
3715
|
+
this.wake = null;
|
|
3716
|
+
wake();
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
};
|
|
3720
|
+
function settled(id, result) {
|
|
3721
|
+
return { kind: "tool_settled", id, result };
|
|
3722
|
+
}
|
|
3723
|
+
function createScheduler(runner, concurrency = DEFAULT_CONCURRENCY) {
|
|
3724
|
+
const limit = Math.max(1, Math.floor(concurrency));
|
|
3725
|
+
const rounds = /* @__PURE__ */ new Set();
|
|
3726
|
+
function abort(controller) {
|
|
3727
|
+
rounds.delete(controller);
|
|
3728
|
+
if (!controller.signal.aborted) controller.abort();
|
|
3729
|
+
}
|
|
3730
|
+
async function* run(calls, signal) {
|
|
3731
|
+
const round = new AbortController();
|
|
3732
|
+
rounds.add(round);
|
|
3733
|
+
const onOuterAbort = () => abort(round);
|
|
3734
|
+
if (signal.aborted) round.abort();
|
|
3735
|
+
else signal.addEventListener("abort", onOuterAbort, { once: true });
|
|
3736
|
+
const pending = calls.map((call) => ({
|
|
3737
|
+
id: call.id,
|
|
3738
|
+
name: call.name,
|
|
3739
|
+
stage: "queued"
|
|
3740
|
+
}));
|
|
3741
|
+
const mailbox = new SignalMailbox();
|
|
3742
|
+
const setStage = (id, stage) => {
|
|
3743
|
+
const at = pending.findIndex((entry) => entry.id === id);
|
|
3744
|
+
if (at !== -1) pending[at] = { ...pending[at], stage };
|
|
3745
|
+
};
|
|
3746
|
+
const drop = (id) => {
|
|
3747
|
+
const at = pending.findIndex((entry) => entry.id === id);
|
|
3748
|
+
if (at !== -1) pending.splice(at, 1);
|
|
3749
|
+
};
|
|
3750
|
+
let cursor = 0;
|
|
3751
|
+
let active = 0;
|
|
3752
|
+
const startNext = () => {
|
|
3753
|
+
while (active < limit && cursor < calls.length && !round.signal.aborted) {
|
|
3754
|
+
const call = calls[cursor++];
|
|
3755
|
+
active++;
|
|
3756
|
+
setStage(call.id, "running");
|
|
3757
|
+
void runOne(call).finally(() => {
|
|
3758
|
+
active--;
|
|
3759
|
+
drop(call.id);
|
|
3760
|
+
if (cursor >= calls.length && active === 0) mailbox.close();
|
|
3761
|
+
else startNext();
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
3764
|
+
if (round.signal.aborted && active === 0) mailbox.close();
|
|
3765
|
+
};
|
|
3766
|
+
const runOne = async (call) => {
|
|
3767
|
+
const child = new AbortController();
|
|
3768
|
+
const onRoundAbort = () => {
|
|
3769
|
+
if (!child.signal.aborted) child.abort();
|
|
3770
|
+
};
|
|
3771
|
+
if (round.signal.aborted) child.abort();
|
|
3772
|
+
else round.signal.addEventListener("abort", onRoundAbort, { once: true });
|
|
3773
|
+
try {
|
|
3774
|
+
const result = await runner.run(call, child.signal);
|
|
3775
|
+
mailbox.deliver(settled(call.id, result));
|
|
3776
|
+
} catch (cause) {
|
|
3777
|
+
mailbox.deliver(
|
|
3778
|
+
settled(call.id, {
|
|
3779
|
+
id: call.id,
|
|
3780
|
+
output: cause instanceof Error ? cause.message : cause,
|
|
3781
|
+
isError: true
|
|
3782
|
+
})
|
|
3783
|
+
);
|
|
3784
|
+
} finally {
|
|
3785
|
+
round.signal.removeEventListener("abort", onRoundAbort);
|
|
3786
|
+
}
|
|
3787
|
+
};
|
|
3788
|
+
if (calls.length === 0) mailbox.close();
|
|
3789
|
+
else startNext();
|
|
3790
|
+
try {
|
|
3791
|
+
for (; ; ) {
|
|
3792
|
+
const next = await mailbox.next();
|
|
3793
|
+
if (next === void 0) return;
|
|
3794
|
+
yield next;
|
|
3795
|
+
}
|
|
3796
|
+
} finally {
|
|
3797
|
+
signal.removeEventListener("abort", onOuterAbort);
|
|
3798
|
+
abort(round);
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
return {
|
|
3802
|
+
run,
|
|
3803
|
+
cancel() {
|
|
3804
|
+
for (const controller of [...rounds]) abort(controller);
|
|
3805
|
+
}
|
|
3806
|
+
};
|
|
3807
|
+
}
|
|
3808
|
+
|
|
3809
|
+
// src/runtime/memory/estimate.ts
|
|
3810
|
+
var CHARS_PER_TOKEN = 4;
|
|
3811
|
+
var TURN_OVERHEAD_TOKENS = 4;
|
|
3812
|
+
function blockCharCount(block) {
|
|
3813
|
+
switch (block.kind) {
|
|
3814
|
+
case "text":
|
|
3815
|
+
return block.text.length;
|
|
3816
|
+
case "thinking":
|
|
3817
|
+
return block.text.length;
|
|
3818
|
+
case "tool_call":
|
|
3819
|
+
return block.name.length + serializedLength(block.input);
|
|
3820
|
+
case "tool_result":
|
|
3821
|
+
return serializedLength(block.output);
|
|
3822
|
+
case "image":
|
|
3823
|
+
return block.dataBase64.length;
|
|
3824
|
+
default:
|
|
3825
|
+
return 0;
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
function serializedLength(value) {
|
|
3829
|
+
if (typeof value === "string") {
|
|
3830
|
+
return value.length;
|
|
3831
|
+
}
|
|
3832
|
+
if (value === void 0 || value === null) {
|
|
3833
|
+
return 0;
|
|
3834
|
+
}
|
|
3835
|
+
try {
|
|
3836
|
+
return JSON.stringify(value).length;
|
|
3837
|
+
} catch {
|
|
3838
|
+
return String(value).length;
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
function estimateContextTokens(messages) {
|
|
3842
|
+
let chars = 0;
|
|
3843
|
+
for (const turn of messages) {
|
|
3844
|
+
for (const block of turn.blocks) {
|
|
3845
|
+
chars += blockCharCount(block);
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
const contentTokens = Math.ceil(chars / CHARS_PER_TOKEN);
|
|
3849
|
+
return contentTokens + messages.length * TURN_OVERHEAD_TOKENS;
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
// src/runtime/memory/compactor.ts
|
|
3853
|
+
var DEFAULT_POLICY = {
|
|
3854
|
+
triggerRatio: 0.8,
|
|
3855
|
+
keepRecent: 8
|
|
3856
|
+
};
|
|
3857
|
+
var DISTILL_INSTRUCTION = [
|
|
3858
|
+
"You are condensing the earlier portion of an ongoing agent conversation so it",
|
|
3859
|
+
"can be dropped from the active window without losing what matters. Read the",
|
|
3860
|
+
"transcript that follows and produce a single, self-contained briefing. Capture:",
|
|
3861
|
+
"the user's overarching goal and any standing constraints; concrete decisions",
|
|
3862
|
+
"already made and the reasoning behind them; facts discovered, files inspected,",
|
|
3863
|
+
"and commands run, together with their salient results; the current state of the",
|
|
3864
|
+
"work and what remains open. Preserve identifiers verbatim \u2014 file paths, function",
|
|
3865
|
+
"and symbol names, error strings, command lines. Write in compact prose or tight",
|
|
3866
|
+
"bullet points. Do not greet, do not editorialize, do not invent anything absent",
|
|
3867
|
+
"from the transcript. The briefing replaces these turns entirely, so anything you",
|
|
3868
|
+
"omit is forgotten."
|
|
3869
|
+
].join(" ");
|
|
3870
|
+
var SUMMARY_HEADING = "[condensed earlier context]";
|
|
3871
|
+
function shouldCompact(messages, model, cfg) {
|
|
3872
|
+
const policy = cfg ?? DEFAULT_POLICY;
|
|
3873
|
+
if (model.contextWindow <= 0) {
|
|
3874
|
+
return false;
|
|
3875
|
+
}
|
|
3876
|
+
const estimate = estimateContextTokens(messages);
|
|
3877
|
+
return estimate >= model.contextWindow * policy.triggerRatio;
|
|
3878
|
+
}
|
|
3879
|
+
function carriesToolResult(turn) {
|
|
3880
|
+
return turn.blocks.some((block) => block.kind === "tool_result");
|
|
3881
|
+
}
|
|
3882
|
+
function findCutPoint(messages, keepRecent) {
|
|
3883
|
+
const total = messages.length;
|
|
3884
|
+
const keep = Math.max(0, Math.floor(keepRecent));
|
|
3885
|
+
if (keep >= total) {
|
|
3886
|
+
return 0;
|
|
3887
|
+
}
|
|
3888
|
+
let cut = total - keep;
|
|
3889
|
+
while (cut < total && carriesToolResult(messages[cut])) {
|
|
3890
|
+
cut += 1;
|
|
3891
|
+
}
|
|
3892
|
+
return cut;
|
|
3893
|
+
}
|
|
3894
|
+
function renderBlock(block) {
|
|
3895
|
+
switch (block.kind) {
|
|
3896
|
+
case "text":
|
|
3897
|
+
return block.text;
|
|
3898
|
+
case "thinking":
|
|
3899
|
+
return block.text;
|
|
3900
|
+
case "tool_call":
|
|
3901
|
+
return `tool_call ${block.name}(${stringify(block.input)})`;
|
|
3902
|
+
case "tool_result":
|
|
3903
|
+
return `tool_result -> ${stringify(block.output)}`;
|
|
3904
|
+
case "image":
|
|
3905
|
+
return `[image ${block.mediaType}]`;
|
|
3906
|
+
default:
|
|
3907
|
+
return "";
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
function stringify(value) {
|
|
3911
|
+
if (typeof value === "string") {
|
|
3912
|
+
return value;
|
|
3913
|
+
}
|
|
3914
|
+
try {
|
|
3915
|
+
return JSON.stringify(value);
|
|
3916
|
+
} catch {
|
|
3917
|
+
return String(value);
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
function renderTurn(turn) {
|
|
3921
|
+
const body = turn.blocks.map(renderBlock).filter((part) => part.length > 0).join("\n");
|
|
3922
|
+
return `${turn.role}:
|
|
3923
|
+
${body}`;
|
|
3924
|
+
}
|
|
3925
|
+
async function collectText(channel) {
|
|
3926
|
+
let streamed = "";
|
|
3927
|
+
let final;
|
|
3928
|
+
for await (const emission of channel) {
|
|
3929
|
+
if (emission.kind === "text" && typeof emission.delta === "string") {
|
|
3930
|
+
streamed += emission.delta;
|
|
3931
|
+
} else if (emission.kind === "done" && emission.reply) {
|
|
3932
|
+
final = emission.reply.blocks.filter((b) => b.kind === "text").map((b) => b.text).join("");
|
|
3933
|
+
} else if (emission.kind === "error") {
|
|
3934
|
+
throw new Error(emission.error?.message ?? "model channel errored during condensation");
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
const text = (final ?? streamed).trim();
|
|
3938
|
+
return text;
|
|
3939
|
+
}
|
|
3940
|
+
async function summarize(messages, invoke) {
|
|
3941
|
+
const transcript = messages.map(renderTurn).join("\n\n");
|
|
3942
|
+
const conversation = {
|
|
3943
|
+
system: DISTILL_INSTRUCTION,
|
|
3944
|
+
turns: [
|
|
3945
|
+
{
|
|
3946
|
+
role: "user",
|
|
3947
|
+
blocks: [{ kind: "text", text: transcript }]
|
|
3948
|
+
}
|
|
3949
|
+
]
|
|
3950
|
+
};
|
|
3951
|
+
const channel = invoke(conversation, {});
|
|
3952
|
+
const gist = await collectText(channel);
|
|
3953
|
+
const summary = {
|
|
3954
|
+
role: "user",
|
|
3955
|
+
blocks: [{ kind: "text", text: `${SUMMARY_HEADING}
|
|
3956
|
+
${gist}` }]
|
|
3957
|
+
};
|
|
3958
|
+
return summary;
|
|
3959
|
+
}
|
|
3960
|
+
async function compact(messages, model, cfg, invoke) {
|
|
3961
|
+
void model;
|
|
3962
|
+
const policy = cfg ?? DEFAULT_POLICY;
|
|
3963
|
+
const cut = findCutPoint(messages, policy.keepRecent);
|
|
3964
|
+
if (cut <= 0) {
|
|
3965
|
+
return messages;
|
|
3966
|
+
}
|
|
3967
|
+
const prefix = messages.slice(0, cut);
|
|
3968
|
+
const tail = messages.slice(cut);
|
|
3969
|
+
const summary = await summarize(prefix, invoke);
|
|
3970
|
+
return [summary, ...tail];
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3973
|
+
// src/runtime/ledger/bus.ts
|
|
3974
|
+
var RunLedger = class {
|
|
3975
|
+
/** The live set of handlers. Insertion order is preserved for delivery. */
|
|
3976
|
+
handlers = /* @__PURE__ */ new Set();
|
|
3977
|
+
/**
|
|
3978
|
+
* Register a handler for every subsequent event.
|
|
3979
|
+
*
|
|
3980
|
+
* @returns a disposer that removes this handler. Calling it more than once is
|
|
3981
|
+
* harmless. The same function reference may be subscribed only once; a
|
|
3982
|
+
* duplicate subscription is a no-op that still returns a working disposer.
|
|
3983
|
+
*/
|
|
3984
|
+
subscribe(handler) {
|
|
3985
|
+
this.handlers.add(handler);
|
|
3986
|
+
return () => {
|
|
3987
|
+
this.handlers.delete(handler);
|
|
3988
|
+
};
|
|
3989
|
+
}
|
|
3990
|
+
/**
|
|
3991
|
+
* Deliver an event to every currently registered handler.
|
|
3992
|
+
*
|
|
3993
|
+
* Handlers are notified in registration order over a snapshot of the set, so
|
|
3994
|
+
* mutations made during delivery apply to the next publish. A throwing
|
|
3995
|
+
* handler is isolated: its error is swallowed so siblings still receive the
|
|
3996
|
+
* event and the loop is never broken by a faulty subscriber.
|
|
3997
|
+
*/
|
|
3998
|
+
publish(event) {
|
|
3999
|
+
for (const handler of [...this.handlers]) {
|
|
4000
|
+
try {
|
|
4001
|
+
handler(event);
|
|
4002
|
+
} catch {
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
/** Drop every handler. Useful when tearing a host down. */
|
|
4007
|
+
clear() {
|
|
4008
|
+
this.handlers.clear();
|
|
4009
|
+
}
|
|
4010
|
+
/** How many handlers are currently registered. */
|
|
4011
|
+
get size() {
|
|
4012
|
+
return this.handlers.size;
|
|
4013
|
+
}
|
|
4014
|
+
};
|
|
4015
|
+
|
|
4016
|
+
// src/runtime/store/hash.ts
|
|
4017
|
+
import { createHash } from "node:crypto";
|
|
4018
|
+
var HASH_WIDTH = 32;
|
|
4019
|
+
function hashNode(parent, turn, createdAt) {
|
|
4020
|
+
const payload = JSON.stringify({
|
|
4021
|
+
parent: parent ?? null,
|
|
4022
|
+
turn,
|
|
4023
|
+
createdAt
|
|
4024
|
+
});
|
|
4025
|
+
return createHash("sha256").update(payload).digest("hex").slice(0, HASH_WIDTH);
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
// src/runtime/store/dag.ts
|
|
4029
|
+
var SessionGraph = class _SessionGraph {
|
|
4030
|
+
constructor(sessionId, clock = Date.now) {
|
|
4031
|
+
this.sessionId = sessionId;
|
|
4032
|
+
this.clock = clock;
|
|
4033
|
+
}
|
|
4034
|
+
sessionId;
|
|
4035
|
+
nodes = /* @__PURE__ */ new Map();
|
|
4036
|
+
head = null;
|
|
4037
|
+
clock;
|
|
4038
|
+
/**
|
|
4039
|
+
* Rebuild a graph from previously persisted nodes plus its leaf pointer.
|
|
4040
|
+
*
|
|
4041
|
+
* Nodes may arrive in any order; parents are not required to precede children.
|
|
4042
|
+
* The provided `leaf` becomes the head, or the loner node if exactly one
|
|
4043
|
+
* exists and no leaf is named.
|
|
4044
|
+
*/
|
|
4045
|
+
static hydrate(sessionId, nodes, leaf, clock = Date.now) {
|
|
4046
|
+
const graph = new _SessionGraph(sessionId, clock);
|
|
4047
|
+
for (const node of nodes) {
|
|
4048
|
+
graph.nodes.set(node.id, node);
|
|
4049
|
+
}
|
|
4050
|
+
if (leaf !== null && graph.nodes.has(leaf)) {
|
|
4051
|
+
graph.head = leaf;
|
|
4052
|
+
} else if (nodes.length === 1) {
|
|
4053
|
+
graph.head = nodes[0].id;
|
|
4054
|
+
}
|
|
4055
|
+
return graph;
|
|
4056
|
+
}
|
|
4057
|
+
/** The id of the node a fresh append will chain onto, or `null` if empty. */
|
|
4058
|
+
get leaf() {
|
|
4059
|
+
return this.head;
|
|
4060
|
+
}
|
|
4061
|
+
/** Total number of nodes across every branch. */
|
|
4062
|
+
get size() {
|
|
4063
|
+
return this.nodes.size;
|
|
4064
|
+
}
|
|
4065
|
+
/** Look up a node by its content hash. */
|
|
4066
|
+
get(nodeId) {
|
|
4067
|
+
return this.nodes.get(nodeId);
|
|
4068
|
+
}
|
|
4069
|
+
/** Every node in the graph, in insertion order. */
|
|
4070
|
+
all() {
|
|
4071
|
+
return [...this.nodes.values()];
|
|
4072
|
+
}
|
|
4073
|
+
/**
|
|
4074
|
+
* Chain a new turn onto the current head and advance the head to it.
|
|
4075
|
+
*
|
|
4076
|
+
* Returns the freshly minted node. If the content hash collides with an
|
|
4077
|
+
* existing node (identical lineage, turn, and timestamp), the head simply
|
|
4078
|
+
* advances to that node without duplicating it.
|
|
4079
|
+
*/
|
|
4080
|
+
append(turn) {
|
|
4081
|
+
const parent = this.head;
|
|
4082
|
+
const createdAt = this.clock();
|
|
4083
|
+
const id = hashNode(parent, turn, createdAt);
|
|
4084
|
+
const existing = this.nodes.get(id);
|
|
4085
|
+
if (existing) {
|
|
4086
|
+
this.head = id;
|
|
4087
|
+
return existing;
|
|
4088
|
+
}
|
|
4089
|
+
const node = { id, parent, turn, createdAt };
|
|
4090
|
+
this.nodes.set(id, node);
|
|
4091
|
+
this.head = id;
|
|
4092
|
+
return node;
|
|
4093
|
+
}
|
|
4094
|
+
/**
|
|
4095
|
+
* Move the head to an existing node so the next append forks from there.
|
|
4096
|
+
*
|
|
4097
|
+
* Throws if `nodeId` is not part of this graph.
|
|
4098
|
+
*/
|
|
4099
|
+
branchFrom(nodeId) {
|
|
4100
|
+
const node = this.nodes.get(nodeId);
|
|
4101
|
+
if (!node) {
|
|
4102
|
+
throw new Error(`cannot branch from unknown node "${nodeId}"`);
|
|
4103
|
+
}
|
|
4104
|
+
this.head = nodeId;
|
|
4105
|
+
return node;
|
|
4106
|
+
}
|
|
4107
|
+
/**
|
|
4108
|
+
* Resolve the linear context ending at `leaf`, ordered root→leaf.
|
|
4109
|
+
*
|
|
4110
|
+
* Walks parent pointers from the leaf back to a root, then reverses. Throws if
|
|
4111
|
+
* `leaf` is unknown or the chain references a missing parent.
|
|
4112
|
+
*/
|
|
4113
|
+
pathTo(leaf) {
|
|
4114
|
+
const turns = [];
|
|
4115
|
+
let cursor = leaf;
|
|
4116
|
+
while (cursor !== null) {
|
|
4117
|
+
const node = this.nodes.get(cursor);
|
|
4118
|
+
if (!node) {
|
|
4119
|
+
throw new Error(`broken lineage: missing node "${cursor}"`);
|
|
4120
|
+
}
|
|
4121
|
+
turns.push(node.turn);
|
|
4122
|
+
cursor = node.parent;
|
|
4123
|
+
}
|
|
4124
|
+
turns.reverse();
|
|
4125
|
+
return turns;
|
|
4126
|
+
}
|
|
4127
|
+
/**
|
|
4128
|
+
* Adopt `leaf` as the head and return its full root→leaf turn context.
|
|
4129
|
+
*
|
|
4130
|
+
* Convenience for re-entering a session at a chosen tip: combines {@link
|
|
4131
|
+
* branchFrom} (positioning) with {@link pathTo} (context).
|
|
4132
|
+
*/
|
|
4133
|
+
resume(leaf) {
|
|
4134
|
+
this.branchFrom(leaf);
|
|
4135
|
+
return this.pathTo(leaf);
|
|
4136
|
+
}
|
|
4137
|
+
};
|
|
4138
|
+
|
|
4139
|
+
// src/runtime/store/persist.ts
|
|
4140
|
+
import { mkdir, readdir, readFile, appendFile } from "node:fs/promises";
|
|
4141
|
+
import { join } from "node:path";
|
|
4142
|
+
var SESSION_EXT = ".jsonl";
|
|
4143
|
+
function upgradeRaw(raw, migrators) {
|
|
4144
|
+
let current = raw;
|
|
4145
|
+
for (const migrator of migrators) {
|
|
4146
|
+
current = migrator.upgrade(current);
|
|
4147
|
+
}
|
|
4148
|
+
return current;
|
|
4149
|
+
}
|
|
4150
|
+
function asStoredRecord(value) {
|
|
4151
|
+
if (typeof value !== "object" || value === null) {
|
|
4152
|
+
return null;
|
|
4153
|
+
}
|
|
4154
|
+
const candidate = value;
|
|
4155
|
+
if (candidate.type === "node" || candidate.type === "head") {
|
|
4156
|
+
return value;
|
|
4157
|
+
}
|
|
4158
|
+
return null;
|
|
4159
|
+
}
|
|
4160
|
+
var SessionStore = class {
|
|
4161
|
+
constructor(root, migrators = []) {
|
|
4162
|
+
this.root = root;
|
|
4163
|
+
this.migrators = migrators;
|
|
4164
|
+
}
|
|
4165
|
+
root;
|
|
4166
|
+
migrators;
|
|
4167
|
+
/** Absolute path of a session's JSONL file. */
|
|
4168
|
+
fileFor(sessionId) {
|
|
4169
|
+
return join(this.root, sessionId + SESSION_EXT);
|
|
4170
|
+
}
|
|
4171
|
+
/** Ensure the sessions root directory exists. */
|
|
4172
|
+
async ensureRoot() {
|
|
4173
|
+
await mkdir(this.root, { recursive: true });
|
|
4174
|
+
}
|
|
4175
|
+
/**
|
|
4176
|
+
* Append one node to a session and advance its persisted head.
|
|
4177
|
+
*
|
|
4178
|
+
* Writes the node record followed by a head record pointing at it, so the last
|
|
4179
|
+
* head marker in the file always names the live leaf.
|
|
4180
|
+
*/
|
|
4181
|
+
async appendNode(sessionId, node) {
|
|
4182
|
+
await this.ensureRoot();
|
|
4183
|
+
const nodeRecord = { type: "node", node };
|
|
4184
|
+
const headRecord = { type: "head", leaf: node.id };
|
|
4185
|
+
const lines = JSON.stringify(nodeRecord) + "\n" + JSON.stringify(headRecord) + "\n";
|
|
4186
|
+
await appendFile(this.fileFor(sessionId), lines, "utf8");
|
|
4187
|
+
}
|
|
4188
|
+
/**
|
|
4189
|
+
* Load a session from disk into a {@link SessionGraph}.
|
|
4190
|
+
*
|
|
4191
|
+
* Replays every line, upgrades it through the migrator pipeline, collects the
|
|
4192
|
+
* node records, and tracks the last head marker as the leaf. Blank lines are
|
|
4193
|
+
* skipped. A missing session file yields an empty graph.
|
|
4194
|
+
*/
|
|
4195
|
+
async loadSession(sessionId) {
|
|
4196
|
+
let raw;
|
|
4197
|
+
try {
|
|
4198
|
+
raw = await readFile(this.fileFor(sessionId), "utf8");
|
|
4199
|
+
} catch (cause) {
|
|
4200
|
+
if (isNotFound(cause)) {
|
|
4201
|
+
return new SessionGraph(sessionId);
|
|
4202
|
+
}
|
|
4203
|
+
throw cause;
|
|
4204
|
+
}
|
|
4205
|
+
const nodes = [];
|
|
4206
|
+
let leaf = null;
|
|
4207
|
+
for (const line of raw.split("\n")) {
|
|
4208
|
+
const trimmed = line.trim();
|
|
4209
|
+
if (trimmed.length === 0) {
|
|
4210
|
+
continue;
|
|
4211
|
+
}
|
|
4212
|
+
const decoded = upgradeRaw(JSON.parse(trimmed), this.migrators);
|
|
4213
|
+
const record = asStoredRecord(decoded);
|
|
4214
|
+
if (!record) {
|
|
4215
|
+
continue;
|
|
4216
|
+
}
|
|
4217
|
+
if (record.type === "node") {
|
|
4218
|
+
nodes.push(record.node);
|
|
4219
|
+
} else {
|
|
4220
|
+
leaf = record.leaf;
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
return SessionGraph.hydrate(sessionId, nodes, leaf);
|
|
4224
|
+
}
|
|
4225
|
+
/**
|
|
4226
|
+
* List the ids of every session present in the store.
|
|
4227
|
+
*
|
|
4228
|
+
* Returns an empty list if the root does not yet exist.
|
|
4229
|
+
*/
|
|
4230
|
+
async listSessions() {
|
|
4231
|
+
let entries;
|
|
4232
|
+
try {
|
|
4233
|
+
entries = await readdir(this.root);
|
|
4234
|
+
} catch (cause) {
|
|
4235
|
+
if (isNotFound(cause)) {
|
|
4236
|
+
return [];
|
|
4237
|
+
}
|
|
4238
|
+
throw cause;
|
|
4239
|
+
}
|
|
4240
|
+
return entries.filter((name) => name.endsWith(SESSION_EXT)).map((name) => name.slice(0, -SESSION_EXT.length));
|
|
4241
|
+
}
|
|
4242
|
+
};
|
|
4243
|
+
function isNotFound(cause) {
|
|
4244
|
+
return typeof cause === "object" && cause !== null && cause.code === "ENOENT";
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
// src/runtime/conductor/agent.ts
|
|
4248
|
+
var DEFAULT_KEEP_RECENT_POLICY = {
|
|
4249
|
+
triggerRatio: 0.8,
|
|
4250
|
+
keepRecent: 8
|
|
4251
|
+
};
|
|
4252
|
+
function asTurns(input) {
|
|
4253
|
+
if (typeof input === "string") {
|
|
4254
|
+
return [{ role: "user", blocks: [{ kind: "text", text: input }] }];
|
|
4255
|
+
}
|
|
4256
|
+
return input;
|
|
4257
|
+
}
|
|
4258
|
+
function isTerminal(snapshot) {
|
|
4259
|
+
return snapshot.phase === "settled" || snapshot.phase === "faulted";
|
|
4260
|
+
}
|
|
4261
|
+
var SignalQueue = class {
|
|
4262
|
+
buffer = [];
|
|
4263
|
+
wake = null;
|
|
4264
|
+
/** Enqueue one signal and wake a parked consumer, if any. */
|
|
4265
|
+
push(signal) {
|
|
4266
|
+
this.buffer.push(signal);
|
|
4267
|
+
const wake = this.wake;
|
|
4268
|
+
if (wake) {
|
|
4269
|
+
this.wake = null;
|
|
4270
|
+
wake();
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
/** Pull the next signal, parking until one is available. */
|
|
4274
|
+
async pull() {
|
|
4275
|
+
for (; ; ) {
|
|
4276
|
+
const ready = this.buffer.shift();
|
|
4277
|
+
if (ready !== void 0) return ready;
|
|
4278
|
+
await new Promise((resolve) => {
|
|
4279
|
+
this.wake = resolve;
|
|
4280
|
+
});
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
/** Whether at least one signal is buffered and ready. */
|
|
4284
|
+
get hasPending() {
|
|
4285
|
+
return this.buffer.length > 0;
|
|
4286
|
+
}
|
|
4287
|
+
};
|
|
4288
|
+
function createAgent(config, deps2 = {}) {
|
|
4289
|
+
const step2 = cadence(config);
|
|
4290
|
+
const invokeModel = deps2.invokeModel ?? ((conversation, options) => stream(config.model, conversation, options));
|
|
4291
|
+
const ledger = deps2.ledger instanceof RunLedger ? deps2.ledger : new RunLedger();
|
|
4292
|
+
const runner = config.tools?.runner;
|
|
4293
|
+
const scheduler = runner ? createScheduler(runner) : void 0;
|
|
4294
|
+
const store = deps2.store instanceof SessionStore ? deps2.store : void 0;
|
|
4295
|
+
const sessionId = crypto.randomUUID();
|
|
4296
|
+
let snapshot = initialSnapshot(sessionId, config.model);
|
|
4297
|
+
const queue = new SignalQueue();
|
|
4298
|
+
let active = null;
|
|
4299
|
+
let driving = null;
|
|
4300
|
+
function onPublish(event) {
|
|
4301
|
+
ledger.publish(event);
|
|
4302
|
+
}
|
|
4303
|
+
async function onInvokeModel(conversation, options) {
|
|
4304
|
+
const card = getCard(config.model);
|
|
4305
|
+
if (card && shouldCompact(snapshot.messages, card, config.compaction)) {
|
|
4306
|
+
const redirected = await condenseIfShrinks(snapshot.messages);
|
|
4307
|
+
if (redirected) return;
|
|
4308
|
+
}
|
|
4309
|
+
const controller = active;
|
|
4310
|
+
const withSignal = controller ? { ...options, signal: controller.signal } : options;
|
|
4311
|
+
let channel;
|
|
4312
|
+
try {
|
|
4313
|
+
channel = invokeModel(conversation, withSignal);
|
|
4314
|
+
} catch (thrown) {
|
|
4315
|
+
queue.push({
|
|
4316
|
+
kind: "fault",
|
|
4317
|
+
error: runError(
|
|
4318
|
+
"model_failed",
|
|
4319
|
+
thrown instanceof Error ? thrown.message : "model invocation failed",
|
|
4320
|
+
thrown
|
|
4321
|
+
)
|
|
4322
|
+
});
|
|
4323
|
+
return;
|
|
4324
|
+
}
|
|
4325
|
+
for await (const signal of driveTurn(channel)) {
|
|
4326
|
+
if (controller?.signal.aborted) return;
|
|
4327
|
+
queue.push(signal);
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
async function condenseIfShrinks(messages) {
|
|
4331
|
+
const card = getCard(config.model);
|
|
4332
|
+
if (!card) return false;
|
|
4333
|
+
const keepRecent = (config.compaction ?? DEFAULT_KEEP_RECENT_POLICY).keepRecent;
|
|
4334
|
+
if (findCutPoint(messages, keepRecent) <= 1) return false;
|
|
4335
|
+
try {
|
|
4336
|
+
const condensed = await compact(
|
|
4337
|
+
messages,
|
|
4338
|
+
card,
|
|
4339
|
+
config.compaction,
|
|
4340
|
+
invokeModel
|
|
4341
|
+
);
|
|
4342
|
+
const summary = condensed[0];
|
|
4343
|
+
if (summary && condensed.length < messages.length) {
|
|
4344
|
+
queue.push({ kind: "compacted", summary });
|
|
4345
|
+
return true;
|
|
4346
|
+
}
|
|
4347
|
+
return false;
|
|
4348
|
+
} catch (thrown) {
|
|
4349
|
+
queue.push({
|
|
4350
|
+
kind: "fault",
|
|
4351
|
+
error: runError(
|
|
4352
|
+
"compaction_failed",
|
|
4353
|
+
thrown instanceof Error ? thrown.message : "history condensation failed",
|
|
4354
|
+
thrown
|
|
4355
|
+
)
|
|
4356
|
+
});
|
|
4357
|
+
return true;
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
async function onRunTools(calls) {
|
|
4361
|
+
if (!scheduler) {
|
|
4362
|
+
queue.push({
|
|
4363
|
+
kind: "fault",
|
|
4364
|
+
error: runError(
|
|
4365
|
+
"tool_failed",
|
|
4366
|
+
"a tool was requested but no tool runner is configured"
|
|
4367
|
+
)
|
|
4368
|
+
});
|
|
4369
|
+
return;
|
|
4370
|
+
}
|
|
4371
|
+
const controller = active ?? new AbortController();
|
|
4372
|
+
for await (const settled2 of scheduler.run(calls, controller.signal)) {
|
|
4373
|
+
queue.push(settled2);
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
async function onCompact(messages) {
|
|
4377
|
+
await condenseIfShrinks(messages);
|
|
4378
|
+
}
|
|
4379
|
+
async function onPersist(target) {
|
|
4380
|
+
if (!store) return;
|
|
4381
|
+
try {
|
|
4382
|
+
const graph = await store.loadSession(target.sessionId);
|
|
4383
|
+
await appendNewTurns(graph, target.sessionId, target.messages);
|
|
4384
|
+
} catch {
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
async function appendNewTurns(graph, session, messages) {
|
|
4388
|
+
for (const turn of messages) {
|
|
4389
|
+
const before = graph.leaf;
|
|
4390
|
+
const node = graph.append(turn);
|
|
4391
|
+
if (node.id !== before) {
|
|
4392
|
+
await store.appendNode(session, node);
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
function performAll(effects, inflight) {
|
|
4397
|
+
const batch = [];
|
|
4398
|
+
for (const effect of effects) {
|
|
4399
|
+
switch (effect.kind) {
|
|
4400
|
+
case "publish":
|
|
4401
|
+
onPublish(effect.event);
|
|
4402
|
+
break;
|
|
4403
|
+
case "invoke_model":
|
|
4404
|
+
track(onInvokeModel(effect.conversation, effect.options), inflight);
|
|
4405
|
+
break;
|
|
4406
|
+
case "run_tool":
|
|
4407
|
+
batch.push(effect.call);
|
|
4408
|
+
break;
|
|
4409
|
+
case "compact":
|
|
4410
|
+
track(onCompact(effect.messages), inflight);
|
|
4411
|
+
break;
|
|
4412
|
+
case "persist":
|
|
4413
|
+
track(onPersist(effect.snapshot), inflight);
|
|
4414
|
+
break;
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
if (batch.length > 0) {
|
|
4418
|
+
track(onRunTools(batch), inflight);
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
function track(work, inflight) {
|
|
4422
|
+
const tracked = work.finally(() => {
|
|
4423
|
+
inflight.delete(tracked);
|
|
4424
|
+
});
|
|
4425
|
+
inflight.add(tracked);
|
|
4426
|
+
}
|
|
4427
|
+
async function drive(seed) {
|
|
4428
|
+
const inflight = /* @__PURE__ */ new Set();
|
|
4429
|
+
const maxTurns = config.maxTurns ?? 64;
|
|
4430
|
+
let modelTurns = 0;
|
|
4431
|
+
queue.push(seed);
|
|
4432
|
+
for (; ; ) {
|
|
4433
|
+
while (isTerminal(snapshot) && !queue.hasPending) {
|
|
4434
|
+
if (inflight.size === 0) return snapshot;
|
|
4435
|
+
await Promise.race([...inflight]);
|
|
4436
|
+
}
|
|
4437
|
+
const signal = await queue.pull();
|
|
4438
|
+
const transition = step2(snapshot, signal);
|
|
4439
|
+
snapshot = transition.state;
|
|
4440
|
+
const invokes = transition.effects.reduce(
|
|
4441
|
+
(n, e) => e.kind === "invoke_model" ? n + 1 : n,
|
|
4442
|
+
0
|
|
4443
|
+
);
|
|
4444
|
+
if (invokes > 0 && modelTurns + invokes > maxTurns) {
|
|
4445
|
+
queue.push({
|
|
4446
|
+
kind: "fault",
|
|
4447
|
+
error: runError(
|
|
4448
|
+
"turn_budget",
|
|
4449
|
+
`run exceeded its ${maxTurns}-turn budget`
|
|
4450
|
+
)
|
|
4451
|
+
});
|
|
4452
|
+
performAll(
|
|
4453
|
+
transition.effects.filter((e) => e.kind !== "invoke_model"),
|
|
4454
|
+
inflight
|
|
4455
|
+
);
|
|
4456
|
+
continue;
|
|
4457
|
+
}
|
|
4458
|
+
modelTurns += invokes;
|
|
4459
|
+
performAll(transition.effects, inflight);
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
function submit(input) {
|
|
4463
|
+
const turns = asTurns(input);
|
|
4464
|
+
if (driving) {
|
|
4465
|
+
queue.push({ kind: "submit", input: turns });
|
|
4466
|
+
return driving;
|
|
4467
|
+
}
|
|
4468
|
+
active = new AbortController();
|
|
4469
|
+
const run = drive({ kind: "submit", input: turns }).finally(() => {
|
|
4470
|
+
driving = null;
|
|
4471
|
+
active = null;
|
|
4472
|
+
});
|
|
4473
|
+
driving = run;
|
|
4474
|
+
return run;
|
|
4475
|
+
}
|
|
4476
|
+
function subscribe(handler) {
|
|
4477
|
+
return ledger.subscribe(handler);
|
|
4478
|
+
}
|
|
4479
|
+
function abort() {
|
|
4480
|
+
active?.abort();
|
|
4481
|
+
if (scheduler) scheduler.cancel();
|
|
4482
|
+
queue.push({ kind: "abort" });
|
|
4483
|
+
}
|
|
4484
|
+
function readSnapshot() {
|
|
4485
|
+
return snapshot;
|
|
4486
|
+
}
|
|
4487
|
+
async function resume(targetSession) {
|
|
4488
|
+
if (!store) {
|
|
4489
|
+
throw runErrorThrowable(
|
|
4490
|
+
"invalid_state",
|
|
4491
|
+
"cannot resume without a configured session store"
|
|
4492
|
+
);
|
|
4493
|
+
}
|
|
4494
|
+
const graph = await store.loadSession(targetSession);
|
|
4495
|
+
const leaf = graph.leaf;
|
|
4496
|
+
const history = leaf ? graph.pathTo(leaf) : [];
|
|
4497
|
+
snapshot = {
|
|
4498
|
+
...initialSnapshot(targetSession, config.model, snapshot.runId),
|
|
4499
|
+
messages: history
|
|
4500
|
+
};
|
|
4501
|
+
}
|
|
4502
|
+
return {
|
|
4503
|
+
submit,
|
|
4504
|
+
subscribe,
|
|
4505
|
+
abort,
|
|
4506
|
+
snapshot: readSnapshot,
|
|
4507
|
+
resume
|
|
4508
|
+
};
|
|
4509
|
+
}
|
|
4510
|
+
function runErrorThrowable(kind, message) {
|
|
4511
|
+
const err = new Error(message);
|
|
4512
|
+
err.runError = runError(kind, message);
|
|
4513
|
+
return err;
|
|
4514
|
+
}
|
|
4515
|
+
export {
|
|
4516
|
+
cadence,
|
|
4517
|
+
createAgent,
|
|
4518
|
+
initialSnapshot,
|
|
4519
|
+
runError,
|
|
4520
|
+
cadence as step
|
|
4521
|
+
};
|