@vellumai/assistant 0.5.1 → 0.5.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/ARCHITECTURE.md +54 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
- package/src/__tests__/conversation-agent-loop.test.ts +290 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +32 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-recall-quality.test.ts +5 -5
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/loader.ts +64 -0
- package/src/config/raw-config-utils.ts +30 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/services.ts +8 -6
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +48 -7
- package/src/daemon/conversation-agent-loop.ts +56 -19
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +39 -15
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +21 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +143 -20
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +51 -29
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +148 -0
- package/src/daemon/handlers/config-model.ts +71 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +106 -64
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +19 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +442 -3
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +9 -7
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +1 -4
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +6 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/oauth.ts +6 -0
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +97 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +71 -8
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +68 -0
- package/src/runtime/routes/conversation-query-routes.ts +180 -10
- package/src/runtime/routes/conversation-routes.ts +222 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1199 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +4 -0
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +28 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +9 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +2 -7
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +6 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
|
@@ -5,9 +5,11 @@ import { seedProviders } from "./oauth-store.js";
|
|
|
5
5
|
*
|
|
6
6
|
* These values are upserted into the `oauth_providers` SQLite table on
|
|
7
7
|
* every startup. Only Vellum implementation fields (authUrl, tokenUrl,
|
|
8
|
-
* tokenEndpointAuthMethod, extraParams, callbackTransport,
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* tokenEndpointAuthMethod, userinfoUrl, extraParams, callbackTransport,
|
|
9
|
+
* pingUrl, managedServiceConfigKey) and display metadata (displayName,
|
|
10
|
+
* description, dashboardUrl, clientIdPlaceholder, requiresClientSecret)
|
|
11
|
+
* are overwritten on subsequent startups — user-customizable
|
|
12
|
+
* fields (defaultScopes, scopePolicy, baseUrl) are only
|
|
11
13
|
* written on initial insert and preserved across restarts.
|
|
12
14
|
*
|
|
13
15
|
* Code-side behavioral fields (identityVerifier, injectionTemplates,
|
|
@@ -32,6 +34,12 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
32
34
|
};
|
|
33
35
|
extraParams?: Record<string, string>;
|
|
34
36
|
callbackTransport?: string;
|
|
37
|
+
managedServiceConfigKey?: string;
|
|
38
|
+
displayName: string;
|
|
39
|
+
description: string;
|
|
40
|
+
dashboardUrl: string | null;
|
|
41
|
+
clientIdPlaceholder: string | null;
|
|
42
|
+
requiresClientSecret?: boolean;
|
|
35
43
|
}
|
|
36
44
|
> = {
|
|
37
45
|
"integration:google": {
|
|
@@ -41,6 +49,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
41
49
|
userinfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
42
50
|
pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
43
51
|
baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
|
|
52
|
+
displayName: "Google",
|
|
53
|
+
description: "Gmail, Calendar, and Contacts",
|
|
54
|
+
dashboardUrl: "https://console.cloud.google.com/apis/credentials",
|
|
55
|
+
clientIdPlaceholder: "123456789.apps.googleusercontent.com",
|
|
44
56
|
defaultScopes: [
|
|
45
57
|
"https://www.googleapis.com/auth/gmail.readonly",
|
|
46
58
|
"https://www.googleapis.com/auth/gmail.modify",
|
|
@@ -60,6 +72,7 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
60
72
|
},
|
|
61
73
|
extraParams: { access_type: "offline", prompt: "consent" },
|
|
62
74
|
callbackTransport: "loopback",
|
|
75
|
+
managedServiceConfigKey: "google-oauth",
|
|
63
76
|
},
|
|
64
77
|
|
|
65
78
|
"integration:slack": {
|
|
@@ -68,6 +81,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
68
81
|
tokenUrl: "https://slack.com/api/oauth.v2.access",
|
|
69
82
|
pingUrl: "https://slack.com/api/auth.test",
|
|
70
83
|
baseUrl: "https://slack.com/api",
|
|
84
|
+
displayName: "Slack",
|
|
85
|
+
description: "Workspace messaging",
|
|
86
|
+
dashboardUrl: "https://api.slack.com/apps",
|
|
87
|
+
clientIdPlaceholder: null,
|
|
71
88
|
defaultScopes: [
|
|
72
89
|
"channels:read",
|
|
73
90
|
"channels:history",
|
|
@@ -101,6 +118,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
101
118
|
tokenUrl: "https://api.notion.com/v1/oauth/token",
|
|
102
119
|
pingUrl: "https://api.notion.com/v1/users/me",
|
|
103
120
|
baseUrl: "https://api.notion.com",
|
|
121
|
+
displayName: "Notion",
|
|
122
|
+
description: "Pages and databases",
|
|
123
|
+
dashboardUrl: "https://www.notion.so/my-integrations",
|
|
124
|
+
clientIdPlaceholder: null,
|
|
104
125
|
defaultScopes: [],
|
|
105
126
|
scopePolicy: {
|
|
106
127
|
allowAdditionalScopes: false,
|
|
@@ -118,6 +139,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
118
139
|
tokenUrl: "https://api.x.com/2/oauth2/token",
|
|
119
140
|
pingUrl: "https://api.x.com/2/users/me",
|
|
120
141
|
baseUrl: "https://api.x.com",
|
|
142
|
+
displayName: "Twitter",
|
|
143
|
+
description: "Posts and direct messages",
|
|
144
|
+
dashboardUrl: "https://developer.twitter.com/en/portal/dashboard",
|
|
145
|
+
clientIdPlaceholder: null,
|
|
121
146
|
defaultScopes: [
|
|
122
147
|
"tweet.read",
|
|
123
148
|
"tweet.write",
|
|
@@ -139,6 +164,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
139
164
|
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
140
165
|
pingUrl: "https://api.github.com/user",
|
|
141
166
|
baseUrl: "https://api.github.com",
|
|
167
|
+
displayName: "GitHub",
|
|
168
|
+
description: "Repositories and issues",
|
|
169
|
+
dashboardUrl: "https://github.com/settings/developers",
|
|
170
|
+
clientIdPlaceholder: null,
|
|
142
171
|
defaultScopes: ["repo", "read:user", "notifications"],
|
|
143
172
|
scopePolicy: {
|
|
144
173
|
allowAdditionalScopes: true,
|
|
@@ -159,6 +188,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
159
188
|
tokenUrl: "https://api.linear.app/oauth/token",
|
|
160
189
|
pingUrl: "https://api.linear.app/graphql",
|
|
161
190
|
baseUrl: "https://api.linear.app",
|
|
191
|
+
displayName: "Linear",
|
|
192
|
+
description: "Issues and projects",
|
|
193
|
+
dashboardUrl: "https://linear.app/settings/api",
|
|
194
|
+
clientIdPlaceholder: null,
|
|
162
195
|
defaultScopes: ["read", "write", "issues:create"],
|
|
163
196
|
scopePolicy: {
|
|
164
197
|
allowAdditionalScopes: false,
|
|
@@ -175,6 +208,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
175
208
|
tokenUrl: "https://accounts.spotify.com/api/token",
|
|
176
209
|
pingUrl: "https://api.spotify.com/v1/me",
|
|
177
210
|
baseUrl: "https://api.spotify.com/v1",
|
|
211
|
+
displayName: "Spotify",
|
|
212
|
+
description: "Music and playlists",
|
|
213
|
+
dashboardUrl: "https://developer.spotify.com/dashboard",
|
|
214
|
+
clientIdPlaceholder: null,
|
|
178
215
|
defaultScopes: [
|
|
179
216
|
"user-read-playback-state",
|
|
180
217
|
"user-modify-playback-state",
|
|
@@ -201,6 +238,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
201
238
|
tokenUrl: "https://todoist.com/oauth/access_token",
|
|
202
239
|
pingUrl: "https://api.todoist.com/rest/v2/projects",
|
|
203
240
|
baseUrl: "https://api.todoist.com/rest/v2",
|
|
241
|
+
displayName: "Todoist",
|
|
242
|
+
description: "Tasks and projects",
|
|
243
|
+
dashboardUrl: "https://developer.todoist.com/appconsole.html",
|
|
244
|
+
clientIdPlaceholder: null,
|
|
204
245
|
defaultScopes: ["data:read_write"],
|
|
205
246
|
scopePolicy: {
|
|
206
247
|
allowAdditionalScopes: false,
|
|
@@ -216,6 +257,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
216
257
|
tokenUrl: "https://discord.com/api/v10/oauth2/token",
|
|
217
258
|
pingUrl: "https://discord.com/api/v10/users/@me",
|
|
218
259
|
baseUrl: "https://discord.com/api/v10",
|
|
260
|
+
displayName: "Discord",
|
|
261
|
+
description: "Servers and messages",
|
|
262
|
+
dashboardUrl: "https://discord.com/developers/applications",
|
|
263
|
+
clientIdPlaceholder: null,
|
|
219
264
|
defaultScopes: [
|
|
220
265
|
"identify",
|
|
221
266
|
"guilds",
|
|
@@ -236,6 +281,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
236
281
|
tokenUrl: "https://api.dropboxapi.com/oauth2/token",
|
|
237
282
|
pingUrl: "https://api.dropboxapi.com/2/users/get_current_account",
|
|
238
283
|
baseUrl: "https://api.dropboxapi.com/2",
|
|
284
|
+
displayName: "Dropbox",
|
|
285
|
+
description: "Files and folders",
|
|
286
|
+
dashboardUrl: "https://www.dropbox.com/developers/apps",
|
|
287
|
+
clientIdPlaceholder: null,
|
|
239
288
|
defaultScopes: [
|
|
240
289
|
"files.metadata.read",
|
|
241
290
|
"files.content.read",
|
|
@@ -257,6 +306,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
257
306
|
tokenUrl: "https://app.asana.com/-/oauth_token",
|
|
258
307
|
pingUrl: "https://app.asana.com/api/1.0/users/me",
|
|
259
308
|
baseUrl: "https://app.asana.com/api/1.0",
|
|
309
|
+
displayName: "Asana",
|
|
310
|
+
description: "Tasks and projects",
|
|
311
|
+
dashboardUrl: "https://app.asana.com/0/my-apps",
|
|
312
|
+
clientIdPlaceholder: null,
|
|
260
313
|
defaultScopes: ["default"],
|
|
261
314
|
scopePolicy: {
|
|
262
315
|
allowAdditionalScopes: false,
|
|
@@ -272,6 +325,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
272
325
|
tokenUrl: "https://airtable.com/oauth2/v1/token",
|
|
273
326
|
pingUrl: "https://api.airtable.com/v0/meta/whoami",
|
|
274
327
|
baseUrl: "https://api.airtable.com/v0",
|
|
328
|
+
displayName: "Airtable",
|
|
329
|
+
description: "Bases and records",
|
|
330
|
+
dashboardUrl: "https://airtable.com/create/tokens",
|
|
331
|
+
clientIdPlaceholder: null,
|
|
275
332
|
defaultScopes: [
|
|
276
333
|
"data.records:read",
|
|
277
334
|
"data.records:write",
|
|
@@ -292,6 +349,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
292
349
|
tokenUrl: "https://api.hubapi.com/oauth/v1/token",
|
|
293
350
|
pingUrl: "https://api.hubapi.com/crm/v3/objects/contacts?limit=1",
|
|
294
351
|
baseUrl: "https://api.hubapi.com",
|
|
352
|
+
displayName: "HubSpot",
|
|
353
|
+
description: "CRM contacts and deals",
|
|
354
|
+
dashboardUrl: "https://developers.hubspot.com/",
|
|
355
|
+
clientIdPlaceholder: null,
|
|
295
356
|
defaultScopes: [
|
|
296
357
|
"crm.objects.contacts.read",
|
|
297
358
|
"crm.objects.contacts.write",
|
|
@@ -316,6 +377,10 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
316
377
|
tokenUrl: "https://api.figma.com/v1/oauth/token",
|
|
317
378
|
pingUrl: "https://api.figma.com/v1/me",
|
|
318
379
|
baseUrl: "https://api.figma.com/v1",
|
|
380
|
+
displayName: "Figma",
|
|
381
|
+
description: "Design files and comments",
|
|
382
|
+
dashboardUrl: "https://www.figma.com/developers/apps",
|
|
383
|
+
clientIdPlaceholder: null,
|
|
319
384
|
defaultScopes: ["files:read", "file_comments:write"],
|
|
320
385
|
scopePolicy: {
|
|
321
386
|
allowAdditionalScopes: false,
|
|
@@ -335,6 +400,11 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
335
400
|
tokenUrl: "urn:manual-token",
|
|
336
401
|
pingUrl: "https://slack.com/api/auth.test",
|
|
337
402
|
baseUrl: "https://slack.com/api",
|
|
403
|
+
displayName: "Slack Channel",
|
|
404
|
+
description: "Channel bot token",
|
|
405
|
+
dashboardUrl: null,
|
|
406
|
+
clientIdPlaceholder: null,
|
|
407
|
+
requiresClientSecret: false,
|
|
338
408
|
defaultScopes: [],
|
|
339
409
|
scopePolicy: {
|
|
340
410
|
allowAdditionalScopes: false,
|
|
@@ -348,6 +418,11 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
348
418
|
authUrl: "urn:manual-token",
|
|
349
419
|
tokenUrl: "urn:manual-token",
|
|
350
420
|
baseUrl: "https://api.telegram.org",
|
|
421
|
+
displayName: "Telegram",
|
|
422
|
+
description: "Bot messaging",
|
|
423
|
+
dashboardUrl: null,
|
|
424
|
+
clientIdPlaceholder: null,
|
|
425
|
+
requiresClientSecret: false,
|
|
351
426
|
defaultScopes: [],
|
|
352
427
|
scopePolicy: {
|
|
353
428
|
allowAdditionalScopes: false,
|
|
@@ -28,9 +28,8 @@ import {
|
|
|
28
28
|
import { runPostConnectHook } from "../tools/credentials/post-connect-hooks.js";
|
|
29
29
|
import {
|
|
30
30
|
createConnection,
|
|
31
|
+
getActiveConnection,
|
|
31
32
|
getApp,
|
|
32
|
-
getConnectionByProvider,
|
|
33
|
-
getConnectionByProviderAndAccount,
|
|
34
33
|
listActiveConnectionsByProvider,
|
|
35
34
|
updateConnection,
|
|
36
35
|
upsertApp,
|
|
@@ -59,8 +58,12 @@ export interface StoreOAuth2TokensParams {
|
|
|
59
58
|
clientId: string;
|
|
60
59
|
clientSecret?: string;
|
|
61
60
|
userinfoUrl?: string;
|
|
62
|
-
/**
|
|
63
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Best-effort account identifier parsed from the provider's identity
|
|
63
|
+
* endpoint (e.g. email, @username, display name). The format varies by
|
|
64
|
+
* provider and may be undefined if the API call fails.
|
|
65
|
+
*/
|
|
66
|
+
parsedAccountIdentifier?: string;
|
|
64
67
|
/** Pre-resolved oauth_app ID — skips the upsertApp() call if provided. */
|
|
65
68
|
oauthAppId?: string;
|
|
66
69
|
}
|
|
@@ -94,6 +97,10 @@ export async function storeOAuth2Tokens(
|
|
|
94
97
|
? Date.now() + tokens.expiresIn * 1000
|
|
95
98
|
: null;
|
|
96
99
|
|
|
100
|
+
// Account identifier parsing is best-effort. The format varies by provider
|
|
101
|
+
// (email for Google, username for GitHub, display name for Spotify, etc.)
|
|
102
|
+
// and may be undefined if the userinfo/identity API call fails or the
|
|
103
|
+
// required scope wasn't granted.
|
|
97
104
|
let accountInfo: string | undefined;
|
|
98
105
|
if (userinfoUrl && grantedScopes.some((s) => s.includes("userinfo"))) {
|
|
99
106
|
try {
|
|
@@ -109,7 +116,7 @@ export async function storeOAuth2Tokens(
|
|
|
109
116
|
}
|
|
110
117
|
}
|
|
111
118
|
|
|
112
|
-
const resolvedAccountInfo = accountInfo ?? params.
|
|
119
|
+
const resolvedAccountInfo = accountInfo ?? params.parsedAccountIdentifier;
|
|
113
120
|
|
|
114
121
|
// -------------------------------------------------------------------
|
|
115
122
|
// SQLite oauth_app + oauth_connection + new-format secure keys
|
|
@@ -144,12 +151,11 @@ export async function storeOAuth2Tokens(
|
|
|
144
151
|
// lookup so that re-auth without userinfo still updates the right row.
|
|
145
152
|
// However, treat provider-only matches as ambiguous when multiple active
|
|
146
153
|
// connections exist to avoid overwriting the wrong account's tokens.
|
|
147
|
-
let existingConn: ReturnType<typeof
|
|
154
|
+
let existingConn: ReturnType<typeof getActiveConnection>;
|
|
148
155
|
if (resolvedAccountInfo) {
|
|
149
|
-
existingConn =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
);
|
|
156
|
+
existingConn = getActiveConnection(service, {
|
|
157
|
+
account: resolvedAccountInfo,
|
|
158
|
+
});
|
|
153
159
|
} else {
|
|
154
160
|
const activeConns = listActiveConnectionsByProvider(service);
|
|
155
161
|
// Only reuse the provider-only match when it's unambiguous (single connection).
|
|
@@ -144,6 +144,7 @@ const LOW_RISK_PROGRAMS = new Set([
|
|
|
144
144
|
"du",
|
|
145
145
|
"df",
|
|
146
146
|
"assistant",
|
|
147
|
+
"vellum",
|
|
147
148
|
]);
|
|
148
149
|
|
|
149
150
|
// High-risk shell programs / patterns
|
|
@@ -197,6 +198,32 @@ const LOW_RISK_GIT_SUBCOMMANDS = new Set([
|
|
|
197
198
|
"reflog",
|
|
198
199
|
]);
|
|
199
200
|
|
|
201
|
+
// Mutating assistant/vellum CLI subcommands that should be escalated to Medium
|
|
202
|
+
// risk. Most assistant/vellum subcommands are read-only and stay Low risk.
|
|
203
|
+
// This mirrors the git subcommand pattern — only known mutating operations
|
|
204
|
+
// get escalated.
|
|
205
|
+
const MEDIUM_RISK_CLI_SUBCOMMANDS = new Set([
|
|
206
|
+
"credentials",
|
|
207
|
+
"config",
|
|
208
|
+
"bash",
|
|
209
|
+
"trust",
|
|
210
|
+
"autonomy",
|
|
211
|
+
"contacts",
|
|
212
|
+
"mcp",
|
|
213
|
+
"keys",
|
|
214
|
+
"wake",
|
|
215
|
+
"sleep",
|
|
216
|
+
"hatch",
|
|
217
|
+
"retire",
|
|
218
|
+
"clean",
|
|
219
|
+
"setup",
|
|
220
|
+
"upgrade",
|
|
221
|
+
"recover",
|
|
222
|
+
"login",
|
|
223
|
+
"use",
|
|
224
|
+
"pair",
|
|
225
|
+
]);
|
|
226
|
+
|
|
200
227
|
// Commands that wrap another program — the real program appears as the first
|
|
201
228
|
// non-flag argument. When one of these is the segment program we look through
|
|
202
229
|
// its args to find the effective program (e.g. `env curl …` → curl).
|
|
@@ -219,15 +246,40 @@ const WRAPPER_PROGRAMS = new Set([
|
|
|
219
246
|
// value of -u) as the wrapped program instead of `echo`.
|
|
220
247
|
const ENV_VALUE_FLAGS = new Set(["-u", "--unset", "-C", "--chdir"]);
|
|
221
248
|
|
|
249
|
+
// `git` global flags that consume the next positional argument as their value.
|
|
250
|
+
// Without this, `git -C status commit` would incorrectly identify `status`
|
|
251
|
+
// (the directory path) as the subcommand instead of `commit`.
|
|
252
|
+
const GIT_VALUE_FLAGS = new Set([
|
|
253
|
+
"-C",
|
|
254
|
+
"-c",
|
|
255
|
+
"--git-dir",
|
|
256
|
+
"--work-tree",
|
|
257
|
+
"--namespace",
|
|
258
|
+
"--super-prefix",
|
|
259
|
+
"--config-env",
|
|
260
|
+
]);
|
|
261
|
+
|
|
222
262
|
/**
|
|
223
|
-
* Return the first non-flag argument from an argument list
|
|
224
|
-
* Flags are arguments that start with `-`.
|
|
225
|
-
* options (e.g. `--verbose`, `-h
|
|
226
|
-
* CLIs like `git`, `vellum`, and
|
|
263
|
+
* Return the first non-flag argument from an argument list, optionally
|
|
264
|
+
* skipping value-taking flags. Flags are arguments that start with `-`.
|
|
265
|
+
* This is used to skip global options (e.g. `--verbose`, `-h`, `-C <path>`)
|
|
266
|
+
* when extracting the subcommand from CLIs like `git`, `vellum`, and
|
|
267
|
+
* `assistant`.
|
|
268
|
+
*
|
|
269
|
+
* When `valueFlags` is provided, any flag in that set causes the next
|
|
270
|
+
* argument to be skipped as well (it is the flag's value, not a positional).
|
|
227
271
|
*/
|
|
228
|
-
function firstPositionalArg(
|
|
229
|
-
|
|
230
|
-
|
|
272
|
+
function firstPositionalArg(
|
|
273
|
+
args: string[],
|
|
274
|
+
valueFlags?: Set<string>,
|
|
275
|
+
): string | undefined {
|
|
276
|
+
for (let i = 0; i < args.length; i++) {
|
|
277
|
+
const arg = args[i];
|
|
278
|
+
if (arg.startsWith("-")) {
|
|
279
|
+
if (valueFlags?.has(arg)) i++; // skip the next arg (the flag's value)
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
return arg;
|
|
231
283
|
}
|
|
232
284
|
return undefined;
|
|
233
285
|
}
|
|
@@ -652,7 +704,7 @@ async function classifyRiskUncached(
|
|
|
652
704
|
}
|
|
653
705
|
|
|
654
706
|
if (prog === "git") {
|
|
655
|
-
const subcommand = firstPositionalArg(seg.args);
|
|
707
|
+
const subcommand = firstPositionalArg(seg.args, GIT_VALUE_FLAGS);
|
|
656
708
|
if (subcommand && LOW_RISK_GIT_SUBCOMMANDS.has(subcommand)) {
|
|
657
709
|
// Stay at current risk
|
|
658
710
|
continue;
|
|
@@ -662,6 +714,17 @@ async function classifyRiskUncached(
|
|
|
662
714
|
continue;
|
|
663
715
|
}
|
|
664
716
|
|
|
717
|
+
if (prog === "vellum" || prog === "assistant") {
|
|
718
|
+
const subcommand = firstPositionalArg(seg.args);
|
|
719
|
+
if (subcommand && MEDIUM_RISK_CLI_SUBCOMMANDS.has(subcommand)) {
|
|
720
|
+
// Known mutating subcommands are medium
|
|
721
|
+
maxRisk = RiskLevel.Medium;
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
// Read-only / unknown subcommands stay at current risk
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
|
|
665
728
|
if (!LOW_RISK_PROGRAMS.has(prog)) {
|
|
666
729
|
// Unknown program → medium
|
|
667
730
|
if (maxRisk === RiskLevel.Low) {
|
|
@@ -208,6 +208,8 @@ Once you've completed Phase 1 and made reasonable progress through Phase 2, you'
|
|
|
208
208
|
|
|
209
209
|
If you still haven't shown the two suggestions (Phase 2 step 4), do that before wrapping.
|
|
210
210
|
|
|
211
|
+
When you're confident onboarding is complete, delete `BOOTSTRAP.md` so it doesn't re-trigger on the next conversation.
|
|
212
|
+
|
|
211
213
|
---
|
|
212
214
|
|
|
213
215
|
_Good luck out there. Make it count._
|
|
@@ -16,6 +16,9 @@ import type {
|
|
|
16
16
|
|
|
17
17
|
const log = getLogger("anthropic-client");
|
|
18
18
|
|
|
19
|
+
/** Validation-specific timeout (10s) so a stalled network doesn't block key submission. */
|
|
20
|
+
const VALIDATION_TIMEOUT_MS = 10_000;
|
|
21
|
+
|
|
19
22
|
/**
|
|
20
23
|
* Validate an Anthropic API key by making a lightweight GET /v1/models call.
|
|
21
24
|
* Returns `{ valid: true }` on success or `{ valid: false, reason: string }` on failure.
|
|
@@ -24,7 +27,11 @@ export async function validateAnthropicApiKey(
|
|
|
24
27
|
apiKey: string,
|
|
25
28
|
): Promise<{ valid: true } | { valid: false; reason: string }> {
|
|
26
29
|
try {
|
|
27
|
-
const client = new Anthropic({
|
|
30
|
+
const client = new Anthropic({
|
|
31
|
+
apiKey,
|
|
32
|
+
timeout: VALIDATION_TIMEOUT_MS,
|
|
33
|
+
maxRetries: 0,
|
|
34
|
+
});
|
|
28
35
|
await client.models.list({ limit: 1 });
|
|
29
36
|
return { valid: true };
|
|
30
37
|
} catch (error) {
|
|
@@ -133,7 +133,10 @@ export class FailoverProvider implements Provider {
|
|
|
133
133
|
);
|
|
134
134
|
health.unhealthySince = null;
|
|
135
135
|
}
|
|
136
|
-
return
|
|
136
|
+
return {
|
|
137
|
+
...response,
|
|
138
|
+
actualProvider: response.actualProvider ?? provider.name,
|
|
139
|
+
};
|
|
137
140
|
} catch (error) {
|
|
138
141
|
lastError = error;
|
|
139
142
|
|
|
@@ -3,6 +3,7 @@ import { ApiError, GoogleGenAI } from "@google/genai";
|
|
|
3
3
|
|
|
4
4
|
import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "../../prompts/cache-boundary.js";
|
|
5
5
|
import { ProviderError } from "../../util/errors.js";
|
|
6
|
+
import { getLogger } from "../../util/logger.js";
|
|
6
7
|
import { createStreamTimeout } from "../stream-timeout.js";
|
|
7
8
|
import type {
|
|
8
9
|
ContentBlock,
|
|
@@ -13,6 +14,55 @@ import type {
|
|
|
13
14
|
ToolDefinition,
|
|
14
15
|
} from "../types.js";
|
|
15
16
|
|
|
17
|
+
const log = getLogger("gemini-client");
|
|
18
|
+
|
|
19
|
+
/** Validation-specific timeout (10s) so a stalled network doesn't block key submission. */
|
|
20
|
+
const VALIDATION_TIMEOUT_MS = 10_000;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Validate a Gemini API key by making a lightweight models.list() call.
|
|
24
|
+
* Returns `{ valid: true }` on success or `{ valid: false, reason: string }` on failure.
|
|
25
|
+
*/
|
|
26
|
+
export async function validateGeminiApiKey(
|
|
27
|
+
apiKey: string,
|
|
28
|
+
): Promise<{ valid: true } | { valid: false; reason: string }> {
|
|
29
|
+
try {
|
|
30
|
+
const client = new GoogleGenAI({ apiKey });
|
|
31
|
+
await client.models.list({
|
|
32
|
+
config: {
|
|
33
|
+
pageSize: 1,
|
|
34
|
+
httpOptions: { timeout: VALIDATION_TIMEOUT_MS },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
return { valid: true };
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error instanceof ApiError) {
|
|
40
|
+
if (error.status === 401) {
|
|
41
|
+
return { valid: false, reason: "API key is invalid or expired." };
|
|
42
|
+
}
|
|
43
|
+
if (error.status === 403) {
|
|
44
|
+
return {
|
|
45
|
+
valid: false,
|
|
46
|
+
reason: `Gemini API error (${error.status}): ${error.message}`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Transient errors (429, 5xx, etc.) — validation is inconclusive,
|
|
50
|
+
// allow the key to be stored rather than blocking the user.
|
|
51
|
+
log.warn(
|
|
52
|
+
{ status: error.status },
|
|
53
|
+
"Gemini API returned a transient error during key validation — allowing key storage",
|
|
54
|
+
);
|
|
55
|
+
return { valid: true };
|
|
56
|
+
}
|
|
57
|
+
// Network errors — validation is inconclusive, allow key storage.
|
|
58
|
+
log.warn(
|
|
59
|
+
{ error: error instanceof Error ? error.message : String(error) },
|
|
60
|
+
"Network error during Gemini key validation — allowing key storage",
|
|
61
|
+
);
|
|
62
|
+
return { valid: true };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
16
66
|
export interface GeminiProviderOptions {
|
|
17
67
|
streamTimeoutMs?: number;
|
|
18
68
|
/** When set, routes requests through the managed proxy at this base URL. */
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export interface CatalogModel {
|
|
2
|
+
id: string;
|
|
3
|
+
displayName: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface ProviderCatalogEntry {
|
|
7
|
+
id: string;
|
|
8
|
+
displayName: string;
|
|
9
|
+
models: CatalogModel[];
|
|
10
|
+
defaultModel: string;
|
|
11
|
+
apiKeyUrl?: string;
|
|
12
|
+
apiKeyPlaceholder?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Single source of truth for all inference provider metadata and models. */
|
|
16
|
+
export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
17
|
+
{
|
|
18
|
+
id: "anthropic",
|
|
19
|
+
displayName: "Anthropic",
|
|
20
|
+
models: [
|
|
21
|
+
{ id: "claude-opus-4-6", displayName: "Claude Opus 4.6" },
|
|
22
|
+
{ id: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6" },
|
|
23
|
+
{ id: "claude-haiku-4-5-20251001", displayName: "Claude Haiku 4.5" },
|
|
24
|
+
],
|
|
25
|
+
defaultModel: "claude-opus-4-6",
|
|
26
|
+
apiKeyUrl: "https://console.anthropic.com/settings/keys",
|
|
27
|
+
apiKeyPlaceholder: "sk-ant-api03-...",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "openai",
|
|
31
|
+
displayName: "OpenAI",
|
|
32
|
+
models: [
|
|
33
|
+
{ id: "gpt-5.4", displayName: "GPT-5.4" },
|
|
34
|
+
{ id: "gpt-5.2", displayName: "GPT-5.2" },
|
|
35
|
+
{ id: "gpt-5.4-mini", displayName: "GPT-5.4 Mini" },
|
|
36
|
+
{ id: "gpt-5.4-nano", displayName: "GPT-5.4 Nano" },
|
|
37
|
+
],
|
|
38
|
+
defaultModel: "gpt-5.4",
|
|
39
|
+
apiKeyUrl: "https://platform.openai.com/api-keys",
|
|
40
|
+
apiKeyPlaceholder: "sk-proj-...",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "gemini",
|
|
44
|
+
displayName: "Google Gemini",
|
|
45
|
+
models: [
|
|
46
|
+
{ id: "gemini-3-flash", displayName: "Gemini 3 Flash" },
|
|
47
|
+
{ id: "gemini-3-pro", displayName: "Gemini 3 Pro" },
|
|
48
|
+
],
|
|
49
|
+
defaultModel: "gemini-3-flash",
|
|
50
|
+
apiKeyUrl: "https://aistudio.google.com/apikey",
|
|
51
|
+
apiKeyPlaceholder: "AIza...",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "ollama",
|
|
55
|
+
displayName: "Ollama",
|
|
56
|
+
models: [
|
|
57
|
+
{ id: "llama3.2", displayName: "Llama 3.2" },
|
|
58
|
+
{ id: "mistral", displayName: "Mistral" },
|
|
59
|
+
],
|
|
60
|
+
defaultModel: "llama3.2",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "fireworks",
|
|
64
|
+
displayName: "Fireworks",
|
|
65
|
+
models: [
|
|
66
|
+
{
|
|
67
|
+
id: "accounts/fireworks/models/kimi-k2p5",
|
|
68
|
+
displayName: "Kimi K2.5",
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
defaultModel: "accounts/fireworks/models/kimi-k2p5",
|
|
72
|
+
apiKeyUrl: "https://fireworks.ai/account/api-keys",
|
|
73
|
+
apiKeyPlaceholder: "fw_...",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "openrouter",
|
|
77
|
+
displayName: "OpenRouter",
|
|
78
|
+
models: [
|
|
79
|
+
{ id: "x-ai/grok-4", displayName: "Grok 4" },
|
|
80
|
+
{ id: "x-ai/grok-4.20-beta", displayName: "Grok 4.20 Beta" },
|
|
81
|
+
],
|
|
82
|
+
defaultModel: "x-ai/grok-4",
|
|
83
|
+
apiKeyUrl: "https://openrouter.ai/keys",
|
|
84
|
+
apiKeyPlaceholder: "sk-or-v1-...",
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
/** Check if a model ID is in the catalog for a given provider. */
|
|
89
|
+
export function isModelInCatalog(provider: string, modelId: string): boolean {
|
|
90
|
+
const entry = PROVIDER_CATALOG.find((p) => p.id === provider);
|
|
91
|
+
return entry?.models.some((m) => m.id === modelId) ?? false;
|
|
92
|
+
}
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
+
import { isModelInCatalog, PROVIDER_CATALOG } from "./model-catalog.js";
|
|
1
2
|
import type { ModelIntent } from "./types.js";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type KnownProviderName = keyof typeof PROVIDER_DEFAULT_MODELS;
|
|
4
|
+
/**
|
|
5
|
+
* Derived from PROVIDER_CATALOG — single source of truth for default models.
|
|
6
|
+
* Each provider's `defaultModel` in the catalog populates this map.
|
|
7
|
+
*/
|
|
8
|
+
export const PROVIDER_DEFAULT_MODELS: Record<string, string> =
|
|
9
|
+
Object.fromEntries(
|
|
10
|
+
PROVIDER_CATALOG.map((entry) => [entry.id, entry.defaultModel]),
|
|
11
|
+
);
|
|
13
12
|
|
|
14
|
-
const PROVIDER_MODEL_INTENTS: Record<
|
|
15
|
-
KnownProviderName,
|
|
16
|
-
Record<ModelIntent, string>
|
|
17
|
-
> = {
|
|
13
|
+
const PROVIDER_MODEL_INTENTS: Record<string, Record<ModelIntent, string>> = {
|
|
18
14
|
anthropic: {
|
|
19
15
|
"latency-optimized": "claude-haiku-4-5-20251001",
|
|
20
16
|
"quality-optimized": "claude-opus-4-6",
|
|
@@ -47,29 +43,42 @@ const PROVIDER_MODEL_INTENTS: Record<
|
|
|
47
43
|
},
|
|
48
44
|
};
|
|
49
45
|
|
|
46
|
+
const FALLBACK_DEFAULT_MODEL = "claude-opus-4-6";
|
|
47
|
+
|
|
50
48
|
const MODEL_INTENTS = new Set<ModelIntent>([
|
|
51
49
|
"latency-optimized",
|
|
52
50
|
"quality-optimized",
|
|
53
51
|
"vision-optimized",
|
|
54
52
|
]);
|
|
55
53
|
|
|
54
|
+
// ── Consistency validation ───────────────────────────────────────────
|
|
55
|
+
// Eagerly verify that every model ID referenced by PROVIDER_MODEL_INTENTS
|
|
56
|
+
// exists in PROVIDER_CATALOG, catching drift at module-load time rather
|
|
57
|
+
// than at runtime when a user picks a model.
|
|
58
|
+
for (const [provider, intents] of Object.entries(PROVIDER_MODEL_INTENTS)) {
|
|
59
|
+
for (const [intent, modelId] of Object.entries(intents)) {
|
|
60
|
+
if (!isModelInCatalog(provider, modelId)) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`PROVIDER_MODEL_INTENTS[${provider}][${intent}] references model "${modelId}" ` +
|
|
63
|
+
`which is not in PROVIDER_CATALOG. Update model-catalog.ts or model-intents.ts.`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
56
69
|
export function isModelIntent(value: unknown): value is ModelIntent {
|
|
57
70
|
return typeof value === "string" && MODEL_INTENTS.has(value as ModelIntent);
|
|
58
71
|
}
|
|
59
72
|
|
|
60
73
|
export function getProviderDefaultModel(providerName: string): string {
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
PROVIDER_DEFAULT_MODELS[knownProvider] ?? PROVIDER_DEFAULT_MODELS.anthropic
|
|
64
|
-
);
|
|
74
|
+
return PROVIDER_DEFAULT_MODELS[providerName] ?? FALLBACK_DEFAULT_MODEL;
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
export function resolveModelIntent(
|
|
68
78
|
providerName: string,
|
|
69
79
|
intent: ModelIntent,
|
|
70
80
|
): string {
|
|
71
|
-
const
|
|
72
|
-
const providerIntentModels = PROVIDER_MODEL_INTENTS[knownProvider];
|
|
81
|
+
const providerIntentModels = PROVIDER_MODEL_INTENTS[providerName];
|
|
73
82
|
if (providerIntentModels) {
|
|
74
83
|
return providerIntentModels[intent];
|
|
75
84
|
}
|