@vellumai/assistant 0.11.3 → 0.11.4-staging.1
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 +11 -6
- package/docs/architecture/memory.md +11 -0
- package/docs/architecture/turn-actor.md +70 -0
- package/docs/flux-turn-detection-spike.md +243 -0
- package/docs/stt-provider-onboarding.md +3 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/openapi.yaml +140 -38
- package/package.json +1 -1
- package/scripts/voice-ttft-spike.ts +3 -3
- package/src/__tests__/app-compiler.test.ts +38 -3
- package/src/__tests__/attachments-store.test.ts +21 -12
- package/src/__tests__/byok-default-profile-ensure.test.ts +2 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
- package/src/__tests__/call-site-routing-provider.test.ts +1 -1
- package/src/__tests__/channel-availability-routes.test.ts +14 -1
- package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
- package/src/__tests__/channel-delivery-store.test.ts +14 -14
- package/src/__tests__/config-loader-backfill.test.ts +3 -3
- package/src/__tests__/config-schema.test.ts +25 -10
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop.test.ts +28 -20
- package/src/__tests__/conversation-attention-store.test.ts +63 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
- package/src/__tests__/conversation-fork-crud.test.ts +69 -0
- package/src/__tests__/conversation-fork-referential.test.ts +67 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
- package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
- package/src/__tests__/conversation-queue.test.ts +55 -4
- package/src/__tests__/conversation-runtime-assembly.test.ts +134 -102
- package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
- package/src/__tests__/credential-prompt-route.test.ts +7 -10
- package/src/__tests__/custom-profile-ensure.test.ts +5 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
- package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
- package/src/__tests__/document-append-idempotency.test.ts +233 -0
- package/src/__tests__/edit-propagation.test.ts +0 -7
- package/src/__tests__/helpers/mock-actor-context.ts +49 -0
- package/src/__tests__/helpers/mock-conversation.ts +13 -1
- package/src/__tests__/injector-chain.test.ts +63 -41
- package/src/__tests__/injector-disk-pressure.test.ts +11 -23
- package/src/__tests__/llm-context-resolution.test.ts +73 -1
- package/src/__tests__/llm-schema.test.ts +5 -2
- package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +6 -5
- package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
- package/src/__tests__/mtime-cache.test.ts +1 -1
- package/src/__tests__/non-member-access-request.test.ts +0 -20
- package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -0
- package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +434 -105
- package/src/__tests__/scoped-approval-grants.test.ts +11 -6
- package/src/__tests__/secret-ingress-channel.test.ts +0 -1
- package/src/__tests__/skills.test.ts +32 -0
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
- package/src/__tests__/subagent-spawn-and-await.test.ts +14 -10
- package/src/__tests__/turn-events-store.test.ts +43 -0
- package/src/__tests__/user-plugin-loader.test.ts +1 -1
- package/src/__tests__/visible-app-context.test.ts +16 -9
- package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
- package/src/__tests__/worker-plugin-surface.test.ts +77 -0
- package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
- package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
- package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
- package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
- package/src/acp/__tests__/auth-required.test.ts +161 -0
- package/src/acp/acp-claude-oauth.ts +19 -2
- package/src/acp/agent-process.test.ts +100 -0
- package/src/acp/agent-process.ts +29 -26
- package/src/acp/auth-required.ts +102 -0
- package/src/acp/session-manager.test.ts +119 -0
- package/src/acp/session-manager.ts +68 -2
- package/src/api/events/acp-auth-required.ts +55 -0
- package/src/api/index.ts +7 -0
- package/src/apps/app-store.ts +3 -0
- package/src/bundler/package-resolver.ts +2 -30
- package/src/calls/__tests__/voice-session-bridge.test.ts +173 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +94 -0
- package/src/calls/call-controller.ts +9 -2
- package/src/calls/call-setup-flow.ts +0 -1
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/calls/voice-session-bridge.ts +71 -16
- package/src/calls/voice-triage-escalate.ts +104 -2
- package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
- package/src/channels/config.ts +13 -0
- package/src/channels/plugin-channel-declarations.ts +108 -0
- package/src/channels/types.ts +30 -0
- package/src/cli/AGENTS.md +5 -2
- package/src/cli/commands/credentials.help.ts +2 -2
- package/src/cli/commands/inference-providers.ts +1 -1
- package/src/cli/commands/mcp.help.ts +13 -4
- package/src/cli/commands/mcp.ts +9 -0
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
- package/src/cli/commands/memory/index.help.ts +43 -1
- package/src/cli/commands/memory/memory-v3.ts +64 -0
- package/src/cli/commands/stt.help.ts +27 -2
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/upgrade-plugin.ts +42 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
- package/src/config/__tests__/default-provider.test.ts +6 -1
- package/src/config/__tests__/profile-materialization.test.ts +75 -19
- package/src/config/bundled-skills/acp/SKILL.md +6 -7
- package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
- package/src/config/call-site-defaults.ts +4 -5
- package/src/config/default-profile-catalog.ts +82 -11
- package/src/config/default-profile-names.ts +4 -1
- package/src/config/default-provider-resolution.ts +4 -0
- package/src/config/llm-context-resolution.ts +11 -3
- package/src/config/llm-resolver.ts +28 -1
- package/src/config/profile-materialization.ts +70 -22
- package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
- package/src/config/schemas/call-site-catalog.ts +4 -4
- package/src/config/schemas/live-voice.ts +57 -23
- package/src/config/schemas/llm.ts +59 -32
- package/src/config/schemas/mcp.ts +23 -0
- package/src/config/schemas/plugin-updates.ts +6 -2
- package/src/config/schemas/stt.ts +1 -0
- package/src/context/outbound-sanitize.ts +96 -1
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
- package/src/daemon/conversation-agent-loop.ts +17 -6
- package/src/daemon/conversation-messaging.ts +5 -1
- package/src/daemon/conversation-notifiers.ts +9 -1
- package/src/daemon/conversation-process.ts +9 -6
- package/src/daemon/conversation-runtime-assembly.ts +3 -4
- package/src/daemon/conversation-tool-setup.ts +1 -2
- package/src/daemon/conversation.ts +48 -0
- package/src/daemon/mcp-reload-service.ts +36 -6
- package/src/daemon/process-message.ts +13 -3
- package/src/daemon/providers-setup.ts +6 -3
- package/src/daemon/trust-context-types.ts +29 -0
- package/src/daemon/wake-conversation-ops.ts +3 -2
- package/src/documents/document-store.ts +138 -5
- package/src/hooks/hook-loader.ts +3 -3
- package/src/hooks/registry.ts +50 -6
- package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
- package/src/inbound/oauth-callback-url.ts +61 -0
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
- package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +932 -0
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
- package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
- package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
- package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
- package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
- package/src/live-voice/live-voice-archive.ts +2 -0
- package/src/live-voice/live-voice-metrics.ts +57 -32
- package/src/live-voice/live-voice-photo.ts +1 -2
- package/src/live-voice/live-voice-session.ts +535 -314
- package/src/live-voice/progress-narration.ts +277 -0
- package/src/live-voice/protocol.ts +21 -1
- package/src/mcp/__tests__/effective-config.test.ts +238 -0
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
- package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
- package/src/mcp/client.ts +16 -11
- package/src/mcp/effective-config.ts +113 -0
- package/src/mcp/manager.ts +11 -6
- package/src/mcp/mcp-auth-orchestrator.ts +13 -22
- package/src/mcp/mcp-oauth-provider.ts +205 -240
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
- package/src/monitoring/plugin-auto-update.ts +128 -24
- package/src/notifications/signal.ts +1 -0
- package/src/permissions/confirmation-guardian-request.test.ts +15 -11
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/question-guardian-request.test.ts +14 -6
- package/src/permissions/question-guardian-request.ts +1 -2
- package/src/persistence/attachments-store.ts +8 -1
- package/src/persistence/bookmark-crud.ts +3 -7
- package/src/persistence/conversation-attention-store.ts +16 -45
- package/src/persistence/conversation-crud.ts +33 -4
- package/src/persistence/conversation-lineage.ts +9 -0
- package/src/persistence/conversation-queries.ts +108 -41
- package/src/persistence/delivery-crud.ts +38 -29
- package/src/persistence/external-conversation-store.ts +32 -4
- package/src/persistence/llm-request-log-store.ts +4 -10
- package/src/persistence/llm-usage-store.ts +8 -3
- package/src/persistence/message-reads.test.ts +197 -0
- package/src/persistence/message-reads.ts +211 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
- package/src/persistence/real-user-turn-filter.ts +27 -3
- package/src/persistence/steps.ts +9 -0
- package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
- package/src/plugin-api/conversation-turn.ts +168 -5
- package/src/plugin-api/index.ts +21 -5
- package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
- package/src/plugins/defaults/memory/AGENTS.md +4 -0
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
- package/src/plugins/defaults/memory/buffer-format.ts +165 -0
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
- package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +8 -12
- package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
- package/src/plugins/defaults/memory/indexer.ts +3 -1
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
- package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
- package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +33 -0
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +199 -2
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +25 -18
- package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
- package/src/plugins/defaults/memory/substrate/static-context.ts +160 -4
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
- package/src/plugins/defaults/memory/v3/prune.ts +2 -0
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
- package/src/plugins/defaults/memory/worker.ts +6 -3
- package/src/plugins/external-plugin-loader.ts +47 -0
- package/src/plugins/mcp-servers.ts +361 -0
- package/src/plugins/mtime-cache.ts +23 -49
- package/src/plugins/worker-plugin-surface.ts +33 -0
- package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
- package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
- package/src/providers/__tests__/retry-callsite.test.ts +5 -2
- package/src/providers/call-site-routing.ts +30 -3
- package/src/providers/connection-resolution.ts +194 -11
- package/src/providers/inference/auth.ts +6 -0
- package/src/providers/inference/connection-availability.ts +24 -2
- package/src/providers/inference/connections.ts +2 -0
- package/src/providers/model-intents.ts +26 -6
- package/src/providers/openai/codex-models.ts +2 -1
- package/src/providers/provider-send-message.ts +32 -3
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +620 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +719 -0
- package/src/providers/speech-to-text/provider-catalog.ts +99 -8
- package/src/providers/speech-to-text/resolve.ts +25 -2
- package/src/routes/worker.ts +17 -5
- package/src/runtime/access-request-helper.ts +9 -12
- package/src/runtime/agent-wake.ts +3 -3
- package/src/runtime/pre-first-message-gate.ts +4 -0
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +219 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +62 -1
- package/src/runtime/routes/channel-availability-routes.ts +32 -14
- package/src/runtime/routes/channel-route-shared.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
- package/src/runtime/routes/conversation-list-routes.ts +112 -1
- package/src/runtime/routes/conversation-query-routes.ts +40 -27
- package/src/runtime/routes/credential-prompt-routes.ts +4 -7
- package/src/runtime/routes/default-provider-routes.ts +15 -0
- package/src/runtime/routes/inbound-message-handler.ts +17 -41
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
- package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +1 -3
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -4
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +11 -20
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
- package/src/runtime/routes/inference-profiles-routes.ts +20 -11
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/mcp-auth-routes.ts +148 -57
- package/src/runtime/routes/plugins-routes.ts +21 -3
- package/src/runtime/routes/stt-routes.ts +31 -25
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
- package/src/runtime/routes/user-route-dispatcher.ts +39 -14
- package/src/runtime/routes/user-route-import.ts +108 -0
- package/src/schedule/worker.ts +6 -0
- package/src/security/oauth2.ts +6 -22
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
- package/src/stt/__tests__/types.test.ts +94 -0
- package/src/stt/daemon-batch-transcriber.ts +10 -0
- package/src/stt/stt-stream-session.ts +8 -4
- package/src/stt/types.ts +103 -0
- package/src/subagent/manager.ts +1 -3
- package/src/subagent/types.ts +7 -6
- package/src/tools/acp/spawn.test.ts +97 -0
- package/src/tools/acp/spawn.ts +32 -0
- package/src/tools/document/document-tool.ts +12 -3
- package/src/tools/registry.ts +2 -1
- package/src/tools/workflows/run-workflow.ts +1 -2
- package/src/tts/__tests__/reasoning-tag-filter.test.ts +63 -0
- package/src/tts/reasoning-tag-filter.ts +110 -0
- package/src/workspace/byok-default-profile-ensure.ts +61 -18
- package/src/workspace/custom-profile-ensure.ts +4 -24
- package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
- package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
- package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
- package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/provider-commit-message-generator.ts +7 -5
- package/src/live-voice/__tests__/front-decision.test.ts +0 -645
- package/src/live-voice/front-decision.ts +0 -476
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Repair the deprecated `gpt-5.3-codex` model ID on ChatGPT-subscription
|
|
8
|
+
* config fragments.
|
|
9
|
+
*
|
|
10
|
+
* OpenAI deprecated `gpt-5.3-codex` under ChatGPT sign-in (the Codex
|
|
11
|
+
* endpoint rejects it with HTTP 400), so it is being removed from
|
|
12
|
+
* `CODEX_SUBSCRIPTION_MODEL_IDS`. A workspace can still pin it on
|
|
13
|
+
* `provider: "chatgpt"` fragments (migration 133 wrote exactly that shape
|
|
14
|
+
* for `chatgpt-subscription` entries), and once the ID leaves the allowlist,
|
|
15
|
+
* `LLMSchema.superRefine` rejects such a fragment. The loader's leaf
|
|
16
|
+
* recovery then deletes the fragment's `model`, which still fails ("chatgpt"
|
|
17
|
+
* requires an explicit model), and per-section salvage resets the whole
|
|
18
|
+
* `llm` section, discarding the user's other LLM settings.
|
|
19
|
+
*
|
|
20
|
+
* Repair rewrites the model to `gpt-5.6-terra` (OpenAI's recommended
|
|
21
|
+
* everyday Codex model) on an exact `provider: "chatgpt"` + model match in
|
|
22
|
+
* `llm.default`, `llm.callSites.*`, and `llm.profiles.*`. A call-site
|
|
23
|
+
* fragment is also repaired when it pins the model with no provider of its
|
|
24
|
+
* own: at resolve time such a fragment overlays the winning profile, so the
|
|
25
|
+
* stale pin rides whatever provider and connection the winner supplies
|
|
26
|
+
* (including the subscription connection, which routes it to the Codex
|
|
27
|
+
* endpoint). Fragments with any other explicit provider are left untouched:
|
|
28
|
+
* the allowlist only gates the "chatgpt" routing identity, and a provider
|
|
29
|
+
* like `openai-compatible` may legitimately serve this id.
|
|
30
|
+
*/
|
|
31
|
+
export const repairDeprecatedCodexModelIdMigration: WorkspaceMigration = {
|
|
32
|
+
id: "143-repair-deprecated-codex-model-id",
|
|
33
|
+
description:
|
|
34
|
+
'Repair deprecated gpt-5.3-codex model ID on provider "chatgpt" fragments in workspace LLM config',
|
|
35
|
+
run(workspaceDir: string): void {
|
|
36
|
+
const configPath = join(workspaceDir, "config.json");
|
|
37
|
+
if (!existsSync(configPath)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Read outside the parse catch: a transient filesystem error (EIO,
|
|
42
|
+
// EACCES) must reach the runner so the migration retries, while
|
|
43
|
+
// malformed JSON is a permanent state this migration cannot repair.
|
|
44
|
+
const rawText = readFileSync(configPath, "utf-8");
|
|
45
|
+
|
|
46
|
+
let config: Record<string, unknown>;
|
|
47
|
+
try {
|
|
48
|
+
const raw = JSON.parse(rawText);
|
|
49
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
config = raw as Record<string, unknown>;
|
|
53
|
+
} catch {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const llm = readObject(config.llm);
|
|
58
|
+
if (llm === null) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let changed = false;
|
|
63
|
+
|
|
64
|
+
changed = repairFragment(readObject(llm.default)) || changed;
|
|
65
|
+
|
|
66
|
+
const callSites = readObject(llm.callSites);
|
|
67
|
+
if (callSites !== null) {
|
|
68
|
+
for (const rawConfig of Object.values(callSites)) {
|
|
69
|
+
changed =
|
|
70
|
+
repairFragment(readObject(rawConfig), {
|
|
71
|
+
repairMissingProvider: true,
|
|
72
|
+
}) || changed;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const profiles = readObject(llm.profiles);
|
|
77
|
+
if (profiles !== null) {
|
|
78
|
+
for (const rawProfile of Object.values(profiles)) {
|
|
79
|
+
changed = repairFragment(readObject(rawProfile)) || changed;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!changed) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Write-then-rename so an interrupted write cannot leave config.json
|
|
88
|
+
// truncated: a torn in-place write would parse as invalid JSON on the
|
|
89
|
+
// retry, which the catch above treats as "nothing to do", letting the
|
|
90
|
+
// runner checkpoint the migration as completed against a corrupt file.
|
|
91
|
+
const tmpPath = `${configPath}.migration-143.tmp`;
|
|
92
|
+
writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
93
|
+
renameSync(tmpPath, configPath);
|
|
94
|
+
},
|
|
95
|
+
// The exact-match rewrite is idempotent, so a transient failure (full
|
|
96
|
+
// disk, I/O error) is safe to retry on later startups.
|
|
97
|
+
retryFailedCheckpoint: true,
|
|
98
|
+
down(_workspaceDir: string): void {
|
|
99
|
+
// Forward-only: reintroducing the deprecated model ID would fail schema
|
|
100
|
+
// validation once the allowlist no longer carries it.
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Helpers: self-contained per workspace migrations AGENTS.md
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
const DEPRECATED_MODEL_ID = "gpt-5.3-codex";
|
|
109
|
+
const REPLACEMENT_MODEL_ID = "gpt-5.6-terra";
|
|
110
|
+
|
|
111
|
+
function repairFragment(
|
|
112
|
+
fragment: Record<string, unknown> | null,
|
|
113
|
+
options?: { repairMissingProvider?: boolean },
|
|
114
|
+
): boolean {
|
|
115
|
+
if (fragment === null || fragment.model !== DEPRECATED_MODEL_ID) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const providerRepairable =
|
|
119
|
+
fragment.provider === "chatgpt" ||
|
|
120
|
+
(options?.repairMissingProvider === true &&
|
|
121
|
+
fragment.provider === undefined);
|
|
122
|
+
if (!providerRepairable) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
fragment.model = REPLACEMENT_MODEL_ID;
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
130
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
return value as Record<string, unknown>;
|
|
134
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { Database } from "bun:sqlite";
|
|
4
|
+
|
|
5
|
+
import { getLogger } from "../../util/logger.js";
|
|
6
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
7
|
+
|
|
8
|
+
const log = getLogger("migration-144");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convert stranded subscription-only `provider: "openai"` fragments to the
|
|
12
|
+
* `chatgpt` routing identity.
|
|
13
|
+
*
|
|
14
|
+
* The ChatGPT-subscription connection row carries `provider: "chatgpt"`
|
|
15
|
+
* (DB migration 366 stamps existing rows). An UNPINNED profile or call-site
|
|
16
|
+
* fragment with `provider: "openai"` resolves by listing active openai
|
|
17
|
+
* connections at dispatch; in a workspace whose only OpenAI access is the
|
|
18
|
+
* subscription, that listing is now empty and every request on the fragment
|
|
19
|
+
* fails with missing_connection. Workspace migration 133 converted fragments
|
|
20
|
+
* that PINNED the subscription row; this converts the unpinned cohort.
|
|
21
|
+
*
|
|
22
|
+
* Conversion requires proof from the workspace DB that the fragment is
|
|
23
|
+
* genuinely stranded: the canonical subscription row exists (matched by
|
|
24
|
+
* name + oauth_subscription auth, valid for both the pre- and post-366 row
|
|
25
|
+
* shapes) and no `provider: "openai"` row exists (an API-key row means
|
|
26
|
+
* openai fragments still resolve). An absent DB or table converts nothing;
|
|
27
|
+
* an open or query failure on an existing DB propagates so the runner
|
|
28
|
+
* records a failed checkpoint and retries on a later boot.
|
|
29
|
+
*
|
|
30
|
+
* A converted fragment gets `provider: "chatgpt"`, and a model outside the
|
|
31
|
+
* Codex subscription set is replaced with `gpt-5.6-terra` (the "chatgpt"
|
|
32
|
+
* identity requires a Codex-servable model at schema validation; on the
|
|
33
|
+
* subscription every model bills the same). An unpinned
|
|
34
|
+
* `llm.defaultProvider` of "openai" converts the same way: it anchors the
|
|
35
|
+
* code-owned default profiles and background call sites, which are equally
|
|
36
|
+
* stranded. Fragments with a `provider_connection` pin, a pinned
|
|
37
|
+
* defaultProvider connectionName, any other provider, or a managed source
|
|
38
|
+
* are left untouched. `llm.default` is not swept: migration 133 drops that
|
|
39
|
+
* legacy blob and runs earlier in the chain.
|
|
40
|
+
*/
|
|
41
|
+
export const convertStrandedSubscriptionOpenaiProfilesMigration: WorkspaceMigration =
|
|
42
|
+
{
|
|
43
|
+
id: "144-convert-stranded-subscription-openai-profiles",
|
|
44
|
+
description:
|
|
45
|
+
'Convert unpinned provider "openai" LLM fragments to the "chatgpt" routing identity in subscription-only workspaces',
|
|
46
|
+
run(workspaceDir: string): void {
|
|
47
|
+
const configPath = join(workspaceDir, "config.json");
|
|
48
|
+
if (!existsSync(configPath)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!isSubscriptionOnlyWorkspace(workspaceDir)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Read outside the parse catch: a transient filesystem error (EIO,
|
|
56
|
+
// EACCES) must reach the runner so the migration retries, while
|
|
57
|
+
// malformed JSON is a permanent state this migration cannot repair.
|
|
58
|
+
const rawText = readFileSync(configPath, "utf-8");
|
|
59
|
+
|
|
60
|
+
let config: Record<string, unknown>;
|
|
61
|
+
try {
|
|
62
|
+
const raw = JSON.parse(rawText);
|
|
63
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
config = raw as Record<string, unknown>;
|
|
67
|
+
} catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const llm = readObject(config.llm);
|
|
72
|
+
if (llm === null) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let changed = false;
|
|
77
|
+
|
|
78
|
+
const profiles = readObject(llm.profiles);
|
|
79
|
+
if (profiles !== null) {
|
|
80
|
+
for (const [name, rawProfile] of Object.entries(profiles)) {
|
|
81
|
+
if (convertFragment(readObject(rawProfile))) {
|
|
82
|
+
changed = true;
|
|
83
|
+
log.info({ profile: name }, "Converted stranded openai profile");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const callSites = readObject(llm.callSites);
|
|
89
|
+
if (callSites !== null) {
|
|
90
|
+
for (const [id, rawConfig] of Object.entries(callSites)) {
|
|
91
|
+
if (convertFragment(readObject(rawConfig))) {
|
|
92
|
+
changed = true;
|
|
93
|
+
log.info(
|
|
94
|
+
{ callSite: id },
|
|
95
|
+
"Converted stranded openai call-site fragment",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const defaultProvider = readObject(llm.defaultProvider);
|
|
102
|
+
if (
|
|
103
|
+
defaultProvider !== null &&
|
|
104
|
+
defaultProvider.provider === "openai" &&
|
|
105
|
+
!isConnectionPin(defaultProvider.connectionName)
|
|
106
|
+
) {
|
|
107
|
+
defaultProvider.provider = "chatgpt";
|
|
108
|
+
// An invalid connectionName leaf (null, empty) would fail
|
|
109
|
+
// DefaultProviderSchema at load, whose catch drops the whole value.
|
|
110
|
+
if ("connectionName" in defaultProvider) {
|
|
111
|
+
delete defaultProvider.connectionName;
|
|
112
|
+
}
|
|
113
|
+
changed = true;
|
|
114
|
+
log.info("Converted stranded openai default provider");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!changed) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Write-then-rename so an interrupted write cannot leave config.json
|
|
122
|
+
// truncated: a torn in-place write would parse as invalid JSON on the
|
|
123
|
+
// retry, which the catch above treats as "nothing to do", letting the
|
|
124
|
+
// runner checkpoint the migration as completed against a corrupt file.
|
|
125
|
+
const tmpPath = `${configPath}.migration-144.tmp`;
|
|
126
|
+
writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
127
|
+
renameSync(tmpPath, configPath);
|
|
128
|
+
},
|
|
129
|
+
// The exact-match rewrite is idempotent, so a transient failure (full
|
|
130
|
+
// disk, I/O error) is safe to retry on later startups.
|
|
131
|
+
retryFailedCheckpoint: true,
|
|
132
|
+
down(_workspaceDir: string): void {
|
|
133
|
+
// Forward-only: converting back would restore fragments that cannot
|
|
134
|
+
// resolve a connection.
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// Helpers: self-contained per workspace migrations AGENTS.md
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
const SUBSCRIPTION_CONNECTION_NAME = "chatgpt-subscription";
|
|
143
|
+
|
|
144
|
+
const LEGACY_MANAGED_OPENAI_NAME = "openai-managed";
|
|
145
|
+
|
|
146
|
+
const CODEX_SUBSCRIPTION_MODEL_IDS = new Set([
|
|
147
|
+
"gpt-5.6-sol",
|
|
148
|
+
"gpt-5.6-terra",
|
|
149
|
+
"gpt-5.6-luna",
|
|
150
|
+
"gpt-5.5",
|
|
151
|
+
"gpt-5.4",
|
|
152
|
+
"gpt-5.4-mini",
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
const FALLBACK_CODEX_MODEL = "gpt-5.6-terra";
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* True only when the workspace DB proves the stranded condition: the
|
|
159
|
+
* canonical subscription row is present and no openai row exists. Any
|
|
160
|
+
* failure to read counts as unproven.
|
|
161
|
+
*/
|
|
162
|
+
function isSubscriptionOnlyWorkspace(workspaceDir: string): boolean {
|
|
163
|
+
const dbPath = join(workspaceDir, "data", "db", "assistant.db");
|
|
164
|
+
if (!existsSync(dbPath)) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// An open or query failure on an existing DB (locked, I/O error, corrupt
|
|
169
|
+
// header) is not proof of anything: swallowing it would checkpoint the
|
|
170
|
+
// migration as completed against unread state. Let it propagate so
|
|
171
|
+
// retryFailedCheckpoint reattempts on a later boot.
|
|
172
|
+
const db = new Database(dbPath, { readonly: true });
|
|
173
|
+
try {
|
|
174
|
+
const tableExists = db
|
|
175
|
+
.query(
|
|
176
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'provider_connections'`,
|
|
177
|
+
)
|
|
178
|
+
.get();
|
|
179
|
+
if (!tableExists) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const subscription = db
|
|
184
|
+
.query(`SELECT auth FROM provider_connections WHERE name = ?`)
|
|
185
|
+
.get(SUBSCRIPTION_CONNECTION_NAME) as { auth: string } | null;
|
|
186
|
+
if (!subscription) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const parsed: unknown = JSON.parse(subscription.auth);
|
|
191
|
+
const authType =
|
|
192
|
+
parsed !== null && typeof parsed === "object"
|
|
193
|
+
? (parsed as { type?: unknown }).type
|
|
194
|
+
: undefined;
|
|
195
|
+
if (authType !== "oauth_subscription") {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Two openai-provider rows do not count as usable openai connections.
|
|
203
|
+
// The canonical row itself stores provider "openai" until DB migration
|
|
204
|
+
// 366 flips it, and this migration checkpoints once; counting it would
|
|
205
|
+
// skip the conversion forever on exactly the upgrade boot that needs it
|
|
206
|
+
// (its subscription auth is already verified above). The legacy
|
|
207
|
+
// "openai-managed" row is a pre-consolidation platform leftover that the
|
|
208
|
+
// connection listings hide (LEGACY_MANAGED_CONNECTION_NAMES in
|
|
209
|
+
// providers/inference/connections.ts); treating it as a live connection
|
|
210
|
+
// would block the repair on installs that still carry it.
|
|
211
|
+
const openaiRow = db
|
|
212
|
+
.query(
|
|
213
|
+
`SELECT 1 FROM provider_connections WHERE provider = 'openai' AND name NOT IN (?, ?)`,
|
|
214
|
+
)
|
|
215
|
+
.get(SUBSCRIPTION_CONNECTION_NAME, LEGACY_MANAGED_OPENAI_NAME);
|
|
216
|
+
return openaiRow === null;
|
|
217
|
+
} finally {
|
|
218
|
+
db.close();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Only a non-empty string names a pinned connection. The config loader
|
|
224
|
+
* strips `null`, empty, or non-string values as invalid leaves, so runtime
|
|
225
|
+
* treats fragments carrying them as unpinned; the conversion must judge
|
|
226
|
+
* them the same way or those fragments stay stranded.
|
|
227
|
+
*/
|
|
228
|
+
function isConnectionPin(value: unknown): boolean {
|
|
229
|
+
return typeof value === "string" && value.length > 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function convertFragment(fragment: Record<string, unknown> | null): boolean {
|
|
233
|
+
if (fragment === null) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
if (
|
|
237
|
+
fragment.provider !== "openai" ||
|
|
238
|
+
isConnectionPin(fragment.provider_connection)
|
|
239
|
+
) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
if (fragment.source === "managed") {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
fragment.provider = "chatgpt";
|
|
246
|
+
// An invalid pin leaf (null, empty) rode along as unpinned; the chatgpt
|
|
247
|
+
// identity carries no connection reference, so drop it outright.
|
|
248
|
+
if ("provider_connection" in fragment) {
|
|
249
|
+
delete fragment.provider_connection;
|
|
250
|
+
}
|
|
251
|
+
if (
|
|
252
|
+
typeof fragment.model !== "string" ||
|
|
253
|
+
!CODEX_SUBSCRIPTION_MODEL_IDS.has(fragment.model)
|
|
254
|
+
) {
|
|
255
|
+
fragment.model = FALLBACK_CODEX_MODEL;
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
261
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
return value as Record<string, unknown>;
|
|
265
|
+
}
|