@vellumai/assistant 0.5.1 → 0.5.3
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 +163 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/docs/skills.md +100 -0
- 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 +156 -5
- package/src/__tests__/conversation-agent-loop.test.ts +297 -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-memory-dirty-tail.test.ts +150 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
- 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-wipe.test.ts +226 -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__/db-memory-archive-migration.test.ts +372 -0
- package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -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__/inline-command-runner.test.ts +311 -0
- package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
- package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
- package/src/__tests__/list-messages-attachments.test.ts +96 -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-brief-open-loops.test.ts +530 -0
- package/src/__tests__/memory-brief-time.test.ts +285 -0
- package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
- package/src/__tests__/memory-chunk-archive.test.ts +400 -0
- package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
- package/src/__tests__/memory-episode-archive.test.ts +370 -0
- package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-observation-archive.test.ts +375 -0
- package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
- package/src/__tests__/memory-recall-quality.test.ts +7 -7
- package/src/__tests__/memory-reducer-store.test.ts +728 -0
- package/src/__tests__/memory-reducer-types.test.ts +699 -0
- package/src/__tests__/memory-reducer.test.ts +698 -0
- package/src/__tests__/memory-regressions.test.ts +6 -4
- package/src/__tests__/memory-simplified-config.test.ts +281 -0
- 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__/parse-identity-fields.test.ts +129 -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__/skill-load-inline-command.test.ts +598 -0
- package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
- package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
- package/src/__tests__/skills-transitive-hash.test.ts +333 -0
- 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__/vellum-self-knowledge-inline-command.test.ts +320 -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/SKILL.md +1 -1
- 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 +24 -0
- package/src/config/loader.ts +65 -0
- package/src/config/raw-config-utils.ts +58 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +20 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-simplified.ts +101 -0
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/services.ts +8 -6
- package/src/config/skills.ts +50 -4
- 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 +54 -8
- package/src/daemon/conversation-agent-loop.ts +127 -20
- 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 +50 -16
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +22 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +170 -24
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +69 -30
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +156 -0
- package/src/daemon/handlers/config-model.ts +62 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/identity.ts +12 -1
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +115 -65
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +18 -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/followups/followup-store.ts +47 -1
- 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/archive-store.ts +400 -0
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/brief-formatting.ts +33 -0
- package/src/memory/brief-open-loops.ts +266 -0
- package/src/memory/brief-time.ts +161 -0
- package/src/memory/brief.ts +75 -0
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +591 -8
- 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 +40 -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 +114 -21
- 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 +2 -4
- package/src/memory/job-handlers/embedding.ts +83 -0
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +6 -0
- package/src/memory/jobs-worker.ts +12 -0
- 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/185-memory-brief-state.ts +52 -0
- package/src/memory/migrations/186-memory-archive.ts +109 -0
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/qdrant-client.ts +23 -4
- package/src/memory/reducer-store.ts +271 -0
- package/src/memory/reducer-types.ts +99 -0
- package/src/memory/reducer.ts +453 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +9 -0
- package/src/memory/schema/index.ts +2 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/memory-archive.ts +121 -0
- package/src/memory/schema/memory-brief.ts +55 -0
- package/src/memory/schema/oauth.ts +6 -0
- package/src/memory/search/semantic.ts +17 -4
- 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 +99 -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 +160 -14
- package/src/permissions/defaults.ts +14 -0
- 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 +74 -0
- package/src/runtime/routes/conversation-query-routes.ts +187 -10
- package/src/runtime/routes/conversation-routes.ts +269 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/identity-routes.ts +2 -35
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1212 -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 +94 -5
- 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 +30 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/surface-action-routes.ts +68 -1
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +30 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/skills/inline-command-expansions.ts +204 -0
- package/src/skills/inline-command-render.ts +127 -0
- package/src/skills/inline-command-runner.ts +242 -0
- package/src/skills/transitive-version-hash.ts +88 -0
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/tasks/task-store.ts +43 -1
- 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/permission-checker.ts +8 -1
- 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/skills/load.ts +140 -6
- 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 +24 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- 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/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
- 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 +11 -1
- 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
|
@@ -2,12 +2,15 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
|
|
5
|
+
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
5
6
|
import { getConfig } from "../config/loader.js";
|
|
6
|
-
import { resolveSkillSelector } from "../config/skills.js";
|
|
7
|
+
import { loadSkillCatalog, resolveSkillSelector } from "../config/skills.js";
|
|
8
|
+
import { indexCatalogById } from "../skills/include-graph.js";
|
|
7
9
|
import {
|
|
8
10
|
isSkillSourcePath,
|
|
9
11
|
normalizeFilePath,
|
|
10
12
|
} from "../skills/path-classifier.js";
|
|
13
|
+
import { computeTransitiveSkillVersionHash } from "../skills/transitive-version-hash.js";
|
|
11
14
|
import { computeSkillVersionHash } from "../skills/version-hash.js";
|
|
12
15
|
import type { ManifestOverride } from "../tools/execution-target.js";
|
|
13
16
|
import {
|
|
@@ -144,6 +147,7 @@ const LOW_RISK_PROGRAMS = new Set([
|
|
|
144
147
|
"du",
|
|
145
148
|
"df",
|
|
146
149
|
"assistant",
|
|
150
|
+
"vellum",
|
|
147
151
|
]);
|
|
148
152
|
|
|
149
153
|
// High-risk shell programs / patterns
|
|
@@ -197,6 +201,32 @@ const LOW_RISK_GIT_SUBCOMMANDS = new Set([
|
|
|
197
201
|
"reflog",
|
|
198
202
|
]);
|
|
199
203
|
|
|
204
|
+
// Mutating assistant/vellum CLI subcommands that should be escalated to Medium
|
|
205
|
+
// risk. Most assistant/vellum subcommands are read-only and stay Low risk.
|
|
206
|
+
// This mirrors the git subcommand pattern — only known mutating operations
|
|
207
|
+
// get escalated.
|
|
208
|
+
const MEDIUM_RISK_CLI_SUBCOMMANDS = new Set([
|
|
209
|
+
"credentials",
|
|
210
|
+
"config",
|
|
211
|
+
"bash",
|
|
212
|
+
"trust",
|
|
213
|
+
"autonomy",
|
|
214
|
+
"contacts",
|
|
215
|
+
"mcp",
|
|
216
|
+
"keys",
|
|
217
|
+
"wake",
|
|
218
|
+
"sleep",
|
|
219
|
+
"hatch",
|
|
220
|
+
"retire",
|
|
221
|
+
"clean",
|
|
222
|
+
"setup",
|
|
223
|
+
"upgrade",
|
|
224
|
+
"recover",
|
|
225
|
+
"login",
|
|
226
|
+
"use",
|
|
227
|
+
"pair",
|
|
228
|
+
]);
|
|
229
|
+
|
|
200
230
|
// Commands that wrap another program — the real program appears as the first
|
|
201
231
|
// non-flag argument. When one of these is the segment program we look through
|
|
202
232
|
// its args to find the effective program (e.g. `env curl …` → curl).
|
|
@@ -219,15 +249,40 @@ const WRAPPER_PROGRAMS = new Set([
|
|
|
219
249
|
// value of -u) as the wrapped program instead of `echo`.
|
|
220
250
|
const ENV_VALUE_FLAGS = new Set(["-u", "--unset", "-C", "--chdir"]);
|
|
221
251
|
|
|
252
|
+
// `git` global flags that consume the next positional argument as their value.
|
|
253
|
+
// Without this, `git -C status commit` would incorrectly identify `status`
|
|
254
|
+
// (the directory path) as the subcommand instead of `commit`.
|
|
255
|
+
const GIT_VALUE_FLAGS = new Set([
|
|
256
|
+
"-C",
|
|
257
|
+
"-c",
|
|
258
|
+
"--git-dir",
|
|
259
|
+
"--work-tree",
|
|
260
|
+
"--namespace",
|
|
261
|
+
"--super-prefix",
|
|
262
|
+
"--config-env",
|
|
263
|
+
]);
|
|
264
|
+
|
|
222
265
|
/**
|
|
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
|
|
266
|
+
* Return the first non-flag argument from an argument list, optionally
|
|
267
|
+
* skipping value-taking flags. Flags are arguments that start with `-`.
|
|
268
|
+
* This is used to skip global options (e.g. `--verbose`, `-h`, `-C <path>`)
|
|
269
|
+
* when extracting the subcommand from CLIs like `git`, `vellum`, and
|
|
270
|
+
* `assistant`.
|
|
271
|
+
*
|
|
272
|
+
* When `valueFlags` is provided, any flag in that set causes the next
|
|
273
|
+
* argument to be skipped as well (it is the flag's value, not a positional).
|
|
227
274
|
*/
|
|
228
|
-
function firstPositionalArg(
|
|
229
|
-
|
|
230
|
-
|
|
275
|
+
function firstPositionalArg(
|
|
276
|
+
args: string[],
|
|
277
|
+
valueFlags?: Set<string>,
|
|
278
|
+
): string | undefined {
|
|
279
|
+
for (let i = 0; i < args.length; i++) {
|
|
280
|
+
const arg = args[i];
|
|
281
|
+
if (arg.startsWith("-")) {
|
|
282
|
+
if (valueFlags?.has(arg)) i++; // skip the next arg (the flag's value)
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
return arg;
|
|
231
286
|
}
|
|
232
287
|
return undefined;
|
|
233
288
|
}
|
|
@@ -300,6 +355,34 @@ function resolveSkillIdAndHash(
|
|
|
300
355
|
}
|
|
301
356
|
}
|
|
302
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Check whether a skill (by id) has parsed inline command expansions.
|
|
360
|
+
* Returns false when the skill is not found in the catalog.
|
|
361
|
+
*/
|
|
362
|
+
function hasInlineExpansions(skillId: string): boolean {
|
|
363
|
+
const catalog = loadSkillCatalog();
|
|
364
|
+
const skill = catalog.find((s) => s.id === skillId);
|
|
365
|
+
return (
|
|
366
|
+
skill?.inlineCommandExpansions != null &&
|
|
367
|
+
skill.inlineCommandExpansions.length > 0
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Compute the transitive version hash for a skill, returning `undefined`
|
|
373
|
+
* when computation fails (missing includes, cycle, etc.). The permission
|
|
374
|
+
* layer falls back to the any-version candidate in that case.
|
|
375
|
+
*/
|
|
376
|
+
function computeTransitiveHashSafe(skillId: string): string | undefined {
|
|
377
|
+
try {
|
|
378
|
+
const catalog = loadSkillCatalog();
|
|
379
|
+
const index = indexCatalogById(catalog);
|
|
380
|
+
return computeTransitiveSkillVersionHash(skillId, index);
|
|
381
|
+
} catch {
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
303
386
|
function canonicalizeWebFetchUrl(parsed: URL): URL {
|
|
304
387
|
parsed.hash = "";
|
|
305
388
|
parsed.username = "";
|
|
@@ -381,13 +464,39 @@ async function buildCommandCandidates(
|
|
|
381
464
|
targets.push("");
|
|
382
465
|
} else {
|
|
383
466
|
const resolved = resolveSkillIdAndHash(rawSelector);
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
467
|
+
|
|
468
|
+
// When the resolved skill contains inline command expansions and the
|
|
469
|
+
// feature flag is on, emit skill_load_dynamic: candidates so the
|
|
470
|
+
// higher-priority default ask rule catches them instead of falling
|
|
471
|
+
// through to the permissive skill_load:* allow rule.
|
|
472
|
+
const config = getConfig();
|
|
473
|
+
const inlineEnabled = isAssistantFeatureFlagEnabled(
|
|
474
|
+
"feature_flags.inline-skill-commands.enabled",
|
|
475
|
+
config,
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
if (resolved && inlineEnabled && hasInlineExpansions(resolved.id)) {
|
|
479
|
+
const transitiveHash = computeTransitiveHashSafe(resolved.id);
|
|
480
|
+
if (transitiveHash) {
|
|
481
|
+
targets.push(`skill_load_dynamic:${resolved.id}@${transitiveHash}`);
|
|
482
|
+
}
|
|
483
|
+
targets.push(`skill_load_dynamic:${resolved.id}`);
|
|
484
|
+
// Don't fall through to skill_load:* — dynamic skills use their own
|
|
485
|
+
// candidate namespace so the default ask rule applies.
|
|
486
|
+
} else {
|
|
487
|
+
if (resolved && resolved.versionHash) {
|
|
488
|
+
// Version-specific candidate lets rules pin to an exact skill version
|
|
489
|
+
targets.push(`${resolved.id}@${resolved.versionHash}`);
|
|
490
|
+
}
|
|
491
|
+
targets.push(rawSelector);
|
|
387
492
|
}
|
|
388
|
-
targets.push(rawSelector);
|
|
389
493
|
}
|
|
390
|
-
|
|
494
|
+
|
|
495
|
+
// Dynamic candidates use skill_load_dynamic: prefix; normal ones use skill_load:
|
|
496
|
+
return [...new Set(targets)].map((target) => {
|
|
497
|
+
if (target.startsWith("skill_load_dynamic:")) return target;
|
|
498
|
+
return `${toolName}:${target}`;
|
|
499
|
+
});
|
|
391
500
|
}
|
|
392
501
|
|
|
393
502
|
if (
|
|
@@ -652,7 +761,7 @@ async function classifyRiskUncached(
|
|
|
652
761
|
}
|
|
653
762
|
|
|
654
763
|
if (prog === "git") {
|
|
655
|
-
const subcommand = firstPositionalArg(seg.args);
|
|
764
|
+
const subcommand = firstPositionalArg(seg.args, GIT_VALUE_FLAGS);
|
|
656
765
|
if (subcommand && LOW_RISK_GIT_SUBCOMMANDS.has(subcommand)) {
|
|
657
766
|
// Stay at current risk
|
|
658
767
|
continue;
|
|
@@ -662,6 +771,17 @@ async function classifyRiskUncached(
|
|
|
662
771
|
continue;
|
|
663
772
|
}
|
|
664
773
|
|
|
774
|
+
if (prog === "vellum" || prog === "assistant") {
|
|
775
|
+
const subcommand = firstPositionalArg(seg.args);
|
|
776
|
+
if (subcommand && MEDIUM_RISK_CLI_SUBCOMMANDS.has(subcommand)) {
|
|
777
|
+
// Known mutating subcommands are medium
|
|
778
|
+
maxRisk = RiskLevel.Medium;
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
// Read-only / unknown subcommands stay at current risk
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
|
|
665
785
|
if (!LOW_RISK_PROGRAMS.has(prog)) {
|
|
666
786
|
// Unknown program → medium
|
|
667
787
|
if (maxRisk === RiskLevel.Low) {
|
|
@@ -1021,6 +1141,32 @@ function skillLoadAllowlistStrategy(
|
|
|
1021
1141
|
|
|
1022
1142
|
if (rawSelector) {
|
|
1023
1143
|
const resolved = resolveSkillIdAndHash(rawSelector);
|
|
1144
|
+
|
|
1145
|
+
// Check whether this is a dynamic (inline-command) skill load
|
|
1146
|
+
const config = getConfig();
|
|
1147
|
+
const inlineEnabled = isAssistantFeatureFlagEnabled(
|
|
1148
|
+
"feature_flags.inline-skill-commands.enabled",
|
|
1149
|
+
config,
|
|
1150
|
+
);
|
|
1151
|
+
|
|
1152
|
+
if (resolved && inlineEnabled && hasInlineExpansions(resolved.id)) {
|
|
1153
|
+
const transitiveHash = computeTransitiveHashSafe(resolved.id);
|
|
1154
|
+
const options: AllowlistOption[] = [];
|
|
1155
|
+
if (transitiveHash) {
|
|
1156
|
+
options.push({
|
|
1157
|
+
label: `${resolved.id}@${transitiveHash}`,
|
|
1158
|
+
description: "This exact version (pinned)",
|
|
1159
|
+
pattern: `skill_load_dynamic:${resolved.id}@${transitiveHash}`,
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
options.push({
|
|
1163
|
+
label: resolved.id,
|
|
1164
|
+
description: "This skill (any version)",
|
|
1165
|
+
pattern: `skill_load_dynamic:${resolved.id}`,
|
|
1166
|
+
});
|
|
1167
|
+
return options;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1024
1170
|
if (resolved && resolved.versionHash) {
|
|
1025
1171
|
return [
|
|
1026
1172
|
{
|
|
@@ -198,6 +198,19 @@ export function getDefaultRuleTemplates(): DefaultRuleTemplate[] {
|
|
|
198
198
|
})),
|
|
199
199
|
);
|
|
200
200
|
|
|
201
|
+
// Inline-command skill loads use a distinct candidate namespace
|
|
202
|
+
// (skill_load_dynamic:*) so they prompt by default instead of falling
|
|
203
|
+
// through to the permissive skill_load:* allow rule below. The higher
|
|
204
|
+
// priority ensures this rule wins when both could match.
|
|
205
|
+
const skillLoadDynamicRule: DefaultRuleTemplate = {
|
|
206
|
+
id: "default:ask-skill_load_dynamic-global",
|
|
207
|
+
tool: "skill_load",
|
|
208
|
+
pattern: "skill_load_dynamic:*",
|
|
209
|
+
scope: "everywhere",
|
|
210
|
+
decision: "ask",
|
|
211
|
+
priority: 200,
|
|
212
|
+
};
|
|
213
|
+
|
|
201
214
|
const skillLoadRule: DefaultRuleTemplate = {
|
|
202
215
|
id: "default:allow-skill_load-global",
|
|
203
216
|
tool: "skill_load",
|
|
@@ -294,6 +307,7 @@ export function getDefaultRuleTemplates(): DefaultRuleTemplate[] {
|
|
|
294
307
|
bootstrapDeleteRule,
|
|
295
308
|
updatesDeleteRule,
|
|
296
309
|
...skillSourceMutationRules,
|
|
310
|
+
skillLoadDynamicRule,
|
|
297
311
|
skillLoadRule,
|
|
298
312
|
skillExecuteRule,
|
|
299
313
|
browserNavigateRule,
|
|
@@ -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
|
}
|
|
@@ -2,6 +2,7 @@ import OpenAI from "openai";
|
|
|
2
2
|
|
|
3
3
|
import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "../../prompts/cache-boundary.js";
|
|
4
4
|
import { ProviderError } from "../../util/errors.js";
|
|
5
|
+
import { getLogger } from "../../util/logger.js";
|
|
5
6
|
import { extractRetryAfterMs } from "../../util/retry.js";
|
|
6
7
|
import { escapeXmlAttr } from "../../util/xml.js";
|
|
7
8
|
import { createStreamTimeout } from "../stream-timeout.js";
|
|
@@ -14,6 +15,54 @@ import type {
|
|
|
14
15
|
ToolDefinition,
|
|
15
16
|
} from "../types.js";
|
|
16
17
|
|
|
18
|
+
const log = getLogger("openai-client");
|
|
19
|
+
|
|
20
|
+
/** Validation-specific timeout (10s) so a stalled network doesn't block key submission. */
|
|
21
|
+
const VALIDATION_TIMEOUT_MS = 10_000;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validate an OpenAI API key by making a lightweight GET /v1/models call.
|
|
25
|
+
* Returns `{ valid: true }` on success or `{ valid: false, reason: string }` on failure.
|
|
26
|
+
*/
|
|
27
|
+
export async function validateOpenAIApiKey(
|
|
28
|
+
apiKey: string,
|
|
29
|
+
): Promise<{ valid: true } | { valid: false; reason: string }> {
|
|
30
|
+
try {
|
|
31
|
+
const client = new OpenAI({
|
|
32
|
+
apiKey,
|
|
33
|
+
timeout: VALIDATION_TIMEOUT_MS,
|
|
34
|
+
maxRetries: 0,
|
|
35
|
+
});
|
|
36
|
+
await client.models.list();
|
|
37
|
+
return { valid: true };
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error instanceof OpenAI.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: `OpenAI 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
|
+
"OpenAI 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 OpenAI key validation — allowing key storage",
|
|
61
|
+
);
|
|
62
|
+
return { valid: true };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
17
66
|
export interface OpenAICompatibleProviderOptions {
|
|
18
67
|
baseURL?: string;
|
|
19
68
|
providerName?: string;
|
package/src/providers/types.ts
CHANGED
|
@@ -93,6 +93,8 @@ export type ModelIntent =
|
|
|
93
93
|
export interface ProviderResponse {
|
|
94
94
|
content: ContentBlock[];
|
|
95
95
|
model: string;
|
|
96
|
+
/** Provider that actually produced this response, which may differ from a wrapper provider name. */
|
|
97
|
+
actualProvider?: string;
|
|
96
98
|
usage: {
|
|
97
99
|
/** Total input tokens (input_tokens + cache_creation + cache_read). */
|
|
98
100
|
inputTokens: number;
|