@vellumai/assistant 0.11.1 → 0.11.2-staging.2
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/Dockerfile +1 -3
- package/README.md +1 -1
- package/eslint.config.mjs +28 -8
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +48 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +25 -6
- package/openapi.yaml +292 -5
- package/package.json +1 -1
- package/src/__tests__/agent-loop-resume-interrupted.test.ts +223 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +43 -0
- package/src/__tests__/cli-logger-boundary-guard.test.ts +87 -0
- package/src/__tests__/compaction-events.test.ts +139 -2
- package/src/__tests__/config-loader-backfill.test.ts +35 -3
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +30 -0
- package/src/__tests__/conversation-agent-loop.test.ts +180 -7
- package/src/__tests__/conversation-error.test.ts +211 -4
- package/src/__tests__/conversation-options-turn-scoped-transport.test.ts +64 -0
- package/src/__tests__/conversation-queue.test.ts +126 -0
- package/src/__tests__/conversation-retry-route.test.ts +46 -7
- package/src/__tests__/conversation-slash.test.ts +4 -2
- package/src/__tests__/conversation-summarize-route.test.ts +7 -1
- package/src/__tests__/conversation-summarize-up-to.test.ts +71 -5
- package/src/__tests__/document-create-dedupe.test.ts +2 -1
- package/src/__tests__/document-find-replace.test.ts +2 -1
- package/src/__tests__/document-tool-security.test.ts +2 -1
- package/src/__tests__/document-update-default-surface.test.ts +2 -1
- package/src/__tests__/document-workspace-file.test.ts +467 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +290 -49
- package/src/__tests__/guardian-card-withdrawal.test.ts +91 -3
- package/src/__tests__/http-user-message-parity.test.ts +66 -0
- package/src/__tests__/list-messages-attachments.test.ts +87 -0
- package/src/__tests__/list-messages-provider-error.test.ts +143 -0
- package/src/__tests__/list-messages-system-card.test.ts +100 -0
- package/src/__tests__/llm-resolver.test.ts +19 -9
- package/src/__tests__/managed-profile-guard.test.ts +23 -0
- package/src/__tests__/notification-platform-adapter.test.ts +130 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +6 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +45 -0
- package/src/__tests__/plugin-api-model-profiles.test.ts +10 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/provider-error-scenarios.test.ts +140 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +95 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +66 -3
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +209 -0
- package/src/__tests__/skills.test.ts +27 -0
- package/src/__tests__/slack-channels-routes.test.ts +0 -2
- package/src/__tests__/slack-share-routes.test.ts +0 -3
- package/src/__tests__/slack-users-routes.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +11 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +58 -0
- package/src/__tests__/tool-result-spool.test.ts +5 -4
- package/src/__tests__/turn-boundary-resolution.test.ts +53 -0
- package/src/__tests__/turn-events-store.test.ts +26 -0
- package/src/__tests__/ui-visual-surface.test.ts +695 -0
- package/src/__tests__/unified-turn-context-visible-app.test.ts +99 -0
- package/src/__tests__/visible-app-context.test.ts +189 -0
- package/src/__tests__/workspace-git-service.test.ts +173 -33
- package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +157 -0
- package/src/__tests__/workspace-migration-138-backfill-home-feed-titles.test.ts +373 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +137 -0
- package/src/agent/loop.ts +36 -7
- package/src/api/events/context-window-usage.ts +31 -0
- package/src/api/events/notification-intent.ts +8 -0
- package/src/api/events/ui-surface-pending.ts +35 -0
- package/src/api/index.ts +14 -0
- package/src/api/responses/conversation-message.ts +25 -4
- package/src/api/surfaces.ts +90 -1
- package/src/approvals/guardian-card-withdrawal.ts +66 -31
- package/src/approvals/guardian-decision-primitive.ts +4 -0
- package/src/calls/__tests__/call-setup-router.test.ts +156 -26
- package/src/calls/__tests__/voice-session-bridge.test.ts +201 -12
- package/src/calls/call-setup-router.ts +80 -36
- package/src/calls/voice-session-bridge.ts +173 -26
- package/src/cli/commands/inference.help.ts +3 -3
- package/src/cli/commands/notifications.help.ts +3 -2
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +42 -128
- package/src/cli/commands/platform/__tests__/credits.test.ts +9 -78
- package/src/cli/commands/platform/__tests__/helpers.ts +90 -0
- package/src/cli/commands/platform/__tests__/invoices.test.ts +238 -0
- package/src/cli/commands/platform/__tests__/plans.test.ts +9 -88
- package/src/cli/commands/platform/__tests__/status.test.ts +12 -87
- package/src/cli/commands/platform/__tests__/subscription.test.ts +9 -86
- package/src/cli/commands/platform/index.help.ts +92 -0
- package/src/cli/commands/platform/index.ts +7 -0
- package/src/cli/commands/platform/invoices.ts +132 -0
- package/src/cli/commands/usage.help.ts +1 -1
- package/src/cli/lib/list-installed-plugins.ts +2 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +7 -10
- package/src/config/__tests__/deployment-context-defaults.test.ts +27 -5
- package/src/config/assistant-feature-flags.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -2
- package/src/config/bundled-skills/subagent/SKILL.md +3 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +3 -3
- package/src/config/bundled-skills/visualize/SKILL.md +163 -0
- package/src/config/call-site-defaults.ts +3 -2
- package/src/config/default-profile-catalog.ts +75 -70
- package/src/config/default-profile-names.ts +13 -28
- package/src/config/env-registry.ts +1 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/llm-resolver.ts +21 -5
- package/src/config/loader.ts +18 -11
- package/src/config/schemas/llm.ts +1 -9
- package/src/config/schemas/memory-retrospective.ts +9 -0
- package/src/config/schemas/monitoring.ts +28 -2
- package/src/config/schemas/workspace-git.ts +15 -0
- package/src/config/seed-inference-profiles.ts +19 -5
- package/src/context/post-turn-tool-result-truncation.ts +2 -2
- package/src/conversations/__tests__/message-consolidation.test.ts +54 -0
- package/src/conversations/message-consolidation.ts +17 -15
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +182 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +181 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +90 -7
- package/src/daemon/conversation-agent-loop.ts +107 -22
- package/src/daemon/conversation-error.ts +169 -48
- package/src/daemon/conversation-messaging.ts +87 -4
- package/src/daemon/conversation-process.ts +57 -45
- package/src/daemon/conversation-runtime-assembly.ts +57 -0
- package/src/daemon/conversation-store.ts +36 -1
- package/src/daemon/conversation-surfaces.ts +68 -6
- package/src/daemon/conversation-turn-finalize.ts +76 -18
- package/src/daemon/conversation.ts +122 -27
- package/src/daemon/lifecycle.ts +9 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/surfaces.ts +3 -0
- package/src/documents/document-store.ts +247 -10
- package/src/home/__tests__/feed-types.test.ts +43 -8
- package/src/home/__tests__/feed-writer.test.ts +59 -0
- package/src/home/feed-types.ts +34 -7
- package/src/home/feed-writer.ts +15 -6
- package/src/live-voice/__tests__/activity-label.test.ts +95 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +86 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +310 -25
- package/src/live-voice/__tests__/live-voice-events.test.ts +4 -4
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +8 -4
- package/src/live-voice/__tests__/live-voice-vad.test.ts +9 -1
- package/src/live-voice/activity-label.ts +169 -0
- package/src/live-voice/live-activity-reporter.ts +35 -6
- package/src/live-voice/live-voice-session.ts +282 -41
- package/src/live-voice/protocol.ts +35 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +84 -2
- package/src/messaging/providers/slack/adapter.ts +86 -19
- package/src/messaging/providers/slack/api.test.ts +85 -1
- package/src/messaging/providers/slack/api.ts +121 -288
- package/src/messaging/providers/slack/client.ts +20 -251
- package/src/messaging/providers/slack/send.test.ts +4 -9
- package/src/messaging/providers/slack/send.ts +1 -1
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/messaging/providers/slack/web-api-transport.test.ts +181 -0
- package/src/messaging/providers/slack/web-api-transport.ts +367 -0
- package/src/messaging/providers/slack/withdraw.ts +6 -14
- package/src/messaging/providers/telegram-bot/send.test.ts +31 -1
- package/src/messaging/providers/telegram-bot/send.ts +23 -2
- package/src/messaging/providers/telegram-bot/withdraw.test.ts +152 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +166 -0
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +18 -4
- package/src/monitoring/__tests__/file-descriptors.test.ts +144 -0
- package/src/monitoring/file-descriptors.ts +262 -0
- package/src/monitoring/process-memory.ts +4 -11
- package/src/monitoring/resource-sampler.ts +5 -0
- package/src/monitoring/worker.ts +12 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +740 -0
- package/src/notifications/__tests__/broadcaster.test.ts +347 -7
- package/src/notifications/__tests__/copy-composer.test.ts +53 -3
- package/src/notifications/__tests__/decision-engine.test.ts +349 -29
- package/src/notifications/__tests__/deterministic-checks.test.ts +21 -0
- package/src/notifications/__tests__/edit-notification.test.ts +330 -0
- package/src/notifications/__tests__/guardian-delivery-recorder.test.ts +71 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +170 -12
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/adapters/platform.ts +90 -14
- package/src/notifications/adapters/telegram.ts +6 -4
- package/src/notifications/assistant-reply-producer.ts +219 -0
- package/src/notifications/broadcaster.ts +523 -317
- package/src/notifications/copy-composer.ts +27 -6
- package/src/notifications/decision-engine.ts +154 -77
- package/src/notifications/deterministic-checks.ts +6 -7
- package/src/notifications/edit-notification.ts +8 -4
- package/src/notifications/emit-signal.ts +49 -16
- package/src/notifications/guardian-delivery-recorder.ts +8 -2
- package/src/notifications/home-feed-side-effect.ts +43 -6
- package/src/notifications/notification-utils.ts +39 -4
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +17 -0
- package/src/persistence/bookmark-crud.ts +18 -9
- package/src/persistence/conversation-attention-store.ts +26 -0
- package/src/persistence/conversation-crud.ts +79 -32
- package/src/persistence/conversation-queries.ts +30 -15
- package/src/persistence/conversation-title-service.ts +5 -170
- package/src/persistence/conversation-types.ts +192 -1
- package/src/persistence/db-init.ts +14 -1
- package/src/persistence/db-maintenance.ts +5 -4
- package/src/persistence/embeddings/__tests__/plugin-index-qdrant-init.test.ts +219 -0
- package/src/persistence/embeddings/__tests__/plugin-index.test.ts +22 -0
- package/src/persistence/embeddings/__tests__/worker-script-version.test.ts +76 -0
- package/src/persistence/embeddings/embedding-local.ts +2 -1
- package/src/persistence/embeddings/embedding-runtime-manager.ts +29 -5
- package/src/persistence/embeddings/plugin-index.ts +84 -12
- package/src/persistence/migrations/360-add-document-workspace-path.test.ts +110 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +39 -0
- package/src/persistence/planner-statistics.ts +14 -0
- package/src/persistence/schema/documents.ts +26 -11
- package/src/persistence/steps.ts +2 -0
- package/src/platform/client.test.ts +172 -2
- package/src/platform/client.ts +178 -55
- package/src/plugin-api/conversation-turn.ts +13 -0
- package/src/plugin-api/model-profiles.test.ts +6 -2
- package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +34 -0
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +22 -0
- package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +11 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +199 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +98 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +105 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +24 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +524 -0
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1 -1
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +9 -4
- package/src/plugins/defaults/memory/host-utils.ts +5 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +105 -1
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +64 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +36 -1
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +12 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +1 -0
- package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +195 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +1 -1
- package/src/plugins/defaults/memory/substrate/skill-store.ts +167 -29
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +131 -0
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1 -0
- package/src/plugins/defaults/memory/v2/activation-log-store.ts +4 -0
- package/src/plugins/defaults/memory/v2/injection.ts +48 -3
- package/src/plugins/defaults/memory/v2/rerank-local.ts +6 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -1
- package/src/plugins/defaults/platform-hosted/routes/reengage.ts +1 -1
- package/src/plugins/defaults/turn-context/injectors.ts +1 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +26 -0
- package/src/plugins/types.ts +17 -0
- package/src/prompts/templates/system-sections.ts +2 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +43 -2
- package/src/providers/__tests__/vellum-mismatch-routing.test.ts +8 -0
- package/src/providers/call-site-routing.ts +77 -14
- package/src/providers/connection-resolution.ts +42 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +127 -1
- package/src/providers/inference/adapter-factory.ts +70 -12
- package/src/providers/model-catalog.ts +0 -11
- package/src/providers/model-intents.ts +11 -0
- package/src/providers/registry.ts +8 -0
- package/src/providers/retry.ts +85 -17
- package/src/providers/types.ts +8 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/agent-wake.ts +132 -9
- package/src/runtime/channel-approval-types.ts +25 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +2 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +11 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +121 -9
- package/src/runtime/routes/__tests__/platform-invoice-routes.test.ts +392 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/workspace-commit-routes.test.ts +63 -0
- package/src/runtime/routes/consolidation-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +18 -6
- package/src/runtime/routes/conversation-query-routes.ts +14 -23
- package/src/runtime/routes/conversation-routes.ts +113 -8
- package/src/runtime/routes/credential-routes.ts +1 -5
- package/src/runtime/routes/documents-routes.ts +207 -12
- package/src/runtime/routes/identity-routes.ts +3 -93
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +31 -1
- package/src/runtime/routes/inbound-stages/admission-policy.ts +18 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +52 -3
- package/src/runtime/routes/notification-routes.ts +4 -1
- package/src/runtime/routes/platform-routes.ts +238 -3
- package/src/runtime/routes/playground/guard.ts +1 -2
- package/src/runtime/routes/slack-channel-routes.ts +2 -4
- package/src/runtime/routes/workspace-commit-routes.ts +49 -2
- package/src/runtime/routes/workspace-routes.ts +11 -12
- package/src/runtime/routes/workspace-utils.ts +49 -2
- package/src/runtime/services/conversation-serializer.ts +2 -4
- package/src/subagent/__tests__/consult-context-gating.test.ts +98 -0
- package/src/subagent/__tests__/consult-context-skills.test.ts +82 -0
- package/src/subagent/__tests__/consult-context.test.ts +84 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +36 -0
- package/src/subagent/consult-context.ts +410 -0
- package/src/subagent/consult-prompt.ts +38 -5
- package/src/subagent/manager.ts +9 -4
- package/src/subagent/types.ts +8 -0
- package/src/telemetry/telemetry-event-sources.ts +7 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +1 -0
- package/src/telemetry/turn-events-store.ts +30 -1
- package/src/telemetry/types.ts +24 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/tools/browser/pinned-tabs.ts +3 -1
- package/src/tools/subagent/spawn.ts +23 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/ui-surface/surface-shape-docs.ts +38 -4
- package/src/tools/ui-surface/visual-validation.ts +787 -0
- package/src/tools/workflows/run-workflow.test.ts +1 -0
- package/src/util/__tests__/short-title.test.ts +229 -0
- package/src/util/__tests__/worker-compute.test.ts +65 -0
- package/src/util/cgroup-cpu.ts +93 -0
- package/src/util/errors.ts +27 -0
- package/src/util/process-tree.ts +19 -0
- package/src/util/short-title.ts +189 -0
- package/src/util/worker-compute.ts +83 -0
- package/src/workspace/byok-default-profile-ensure.ts +25 -15
- package/src/workspace/git-service.ts +79 -30
- package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +131 -0
- package/src/workspace/migrations/138-backfill-home-feed-titles.ts +179 -0
- package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +92 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/plugins/defaults/memory/substrate/constants.ts +0 -8
|
@@ -407,7 +407,14 @@ describe("routing identities", () => {
|
|
|
407
407
|
"claude-opus-4-8",
|
|
408
408
|
);
|
|
409
409
|
|
|
410
|
-
expect(provider).toEqual({
|
|
410
|
+
expect(provider).toEqual({
|
|
411
|
+
name: "anthropic",
|
|
412
|
+
tag: "managed",
|
|
413
|
+
routeAttribution: {
|
|
414
|
+
connectionName: "vellum",
|
|
415
|
+
isManagedRoute: true,
|
|
416
|
+
},
|
|
417
|
+
} as never);
|
|
411
418
|
expect(resolveProviderCalls[0]?.name).toBe("vellum");
|
|
412
419
|
expect(resolveProviderOpts[0]?.providerOverride).toBe("anthropic");
|
|
413
420
|
});
|
|
@@ -433,13 +440,47 @@ describe("routing identities", () => {
|
|
|
433
440
|
"gpt-5.5",
|
|
434
441
|
);
|
|
435
442
|
|
|
436
|
-
expect(provider).toEqual({
|
|
443
|
+
expect(provider).toEqual({
|
|
444
|
+
name: "openai",
|
|
445
|
+
tag: "subscription",
|
|
446
|
+
routeAttribution: {
|
|
447
|
+
connectionName: "chatgpt-subscription",
|
|
448
|
+
isManagedRoute: false,
|
|
449
|
+
},
|
|
450
|
+
} as never);
|
|
437
451
|
expect(resolveProviderCalls[0]?.name).toBe("chatgpt-subscription");
|
|
438
452
|
// No override needed: the subscription row itself carries provider
|
|
439
453
|
// "openai", so the adapter resolves from the row.
|
|
440
454
|
expect(resolveProviderCalls[0]?.provider).toBe("openai");
|
|
441
455
|
});
|
|
442
456
|
|
|
457
|
+
// A second managed row is creatable under any non-reserved name: only the
|
|
458
|
+
// canonical name is refused at the create route, and `provider: "vellum"`
|
|
459
|
+
// derives platform auth. Managed-ness is the row, never its name.
|
|
460
|
+
test("a managed row under a non-canonical name carries managed route attribution", async () => {
|
|
461
|
+
fakeConnections.set("managed-openai", {
|
|
462
|
+
name: "managed-openai",
|
|
463
|
+
provider: "vellum",
|
|
464
|
+
auth: { type: "platform" },
|
|
465
|
+
});
|
|
466
|
+
fakeProviders.set("conn:managed-openai", {
|
|
467
|
+
name: "openai",
|
|
468
|
+
tag: "managed",
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
const provider = await tryResolveProviderForConnectionName(
|
|
472
|
+
"managed-openai",
|
|
473
|
+
{ llm: {} } as never,
|
|
474
|
+
"openai",
|
|
475
|
+
"gpt-5.5",
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
expect(provider?.routeAttribution).toEqual({
|
|
479
|
+
connectionName: "managed-openai",
|
|
480
|
+
isManagedRoute: true,
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
|
|
443
484
|
test("chatgpt identity with no subscription row throws not_found (never a silent fallback)", async () => {
|
|
444
485
|
await expect(
|
|
445
486
|
tryResolveProviderForConnectionName(
|
|
@@ -86,6 +86,10 @@ describe("vellum connection mismatch handling", () => {
|
|
|
86
86
|
"accounts/fireworks/models/kimi-k2p5",
|
|
87
87
|
);
|
|
88
88
|
expect(provider).not.toBeNull();
|
|
89
|
+
expect(provider?.routeAttribution).toEqual({
|
|
90
|
+
connectionName: "vellum",
|
|
91
|
+
isManagedRoute: true,
|
|
92
|
+
});
|
|
89
93
|
expect(resolveCalls).toHaveLength(1);
|
|
90
94
|
expect(resolveCalls[0].connection.name).toBe("vellum");
|
|
91
95
|
expect(resolveCalls[0].opts.providerOverride).toBe("fireworks");
|
|
@@ -129,6 +133,10 @@ describe("vellum connection mismatch handling", () => {
|
|
|
129
133
|
"anthropic/claude-fable-5",
|
|
130
134
|
);
|
|
131
135
|
expect(provider).not.toBeNull();
|
|
136
|
+
expect(provider?.routeAttribution).toEqual({
|
|
137
|
+
connectionName: "openrouter-personal",
|
|
138
|
+
isManagedRoute: false,
|
|
139
|
+
});
|
|
132
140
|
expect(resolveCalls).toHaveLength(1);
|
|
133
141
|
expect(resolveCalls[0].connection.name).toBe("openrouter-personal");
|
|
134
142
|
expect(resolveCalls[0].opts.providerOverride).toBeUndefined();
|
|
@@ -21,9 +21,16 @@
|
|
|
21
21
|
|
|
22
22
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
23
23
|
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
resolveCallSiteConfig,
|
|
26
|
+
resolveCallSiteConfigWithProfile,
|
|
27
|
+
} from "../config/llm-resolver.js";
|
|
25
28
|
import { getConfig } from "../config/loader.js";
|
|
26
29
|
import { getDb } from "../persistence/db-connection.js";
|
|
30
|
+
import {
|
|
31
|
+
ProviderError,
|
|
32
|
+
type ProviderRouteAttribution,
|
|
33
|
+
} from "../util/errors.js";
|
|
27
34
|
import { getLogger } from "../util/logger.js";
|
|
28
35
|
import {
|
|
29
36
|
describeSubscriptionModelIncompatibility,
|
|
@@ -31,6 +38,7 @@ import {
|
|
|
31
38
|
} from "./connection-model-compat.js";
|
|
32
39
|
import {
|
|
33
40
|
ConnectionResolutionError,
|
|
41
|
+
isManagedConnectionRoute,
|
|
34
42
|
resolveRoutingIdentity,
|
|
35
43
|
tryResolveProviderForConnectionName,
|
|
36
44
|
} from "./connection-resolution.js";
|
|
@@ -46,6 +54,14 @@ import type {
|
|
|
46
54
|
} from "./types.js";
|
|
47
55
|
|
|
48
56
|
const log = getLogger("providers/call-site-routing");
|
|
57
|
+
|
|
58
|
+
interface SelectedProviderRoute {
|
|
59
|
+
provider: Provider;
|
|
60
|
+
connectionName?: string;
|
|
61
|
+
profileName?: string;
|
|
62
|
+
isManagedRoute?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
export class CallSiteRoutingProvider implements Provider {
|
|
50
66
|
public readonly tokenEstimationProvider?: string;
|
|
51
67
|
// Forward native web-search capability so it survives the wrapper chain
|
|
@@ -99,6 +115,7 @@ export class CallSiteRoutingProvider implements Provider {
|
|
|
99
115
|
expectedProvider: string,
|
|
100
116
|
model: string | undefined,
|
|
101
117
|
) => Promise<Provider | null>,
|
|
118
|
+
private readonly defaultRouteAttribution?: ProviderRouteAttribution,
|
|
102
119
|
) {
|
|
103
120
|
this.tokenEstimationProvider = defaultProvider.tokenEstimationProvider;
|
|
104
121
|
this.supportsNativeWebSearch = defaultProvider.supportsNativeWebSearch;
|
|
@@ -112,11 +129,30 @@ export class CallSiteRoutingProvider implements Provider {
|
|
|
112
129
|
messages: Message[],
|
|
113
130
|
options?: SendMessageOptions,
|
|
114
131
|
): Promise<ProviderResponse> {
|
|
115
|
-
const
|
|
132
|
+
const selectedRoute = await this.selectProvider(options);
|
|
133
|
+
const target = selectedRoute.provider;
|
|
116
134
|
const isRouted = target !== this.defaultProvider;
|
|
117
135
|
|
|
118
136
|
const doSend = async (): Promise<ProviderResponse> => {
|
|
119
|
-
|
|
137
|
+
let response: ProviderResponse;
|
|
138
|
+
try {
|
|
139
|
+
response = await target.sendMessage(messages, options);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error instanceof ProviderError) {
|
|
142
|
+
error.attachRouteAttribution({
|
|
143
|
+
...(selectedRoute.connectionName
|
|
144
|
+
? { connectionName: selectedRoute.connectionName }
|
|
145
|
+
: {}),
|
|
146
|
+
...(selectedRoute.profileName
|
|
147
|
+
? { profileName: selectedRoute.profileName }
|
|
148
|
+
: {}),
|
|
149
|
+
...(selectedRoute.isManagedRoute !== undefined
|
|
150
|
+
? { isManagedRoute: selectedRoute.isManagedRoute }
|
|
151
|
+
: {}),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
120
156
|
// Also stamp actualProvider on the response so that handleUsage
|
|
121
157
|
// (which reads event.actualProvider, not provider.name) attributes
|
|
122
158
|
// the call to the right provider.
|
|
@@ -199,10 +235,10 @@ export class CallSiteRoutingProvider implements Provider {
|
|
|
199
235
|
*/
|
|
200
236
|
private async selectProvider(
|
|
201
237
|
options?: SendMessageOptions,
|
|
202
|
-
): Promise<
|
|
238
|
+
): Promise<SelectedProviderRoute> {
|
|
203
239
|
const callSite = options?.config?.callSite;
|
|
204
240
|
if (!callSite) {
|
|
205
|
-
return this.
|
|
241
|
+
return this.defaultRoute();
|
|
206
242
|
}
|
|
207
243
|
|
|
208
244
|
const overrideProfile = options?.config?.overrideProfile;
|
|
@@ -213,11 +249,15 @@ export class CallSiteRoutingProvider implements Provider {
|
|
|
213
249
|
// request params.
|
|
214
250
|
const forceOverrideProfile = options?.config?.forceOverrideProfile;
|
|
215
251
|
const selectionSeed = options?.config?.selectionSeed;
|
|
216
|
-
const resolved =
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
252
|
+
const { config: resolved, profileName } = resolveCallSiteConfigWithProfile(
|
|
253
|
+
callSite,
|
|
254
|
+
getConfig().llm,
|
|
255
|
+
{
|
|
256
|
+
overrideProfile,
|
|
257
|
+
forceOverrideProfile,
|
|
258
|
+
selectionSeed,
|
|
259
|
+
},
|
|
260
|
+
);
|
|
221
261
|
|
|
222
262
|
let connectionName = resolved.provider_connection;
|
|
223
263
|
|
|
@@ -267,13 +307,27 @@ export class CallSiteRoutingProvider implements Provider {
|
|
|
267
307
|
resolved.model,
|
|
268
308
|
);
|
|
269
309
|
if (connectionProvider) {
|
|
310
|
+
const actualRoute = {
|
|
311
|
+
connectionName:
|
|
312
|
+
connectionProvider.routeAttribution?.connectionName ??
|
|
313
|
+
connectionName,
|
|
314
|
+
isManagedRoute:
|
|
315
|
+
connectionProvider.routeAttribution?.isManagedRoute ??
|
|
316
|
+
isManagedConnectionRoute(connectionName),
|
|
317
|
+
};
|
|
270
318
|
// The connection whose credential the call authenticates with is only
|
|
271
319
|
// known here, and diagnostics for a failed request are unactionable
|
|
272
320
|
// without it ("which key was this?"). Recorded once the adapter exists,
|
|
273
321
|
// so a connection that fell back to the default transport is not
|
|
274
322
|
// reported as the one that signed the request.
|
|
275
|
-
recordProviderRequestDiagnostics({
|
|
276
|
-
|
|
323
|
+
recordProviderRequestDiagnostics({
|
|
324
|
+
connection_name: actualRoute.connectionName,
|
|
325
|
+
});
|
|
326
|
+
return {
|
|
327
|
+
provider: connectionProvider,
|
|
328
|
+
...actualRoute,
|
|
329
|
+
...(profileName ? { profileName } : {}),
|
|
330
|
+
};
|
|
277
331
|
}
|
|
278
332
|
// Soft credential failure: the routed connection yielded no usable
|
|
279
333
|
// adapter and dispatch is landing on the default transport, which may
|
|
@@ -289,11 +343,11 @@ export class CallSiteRoutingProvider implements Provider {
|
|
|
289
343
|
},
|
|
290
344
|
"Routed connection yielded no adapter — falling back to the default transport",
|
|
291
345
|
);
|
|
292
|
-
return this.
|
|
346
|
+
return this.defaultRoute(profileName);
|
|
293
347
|
}
|
|
294
348
|
|
|
295
349
|
if (resolved.provider === this.defaultProvider.name) {
|
|
296
|
-
return this.
|
|
350
|
+
return this.defaultRoute(profileName);
|
|
297
351
|
}
|
|
298
352
|
|
|
299
353
|
if (autoResolveCandidates) {
|
|
@@ -317,6 +371,14 @@ export class CallSiteRoutingProvider implements Provider {
|
|
|
317
371
|
`call-site "${callSite}" resolves to provider "${resolved.provider}" but no provider_connection is set — alternate-provider routing requires a connection`,
|
|
318
372
|
);
|
|
319
373
|
}
|
|
374
|
+
|
|
375
|
+
private defaultRoute(profileName?: string): SelectedProviderRoute {
|
|
376
|
+
return {
|
|
377
|
+
provider: this.defaultProvider,
|
|
378
|
+
...this.defaultRouteAttribution,
|
|
379
|
+
...(profileName ? { profileName } : {}),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
320
382
|
}
|
|
321
383
|
|
|
322
384
|
/**
|
|
@@ -341,5 +403,6 @@ export function wrapWithCallSiteRouting(
|
|
|
341
403
|
expectedProvider,
|
|
342
404
|
model,
|
|
343
405
|
),
|
|
406
|
+
base.routeAttribution,
|
|
344
407
|
);
|
|
345
408
|
}
|
|
@@ -132,11 +132,12 @@ export async function tryResolveProviderForConnectionName(
|
|
|
132
132
|
connectionName === VELLUM_MANAGED_CONNECTION_NAME &&
|
|
133
133
|
!isVellumManagedConnection(connection)
|
|
134
134
|
) {
|
|
135
|
-
|
|
135
|
+
const provider = await resolveThroughPlatform(
|
|
136
136
|
config,
|
|
137
137
|
expectedProvider ?? declaredProvider,
|
|
138
138
|
model,
|
|
139
139
|
);
|
|
140
|
+
return attachProviderRoute(provider, canonicalVellumConnection());
|
|
140
141
|
}
|
|
141
142
|
// The provider-agnostic Vellum-managed connection carries only the `vellum`
|
|
142
143
|
// sentinel, so the usual `connection.provider === expectedProvider` equality
|
|
@@ -221,10 +222,11 @@ export async function tryResolveProviderForConnectionName(
|
|
|
221
222
|
// catch is specifically for in-flight failures that should not take
|
|
222
223
|
// dispatch offline.
|
|
223
224
|
try {
|
|
224
|
-
|
|
225
|
+
const provider = await resolveProviderFromConnection(connection, config, {
|
|
225
226
|
model,
|
|
226
227
|
providerOverride: isVellumRoute ? expectedProvider : undefined,
|
|
227
228
|
});
|
|
229
|
+
return attachProviderRoute(provider, connection);
|
|
228
230
|
} catch (err) {
|
|
229
231
|
log.warn(
|
|
230
232
|
{ err, connectionName },
|
|
@@ -234,6 +236,44 @@ export async function tryResolveProviderForConnectionName(
|
|
|
234
236
|
}
|
|
235
237
|
}
|
|
236
238
|
|
|
239
|
+
function attachProviderRoute(
|
|
240
|
+
provider: Provider | null,
|
|
241
|
+
connection: { name: string; provider: string; auth: { type: string } },
|
|
242
|
+
): Provider | null {
|
|
243
|
+
if (provider) {
|
|
244
|
+
provider.routeAttribution = {
|
|
245
|
+
connectionName: connection.name,
|
|
246
|
+
isManagedRoute: isVellumManagedConnection(connection),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return provider;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Whether a route through this connection name is Vellum-managed
|
|
254
|
+
* (platform-billed), for callers that hold only the name. Returns undefined
|
|
255
|
+
* when the row can't be read, so the caller can fall back rather than assert
|
|
256
|
+
* a BYOK route it never confirmed.
|
|
257
|
+
*
|
|
258
|
+
* The canonical name is always managed: a user-owned row claiming it is
|
|
259
|
+
* ignored and the route resolves through platform auth regardless (see
|
|
260
|
+
* `tryResolveProviderForConnectionName`).
|
|
261
|
+
*/
|
|
262
|
+
export function isManagedConnectionRoute(
|
|
263
|
+
connectionName: string,
|
|
264
|
+
): boolean | undefined {
|
|
265
|
+
if (connectionName === VELLUM_MANAGED_CONNECTION_NAME) {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
let connection;
|
|
269
|
+
try {
|
|
270
|
+
connection = getConnection(getDb(), connectionName);
|
|
271
|
+
} catch {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
return connection ? isVellumManagedConnection(connection) : undefined;
|
|
275
|
+
}
|
|
276
|
+
|
|
237
277
|
/**
|
|
238
278
|
* Resolve a managed route through platform auth without reading a connection
|
|
239
279
|
* row. Used when the canonical `vellum` row is claimed by a user-owned
|
|
@@ -7,7 +7,33 @@ import {
|
|
|
7
7
|
} from "../adapter-factory.js";
|
|
8
8
|
import type { ProviderConnection, ResolvedAuth } from "../auth.js";
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
interface RetryOptions {
|
|
11
|
+
credentialSource?: string;
|
|
12
|
+
connectionName?: string;
|
|
13
|
+
refreshCredentialProvider?: () => Promise<unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Read the `RetryProvider` options the factory stamped. Walks the `inner`
|
|
18
|
+
* chain rather than assuming a fixed wrapper nesting, so reordering the
|
|
19
|
+
* wrappers fails a behavior assertion instead of this accessor.
|
|
20
|
+
*/
|
|
21
|
+
function retryOptions(adapter: unknown): RetryOptions {
|
|
22
|
+
let node = adapter;
|
|
23
|
+
for (let depth = 0; node && depth < 8; depth++) {
|
|
24
|
+
const { options, inner } = node as {
|
|
25
|
+
options?: RetryOptions;
|
|
26
|
+
inner?: unknown;
|
|
27
|
+
};
|
|
28
|
+
if (options) {
|
|
29
|
+
return options;
|
|
30
|
+
}
|
|
31
|
+
node = inner;
|
|
32
|
+
}
|
|
33
|
+
throw new Error("no RetryProvider found in the adapter wrapper chain");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("adapter factory", () => {
|
|
11
37
|
test("buildProviderAdapter returns an OpenAIChatCompletionsProvider", () => {
|
|
12
38
|
const adapter = buildProviderAdapter("openai-compatible", {
|
|
13
39
|
apiKey: "test-key",
|
|
@@ -71,6 +97,10 @@ describe("openai-compatible adapter factory", () => {
|
|
|
71
97
|
});
|
|
72
98
|
|
|
73
99
|
expect(adapter).not.toBeNull();
|
|
100
|
+
expect(retryOptions(adapter)).toMatchObject({
|
|
101
|
+
credentialSource: "no-auth",
|
|
102
|
+
connectionName: "my-vllm",
|
|
103
|
+
});
|
|
74
104
|
});
|
|
75
105
|
|
|
76
106
|
test("createAdapterFromConnection still rejects 'none' auth for keyed catalog providers", () => {
|
|
@@ -94,4 +124,100 @@ describe("openai-compatible adapter factory", () => {
|
|
|
94
124
|
|
|
95
125
|
expect(adapter).toBeNull();
|
|
96
126
|
});
|
|
127
|
+
|
|
128
|
+
test("attributes OAuth subscription credentials separately from API keys", () => {
|
|
129
|
+
const connection: ProviderConnection = {
|
|
130
|
+
name: "chatgpt-subscription",
|
|
131
|
+
provider: "openai",
|
|
132
|
+
auth: {
|
|
133
|
+
type: "oauth_subscription",
|
|
134
|
+
credential: "chatgpt-subscription-oauth",
|
|
135
|
+
},
|
|
136
|
+
label: "ChatGPT",
|
|
137
|
+
baseUrl: null,
|
|
138
|
+
models: null,
|
|
139
|
+
createdAt: Date.now(),
|
|
140
|
+
updatedAt: Date.now(),
|
|
141
|
+
isManaged: false,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const adapter = createAdapterFromConnection(
|
|
145
|
+
connection,
|
|
146
|
+
{
|
|
147
|
+
kind: "header",
|
|
148
|
+
headers: { Authorization: "Bearer test-token" },
|
|
149
|
+
},
|
|
150
|
+
{ model: "gpt-5.4" },
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(adapter).not.toBeNull();
|
|
154
|
+
expect(retryOptions(adapter)).toMatchObject({
|
|
155
|
+
credentialSource: "oauth-subscription",
|
|
156
|
+
connectionName: "chatgpt-subscription",
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("wires managed connections to reload rotated assistant credentials", () => {
|
|
161
|
+
const connection: ProviderConnection = {
|
|
162
|
+
name: "vellum",
|
|
163
|
+
provider: "vellum",
|
|
164
|
+
auth: { type: "platform" },
|
|
165
|
+
label: "Vellum",
|
|
166
|
+
baseUrl: null,
|
|
167
|
+
models: null,
|
|
168
|
+
createdAt: Date.now(),
|
|
169
|
+
updatedAt: Date.now(),
|
|
170
|
+
isManaged: true,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const adapter = createAdapterFromConnection(
|
|
174
|
+
connection,
|
|
175
|
+
{
|
|
176
|
+
kind: "header",
|
|
177
|
+
headers: { Authorization: "Bearer managed-key" },
|
|
178
|
+
baseUrl: "https://example.com/runtime-proxy/anthropic",
|
|
179
|
+
},
|
|
180
|
+
{ model: "claude-opus-4-8", provider: "anthropic" },
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
expect(adapter).not.toBeNull();
|
|
184
|
+
const options = retryOptions(adapter);
|
|
185
|
+
expect(options).toMatchObject({
|
|
186
|
+
credentialSource: "vellum-managed",
|
|
187
|
+
connectionName: "vellum",
|
|
188
|
+
});
|
|
189
|
+
expect(typeof options.refreshCredentialProvider).toBe("function");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("credential refresh declines when it cannot re-read a changed key", async () => {
|
|
193
|
+
const connection: ProviderConnection = {
|
|
194
|
+
name: "vellum",
|
|
195
|
+
provider: "vellum",
|
|
196
|
+
auth: { type: "platform" },
|
|
197
|
+
label: "Vellum",
|
|
198
|
+
baseUrl: null,
|
|
199
|
+
models: null,
|
|
200
|
+
createdAt: Date.now(),
|
|
201
|
+
updatedAt: Date.now(),
|
|
202
|
+
isManaged: true,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const adapter = createAdapterFromConnection(
|
|
206
|
+
connection,
|
|
207
|
+
{
|
|
208
|
+
kind: "header",
|
|
209
|
+
headers: { Authorization: "Bearer managed-key" },
|
|
210
|
+
baseUrl: "https://example.com/runtime-proxy/anthropic",
|
|
211
|
+
},
|
|
212
|
+
{ model: "claude-opus-4-8", provider: "anthropic" },
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Guards the doubled-upstream-call regression: a refresh that cannot
|
|
216
|
+
// produce a credential different from the one that just failed must
|
|
217
|
+
// hand back nothing, so the retry loop surfaces the auth error instead
|
|
218
|
+
// of replaying the request against an identical key.
|
|
219
|
+
const refresh = retryOptions(adapter).refreshCredentialProvider;
|
|
220
|
+
expect(await refresh?.()).toBeNull();
|
|
221
|
+
expect(await refresh?.()).toBeNull();
|
|
222
|
+
});
|
|
97
223
|
});
|
|
@@ -37,6 +37,7 @@ import { UsageTrackingProvider } from "../usage-tracking.js";
|
|
|
37
37
|
import { VercelAIGatewayProvider } from "../vercel-ai-gateway/client.js";
|
|
38
38
|
import type { ResolvedAuth } from "./auth.js";
|
|
39
39
|
import type { ProviderConnection } from "./auth.js";
|
|
40
|
+
import { resolveAuth } from "./resolve-auth.js";
|
|
40
41
|
|
|
41
42
|
/** Unified construction opts. Adapters ignore fields they don't consume. */
|
|
42
43
|
export interface AdapterCreateOpts {
|
|
@@ -225,6 +226,74 @@ export function createAdapterFromConnection(
|
|
|
225
226
|
*/
|
|
226
227
|
provider?: string;
|
|
227
228
|
},
|
|
229
|
+
): Provider | null {
|
|
230
|
+
const provider = opts.provider ?? connection.provider;
|
|
231
|
+
const adapter = buildConnectionAdapter(connection, resolvedAuth, opts);
|
|
232
|
+
if (!adapter) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Re-read the managed credential after an auth rejection and rebuild the
|
|
238
|
+
* adapter around it, so a key rotated out-of-band is picked up without a
|
|
239
|
+
* restart. Returns `null` when the store hands back the same credential
|
|
240
|
+
* that just failed — otherwise a proxy 401 from any other cause (platform
|
|
241
|
+
* auth degraded, revoked account) would make every request pay a second
|
|
242
|
+
* doomed upstream call forever.
|
|
243
|
+
*/
|
|
244
|
+
function makeCredentialRefresher(): () => Promise<Provider | null> {
|
|
245
|
+
let lastAuth = JSON.stringify(resolvedAuth);
|
|
246
|
+
return async (): Promise<Provider | null> => {
|
|
247
|
+
const refreshedAuth = await resolveAuth(connection.auth, provider, {
|
|
248
|
+
baseUrl: connection.baseUrl,
|
|
249
|
+
});
|
|
250
|
+
if (!refreshedAuth.ok) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
const nextAuth = JSON.stringify(refreshedAuth.resolved);
|
|
254
|
+
if (nextAuth === lastAuth) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
lastAuth = nextAuth;
|
|
258
|
+
return buildConnectionAdapter(connection, refreshedAuth.resolved, opts);
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Usage-attribution headers (`X-Vellum-*`) are only meaningful when the
|
|
263
|
+
// request is routed through the Vellum-managed proxy. They carry billing
|
|
264
|
+
// metadata for our own backend. Forwarding them to a third-party endpoint
|
|
265
|
+
// would leak internal Vellum metadata, so gate on the auth type:
|
|
266
|
+
// `platform` is the only auth that flows through our proxy.
|
|
267
|
+
const isManagedProxy = connection.auth.type === "platform";
|
|
268
|
+
return new UsageTrackingProvider(
|
|
269
|
+
new RetryProvider(adapter, {
|
|
270
|
+
forwardUsageAttributionHeaders: isManagedProxy,
|
|
271
|
+
credentialSource: isManagedProxy
|
|
272
|
+
? "vellum-managed"
|
|
273
|
+
: connection.auth.type === "api_key"
|
|
274
|
+
? "byok"
|
|
275
|
+
: connection.auth.type === "oauth_subscription"
|
|
276
|
+
? "oauth-subscription"
|
|
277
|
+
: connection.auth.type === "none"
|
|
278
|
+
? "no-auth"
|
|
279
|
+
: undefined,
|
|
280
|
+
connectionName: connection.name,
|
|
281
|
+
...(isManagedProxy
|
|
282
|
+
? { refreshCredentialProvider: makeCredentialRefresher() }
|
|
283
|
+
: {}),
|
|
284
|
+
}),
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function buildConnectionAdapter(
|
|
289
|
+
connection: ProviderConnection,
|
|
290
|
+
resolvedAuth: ResolvedAuth,
|
|
291
|
+
opts: {
|
|
292
|
+
model: string;
|
|
293
|
+
streamTimeoutMs?: number;
|
|
294
|
+
useNativeWebSearch?: boolean;
|
|
295
|
+
provider?: string;
|
|
296
|
+
},
|
|
228
297
|
): Provider | null {
|
|
229
298
|
const provider = opts.provider ?? connection.provider;
|
|
230
299
|
const entry = PROVIDER_CATALOG.find((e) => e.id === provider);
|
|
@@ -264,16 +333,5 @@ export function createAdapterFromConnection(
|
|
|
264
333
|
if (!adapter) {
|
|
265
334
|
return null;
|
|
266
335
|
}
|
|
267
|
-
|
|
268
|
-
// Usage-attribution headers (`X-Vellum-*`) are only meaningful when the
|
|
269
|
-
// request is routed through the Vellum-managed proxy — they carry billing
|
|
270
|
-
// metadata for our own backend. Forwarding them to a third-party endpoint
|
|
271
|
-
// would leak internal Vellum metadata, so gate on the auth type:
|
|
272
|
-
// `platform` is the only auth that flows through our proxy.
|
|
273
|
-
const isManagedProxy = connection.auth.type === "platform";
|
|
274
|
-
return new UsageTrackingProvider(
|
|
275
|
-
new RetryProvider(adapter, {
|
|
276
|
-
forwardUsageAttributionHeaders: isManagedProxy,
|
|
277
|
-
}),
|
|
278
|
-
);
|
|
336
|
+
return adapter;
|
|
279
337
|
}
|
|
@@ -926,17 +926,6 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
926
926
|
supportsToolUse: true,
|
|
927
927
|
pricing: { inputPer1mTokens: 0.3, outputPer1mTokens: 1.2 },
|
|
928
928
|
},
|
|
929
|
-
{
|
|
930
|
-
id: "accounts/fireworks/models/minimax-m2p5",
|
|
931
|
-
displayName: "MiniMax M2.5",
|
|
932
|
-
contextWindowTokens: 196608,
|
|
933
|
-
maxOutputTokens: 25000,
|
|
934
|
-
supportsThinking: false,
|
|
935
|
-
supportsCaching: false,
|
|
936
|
-
supportsVision: false,
|
|
937
|
-
supportsToolUse: true,
|
|
938
|
-
pricing: { inputPer1mTokens: 0.3, outputPer1mTokens: 1.2 },
|
|
939
|
-
},
|
|
940
929
|
{
|
|
941
930
|
id: "accounts/fireworks/models/deepseek-v4-pro",
|
|
942
931
|
displayName: "DeepSeek V4 Pro",
|
|
@@ -9,45 +9,55 @@ const PROVIDER_DEFAULT_MODELS: Record<string, string> = Object.fromEntries(
|
|
|
9
9
|
PROVIDER_CATALOG.map((entry) => [entry.id, entry.defaultModel]),
|
|
10
10
|
);
|
|
11
11
|
|
|
12
|
+
// `cost-optimized` is the cheapest model a provider serves, `latency-optimized`
|
|
13
|
+
// the fastest to first token. On anthropic, ollama and fireworks the same model
|
|
14
|
+
// is both, so those columns repeat by construction rather than by oversight.
|
|
12
15
|
const PROVIDER_MODEL_INTENTS: Record<string, Record<ModelIntent, string>> = {
|
|
13
16
|
anthropic: {
|
|
14
17
|
balanced: "claude-sonnet-4-6",
|
|
18
|
+
"cost-optimized": "claude-haiku-4-5-20251001",
|
|
15
19
|
"latency-optimized": "claude-haiku-4-5-20251001",
|
|
16
20
|
"quality-optimized": "claude-fable-5",
|
|
17
21
|
"vision-optimized": "claude-opus-4-6",
|
|
18
22
|
},
|
|
19
23
|
openai: {
|
|
20
24
|
balanced: "gpt-5.4-mini",
|
|
25
|
+
"cost-optimized": "gpt-5.4-nano",
|
|
21
26
|
"latency-optimized": "gpt-5.6-luna",
|
|
22
27
|
"quality-optimized": "gpt-5.4",
|
|
23
28
|
"vision-optimized": "gpt-5.4",
|
|
24
29
|
},
|
|
25
30
|
gemini: {
|
|
26
31
|
balanced: "gemini-3-flash-preview",
|
|
32
|
+
"cost-optimized": "gemini-2.5-flash-lite",
|
|
27
33
|
"latency-optimized": "gemini-3.1-flash-lite",
|
|
28
34
|
"quality-optimized": "gemini-3.1-pro-preview",
|
|
29
35
|
"vision-optimized": "gemini-3-flash-preview",
|
|
30
36
|
},
|
|
31
37
|
ollama: {
|
|
32
38
|
balanced: "llama3.2",
|
|
39
|
+
"cost-optimized": "llama3.2",
|
|
33
40
|
"latency-optimized": "llama3.2",
|
|
34
41
|
"quality-optimized": "llama3.2",
|
|
35
42
|
"vision-optimized": "llama3.2",
|
|
36
43
|
},
|
|
37
44
|
fireworks: {
|
|
38
45
|
balanced: "accounts/fireworks/models/minimax-m3",
|
|
46
|
+
"cost-optimized": "accounts/fireworks/models/deepseek-v4-flash",
|
|
39
47
|
"latency-optimized": "accounts/fireworks/models/deepseek-v4-flash",
|
|
40
48
|
"quality-optimized": "accounts/fireworks/models/kimi-k2p6",
|
|
41
49
|
"vision-optimized": "accounts/fireworks/models/kimi-k2p6",
|
|
42
50
|
},
|
|
43
51
|
openrouter: {
|
|
44
52
|
balanced: "anthropic/claude-sonnet-4.6",
|
|
53
|
+
"cost-optimized": "deepseek/deepseek-v4-flash",
|
|
45
54
|
"latency-optimized": "anthropic/claude-haiku-4.5",
|
|
46
55
|
"quality-optimized": "anthropic/claude-fable-5",
|
|
47
56
|
"vision-optimized": "anthropic/claude-opus-4.6",
|
|
48
57
|
},
|
|
49
58
|
"vercel-ai-gateway": {
|
|
50
59
|
balanced: "anthropic/claude-sonnet-4.6",
|
|
60
|
+
"cost-optimized": "deepseek/deepseek-v4-flash",
|
|
51
61
|
"latency-optimized": "anthropic/claude-haiku-4.5",
|
|
52
62
|
"quality-optimized": "anthropic/claude-fable-5",
|
|
53
63
|
"vision-optimized": "anthropic/claude-opus-4.6",
|
|
@@ -58,6 +68,7 @@ const FALLBACK_DEFAULT_MODEL = "claude-opus-4-8";
|
|
|
58
68
|
|
|
59
69
|
const MODEL_INTENTS = new Set<ModelIntent>([
|
|
60
70
|
"balanced",
|
|
71
|
+
"cost-optimized",
|
|
61
72
|
"latency-optimized",
|
|
62
73
|
"quality-optimized",
|
|
63
74
|
"vision-optimized",
|
|
@@ -265,6 +265,14 @@ export async function initializeProviders(
|
|
|
265
265
|
entry.id,
|
|
266
266
|
new RetryProvider(adapter, {
|
|
267
267
|
forwardUsageAttributionHeaders: source === "managed-proxy",
|
|
268
|
+
// A keyless provider booted without a key has no credential to blame,
|
|
269
|
+
// so don't let its rejections render as "update your personal key".
|
|
270
|
+
credentialSource:
|
|
271
|
+
source === "managed-proxy"
|
|
272
|
+
? "vellum-managed"
|
|
273
|
+
: isKeyless && !apiKey
|
|
274
|
+
? "no-auth"
|
|
275
|
+
: "byok",
|
|
268
276
|
}),
|
|
269
277
|
);
|
|
270
278
|
routingSources.set(entry.id, source);
|