mixdog 1.2.1 → 1.2.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/package.json +1 -1
- package/scripts/gate-local.mjs +19 -3
- package/scripts/model-catalog-audit.mjs +1 -15
- package/src/agents/reviewer/AGENT.md +3 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +1 -0
- package/src/repl/slash-commands.mjs +16 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
- package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +96 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +25 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +73 -35
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +34 -7
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +30 -14
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +82 -9
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +38 -5
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +102 -23
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +29 -6
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +18 -1
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +77 -17
- package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +29 -8
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +191 -0
- package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +21 -82
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -12
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +21 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +56 -23
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +68 -19
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +22 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +94 -13
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +42 -25
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +2 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +34 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +5 -9
- package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
- package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +31 -20
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
- package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +118 -219
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
- package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +41 -21
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +155 -29
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
- package/src/runtime/memory/lib/pg/process.mjs +34 -24
- package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
- package/src/runtime/office/README.md +1 -1
- package/src/runtime/office/shared/values.mjs +0 -5
- package/src/runtime/shared/background-tasks.mjs +92 -14
- package/src/runtime/shared/err-text.mjs +25 -0
- package/src/runtime/shared/error-presentation.mjs +159 -19
- package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +123 -134
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
- package/src/runtime/shared/llm/model-catalog.mjs +53 -40
- package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
- package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
- package/src/runtime/shared/model-display.mjs +13 -0
- package/src/runtime/shared/quota-resume-store.mjs +11 -0
- package/src/runtime/shared/session-baseline-store.mjs +960 -0
- package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
- package/src/runtime/shared/task-notification-envelope.mjs +2 -2
- package/src/runtime/shared/turn-changes.mjs +447 -219
- package/src/runtime/shared/turn-snapshot-store.mjs +182 -1
- package/src/runtime/shared/turn-snapshot.mjs +170 -48
- package/src/runtime/shared/turn-worktree-snapshot.mjs +135 -46
- package/src/runtime/shared/worktrees/contract.mjs +0 -24
- package/src/runtime/shared/worktrees/test-support.mjs +19 -4
- package/src/session-runtime/boot/apis.mjs +6 -0
- package/src/session-runtime/boot/infrastructure.mjs +6 -0
- package/src/session-runtime/lifecycle/session-catalog.mjs +2 -0
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/connect.mjs +29 -9
- package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
- package/src/session-runtime/mcp-glue.mjs +3 -0
- package/src/session-runtime/mcp-oauth-login.mjs +174 -0
- package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
- package/src/session-runtime/model-route/set-route.mjs +83 -15
- package/src/session-runtime/model-route/web-search-route.mjs +1 -1
- package/src/session-runtime/native-web-search/routes.mjs +22 -3
- package/src/session-runtime/provider-models/model-meta.mjs +12 -3
- package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
- package/src/session-runtime/quick-web-search-models.mjs +13 -38
- package/src/session-runtime/resource-extension-sync.mjs +4 -0
- package/src/session-runtime/resource-mcp-api.mjs +98 -2
- package/src/session-runtime/runtime-review-api.mjs +13 -0
- package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
- package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
- package/src/session-runtime/settings-compaction-api.mjs +1 -0
- package/src/standalone/daemon-drain-policy.mjs +8 -0
- package/src/standalone/daemon-telemetry.mjs +55 -0
- package/src/standalone/daemon.mjs +3 -7
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
- package/src/standalone/session-service/agent-tree.mjs +12 -0
- package/src/standalone/session-service/quota-resume.mjs +8 -0
- package/src/standalone/session-service/retention/busy.mjs +15 -1
- package/src/standalone/session-service-api.mjs +5 -0
- package/src/standalone/session-service.mjs +1 -0
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/create-app-pickers.mjs +4 -0
- package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
- package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
- package/src/tui/app/extension-pickers.mjs +2 -1
- package/src/tui/app/model-options.mjs +11 -16
- package/src/tui/app/slash-commands.mjs +7 -2
- package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
- package/src/tui/components/StatusLine.jsx +2 -0
- package/src/tui/components/status-line/local-line.mjs +5 -2
- package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
- package/src/tui/dist/index.mjs +816 -43
- package/src/tui/session/quota-resume.mjs +46 -14
- package/src/tui/session/session-api/commands.mjs +33 -24
- package/src/tui/session/session-api/extensions.mjs +22 -0
- package/src/tui/session/session-api/integrations.mjs +7 -0
- package/src/tui/session/session-api/lifecycle.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +71 -7
- package/src/tui/session/session-api/settings.mjs +5 -14
- package/src/tui/session/session-flow/drain.mjs +7 -1
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
- package/src/tui/session/turn.mjs +38 -15
- package/src/tui/statusline-ansi-bridge.mjs +1 -1
- package/src/ui/statusline-agents.mjs +15 -4
- package/src/ui/statusline.mjs +7 -3
- package/src/ui/streaming-markdown-heal.mjs +34 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
- package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
- package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// Explicit MCP OAuth sign-in flows: the only interactive MCP connect. A flow
|
|
2
|
+
// reconnects one configured server with browser login allowed, hands the
|
|
3
|
+
// authorization URL back to the caller (opening the host browser only when
|
|
4
|
+
// asked), and is then polled until the reconnect settles.
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { openInBrowser } from '../runtime/shared/open-url.mjs';
|
|
7
|
+
|
|
8
|
+
// Settled flows stay readable long enough for a poller to observe the result.
|
|
9
|
+
const SETTLED_FLOW_TTL_MS = 10 * 60_000;
|
|
10
|
+
const NOT_COMPLETED = 'MCP OAuth sign-in was not completed';
|
|
11
|
+
|
|
12
|
+
const NOT_PENDING = 'MCP OAuth sign-in is no longer pending';
|
|
13
|
+
|
|
14
|
+
// flowId -> flow, shared by every session runtime of this process. A caller
|
|
15
|
+
// is not bound to the runtime that began a flow (the desktop's control
|
|
16
|
+
// session is replaced after a failed call, and an idle one is evicted), so
|
|
17
|
+
// status / complete / cancel reach the flow from any runtime; each flow keeps
|
|
18
|
+
// the handlers of the runtime that began it (its MCP scope and connect).
|
|
19
|
+
const flows = new Map();
|
|
20
|
+
|
|
21
|
+
// A pending flow keeps its authorization URL readable: a client polling the
|
|
22
|
+
// status (remote sign-in) still shows the page to open.
|
|
23
|
+
const view = (flow) => ({
|
|
24
|
+
flowId: flow.flowId,
|
|
25
|
+
state: flow.state,
|
|
26
|
+
...(flow.state === 'pending' && flow.url ? { url: flow.url } : {}),
|
|
27
|
+
...(flow.error ? { error: flow.error } : {}),
|
|
28
|
+
});
|
|
29
|
+
function settle(flow, state, error = null) {
|
|
30
|
+
if (flow.state !== 'pending') return;
|
|
31
|
+
flow.state = state;
|
|
32
|
+
if (state === 'failed') flow.error = error || NOT_COMPLETED;
|
|
33
|
+
setTimeout(() => flows.delete(flow.flowId), SETTLED_FLOW_TTL_MS).unref?.();
|
|
34
|
+
}
|
|
35
|
+
function flowOf(flowId) {
|
|
36
|
+
const flow = flows.get(String(flowId || ''));
|
|
37
|
+
if (!flow) throw new Error('MCP OAuth login not found');
|
|
38
|
+
return flow;
|
|
39
|
+
}
|
|
40
|
+
async function cancelFlow(flow) {
|
|
41
|
+
if (flow.state === 'pending') {
|
|
42
|
+
flow.cancelled = true;
|
|
43
|
+
flow.owner.cancel(flow.name);
|
|
44
|
+
await flow.done;
|
|
45
|
+
}
|
|
46
|
+
return view(flow);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* `connect(name, openBrowser)` reconnects the server interactively, calling
|
|
51
|
+
* `openBrowser(url)` when a login starts, and resolves `{ connected, error }`.
|
|
52
|
+
* `cancel(name)` aborts that server's pending login. `completeCallback(name,
|
|
53
|
+
* callbackUrl)` answers it with a pasted redirect URL (throws a fixed message
|
|
54
|
+
* for an invalid one). `onComplete(name)` runs after a successful sign-in,
|
|
55
|
+
* before the flow reports complete.
|
|
56
|
+
*/
|
|
57
|
+
export function createMcpOAuthLogins({
|
|
58
|
+
connect,
|
|
59
|
+
cancel,
|
|
60
|
+
completeCallback = () => {
|
|
61
|
+
throw new Error(NOT_PENDING);
|
|
62
|
+
},
|
|
63
|
+
onComplete = async () => {},
|
|
64
|
+
openBrowser = openInBrowser,
|
|
65
|
+
}) {
|
|
66
|
+
// name -> tail of that server's serialized begin/cancel/sign-out steps, so
|
|
67
|
+
// replacing a pending flow is atomic and exactly one can be pending.
|
|
68
|
+
const lanes = new Map();
|
|
69
|
+
function exclusive(name, step) {
|
|
70
|
+
const run = (lanes.get(name) || Promise.resolve()).then(step);
|
|
71
|
+
const tail = run.then(
|
|
72
|
+
() => {},
|
|
73
|
+
() => {}
|
|
74
|
+
);
|
|
75
|
+
lanes.set(name, tail);
|
|
76
|
+
tail.then(() => {
|
|
77
|
+
if (lanes.get(name) === tail) lanes.delete(name);
|
|
78
|
+
});
|
|
79
|
+
return run;
|
|
80
|
+
}
|
|
81
|
+
// This runtime's handlers, kept on each flow it begins.
|
|
82
|
+
const owner = {
|
|
83
|
+
exclusive,
|
|
84
|
+
cancel,
|
|
85
|
+
completeCallback,
|
|
86
|
+
};
|
|
87
|
+
// One sign-in per server of this runtime (its MCP scope).
|
|
88
|
+
const cancelServerFlows = (name) =>
|
|
89
|
+
Promise.all([...flows.values()].filter((flow) => flow.owner === owner && flow.name === name).map(cancelFlow));
|
|
90
|
+
|
|
91
|
+
async function begin(name, options = {}) {
|
|
92
|
+
const { openBrowser: hostBrowser } = options || {};
|
|
93
|
+
if (hostBrowser !== undefined && typeof hostBrowser !== 'boolean') {
|
|
94
|
+
throw new TypeError('openBrowser must be a boolean');
|
|
95
|
+
}
|
|
96
|
+
let announceUrl;
|
|
97
|
+
const urlReady = new Promise((resolve) => {
|
|
98
|
+
announceUrl = resolve;
|
|
99
|
+
});
|
|
100
|
+
// One sign-in per server: a new one replaces the pending one, and the
|
|
101
|
+
// replacement is registered before the next queued step of this server.
|
|
102
|
+
const flow = await exclusive(name, async () => {
|
|
103
|
+
await cancelServerFlows(name);
|
|
104
|
+
const flow = {
|
|
105
|
+
flowId: `mcp_oauth_${randomUUID()}`,
|
|
106
|
+
name,
|
|
107
|
+
owner,
|
|
108
|
+
state: 'pending',
|
|
109
|
+
error: null,
|
|
110
|
+
cancelled: false,
|
|
111
|
+
url: null,
|
|
112
|
+
};
|
|
113
|
+
flows.set(flow.flowId, flow);
|
|
114
|
+
const launch = async (url) => {
|
|
115
|
+
// A login reached after its flow was cancelled (queued behind another
|
|
116
|
+
// connect of this server) never opens anything.
|
|
117
|
+
if (flow.cancelled) throw new Error('MCP OAuth login cancelled');
|
|
118
|
+
flow.awaitingCallback = true;
|
|
119
|
+
flow.url = url;
|
|
120
|
+
announceUrl(url);
|
|
121
|
+
if (hostBrowser !== false) openBrowser(url);
|
|
122
|
+
};
|
|
123
|
+
flow.done = (async () => {
|
|
124
|
+
try {
|
|
125
|
+
const outcome = await connect(name, launch);
|
|
126
|
+
if (flow.cancelled) settle(flow, 'cancelled');
|
|
127
|
+
else if (outcome?.connected) {
|
|
128
|
+
// The sign-in itself succeeded; a failed follow-up never undoes it.
|
|
129
|
+
await Promise.resolve()
|
|
130
|
+
.then(() => onComplete(name))
|
|
131
|
+
.catch(() => {});
|
|
132
|
+
settle(flow, 'complete');
|
|
133
|
+
} else settle(flow, 'failed', outcome?.error);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
settle(flow, flow.cancelled ? 'cancelled' : 'failed', error?.message || String(error));
|
|
136
|
+
}
|
|
137
|
+
})();
|
|
138
|
+
return flow;
|
|
139
|
+
});
|
|
140
|
+
const url = await Promise.race([urlReady, flow.done.then(() => null)]);
|
|
141
|
+
if (url) return { flowId: flow.flowId, state: 'pending', url };
|
|
142
|
+
// Settled without a login: stored credentials (or no challenge) sufficed.
|
|
143
|
+
if (flow.state === 'complete') return view(flow);
|
|
144
|
+
// A superseded or failed begin stays readable (cancelled / failed).
|
|
145
|
+
throw new Error(flow.error || NOT_COMPLETED);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
begin,
|
|
150
|
+
status: (flowId) => view(flowOf(flowId)),
|
|
151
|
+
cancel(flowId) {
|
|
152
|
+
const flow = flowOf(flowId);
|
|
153
|
+
return flow.owner.exclusive(flow.name, () => cancelFlow(flow));
|
|
154
|
+
},
|
|
155
|
+
/** Answer the pending flow with the redirect URL pasted from the browser
|
|
156
|
+
* and resolve once it settles: `{ flowId, state: 'complete' | 'failed',
|
|
157
|
+
* error? }`. An invalid paste throws and leaves the flow pending. */
|
|
158
|
+
async complete(flowId, callbackUrl) {
|
|
159
|
+
const flow = flowOf(flowId);
|
|
160
|
+
if (flow.state !== 'pending' || flow.cancelled || !flow.awaitingCallback) throw new Error(NOT_PENDING);
|
|
161
|
+
flow.owner.completeCallback(flow.name, callbackUrl);
|
|
162
|
+
await flow.done;
|
|
163
|
+
return view(flow);
|
|
164
|
+
},
|
|
165
|
+
/** Cancel every pending sign-in of one server, then run `after` (sign-out)
|
|
166
|
+
* before any later begin of that server starts; resolves its result. */
|
|
167
|
+
cancelServer(name, after = async () => {}) {
|
|
168
|
+
return exclusive(name, async () => {
|
|
169
|
+
await cancelServerFlows(name);
|
|
170
|
+
return after();
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* model-switch-target.mjs — what a user's model selection names, and whether
|
|
3
|
+
* it switches an established conversation to another model. Shared by every
|
|
4
|
+
* surface that offers the choice (session API routes, plain REPL /model).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** The provider/model a selection names; a bare preset id or name names the
|
|
8
|
+
* preset's model. */
|
|
9
|
+
export function selectedModel(runtime, request, sessionProvider, sessionModel) {
|
|
10
|
+
const model = String(request?.model ?? '').trim();
|
|
11
|
+
if (!request?.provider && model) {
|
|
12
|
+
const wanted = model.toLowerCase();
|
|
13
|
+
const presets = runtime?.listPresets?.();
|
|
14
|
+
const preset = (Array.isArray(presets) ? presets : []).find((entry) =>
|
|
15
|
+
[entry?.id, entry?.name].some(
|
|
16
|
+
(key) =>
|
|
17
|
+
String(key ?? '')
|
|
18
|
+
.trim()
|
|
19
|
+
.toLowerCase() === wanted
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
if (preset?.provider && preset?.model) return { provider: String(preset.provider), model: String(preset.model) };
|
|
23
|
+
}
|
|
24
|
+
return { provider: String(request?.provider ?? sessionProvider), model: model || sessionModel };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A selection of another model than the established conversation's own. */
|
|
28
|
+
export function switchesSessionModel(runtime, request) {
|
|
29
|
+
const sessionProvider = String(runtime?.sessionProvider ?? '');
|
|
30
|
+
const sessionModel = String(runtime?.sessionModel ?? '');
|
|
31
|
+
if (!sessionProvider || !sessionModel) return false;
|
|
32
|
+
const { provider, model } = selectedModel(runtime, request, sessionProvider, sessionModel);
|
|
33
|
+
return provider !== sessionProvider || model !== sessionModel;
|
|
34
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* set-route.mjs — select the main route: resolve and validate the request,
|
|
3
3
|
* adopt it into config, then decide how the current session follows it
|
|
4
|
-
* (untouched, tuned, recreated,
|
|
4
|
+
* (untouched, tuned, recreated, updated in place when empty, or switched in
|
|
5
|
+
* place when the selection is addressed to an established conversation).
|
|
5
6
|
*/
|
|
6
7
|
import { clean, hasOwn } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
7
8
|
import {
|
|
@@ -117,6 +118,51 @@ function updateSessionInPlace(deps, session, updateOptions) {
|
|
|
117
118
|
writeStatuslineRoute(statusRoutes, getSession(), route);
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
/** Index of the message a model switch boundary is recorded on: the last one
|
|
122
|
+
* before a trailing unanswered prompt, which a retry may rewind. */
|
|
123
|
+
function modelSwitchAnchorIndex(messages) {
|
|
124
|
+
let index = messages.length - 1;
|
|
125
|
+
while (index >= 0 && (messages[index]?.role === 'user' || messages[index]?.role === 'system')) index -= 1;
|
|
126
|
+
if (index >= 0) return index;
|
|
127
|
+
return messages.findLastIndex((message) => message?.role !== 'system');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Display-only boundary of an in-place model switch, kept in message meta
|
|
131
|
+
* (like an inheritance boundary): the transcript shows it live and after a
|
|
132
|
+
* restore, and the model conversation gains no synthetic message. */
|
|
133
|
+
export function recordModelSwitchBoundary(session, boundary) {
|
|
134
|
+
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
135
|
+
const index = modelSwitchAnchorIndex(messages);
|
|
136
|
+
if (index < 0) return false;
|
|
137
|
+
const anchor = messages[index];
|
|
138
|
+
const switches = Array.isArray(anchor.meta?.modelSwitches) ? anchor.meta.modelSwitches : [];
|
|
139
|
+
if (switches.some((entry) => entry?.at === boundary.at && entry?.modelId === boundary.modelId)) return true;
|
|
140
|
+
// Replace the message object: the session save worker detects changed
|
|
141
|
+
// messages by reference identity, so an in-place meta edit is never saved.
|
|
142
|
+
messages[index] = { ...anchor, meta: { ...anchor.meta, modelSwitches: [...switches, boundary] } };
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Move the addressed, established session onto the adopted route in place.
|
|
147
|
+
* Its provider replay state and context readings are dropped, so the next
|
|
148
|
+
* turn re-measures and auto-compacts against the new model's window. Returns
|
|
149
|
+
* the recorded boundary, or null when the session has nothing to mark. */
|
|
150
|
+
function switchSessionInPlace(deps, session) {
|
|
151
|
+
const { getRoute, getSession } = deps;
|
|
152
|
+
const route = getRoute();
|
|
153
|
+
if (route.provider === session.provider && route.model === session.model) {
|
|
154
|
+
updateSessionInPlace(deps, session);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const boundary = { provider: route.provider, modelId: route.model, at: Date.now() };
|
|
158
|
+
const recorded = recordModelSwitchBoundary(session, boundary);
|
|
159
|
+
updateSessionInPlace(deps, session, { crossProvider: true });
|
|
160
|
+
// A manager that answered with another copy of the session carries the
|
|
161
|
+
// boundary too; the next save persists it.
|
|
162
|
+
if (recorded && getSession() && getSession() !== session) recordModelSwitchBoundary(getSession(), boundary);
|
|
163
|
+
return recorded ? boundary : null;
|
|
164
|
+
}
|
|
165
|
+
|
|
120
166
|
/** Validate, persist and adopt the requested route as the runtime's route. */
|
|
121
167
|
async function adoptRoute(deps, persist, next, options) {
|
|
122
168
|
const {
|
|
@@ -144,7 +190,9 @@ async function adoptRoute(deps, persist, next, options) {
|
|
|
144
190
|
* Quota-exhaustion escape hatch: apply `next` to the addressed, ESTABLISHED
|
|
145
191
|
* session in place. The caller (session API) has already proven the latest
|
|
146
192
|
* settled turn failed on quota and that nothing is running. Starts no turn;
|
|
147
|
-
* the next turn's auto-compaction fits history to the new window.
|
|
193
|
+
* the next turn's auto-compaction fits history to the new window. Answers
|
|
194
|
+
* `{ modelSwitch }` with the recorded boundary (as setRoute does) so the
|
|
195
|
+
* session surface shows the same live divider, or true when nothing switched.
|
|
148
196
|
*/
|
|
149
197
|
export function createContinueSessionInModel(deps, persist) {
|
|
150
198
|
const { getSession, invalidateContextStatusCache } = deps;
|
|
@@ -154,33 +202,53 @@ export function createContinueSessionInModel(deps, persist) {
|
|
|
154
202
|
throw new Error(`session ${sessionId || ''} is not the active session`);
|
|
155
203
|
}
|
|
156
204
|
await adoptRoute(deps, persist, next, {});
|
|
157
|
-
|
|
205
|
+
const modelSwitch = switchSessionInPlace(deps, session);
|
|
158
206
|
invalidateContextStatusCache();
|
|
159
|
-
return true;
|
|
207
|
+
return modelSwitch ? { modelSwitch } : true;
|
|
160
208
|
};
|
|
161
209
|
}
|
|
162
210
|
|
|
211
|
+
/** Rejects an unacceptable selection without applying it. */
|
|
212
|
+
async function validateSelectedRoute(deps, next) {
|
|
213
|
+
await resolveSelectedRoute(deps, next);
|
|
214
|
+
return deps.getRoute();
|
|
215
|
+
}
|
|
216
|
+
|
|
163
217
|
export function createSetRoute(deps, persist) {
|
|
164
218
|
const { getRoute, getSession, invalidateContextStatusCache, createCurrentSession } = deps;
|
|
165
219
|
|
|
166
220
|
return async function setRoute(next, options = {}) {
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
221
|
+
// Without an explicit durable address a selection is for a new/inherited
|
|
222
|
+
// session and never rewrites an existing conversation's model, unless it
|
|
223
|
+
// is the user's model choice for this conversation (`switchEstablished`,
|
|
224
|
+
// the session surface): that one switches an established conversation in
|
|
225
|
+
// place while an empty one follows the unaddressed rules below.
|
|
170
226
|
const applyToCurrentSession = options?.applyToCurrentSession === true;
|
|
227
|
+
const switchEstablished = applyToCurrentSession || options?.switchEstablished === true;
|
|
228
|
+
// Lets a caller prove the selection is acceptable before it does
|
|
229
|
+
// irreversible work (compaction) ahead of the switch; changes nothing.
|
|
230
|
+
if (options?.validateOnly === true) return validateSelectedRoute(deps, next);
|
|
171
231
|
await adoptRoute(deps, persist, next, options);
|
|
172
232
|
const session = getSession();
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
// cache to protect, so /model takes effect live: route + statusline
|
|
233
|
+
// A session the user has already talked in or compacted keeps its route
|
|
234
|
+
// unless the selection was addressed to it. A current session with no
|
|
235
|
+
// model reply, no compaction summary and no in-flight first turn — empty,
|
|
236
|
+
// or holding only refused prompts — takes effect live: route + statusline
|
|
178
237
|
// update immediately. Only a truly empty session is recreated below.
|
|
179
238
|
const currentSessionEmpty = !!session && !sessionRouteCommitted(session);
|
|
180
239
|
if (!currentSessionEmpty) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
240
|
+
if (!switchEstablished) return getRoute();
|
|
241
|
+
const route = getRoute();
|
|
242
|
+
// An addressed selection of another model switches the conversation
|
|
243
|
+
// itself in place; the caller (session surface) refuses it while a
|
|
244
|
+
// turn runs. A same-model update (effort/Fast) only tunes the session
|
|
245
|
+
// from its next turn.
|
|
246
|
+
if (session && (route.provider !== session.provider || route.model !== session.model)) {
|
|
247
|
+
const modelSwitch = switchSessionInPlace(deps, session);
|
|
248
|
+
invalidateContextStatusCache();
|
|
249
|
+
return modelSwitch ? { ...getRoute(), modelSwitch } : getRoute();
|
|
250
|
+
}
|
|
251
|
+
persist.applySessionTuning();
|
|
184
252
|
return getRoute();
|
|
185
253
|
}
|
|
186
254
|
// An addressed runtime keeps its durable id: the empty session is updated
|
|
@@ -62,7 +62,7 @@ export function createWebSearchRouteApi(deps) {
|
|
|
62
62
|
await awaitKeychainPrewarm();
|
|
63
63
|
ensureFullConfig();
|
|
64
64
|
await ensureProvidersReady(ensureProviderEnabled(getConfig(), selectedRoute.provider));
|
|
65
|
-
const modelMeta = await lookupModelMeta(selectedRoute.provider, selectedRoute.model);
|
|
65
|
+
const modelMeta = await lookupModelMeta(selectedRoute.provider, selectedRoute.model, { allowFetch: true });
|
|
66
66
|
if (!webSearchCapableFor(selectedRoute.provider, modelMeta)) {
|
|
67
67
|
throw new Error(`model "${selectedRoute.model}" is not marked as web-search capable`);
|
|
68
68
|
}
|
|
@@ -7,19 +7,37 @@ import { webSearchRouteOrDefault } from '../../runtime/agent/orchestrator/runtim
|
|
|
7
7
|
|
|
8
8
|
export function createWebSearchRouteCandidates({
|
|
9
9
|
getRoute,
|
|
10
|
+
getReg,
|
|
10
11
|
getWebSearchRoute,
|
|
11
12
|
setWebSearchRoute,
|
|
12
13
|
ensureFullConfig,
|
|
13
14
|
awaitKeychainPrewarm,
|
|
15
|
+
ensureProvidersReady,
|
|
16
|
+
ensureProviderEnabled,
|
|
14
17
|
normalizeWebSearchProviderId,
|
|
15
18
|
isDefaultWebSearchRouteConfig,
|
|
16
19
|
isWebSearchCapableProvider,
|
|
17
20
|
webSearchCapableFor,
|
|
18
21
|
}) {
|
|
19
|
-
function currentMainWebSearchModelMeta() {
|
|
22
|
+
function currentMainWebSearchModelMeta(loadedRow = null) {
|
|
20
23
|
const route = getRoute();
|
|
21
24
|
if (!route?.provider || !route?.model) return null;
|
|
22
|
-
|
|
25
|
+
// Capability comes from the provider's catalog row (supportedTools etc.),
|
|
26
|
+
// not from the bare model id.
|
|
27
|
+
const catalogRow = getReg()?.getProvider(route.provider)?.getCachedModelInfo?.(route.model) || loadedRow;
|
|
28
|
+
return { ...catalogRow, ...route, id: route.model, display: route.model, name: route.model };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A cold catalog (fresh or model-proxy provider instance) is loaded once
|
|
32
|
+
* before the capability check, so it never falls back to the bare id.
|
|
33
|
+
* Returns the listed row, which carries the catalog fields even when the
|
|
34
|
+
* provider keeps no per-model cache. */
|
|
35
|
+
async function loadMainCatalogRow(cfg, route) {
|
|
36
|
+
await ensureProvidersReady(ensureProviderEnabled(cfg, route.provider));
|
|
37
|
+
const provider = getReg().getProvider(route.provider);
|
|
38
|
+
if (provider?.getCachedModelInfo?.(route.model) || typeof provider?.listModels !== 'function') return null;
|
|
39
|
+
const models = await provider.listModels();
|
|
40
|
+
return (Array.isArray(models) ? models.find((m) => m?.id === route.model) : null) || null;
|
|
23
41
|
}
|
|
24
42
|
|
|
25
43
|
async function nativeWebSearchRoutes() {
|
|
@@ -33,7 +51,8 @@ export function createWebSearchRouteCandidates({
|
|
|
33
51
|
const webSearchRoute = webSearchRouteOrDefault(cfg.webSearchRoute, getWebSearchRoute());
|
|
34
52
|
setWebSearchRoute(webSearchRoute);
|
|
35
53
|
if (isDefaultWebSearchRouteConfig(webSearchRoute)) {
|
|
36
|
-
const
|
|
54
|
+
const loadedRow = route?.provider && route?.model ? await loadMainCatalogRow(cfg, route) : null;
|
|
55
|
+
const mainModel = currentMainWebSearchModelMeta(loadedRow);
|
|
37
56
|
if (!mainModel || !webSearchCapableFor(route.provider, mainModel)) return [];
|
|
38
57
|
return [
|
|
39
58
|
{
|
|
@@ -10,6 +10,8 @@ export function modelMetaKey(providerId, modelId) {
|
|
|
10
10
|
|
|
11
11
|
export function createModelMetaIndex({ caches, modelMetaByRoute, reg, scheduleProviderModelWarmup }) {
|
|
12
12
|
let observedCatalogRevision = catalogRevision(reg());
|
|
13
|
+
// Keys holding only the bare-id placeholder: a fetching lookup replaces them.
|
|
14
|
+
const placeholderKeys = new Set();
|
|
13
15
|
|
|
14
16
|
/** Invalidates the provider caches when the registry's catalog moved on;
|
|
15
17
|
* returns the current revision either way. */
|
|
@@ -22,6 +24,7 @@ export function createModelMetaIndex({ caches, modelMetaByRoute, reg, schedulePr
|
|
|
22
24
|
caches.providerModelsPromise = null;
|
|
23
25
|
caches.webSearchProviderModelsCache = { models: null, at: 0, revision };
|
|
24
26
|
modelMetaByRoute.clear();
|
|
27
|
+
placeholderKeys.clear();
|
|
25
28
|
return revision;
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -32,14 +35,16 @@ export function createModelMetaIndex({ caches, modelMetaByRoute, reg, schedulePr
|
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
function remember(providerId, modelId, meta) {
|
|
35
|
-
|
|
38
|
+
const key = modelMetaKey(providerId, modelId);
|
|
39
|
+
modelMetaByRoute.set(key, meta);
|
|
40
|
+
placeholderKeys.delete(key);
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
async function lookupModelMeta(providerId, modelId, { allowFetch = false } = {}) {
|
|
39
44
|
const revision = syncCatalogRevision();
|
|
40
45
|
const seq = caches.providerModelsLoadSeq;
|
|
41
46
|
const key = modelMetaKey(providerId, modelId);
|
|
42
|
-
if (modelMetaByRoute.has(key)) return modelMetaByRoute.get(key);
|
|
47
|
+
if (modelMetaByRoute.has(key) && !(allowFetch && placeholderKeys.has(key))) return modelMetaByRoute.get(key);
|
|
43
48
|
const fallback = () => ({ id: modelId, provider: providerId });
|
|
44
49
|
const providerImpl = reg().getProvider(providerId);
|
|
45
50
|
if (!providerImpl || typeof providerImpl.listModels !== 'function') {
|
|
@@ -58,6 +63,7 @@ export function createModelMetaIndex({ caches, modelMetaByRoute, reg, schedulePr
|
|
|
58
63
|
if (!allowFetch) {
|
|
59
64
|
const meta = fallback();
|
|
60
65
|
modelMetaByRoute.set(key, meta);
|
|
66
|
+
placeholderKeys.add(key);
|
|
61
67
|
scheduleProviderModelWarmup();
|
|
62
68
|
return meta;
|
|
63
69
|
}
|
|
@@ -65,7 +71,10 @@ export function createModelMetaIndex({ caches, modelMetaByRoute, reg, schedulePr
|
|
|
65
71
|
const models = await providerImpl.listModels();
|
|
66
72
|
const found = Array.isArray(models) ? models.find((m) => m?.id === modelId) : null;
|
|
67
73
|
const meta = found || fallback();
|
|
68
|
-
if (snapshotIsCurrent(revision, seq))
|
|
74
|
+
if (snapshotIsCurrent(revision, seq)) {
|
|
75
|
+
modelMetaByRoute.set(key, meta);
|
|
76
|
+
placeholderKeys.delete(key);
|
|
77
|
+
}
|
|
69
78
|
return meta;
|
|
70
79
|
} catch {
|
|
71
80
|
// A failed fetch is not authoritative: nothing is pinned.
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
// main model, cached or built-in quick models per capable provider, and the
|
|
3
3
|
// configured/current routes, deduplicated per provider:model.
|
|
4
4
|
import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { quickWebSearchModels } from '../quick-web-search-models.mjs';
|
|
6
|
+
import {
|
|
7
|
+
WEB_SEARCH_DEFAULT_PROVIDER,
|
|
8
|
+
WEB_SEARCH_DEFAULT_MODEL,
|
|
9
|
+
} from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
|
|
7
10
|
import { providerCachedModelsSync } from '../../runtime/agent/orchestrator/providers/provider-catalog-cache.mjs';
|
|
8
11
|
import { hydratedModel } from './model-meta.mjs';
|
|
9
12
|
|
|
@@ -33,7 +36,12 @@ export function createQuickWebSearchRows({
|
|
|
33
36
|
contextWindow: resolved?.contextWindow || null,
|
|
34
37
|
outputTokens: resolved?.outputTokens || null,
|
|
35
38
|
latest: resolved?.latest === true,
|
|
36
|
-
|
|
39
|
+
// Only what the source row itself declares; webSearchCapableFor adds the
|
|
40
|
+
// provider catalog's row and the id fallback. Never forced on.
|
|
41
|
+
...(typeof model?.supportsWebSearch === 'boolean' ? { supportsWebSearch: model.supportsWebSearch } : {}),
|
|
42
|
+
...(Array.isArray(model?.supportedTools) ? { supportedTools: model.supportedTools } : {}),
|
|
43
|
+
...(Array.isArray(model?.tools) ? { tools: model.tools } : {}),
|
|
44
|
+
...(model?.capabilities && typeof model.capabilities === 'object' ? { capabilities: model.capabilities } : {}),
|
|
37
45
|
supportsFunctionCalling: resolved?.supportsFunctionCalling === true,
|
|
38
46
|
supportsPromptCaching: resolved?.supportsPromptCaching === true,
|
|
39
47
|
supportsReasoning: resolved?.supportsReasoning === true,
|
|
@@ -95,7 +103,7 @@ export function createQuickWebSearchRows({
|
|
|
95
103
|
const providerName = normalizeWebSearchProviderId(name);
|
|
96
104
|
if (!providerConfig?.enabled || !isWebSearchCapableProvider(providerName)) continue;
|
|
97
105
|
const cachedModels = providerCachedModelsSync(providerName);
|
|
98
|
-
const quickModels = cachedModels.length ? cachedModels :
|
|
106
|
+
const quickModels = cachedModels.length ? cachedModels : quickWebSearchModels(providerName);
|
|
99
107
|
for (const model of quickModels) {
|
|
100
108
|
addQuickWebSearchModel(rows, seen, providerName, model);
|
|
101
109
|
}
|
|
@@ -110,10 +118,7 @@ export function createQuickWebSearchRows({
|
|
|
110
118
|
}
|
|
111
119
|
const mainModel = currentMainWebSearchModelMeta();
|
|
112
120
|
if (mainModel && webSearchCapableFor(route.provider, mainModel)) {
|
|
113
|
-
addQuickWebSearchModel(rows, seen, route.provider,
|
|
114
|
-
id: route.model,
|
|
115
|
-
display: route.model,
|
|
116
|
-
});
|
|
121
|
+
addQuickWebSearchModel(rows, seen, route.provider, mainModel);
|
|
117
122
|
}
|
|
118
123
|
return webSearchModelsFromRows(rows);
|
|
119
124
|
}
|
|
@@ -1,41 +1,16 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Offline quick-pick model rows for onboarding / web-search setup. No model id
|
|
2
|
+
// is listed here: when a provider has no live roster yet, its newest rows come
|
|
3
|
+
// from the provider-scoped public catalog, so a new release appears without a
|
|
4
|
+
// code change. Rows are hydrated further during catalog construction.
|
|
5
|
+
import { getModelsDevProviderModelsSync } from '../runtime/shared/llm/model-catalog.mjs';
|
|
4
6
|
|
|
5
7
|
export const ONBOARDING_VERSION = 1;
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
{ id:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
openai: [
|
|
16
|
-
{ id: 'gpt-5.5', display: 'GPT-5.5', latest: true },
|
|
17
|
-
{ id: 'gpt-5.4', display: 'GPT-5.4', latest: true },
|
|
18
|
-
{ id: 'gpt-5', display: 'GPT-5' },
|
|
19
|
-
{ id: 'gpt-4.1', display: 'GPT-4.1' },
|
|
20
|
-
{ id: 'gpt-4o', display: 'GPT-4o' },
|
|
21
|
-
],
|
|
22
|
-
'grok-oauth': [{ id: 'grok-4.5', display: 'Grok 4.5', latest: true }],
|
|
23
|
-
xai: [{ id: 'grok-4.5', display: 'Grok 4.5', latest: true }],
|
|
24
|
-
gemini: [
|
|
25
|
-
{ id: 'gemini-3.6-flash', display: 'Gemini 3.6 Flash', latest: true },
|
|
26
|
-
{ id: 'gemini-3.5-flash-lite', display: 'Gemini 3.5 Flash Lite', latest: true },
|
|
27
|
-
{ id: 'gemini-3.1-pro-preview', display: 'Gemini 3.1 Pro Preview' },
|
|
28
|
-
],
|
|
29
|
-
'anthropic-oauth': [
|
|
30
|
-
{ id: 'claude-opus-5-5', display: 'Claude Opus 5.5', latest: true },
|
|
31
|
-
{ id: 'claude-sonnet-5', display: 'Claude Sonnet 5', latest: true },
|
|
32
|
-
{ id: 'claude-fable-5', display: 'Claude Fable 5', latest: true },
|
|
33
|
-
{ id: 'claude-haiku-4-5-20251001', display: 'Claude Haiku 4.5' },
|
|
34
|
-
],
|
|
35
|
-
anthropic: [
|
|
36
|
-
{ id: 'claude-opus-5-5', display: 'Claude Opus 5.5', latest: true },
|
|
37
|
-
{ id: 'claude-sonnet-5', display: 'Claude Sonnet 5', latest: true },
|
|
38
|
-
{ id: 'claude-fable-5', display: 'Claude Fable 5', latest: true },
|
|
39
|
-
{ id: 'claude-haiku-4-5-20251001', display: 'Claude Haiku 4.5' },
|
|
40
|
-
],
|
|
41
|
-
});
|
|
9
|
+
const QUICK_ROWS_PER_PROVIDER = 5;
|
|
10
|
+
|
|
11
|
+
export function quickWebSearchModels(provider, models = getModelsDevProviderModelsSync(provider)) {
|
|
12
|
+
return Object.entries(models || {})
|
|
13
|
+
.map(([id, row]) => ({ id, display: row?.name || id, releaseDate: row?.release_date || null }))
|
|
14
|
+
.sort((a, b) => (Date.parse(b.releaseDate) || 0) - (Date.parse(a.releaseDate) || 0))
|
|
15
|
+
.slice(0, QUICK_ROWS_PER_PROVIDER);
|
|
16
|
+
}
|
|
@@ -5,6 +5,7 @@ import { publishGlobalExtensionChange, subscribeGlobalExtensionChanges } from '.
|
|
|
5
5
|
|
|
6
6
|
export function createExtensionSync({
|
|
7
7
|
connectConfiguredMcp,
|
|
8
|
+
cancelMcpLogin,
|
|
8
9
|
invalidatePreSessionToolSurface,
|
|
9
10
|
refreshEmptySessionToolPolicy,
|
|
10
11
|
reloadFullConfig,
|
|
@@ -60,6 +61,9 @@ export function createExtensionSync({
|
|
|
60
61
|
const chain = mcpToggleChains.get(serverName) || { desired: enabled, running: null };
|
|
61
62
|
chain.desired = enabled;
|
|
62
63
|
mcpToggleChains.set(serverName, chain);
|
|
64
|
+
// A disable riding the chain would otherwise wait out an explicit sign-in
|
|
65
|
+
// (beginMcpOAuthLogin) of this server still in flight.
|
|
66
|
+
if (enabled === false) cancelMcpLogin?.(serverName);
|
|
63
67
|
if (!chain.running) {
|
|
64
68
|
chain.running = (async () => {
|
|
65
69
|
let status;
|