@intentic/sandbox-contract 1.176.3 → 1.209.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/README.md +65 -0
- package/dist/agent-catalog.d.ts +15 -2
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +50 -1
- package/dist/agent-catalog.js.map +1 -1
- package/dist/agent-run-model.d.ts +3 -0
- package/dist/agent-run-model.d.ts.map +1 -0
- package/dist/agent-run-model.js +10 -0
- package/dist/agent-run-model.js.map +1 -0
- package/dist/capability-env.d.ts +2 -0
- package/dist/capability-env.d.ts.map +1 -0
- package/dist/capability-env.js +2 -0
- package/dist/capability-env.js.map +1 -0
- package/dist/chores/chores.d.ts +9 -0
- package/dist/chores/chores.d.ts.map +1 -1
- package/dist/chores/chores.js +257 -18
- package/dist/chores/chores.js.map +1 -1
- package/dist/chores/digest.js +0 -0
- package/dist/chores/digest.js.map +1 -1
- package/dist/chores/extension-update.d.ts +9 -0
- package/dist/chores/extension-update.d.ts.map +1 -0
- package/dist/chores/extension-update.js +15 -0
- package/dist/chores/extension-update.js.map +1 -0
- package/dist/chores/fix-deps.d.ts +10 -0
- package/dist/chores/fix-deps.d.ts.map +1 -0
- package/dist/chores/fix-deps.js +18 -0
- package/dist/chores/fix-deps.js.map +1 -0
- package/dist/chores/index.d.ts +7 -2
- package/dist/chores/index.d.ts.map +1 -1
- package/dist/chores/index.js +4 -1
- package/dist/chores/index.js.map +1 -1
- package/dist/chores/probes.d.ts.map +1 -1
- package/dist/chores/probes.js +127 -3
- package/dist/chores/probes.js.map +1 -1
- package/dist/chores/stack.d.ts +27 -0
- package/dist/chores/stack.d.ts.map +1 -0
- package/dist/chores/stack.js +121 -0
- package/dist/chores/stack.js.map +1 -0
- package/dist/chores/verdict.d.ts +2 -1
- package/dist/chores/verdict.d.ts.map +1 -1
- package/dist/chores/verdict.js +53 -6
- package/dist/chores/verdict.js.map +1 -1
- package/dist/contract-lock.d.ts +3 -0
- package/dist/contract-lock.d.ts.map +1 -0
- package/dist/contract-lock.js +35 -0
- package/dist/contract-lock.js.map +1 -0
- package/dist/contracts/activity.contract.d.ts +10 -0
- package/dist/contracts/activity.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +230 -20
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +2 -1
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +847 -1
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +3 -1
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/automations.contract.d.ts +89 -0
- package/dist/contracts/automations.contract.d.ts.map +1 -1
- package/dist/contracts/automations.contract.js +3 -1
- package/dist/contracts/automations.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +108 -32
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +5 -1
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/chores.contract.d.ts +43 -0
- package/dist/contracts/chores.contract.d.ts.map +1 -1
- package/dist/contracts/ci.contract.d.ts +5 -0
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/ci.contract.js +2 -2
- package/dist/contracts/ci.contract.js.map +1 -1
- package/dist/contracts/claude.contract.d.ts +3 -14
- package/dist/contracts/claude.contract.d.ts.map +1 -1
- package/dist/contracts/claude.contract.js +2 -3
- package/dist/contracts/claude.contract.js.map +1 -1
- package/dist/contracts/drafts.contract.d.ts +4 -0
- package/dist/contracts/drafts.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.d.ts +28 -12
- package/dist/contracts/endpoints.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.js +9 -0
- package/dist/contracts/endpoints.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +399 -69
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.js +15 -1
- package/dist/contracts/extensions.contract.js.map +1 -1
- package/dist/contracts/git.contract.d.ts +209 -11
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.js +12 -2
- package/dist/contracts/git.contract.js.map +1 -1
- package/dist/contracts/grok.contract.d.ts +0 -13
- package/dist/contracts/grok.contract.d.ts.map +1 -1
- package/dist/contracts/grok.contract.js +1 -2
- package/dist/contracts/grok.contract.js.map +1 -1
- package/dist/contracts/host.contract.d.ts +40 -0
- package/dist/contracts/host.contract.d.ts.map +1 -1
- package/dist/contracts/host.contract.js +3 -2
- package/dist/contracts/host.contract.js.map +1 -1
- package/dist/contracts/loops.contract.d.ts +343 -0
- package/dist/contracts/loops.contract.d.ts.map +1 -0
- package/dist/contracts/loops.contract.js +11 -0
- package/dist/contracts/loops.contract.js.map +1 -0
- package/dist/contracts/panels.contract.d.ts +6 -0
- package/dist/contracts/panels.contract.d.ts.map +1 -1
- package/dist/contracts/personas.contract.d.ts +112 -0
- package/dist/contracts/personas.contract.d.ts.map +1 -0
- package/dist/contracts/personas.contract.js +13 -0
- package/dist/contracts/personas.contract.js.map +1 -0
- package/dist/contracts/ports.contract.d.ts +1 -0
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.d.ts +1 -0
- package/dist/contracts/prepush.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.js.map +1 -1
- package/dist/contracts/{codex.contract.d.ts → providers.contract.d.ts} +11 -3
- package/dist/contracts/providers.contract.d.ts.map +1 -0
- package/dist/contracts/providers.contract.js +6 -0
- package/dist/contracts/providers.contract.js.map +1 -0
- package/dist/contracts/public.contract.d.ts +24 -0
- package/dist/contracts/public.contract.d.ts.map +1 -0
- package/dist/contracts/public.contract.js +8 -0
- package/dist/contracts/public.contract.js.map +1 -0
- package/dist/contracts/push.contract.d.ts +3 -3
- package/dist/contracts/secrets.contract.d.ts +9 -0
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +16 -1
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.js +1 -1
- package/dist/contracts/sessions.contract.js.map +1 -1
- package/dist/contracts/settings.contract.d.ts +352 -58
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +2 -1
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/share.contract.d.ts +55 -0
- package/dist/contracts/share.contract.d.ts.map +1 -0
- package/dist/contracts/share.contract.js +9 -0
- package/dist/contracts/share.contract.js.map +1 -0
- package/dist/contracts/skills.contract.d.ts +41 -0
- package/dist/contracts/skills.contract.d.ts.map +1 -0
- package/dist/contracts/skills.contract.js +9 -0
- package/dist/contracts/skills.contract.js.map +1 -0
- package/dist/contracts/system.contract.d.ts +205 -8
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +10 -2
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/workflows.contract.d.ts +575 -0
- package/dist/contracts/workflows.contract.d.ts.map +1 -0
- package/dist/contracts/workflows.contract.js +13 -0
- package/dist/contracts/workflows.contract.js.map +1 -0
- package/dist/contracts/workspace.contract.d.ts +18 -4
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +6 -3
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/conversation-ids.d.ts +2 -0
- package/dist/conversation-ids.d.ts.map +1 -0
- package/dist/conversation-ids.js +123 -0
- package/dist/conversation-ids.js.map +1 -0
- package/dist/events.d.ts +523 -17
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +106 -13
- package/dist/events.js.map +1 -1
- package/dist/history-state.d.ts.map +1 -1
- package/dist/history-state.js +10 -0
- package/dist/history-state.js.map +1 -1
- package/dist/hostnames.d.ts +4 -0
- package/dist/hostnames.d.ts.map +1 -1
- package/dist/hostnames.js +7 -2
- package/dist/hostnames.js.map +1 -1
- package/dist/index.d.ts +3792 -478
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -17
- package/dist/index.js.map +1 -1
- package/dist/listener-protocol.d.ts +77 -0
- package/dist/listener-protocol.d.ts.map +1 -0
- package/dist/listener-protocol.js +33 -0
- package/dist/listener-protocol.js.map +1 -0
- package/dist/model-order.d.ts +1 -0
- package/dist/model-order.d.ts.map +1 -1
- package/dist/model-order.js +16 -0
- package/dist/model-order.js.map +1 -1
- package/dist/output-fields.d.ts +30 -0
- package/dist/output-fields.d.ts.map +1 -0
- package/dist/output-fields.js +57 -0
- package/dist/output-fields.js.map +1 -0
- package/dist/path-refs.d.ts.map +1 -1
- package/dist/path-refs.js.map +1 -1
- package/dist/publish-drafts.d.ts +7 -0
- package/dist/publish-drafts.d.ts.map +1 -0
- package/dist/publish-drafts.js +20 -0
- package/dist/publish-drafts.js.map +1 -0
- package/dist/quick-model.d.ts +2 -1
- package/dist/quick-model.d.ts.map +1 -1
- package/dist/quick-model.js +18 -14
- package/dist/quick-model.js.map +1 -1
- package/dist/routes.d.ts +3 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +63 -0
- package/dist/routes.js.map +1 -1
- package/dist/runtime-state.d.ts +33 -0
- package/dist/runtime-state.d.ts.map +1 -0
- package/dist/runtime-state.js +14 -0
- package/dist/runtime-state.js.map +1 -0
- package/dist/schemas.d.ts +4889 -851
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +993 -192
- package/dist/schemas.js.map +1 -1
- package/dist/search-globs.d.ts +6 -0
- package/dist/search-globs.d.ts.map +1 -0
- package/dist/search-globs.js +39 -0
- package/dist/search-globs.js.map +1 -0
- package/dist/share-paths.d.ts +8 -0
- package/dist/share-paths.d.ts.map +1 -0
- package/dist/share-paths.js +21 -0
- package/dist/share-paths.js.map +1 -0
- package/dist/title.d.ts.map +1 -1
- package/dist/title.js +1 -1
- package/dist/title.js.map +1 -1
- package/dist/tunnel-ids.d.ts +1 -0
- package/dist/tunnel-ids.d.ts.map +1 -1
- package/dist/tunnel-ids.js +1 -0
- package/dist/tunnel-ids.js.map +1 -1
- package/dist/versions.d.ts +4 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +15 -0
- package/dist/versions.js.map +1 -0
- package/dist/workflow-faults.d.ts +4 -0
- package/dist/workflow-faults.d.ts.map +1 -0
- package/dist/workflow-faults.js +115 -0
- package/dist/workflow-faults.js.map +1 -0
- package/dist/workspace-state.d.ts +280 -1
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +163 -21
- package/dist/workspace-state.js.map +1 -1
- package/package.json +30 -6
- package/src/agent-catalog.test.ts +147 -7
- package/src/agent-catalog.ts +233 -21
- package/src/agent-run-model.test.ts +60 -0
- package/src/agent-run-model.ts +51 -0
- package/src/capability-env.ts +10 -0
- package/src/capability-ledger.test.ts +110 -0
- package/src/chores/chores.test.ts +32 -0
- package/src/chores/chores.ts +456 -32
- package/src/chores/digest.ts +0 -0
- package/src/chores/extension-update.ts +37 -0
- package/src/chores/fix-deps.ts +40 -0
- package/src/chores/index.ts +7 -2
- package/src/chores/probes.test.ts +155 -3
- package/src/chores/probes.ts +200 -4
- package/src/chores/stack.test.ts +126 -0
- package/src/chores/stack.ts +244 -0
- package/src/chores/verdict.test.ts +357 -19
- package/src/chores/verdict.ts +95 -21
- package/src/contract-lock.test.ts +17 -0
- package/src/contract-lock.ts +66 -0
- package/src/contracts/agent.contract.ts +6 -0
- package/src/contracts/agents.contract.ts +12 -0
- package/src/contracts/automations.contract.ts +17 -1
- package/src/contracts/capabilities.contract.ts +22 -0
- package/src/contracts/ci.contract.ts +10 -2
- package/src/contracts/claude.contract.ts +8 -4
- package/src/contracts/endpoints.contract.ts +21 -0
- package/src/contracts/extensions.contract.ts +42 -0
- package/src/contracts/git.contract.ts +33 -6
- package/src/contracts/grok.contract.ts +4 -3
- package/src/contracts/host.contract.ts +13 -2
- package/src/contracts/loops.contract.ts +62 -0
- package/src/contracts/personas.contract.ts +56 -0
- package/src/contracts/prepush.contract.ts +4 -3
- package/src/contracts/providers.contract.ts +23 -0
- package/src/contracts/public.contract.ts +14 -0
- package/src/contracts/sessions.contract.ts +5 -1
- package/src/contracts/settings.contract.ts +5 -0
- package/src/contracts/share.contract.ts +20 -0
- package/src/contracts/skills.contract.ts +24 -0
- package/src/contracts/system.contract.ts +27 -3
- package/src/contracts/workflows.contract.ts +78 -0
- package/src/contracts/workspace.contract.ts +10 -2
- package/src/conversation-ids.test.ts +61 -0
- package/src/conversation-ids.ts +173 -0
- package/src/events.test.ts +20 -1
- package/src/events.ts +404 -43
- package/src/history-state.ts +30 -1
- package/src/hostnames.ts +20 -6
- package/src/index.ts +58 -17
- package/src/listener-protocol.ts +96 -0
- package/src/model-order.test.ts +32 -1
- package/src/model-order.ts +56 -3
- package/src/output-fields.ts +111 -0
- package/src/path-refs.test.ts +4 -4
- package/src/path-refs.ts +5 -5
- package/src/publish-drafts.ts +59 -0
- package/src/quick-model.test.ts +70 -27
- package/src/quick-model.ts +76 -40
- package/src/routes.test.ts +91 -3
- package/src/routes.ts +135 -0
- package/src/runtime-state.test.ts +45 -0
- package/src/runtime-state.ts +124 -0
- package/src/schemas.test.ts +16 -29
- package/src/schemas.ts +3480 -552
- package/src/search-globs.test.ts +46 -0
- package/src/search-globs.ts +76 -0
- package/src/share-paths.test.ts +48 -0
- package/src/share-paths.ts +68 -0
- package/src/state-portability.ts +8 -8
- package/src/title.test.ts +20 -1
- package/src/title.ts +12 -3
- package/src/tunnel-ids.ts +15 -1
- package/src/versions.test.ts +39 -0
- package/src/versions.ts +48 -0
- package/src/workflow-faults.test.ts +127 -0
- package/src/workflow-faults.ts +188 -0
- package/src/workspace-state.test.ts +169 -15
- package/src/workspace-state.ts +427 -35
- package/dist/contracts/codex.contract.d.ts.map +0 -1
- package/dist/contracts/codex.contract.js +0 -6
- package/dist/contracts/codex.contract.js.map +0 -1
- package/dist/contracts/gemini.contract.d.ts +0 -16
- package/dist/contracts/gemini.contract.d.ts.map +0 -1
- package/dist/contracts/gemini.contract.js +0 -6
- package/dist/contracts/gemini.contract.js.map +0 -1
- package/dist/contracts/kimi.contract.d.ts +0 -16
- package/dist/contracts/kimi.contract.d.ts.map +0 -1
- package/dist/contracts/kimi.contract.js +0 -6
- package/dist/contracts/kimi.contract.js.map +0 -1
- package/dist/contracts/komodo.contract.d.ts +0 -125
- package/dist/contracts/komodo.contract.d.ts.map +0 -1
- package/dist/contracts/komodo.contract.js +0 -14
- package/dist/contracts/komodo.contract.js.map +0 -1
- package/dist/contracts/memory.contract.d.ts +0 -34
- package/dist/contracts/memory.contract.d.ts.map +0 -1
- package/dist/contracts/memory.contract.js +0 -9
- package/dist/contracts/memory.contract.js.map +0 -1
- package/src/contracts/codex.contract.ts +0 -10
- package/src/contracts/gemini.contract.ts +0 -11
- package/src/contracts/kimi.contract.ts +0 -9
- package/src/contracts/komodo.contract.ts +0 -36
- package/src/contracts/memory.contract.ts +0 -13
package/src/agent-catalog.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type AgentHarness,
|
|
3
|
+
type AgentProvider,
|
|
4
|
+
type Model,
|
|
5
|
+
type ModelBadge,
|
|
6
|
+
NATIVE_PROVIDERS,
|
|
7
|
+
type NativeProvider,
|
|
8
|
+
type PermissionMode,
|
|
9
|
+
} from "./schemas.js";
|
|
2
10
|
|
|
3
11
|
/* The provider / harness / model catalog every picker shares (the chat menu, the automations dialog) — pure
|
|
4
12
|
* data keyed by the wire vocabulary in schemas.ts, so the surfaces can't drift. Live state stays with the
|
|
5
|
-
* consumer (native Grok's model list is the daemon's
|
|
13
|
+
* consumer (native Grok's model list is the daemon's own catalog for it, layered on top of modelsFor by the
|
|
6
14
|
* web; ACP providers are merged in from the installed `agent` capabilities). */
|
|
7
15
|
|
|
8
16
|
export interface CatalogOption {
|
|
@@ -46,6 +54,17 @@ export const PROVIDER_ACCESS: Record<NativeProvider, ProviderAccess> = {
|
|
|
46
54
|
gemini: { kind: "free", requirement: "Google sign-in", runs: "Gemini, Claude and GPT-OSS under Claude Code" },
|
|
47
55
|
};
|
|
48
56
|
|
|
57
|
+
/* THE PROVIDERS THAT COST NOTHING — derived from the table above rather than named a second time, and read by
|
|
58
|
+
* every surface that LEADS with a free option instead of merely labelling one.
|
|
59
|
+
*
|
|
60
|
+
* The distinction is worth the export. `accessBadge` answers "what does this row cost" for a row the user is
|
|
61
|
+
* already looking at; this answers "which row should a user who has connected nothing be shown FIRST", which is
|
|
62
|
+
* the connect gate's whole job. Ranking the one free channel fifth among five equal buttons is how a user with
|
|
63
|
+
* no subscription concluded the product needed one. Deriving the list keeps that promotion honest: a channel
|
|
64
|
+
* that stops being free stops being promoted, from one edit to PROVIDER_ACCESS. */
|
|
65
|
+
export const FREE_PROVIDERS: readonly NativeProvider[] = NATIVE_PROVIDERS.filter((provider) => PROVIDER_ACCESS[provider].kind === "free");
|
|
66
|
+
export const isFreeProvider = (provider: AgentProvider): boolean => FREE_PROVIDERS.includes(provider as NativeProvider);
|
|
67
|
+
|
|
49
68
|
/* WHOSE ALLOWANCE A TURN ON THIS PROVIDER SPENDS, as the subject of a sentence — a third naming of the same
|
|
50
69
|
* five ids, and the third is not redundancy. PROVIDERS names the RUNTIME the user picks ("Claude Code", "Kimi
|
|
51
70
|
* Code") and PROVIDER_ACCESS.requirement names the thing they CONNECT ("Claude subscription", "Google sign-in");
|
|
@@ -81,6 +100,26 @@ export const ACCESS_COST: Record<AccessKind, number> = { free: 0, subscription:
|
|
|
81
100
|
* splits a pinned selection on the FIRST colon. `endpoint:ollama:qwen3` would parse as provider "endpoint" with
|
|
82
101
|
* model "ollama:qwen3" — a pin that silently resolves to nothing. The capability id (entryId) excludes both
|
|
83
102
|
* characters, so `endpoint/<id>` stays unambiguous in either direction. */
|
|
103
|
+
/* THE FREE TRIAL'S ENDPOINT ID IS RESERVED, the way `pi` is — an `endpoint`-kind capability like any model API
|
|
104
|
+
* the user configured, except that this one is provisioned by the DAEMON rather than added by a person, and it
|
|
105
|
+
* points at intentic's own pool (see the sandbox's trial/ and the platform's /trial routes).
|
|
106
|
+
*
|
|
107
|
+
* Riding the endpoint kind is the entire reason the trial needed no new turn path, no new provider and no new
|
|
108
|
+
* adapter: the translator already re-serves an OpenAI-compatible upstream to the Claude Code loop, so a trial
|
|
109
|
+
* turn is an endpoint turn and everything downstream — catalog, picker, routing — works unchanged.
|
|
110
|
+
*
|
|
111
|
+
* What the reserved id buys is the part that must NOT look the same. A trial turn passes through intentic's
|
|
112
|
+
* servers, which no other provider in this product does, and a user cannot consent to something they were not
|
|
113
|
+
* told. So every surface that names a provider asks `isTrialProvider` and says so, and the id is here — beside
|
|
114
|
+
* the vocabulary those surfaces already read — rather than spelled out in each of them. */
|
|
115
|
+
export const TRIAL_ENDPOINT_ID = "free-trial";
|
|
116
|
+
export const TRIAL_PROVIDER = "endpoint/free-trial";
|
|
117
|
+
export const isTrialProvider = (provider: AgentProvider): boolean => provider === TRIAL_PROVIDER;
|
|
118
|
+
// What the picker calls it, and the sentence the surfaces put underneath. One wording, so the composer's notice
|
|
119
|
+
// and the picker's row cannot end up describing different bargains.
|
|
120
|
+
export const TRIAL_LABEL = "Free trial";
|
|
121
|
+
export const TRIAL_NOTICE = "Trial messages pass through intentic's servers. Connect an account to chat directly.";
|
|
122
|
+
|
|
84
123
|
export const ENDPOINT_PROVIDER_PREFIX = "endpoint/";
|
|
85
124
|
export const endpointProvider = (id: string): AgentProvider => `${ENDPOINT_PROVIDER_PREFIX}${id}`;
|
|
86
125
|
export const isEndpointProvider = (provider: AgentProvider): boolean => provider.startsWith(ENDPOINT_PROVIDER_PREFIX);
|
|
@@ -119,9 +158,15 @@ export const PLAN_LIMIT_PROVIDERS: readonly NativeProvider[] = ["claude", "codex
|
|
|
119
158
|
export const reportsPlanLimits = (provider: AgentProvider): boolean => PLAN_LIMIT_PROVIDERS.includes(provider as NativeProvider);
|
|
120
159
|
|
|
121
160
|
// The harness (agentic loop) a turn runs on, orthogonal to the provider. `native` = the provider's own runtime;
|
|
122
|
-
// `claude-code` = the Claude Code loop for any provider (codex/grok then route through the translator).
|
|
123
|
-
//
|
|
124
|
-
// to
|
|
161
|
+
// `claude-code` = the Claude Code loop for any provider (codex/grok then route through the translator).
|
|
162
|
+
// Surfaced for codex/grok alone. Claude is always its own Claude Code loop; kimi has no native runtime to switch
|
|
163
|
+
// to (it only exists under this harness); and GEMINI IS THE MIRROR OF KIMI — it only exists under its native
|
|
164
|
+
// one, because Google refuses Claude Code's traffic outright (capabilitiesOf says why). See AgentHarness in
|
|
165
|
+
// schemas.ts.
|
|
166
|
+
//
|
|
167
|
+
// Gemini's `native` is OpenCode rather than a Google CLI: the image ships no Gemini binary, and OpenCode is
|
|
168
|
+
// already here driving Grok. It spends the same translator accounts a routed turn would have — what Google
|
|
169
|
+
// refuses is the loop, never the credential.
|
|
125
170
|
export const HARNESSES: readonly { label: string; value: AgentHarness }[] = [
|
|
126
171
|
{ label: "Native", value: "native" },
|
|
127
172
|
{ label: "Claude Code", value: "claude-code" },
|
|
@@ -129,8 +174,8 @@ export const HARNESSES: readonly { label: string; value: AgentHarness }[] = [
|
|
|
129
174
|
|
|
130
175
|
/* WHAT A PROVIDER/HARNESS PAIR CAN ACTUALLY DO — one declaration, read by both sides of the wire.
|
|
131
176
|
*
|
|
132
|
-
*
|
|
133
|
-
* loop, Codex
|
|
177
|
+
* Five runtimes serve turns behind one seam (AgentRequest in, AgentEvent frames out): the Claude Code Agent SDK
|
|
178
|
+
* loop, Codex app-server, OpenCode, any ACP agent, and Pi's RPC surface. They do NOT do the same things, and for a long time
|
|
134
179
|
* the only thing that said so was a comment inside each adapter — "Ignores the Claude-only request fields" —
|
|
135
180
|
* which no surface above it could read. So the composer offered "Ask before each file edit" on a runtime whose
|
|
136
181
|
* every tool call is pre-approved, and offered a reasoning-effort scale to a runtime that drops the field.
|
|
@@ -141,13 +186,22 @@ export const HARNESSES: readonly { label: string; value: AgentHarness }[] = [
|
|
|
141
186
|
*
|
|
142
187
|
* Adding a provider is a row here, not a hunt for literals; agent-catalog.test.ts walks PROVIDERS × HARNESSES
|
|
143
188
|
* and demands one, so a pair can never be silently absent. */
|
|
189
|
+
|
|
190
|
+
// An execution backend: one way a turn runs work of its own, named for the AgentCapabilities.execution axis
|
|
191
|
+
// and for the persona switch that grants it. Adding a language is a member here and a backend in the daemon's
|
|
192
|
+
// execution/ module — never a new one-off tool wired where nothing else can see it.
|
|
193
|
+
export type ExecutionBackend = "shell" | "js";
|
|
194
|
+
|
|
144
195
|
export interface AgentCapabilities {
|
|
145
196
|
// Which agentic loop actually serves the turn — the question "is the harness `claude-code`" only looks like.
|
|
146
|
-
// Claude is always its own Claude Code loop
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
|
|
197
|
+
// Claude is always its own Claude Code loop and Kimi has no native runtime, so both run it whatever harness
|
|
198
|
+
// the client sent; codex/grok/gemini each have a native runtime to switch away from. Names the session store
|
|
199
|
+
// a finished conversation's transcript is backfilled from, too.
|
|
200
|
+
//
|
|
201
|
+
// `opencode-gemini` is the OpenCode loop pointed at Gemini rather than at xAI, and it is a SEPARATE runtime
|
|
202
|
+
// id from `opencode` on purpose: adapter health is keyed by this field (adapter-health.ts), so sharing one
|
|
203
|
+
// would make Grok's xAI credential decide whether the picker greys out Gemini, and the reverse.
|
|
204
|
+
readonly runtime: "claude-code" | "codex" | "opencode" | "opencode-gemini" | "acp" | "pi";
|
|
151
205
|
// Mid-turn injection (the SteeringQueue behind /agent/steer). Needs the SDK's streaming-input mode.
|
|
152
206
|
readonly steering: boolean;
|
|
153
207
|
// How much of the permission-mode axis the runtime honours. "modes" = every PermissionMode, with per-tool
|
|
@@ -160,8 +214,26 @@ export interface AgentCapabilities {
|
|
|
160
214
|
// checkouts + the browser servers; "http" = the http MCP tools alone, and only if the agent advertises http
|
|
161
215
|
// MCP support; "none" = the runtime has no seam for them at all.
|
|
162
216
|
readonly mcp: "full" | "http" | "none";
|
|
217
|
+
/* WHICH EXECUTION BACKENDS THE RUNTIME HOSTS — the ways a turn RUNS things, as opposed to the tools it is
|
|
218
|
+
* handed. "shell" is the runtime's own command tool (Bash on the Claude Code loop, each foreign loop's
|
|
219
|
+
* equivalent); "js" is the sandbox's JavaScript backend (execution/ in the daemon): the model writes a
|
|
220
|
+
* script instead of a command line, and the daemon runs it in a permission-fenced Node subprocess.
|
|
221
|
+
*
|
|
222
|
+
* A first-class axis rather than a corollary of `mcp`, because the two answer different questions: `mcp`
|
|
223
|
+
* says which TOOLS reach the model's context, this says which ways of EXECUTING the daemon can stand
|
|
224
|
+
* behind for this runtime — with the same guard, secret and persona seams the shell gets. A runtime that
|
|
225
|
+
* cannot host a backend simply never shows it, and the persona switch for it (PersonaPowersSchema.code)
|
|
226
|
+
* then has nothing to grant there. */
|
|
227
|
+
readonly execution: readonly ExecutionBackend[];
|
|
163
228
|
// Reasoning-effort selection is forwarded to the model.
|
|
164
229
|
readonly effort: boolean;
|
|
230
|
+
/* The runtime can serve a turn at fast speed when asked (AgentTurn.fast). A statement about the LOOP, not
|
|
231
|
+
* about the route: the Claude Code loop knows how to ask for it, which is why every provider this record
|
|
232
|
+
* hands the loop to reads true here — including the ones served through the translator, whose turns the
|
|
233
|
+
* harness will then refuse fast mode for because a translator endpoint is not first-party. That second
|
|
234
|
+
* question is answered where the endpoint is decided (planHarnessTurn), because it is a fact about the
|
|
235
|
+
* CREDENTIAL rather than about the runtime, and this record is a pure function of (provider, harness). */
|
|
236
|
+
readonly fastMode: boolean;
|
|
165
237
|
// How an isolated conversation's worktree is enforced. "namespace" = the worktree IS /work inside the turn's
|
|
166
238
|
// mount namespace (with the tool-input rewrite as the fallback when the container can't build one); "cwd" =
|
|
167
239
|
// the turn is merely cwd'd into the worktree, so an absolute /work path still reaches the shared checkout —
|
|
@@ -174,6 +246,28 @@ export interface AgentCapabilities {
|
|
|
174
246
|
// Fails with the coded frames the daemon's auto-resume keys off (rate_limit, provider-outage), so a turn the
|
|
175
247
|
// provider killed is re-run once the breaker says the provider is back (turn-resume.ts).
|
|
176
248
|
readonly recovery: boolean;
|
|
249
|
+
/* HOW MUCH OF ITS STANDING INSTRUCTIONS THIS RUNTIME WILL TAKE FROM US — the axis behind the sandbox's
|
|
250
|
+
* system-prompt setting (SandboxSettings.systemPromptMode) and the persona's own override.
|
|
251
|
+
*
|
|
252
|
+
* It exists because that setting was silently a Claude Code setting. The composer offers Codex, Grok and
|
|
253
|
+
* Gemini on their own runtimes, and a turn on any of them ignored the prompt the owner had written without
|
|
254
|
+
* saying so anywhere — the one failure mode a settings page cannot recover from, because nothing on screen
|
|
255
|
+
* is wrong. Naming it here means every surface reads the same answer and the daemon composes to it
|
|
256
|
+
* (agent/system-prompt.ts), rather than each learning the exception separately.
|
|
257
|
+
*
|
|
258
|
+
* "replace" — the whole base prompt can be swapped for the owner's text, and extra guidance appended on
|
|
259
|
+
* top of whichever base is in force. The Claude Code loop (SDK `systemPrompt`) and native
|
|
260
|
+
* Codex (`model_instructions_file` replaces its base; `developer_instructions` adds a
|
|
261
|
+
* developer message — both verified on the wire against codex-cli 0.147).
|
|
262
|
+
* "append" — extra system text only; the runtime's own base prompt stands. OpenCode takes one per
|
|
263
|
+
* message (`system` on the prompt body), and there is no seam for replacing its base.
|
|
264
|
+
* "none" — no system seam at all. What must still reach the model (the persona note) rides the user
|
|
265
|
+
* message instead, which is the door the delegation note already uses.
|
|
266
|
+
*
|
|
267
|
+
* The BASE CHOICE — Intentic's prompt or Claude Code's — is a "replace" runtime's question and, of those,
|
|
268
|
+
* only the Claude Code loop's: Codex's own base describes Codex's own tools, so swapping it for a prompt
|
|
269
|
+
* written about another harness is the owner's deliberate act (their custom text), never ours. */
|
|
270
|
+
readonly instructions: "replace" | "append" | "none";
|
|
177
271
|
}
|
|
178
272
|
|
|
179
273
|
// The Claude Code Agent SDK loop — the ceiling every other runtime is measured against, and the only one that
|
|
@@ -185,41 +279,78 @@ const CLAUDE_CODE: AgentCapabilities = {
|
|
|
185
279
|
permissions: "modes",
|
|
186
280
|
questions: true,
|
|
187
281
|
mcp: "full",
|
|
282
|
+
// The one loop with a seam the daemon can put its own backend through — so it hosts the JS backend beside
|
|
283
|
+
// its Bash. Every other runtime below hosts only its own shell.
|
|
284
|
+
execution: ["shell", "js"],
|
|
188
285
|
effort: true,
|
|
286
|
+
fastMode: true,
|
|
189
287
|
isolation: "namespace",
|
|
190
288
|
commands: true,
|
|
191
289
|
terminals: true,
|
|
192
290
|
recovery: true,
|
|
291
|
+
instructions: "replace",
|
|
193
292
|
};
|
|
194
293
|
|
|
195
|
-
// Codex
|
|
196
|
-
//
|
|
294
|
+
// Codex app-server: item-level events plus richer request/MCP channels. This client deliberately declines
|
|
295
|
+
// server-initiated approvals and has not connected questions or MCP to Intentic's policy seams, so only the
|
|
296
|
+
// item stream, reasoning effort and the instruction seam are claimed here.
|
|
197
297
|
const CODEX: AgentCapabilities = {
|
|
198
298
|
runtime: "codex",
|
|
199
299
|
steering: false,
|
|
200
300
|
permissions: "plan",
|
|
201
301
|
questions: false,
|
|
202
302
|
mcp: "none",
|
|
303
|
+
execution: ["shell"],
|
|
203
304
|
effort: true,
|
|
305
|
+
fastMode: false,
|
|
204
306
|
isolation: "cwd",
|
|
205
307
|
commands: false,
|
|
206
308
|
terminals: false,
|
|
207
309
|
recovery: false,
|
|
310
|
+
/* Both halves, through the per-thread `config` block the adapter already sends: `model_instructions_file`
|
|
311
|
+
* takes the place of Codex's own base prompt, `developer_instructions` arrives as an extra developer
|
|
312
|
+
* message ahead of its skills and team blocks. Verified against codex-cli 0.147 by reading what actually
|
|
313
|
+
* reached the wire — the keys are undocumented, and a strings dump proves only that they parse. */
|
|
314
|
+
instructions: "replace",
|
|
208
315
|
};
|
|
209
316
|
|
|
210
|
-
// OpenCode (the Grok runtime): its own agentic loop, its own tools, allow-all permissions. It takes a model id
|
|
211
|
-
//
|
|
317
|
+
// OpenCode (the Grok runtime): its own agentic loop, its own tools, allow-all permissions. It takes a model id,
|
|
318
|
+
// a prompt and one system message of ours — no effort scale, no tools of ours, no command list.
|
|
212
319
|
const OPENCODE: AgentCapabilities = {
|
|
213
320
|
runtime: "opencode",
|
|
214
321
|
steering: false,
|
|
215
322
|
permissions: "plan",
|
|
216
323
|
questions: false,
|
|
217
324
|
mcp: "none",
|
|
325
|
+
execution: ["shell"],
|
|
218
326
|
effort: false,
|
|
327
|
+
fastMode: false,
|
|
219
328
|
isolation: "cwd",
|
|
220
329
|
commands: false,
|
|
221
330
|
terminals: false,
|
|
222
331
|
recovery: false,
|
|
332
|
+
// `system` on the prompt body, per message. It ADDS to OpenCode's own prompt — there is no seam for
|
|
333
|
+
// replacing that — so a custom prompt lands here as extra instructions, and the settings page says so
|
|
334
|
+
// rather than letting "replaces everything" quietly mean something else on two providers.
|
|
335
|
+
instructions: "append",
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
/* The same OpenCode loop, serving Gemini instead of xAI — identical abilities, which is the point of giving it
|
|
339
|
+
* its own row rather than its own record shape.
|
|
340
|
+
*
|
|
341
|
+
* It exists because the alternative was Gemini's ONLY route being the Claude Code loop, and that loop announces
|
|
342
|
+
* itself to whatever it is pointed at: the CLI prepends its own "You are a Claude agent, built on Anthropic's
|
|
343
|
+
* Claude Agent SDK." to every request, baked into the binary with no option to suppress it. Google's Antigravity
|
|
344
|
+
* channel matches that exact sentence and refuses the request — reported as a quota error, which sent the
|
|
345
|
+
* translator walking all 31 connected accounts looking for one with room, ~60s per attempt, none of which could
|
|
346
|
+
* ever have answered. Under this runtime the request carries OpenCode's own prompt, so the turn is simply not
|
|
347
|
+
* Claude Code traffic and the block has nothing to match.
|
|
348
|
+
*
|
|
349
|
+
* The credential is unchanged: both harnesses reach Google through the translator and the same auth files. Only
|
|
350
|
+
* the loop around the model differs. */
|
|
351
|
+
const OPENCODE_GEMINI: AgentCapabilities = {
|
|
352
|
+
...OPENCODE,
|
|
353
|
+
runtime: "opencode-gemini",
|
|
223
354
|
};
|
|
224
355
|
|
|
225
356
|
// Any agent speaking the Agent Client Protocol: a documented floor rather than the native ceiling. It publishes
|
|
@@ -231,17 +362,53 @@ const ACP: AgentCapabilities = {
|
|
|
231
362
|
permissions: "plan",
|
|
232
363
|
questions: false,
|
|
233
364
|
mcp: "http",
|
|
365
|
+
execution: ["shell"],
|
|
234
366
|
effort: false,
|
|
367
|
+
fastMode: false,
|
|
235
368
|
isolation: "cwd",
|
|
236
369
|
commands: true,
|
|
237
370
|
terminals: true,
|
|
238
371
|
recovery: false,
|
|
372
|
+
// ACP's `session/new` and `session/prompt` carry no system field: the agent owns its own instructions the
|
|
373
|
+
// same way it owns its model and its permission posture. The persona note takes the user message instead.
|
|
374
|
+
instructions: "none",
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
/* THE PI CAPABILITY ID IS RESERVED, the same way the five native ids are: an `agent`-kind capability installed
|
|
378
|
+
* under it is served over Pi's own RPC protocol rather than ACP — Pi closed ACP support deliberately (its RPC
|
|
379
|
+
* mode is the embedding surface), and the two want different records. A bare id rather than a namespace like
|
|
380
|
+
* `endpoint/`, because there is exactly one Pi runtime to name; capabilitiesOf still answers from the id alone,
|
|
381
|
+
* which is what keeps it a pure function of (provider, harness). */
|
|
382
|
+
export const PI_PROVIDER = "pi";
|
|
383
|
+
|
|
384
|
+
// Pi driven over its RPC mode (`pi --mode rpc`, strict-LF JSONL over stdio): above the ACP floor and below the
|
|
385
|
+
// Claude Code ceiling. Its `steer` command is real mid-turn injection; `set_thinking_level` takes the effort
|
|
386
|
+
// tiers; `get_commands` publishes its extension/skill commands. It has no MCP seam (Pi's own extensions are its
|
|
387
|
+
// tool surface), no approval channel (plan is the shared two-phase emulation), and runs bash in-process — no
|
|
388
|
+
// tmux session for the terminal panel to attach to.
|
|
389
|
+
const PI: AgentCapabilities = {
|
|
390
|
+
runtime: "pi",
|
|
391
|
+
steering: true,
|
|
392
|
+
permissions: "plan",
|
|
393
|
+
questions: false,
|
|
394
|
+
mcp: "none",
|
|
395
|
+
execution: ["shell"],
|
|
396
|
+
effort: true,
|
|
397
|
+
fastMode: false,
|
|
398
|
+
isolation: "cwd",
|
|
399
|
+
commands: true,
|
|
400
|
+
terminals: false,
|
|
401
|
+
recovery: false,
|
|
402
|
+
// Pi's RPC opens a session with a prompt and steers it; nothing in that protocol sets standing
|
|
403
|
+
// instructions, so like ACP it hears the persona note through the user message.
|
|
404
|
+
instructions: "none",
|
|
239
405
|
};
|
|
240
406
|
|
|
241
407
|
// The pair → its record. An `endpoint/<id>` provider is a model API the user configured, driven BY the Claude
|
|
242
408
|
// Code loop on either harness — so it gets that loop's full ceiling, which is the entire point of routing a
|
|
243
|
-
// model through it rather than adopting a second runtime.
|
|
244
|
-
//
|
|
409
|
+
// model through it rather than adopting a second runtime. The reserved `pi` id is the Pi coding agent on its
|
|
410
|
+
// own RPC runtime (harness doesn't apply — Pi is its own loop, like ACP). Any other id that names no native
|
|
411
|
+
// provider is an installed `agent`-kind capability, served over ACP.
|
|
245
412
|
export const capabilitiesOf = (provider: AgentProvider, harness: AgentHarness): AgentCapabilities => {
|
|
246
413
|
if (provider === "codex") {
|
|
247
414
|
return harness === "claude-code" ? CLAUDE_CODE : CODEX;
|
|
@@ -249,9 +416,24 @@ export const capabilitiesOf = (provider: AgentProvider, harness: AgentHarness):
|
|
|
249
416
|
if (provider === "grok") {
|
|
250
417
|
return harness === "claude-code" ? CLAUDE_CODE : OPENCODE;
|
|
251
418
|
}
|
|
419
|
+
/* GEMINI IGNORES THE HARNESS, and it is the only routed provider that does. The Claude Code loop announces
|
|
420
|
+
* itself in every request it sends and Google refuses on that announcement (see OPENCODE_GEMINI), so
|
|
421
|
+
* "Gemini under Claude Code" was never a slower or poorer option — it was one that could not complete a
|
|
422
|
+
* single turn, on any of the connected accounts, ever.
|
|
423
|
+
*
|
|
424
|
+
* Answering OPENCODE_GEMINI whatever the caller asked for is what makes that structural rather than a rule
|
|
425
|
+
* each surface has to remember. Everything downstream reads the runtime off this record — the adapter that
|
|
426
|
+
* serves a turn, the transcript store, the quick helper's choice of loop — so there is exactly one place
|
|
427
|
+
* where Gemini's loop is decided, and no way left to route Claude Code traffic at Google by asking for it. */
|
|
428
|
+
if (provider === "gemini") {
|
|
429
|
+
return OPENCODE_GEMINI;
|
|
430
|
+
}
|
|
252
431
|
if (isEndpointProvider(provider)) {
|
|
253
432
|
return CLAUDE_CODE;
|
|
254
433
|
}
|
|
434
|
+
if (provider === PI_PROVIDER) {
|
|
435
|
+
return PI;
|
|
436
|
+
}
|
|
255
437
|
return (NATIVE_PROVIDERS as readonly string[]).includes(provider) ? CLAUDE_CODE : ACP;
|
|
256
438
|
};
|
|
257
439
|
|
|
@@ -265,18 +447,30 @@ export const modesFor = (capabilities: AgentCapabilities): readonly PermissionMo
|
|
|
265
447
|
export const clampMode = (mode: PermissionMode, capabilities: AgentCapabilities): PermissionMode =>
|
|
266
448
|
modesFor(capabilities).includes(mode) ? mode : "bypassPermissions";
|
|
267
449
|
|
|
268
|
-
|
|
269
|
-
|
|
450
|
+
/* What this pair does NOT do, phrased for the person about to send a message to it — the honest half of the
|
|
451
|
+
* picker, and the reason the record carries axes the daemon itself never branches on. Empty ⇒ the full ceiling.
|
|
452
|
+
*
|
|
453
|
+
* `fastMode` is deliberately NOT disclosed here, and it is the one axis that can't be: every other axis is fully
|
|
454
|
+
* determined by the record, while fast mode also depends on the route and the model. The record says true for
|
|
455
|
+
* every provider the Claude Code loop serves — including the ones routed through the translator, which can never
|
|
456
|
+
* go fast — so a sentence derived from it would stay silent for exactly the turns that most need to hear it.
|
|
457
|
+
* fastAllowed answers the real question, and the `fast_mode` frame reports what the turn actually got. */
|
|
270
458
|
export const limitationsOf = (capabilities: AgentCapabilities): string[] => [
|
|
271
459
|
...(capabilities.permissions === "plan" ? ["no per-tool approvals"] : []),
|
|
272
460
|
...(capabilities.questions ? [] : ["no clarifying questions"]),
|
|
273
461
|
...(capabilities.steering ? [] : ["no mid-turn steering"]),
|
|
274
462
|
...(capabilities.mcp === "none" ? ["no MCP tools or plugins"] : capabilities.mcp === "http" ? ["MCP tools only — no plugins or browser"] : []),
|
|
463
|
+
...(capabilities.execution.includes("js") ? [] : ["no code runs — its shell is the one way to execute"]),
|
|
275
464
|
...(capabilities.effort ? [] : ["no effort control"]),
|
|
276
465
|
...(capabilities.commands ? [] : ["no slash commands"]),
|
|
277
466
|
...(capabilities.terminals ? [] : ["no terminal panel"]),
|
|
278
467
|
...(capabilities.isolation === "namespace" ? [] : ["worktree by working directory only"]),
|
|
279
468
|
...(capabilities.recovery ? [] : ["no auto-resume after an outage"]),
|
|
469
|
+
/* The two weaker answers on the instruction axis, and only those: "replace" is the ceiling this list
|
|
470
|
+
* measures against, so it has nothing to disclose. Both phrasings name the OWNER'S prompt rather than the
|
|
471
|
+
* mechanism, because that is the thing they wrote and the thing that will or will not be in force. */
|
|
472
|
+
...(capabilities.instructions === "append" ? ["your system prompt is added to theirs, not replacing it"] : []),
|
|
473
|
+
...(capabilities.instructions === "none" ? ["your system prompt isn't applied"] : []),
|
|
280
474
|
];
|
|
281
475
|
|
|
282
476
|
// Claude's compile-time model floor, shared by the daemon's catalog (claude-models.ts — its last rung, reached
|
|
@@ -293,7 +487,7 @@ export const CLAUDE_SEED_MODELS: readonly Model[] = [
|
|
|
293
487
|
];
|
|
294
488
|
|
|
295
489
|
// The STATIC floor of the model catalog, harness-independent: every provider's real list is the daemon's live
|
|
296
|
-
// catalog (/
|
|
490
|
+
// catalog (/providers/{provider}/models — discovery with a persisted/seed floor, never empty),
|
|
297
491
|
// which consumers layer on top. Codex/grok are empty here (nothing sensible to offer before the live load — and
|
|
298
492
|
// under the Claude Code harness they route through the translator, which serves the SAME subscription model ids
|
|
299
493
|
// as the native catalog, so the harness no longer changes the list).
|
|
@@ -329,3 +523,21 @@ export const effortAllowed = (effort: string, provider: AgentProvider, thinking:
|
|
|
329
523
|
* 400 by spending the user's money. */
|
|
330
524
|
export const sendableEffort = (effort: string | undefined, thinking: boolean | undefined): string | undefined =>
|
|
331
525
|
effort === "max" && thinking !== true ? "high" : effort;
|
|
526
|
+
|
|
527
|
+
/* WHETHER FAST SPEED CAN BE OFFERED for a provider/harness/model triple — the picker-side filter, the same
|
|
528
|
+
* shape and the same reason as effortAllowed: the composer must not show a control that does nothing.
|
|
529
|
+
*
|
|
530
|
+
* Three conditions, each answering a different question, and all three are load-bearing:
|
|
531
|
+
*
|
|
532
|
+
* - the RUNTIME has to know how to ask (capabilities.fastMode). Only the Claude Code loop does.
|
|
533
|
+
* - the ROUTE has to be first-party. Every non-Claude provider the Claude Code loop serves is served through
|
|
534
|
+
* the sandbox's translator, and the harness refuses fast mode on a non-Anthropic endpoint ("not_first_party")
|
|
535
|
+
* — so a `grok` turn on the claude-code harness reads true on the capability and still cannot go fast.
|
|
536
|
+
* - the MODEL has to publish it, which is the `fast` badge Anthropic's own catalog reports per model
|
|
537
|
+
* (claude-models.ts maps supportsFastMode onto it). Curating a list of ids here instead is what this repo
|
|
538
|
+
* deliberately does not do — a model that gains or loses fast mode moves the badge, and this follows.
|
|
539
|
+
*
|
|
540
|
+
* `badges` absent ⇒ false. That is the honest reading: a catalog row that published no capabilities said
|
|
541
|
+
* nothing about fast mode, and the seed floor a picker shows before its first live load is exactly that row. */
|
|
542
|
+
export const fastAllowed = (capabilities: AgentCapabilities, provider: AgentProvider, badges: readonly ModelBadge[] | undefined): boolean =>
|
|
543
|
+
capabilities.fastMode && provider === "claude" && (badges ?? []).includes("fast");
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { resolveAgentRunModels } from "./agent-run-model.js";
|
|
3
|
+
import type { QuickModelSource } from "./quick-model.js";
|
|
4
|
+
|
|
5
|
+
/* Which model a run somebody's BUTTON started opens on. The rule answers the same two surfaces its quick-model
|
|
6
|
+
* sibling does — the daemon walks it, the settings row names it — so what these pin is the pair of properties
|
|
7
|
+
* that separate the two: an account this sandbox cannot reach never sits at the head of the chain, and an empty
|
|
8
|
+
* answer stays empty rather than being filled in with a tier nobody chose. */
|
|
9
|
+
|
|
10
|
+
const CLAUDE: QuickModelSource = { provider: `claude`, ready: true, models: [`claude-opus-5`, `claude-sonnet-5`] };
|
|
11
|
+
const CODEX: QuickModelSource = { provider: `codex`, ready: true, models: [`gpt-5.6`] };
|
|
12
|
+
const GOOGLE: QuickModelSource = { provider: `gemini`, ready: true, models: [`gemini-3-pro`] };
|
|
13
|
+
|
|
14
|
+
const offline = (source: QuickModelSource): QuickModelSource => ({ ...source, ready: false });
|
|
15
|
+
|
|
16
|
+
test("keeps the user's own order — this list is read, never ranked", () => {
|
|
17
|
+
// The opposite of the quick chain, which sorts by tier and cost. Here the order IS the setting: someone who
|
|
18
|
+
// put Opus above GPT wants Opus first, and a resolver that knew better would spend the wrong account.
|
|
19
|
+
expect(resolveAgentRunModels([CLAUDE, CODEX], [`codex:gpt-5.6`, `claude:claude-opus-5`])).toEqual([
|
|
20
|
+
{ provider: `codex`, model: `gpt-5.6` },
|
|
21
|
+
{ provider: `claude`, model: `claude-opus-5` },
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("steps over a provider this sandbox has no credential for", () => {
|
|
26
|
+
// The whole reason the setting is a list. With Claude disconnected the head would otherwise be an account
|
|
27
|
+
// that fails every Fix with agent, while a perfectly good Codex sits underneath it.
|
|
28
|
+
expect(resolveAgentRunModels([offline(CLAUDE), CODEX], [`claude:claude-opus-5`, `codex:gpt-5.6`])).toEqual([
|
|
29
|
+
{ provider: `codex`, model: `gpt-5.6` },
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("resolves to nothing when no pin is reachable — it does NOT fall back to whatever is connected", () => {
|
|
34
|
+
// The deliberate difference from resolveQuickModels, which lands on its Auto ladder here. An agent run is
|
|
35
|
+
// billed in whole sessions, so an unreachable list hands the choice back to the caller's floor (the user's
|
|
36
|
+
// own composer pick) rather than spending an account they never pointed at.
|
|
37
|
+
expect(resolveAgentRunModels([offline(CLAUDE), GOOGLE], [`claude:claude-opus-5`])).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("an empty list resolves to nothing even with accounts connected", () => {
|
|
41
|
+
expect(resolveAgentRunModels([CLAUDE, CODEX, GOOGLE], [])).toEqual([]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("drops a duplicate rather than spending two attempts proving one account is out", () => {
|
|
45
|
+
expect(resolveAgentRunModels([CLAUDE], [`claude:claude-opus-5`, `claude:claude-opus-5`])).toEqual([
|
|
46
|
+
{ provider: `claude`, model: `claude-opus-5` },
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("drops a malformed key instead of sending it to a provider", () => {
|
|
51
|
+
expect(resolveAgentRunModels([CLAUDE], [`claude-opus-5`, `claude:`, `:claude-opus-5`, `claude:claude-sonnet-5`])).toEqual([
|
|
52
|
+
{ provider: `claude`, model: `claude-sonnet-5` },
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("carries a model id the static catalog has never heard of", () => {
|
|
57
|
+
// The picker offers a custom-id escape hatch, so a pin can name a model released after this build. Second-
|
|
58
|
+
// guessing it here would quietly run something other than what the settings row says.
|
|
59
|
+
expect(resolveAgentRunModels([CLAUDE], [`claude:claude-opus-9-preview`])).toEqual([{ provider: `claude`, model: `claude-opus-9-preview` }]);
|
|
60
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { parsePinned, type QuickModelChoice, quickModelKey, type QuickModelSource } from "./quick-model.js";
|
|
2
|
+
|
|
3
|
+
/* WHAT A SURFACE-STARTED AGENT RUN OPENS ON — the resolver for `agentRunModels`, sibling to resolveQuickModels
|
|
4
|
+
* and deliberately not the same function.
|
|
5
|
+
*
|
|
6
|
+
* BOTH ARE ORDERED LISTS, FOR THE SAME REASON. One connected account whose allowance went on the chat this
|
|
7
|
+
* morning is enough to take every one of these down: the user presses Fix with agent on a red pipeline, an
|
|
8
|
+
* isolated session opens, and it dies on a credential error they cannot see from the row. Written in order, the
|
|
9
|
+
* next entry catches it.
|
|
10
|
+
*
|
|
11
|
+
* THEY DIFFER ON WHAT AN EMPTY LIST MEANS, and that difference is the whole reason this is its own file rather
|
|
12
|
+
* than a flag on the other one. A quick helper exists to stay OFF the frontier tier, so "work it out from what
|
|
13
|
+
* is connected" is a good answer and quickModel's empty list resolves to a derived Auto ladder. An agent run is
|
|
14
|
+
* a full session with a worktree, billed whole: nothing here can judge whether a job is worth the frontier tier,
|
|
15
|
+
* so an empty list resolves to NOTHING and the caller falls back to the model the user picked for their own
|
|
16
|
+
* chat — a choice they made, rather than one this file guessed for them. For the same reason there is no Auto
|
|
17
|
+
* ladder underneath a list that has been emptied by disconnection: it would spend an account the user never
|
|
18
|
+
* pointed at, on the most expensive kind of run this app starts.
|
|
19
|
+
*
|
|
20
|
+
* WHAT "STEPPED OVER" MEANS HERE IS NARROWER than the quick chain's, and worth being exact about. The quick
|
|
21
|
+
* chain re-asks the next rung when a call comes back refused, because a one-shot that failed has cost nothing
|
|
22
|
+
* and can simply be run again. An agent session cannot be replayed that way — by the time a provider refuses
|
|
23
|
+
* mid-turn the agent may have already edited files — so this list is read ONCE, at the moment the turn is
|
|
24
|
+
* composed, and steps over exactly one thing: an account that is not connected. A model that accepts the turn
|
|
25
|
+
* and fails later is a failed run the user reads on the card, like any other. */
|
|
26
|
+
|
|
27
|
+
/* The pins that could actually be started right now, in the user's own order.
|
|
28
|
+
*
|
|
29
|
+
* `sources` is the same readiness view resolveQuickModels takes, so both settings rows agree about which
|
|
30
|
+
* accounts this sandbox can send to — a pin greyed as "Not connected" in one row and silently spent by the
|
|
31
|
+
* other would be the worst of both.
|
|
32
|
+
*
|
|
33
|
+
* A pin whose provider is gone is DROPPED rather than held: it would otherwise sit at the head of the chain
|
|
34
|
+
* failing every run, which is exactly what the list exists to prevent. It stays on SCREEN, greyed — the settings
|
|
35
|
+
* row renders the stored list, not this one — because a setting that vanished from view would look like the app
|
|
36
|
+
* had eaten it.
|
|
37
|
+
*
|
|
38
|
+
* Empty out means nobody has pinned anything this sandbox can reach, and the caller's floor takes over. */
|
|
39
|
+
export const resolveAgentRunModels = (sources: readonly QuickModelSource[], pinned: readonly string[]): readonly QuickModelChoice[] => {
|
|
40
|
+
const ready = new Set(sources.filter((source) => source.ready).map((source) => source.provider));
|
|
41
|
+
const requested = pinned.flatMap((key) => {
|
|
42
|
+
// Verbatim, unvalidated against the catalog — the same call resolveQuickModels makes and for the same
|
|
43
|
+
// reason: the picker offers a custom-id escape hatch for a model the static catalog has not caught up
|
|
44
|
+
// with, and second-guessing the id here would run a different model than the settings row names.
|
|
45
|
+
const choice = parsePinned(key);
|
|
46
|
+
return choice === undefined || !ready.has(choice.provider) ? [] : [choice];
|
|
47
|
+
});
|
|
48
|
+
// The same model twice would spend two attempts proving the same account is out. Hand-edited list, so this
|
|
49
|
+
// is a real state rather than a defensive branch.
|
|
50
|
+
return [...new Map(requested.map((choice) => [quickModelKey(choice), choice])).values()];
|
|
51
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* HOW A CLI CAPABILITY'S ENV VARS ARE NAMED — the one rule both ends of that wire have to apply.
|
|
2
|
+
*
|
|
3
|
+
* The daemon writes the agent's shell env by suffixing every var a cli connector declares with the instance id
|
|
4
|
+
* (cli-env.ts), so two connections of the same provider coexist on one flat environment. A connector's own
|
|
5
|
+
* TOOL then has to read them back, and an extension may not import daemon internals — so without this the
|
|
6
|
+
* rule would be spelled twice, and a change to it would silently split the writer from the reader. */
|
|
7
|
+
|
|
8
|
+
// `analytics` → `POSTGRES_URL_ANALYTICS`; the default-named `github` → `GITHUB_TOKEN_GITHUB`.
|
|
9
|
+
// ponytail: ids differing only by case or `-`/`_` (my-db vs my_db) map to the same suffix — last wins.
|
|
10
|
+
export const envSuffix = (id: string): string => id.toUpperCase().replaceAll("-", "_");
|