@kal-elsam/kairo-runtime 0.4.2 → 0.5.0
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 +124 -17
- package/global-template/components/catalog.json +63 -6
- package/global-template/components/sdd-core/personas/teaching.md +42 -0
- package/global-template/components/sdd-core/skills/sdd-apply/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-apply/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-archive/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-archive/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-design/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-design/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-explore/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-explore/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-init/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-init/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-propose/SKILL.md +35 -0
- package/global-template/components/sdd-core/skills/sdd-propose/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-spec/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-spec/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-tasks/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-tasks/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-verify/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-verify/references/contract.md +4 -0
- package/package.json +1 -1
- package/scripts/cockpit-smoke.mjs +29 -8
- package/scripts/smoke-test.sh +9 -0
- package/src/cli.js +54 -5
- package/src/global/action-planner.js +17 -4
- package/src/global/component-authoring.js +1 -2
- package/src/global/component-ecosystem-checks.js +74 -15
- package/src/global/component-health.js +56 -0
- package/src/global/component-integration-cli.js +302 -0
- package/src/global/component-manifest.js +214 -0
- package/src/global/component-registry.js +19 -2
- package/src/global/component-resolve.js +51 -0
- package/src/global/components/sdd-core.js +6 -0
- package/src/global/control-plane-proposals.js +229 -0
- package/src/global/control-plane-snapshot.js +212 -0
- package/src/global/dashboard-guidance.js +27 -3
- package/src/global/diff.js +2 -5
- package/src/global/global-cli.js +16 -1
- package/src/global/global-doctor.js +75 -2
- package/src/global/global-installer.js +43 -6
- package/src/global/governance-actions.js +212 -0
- package/src/global/governance-repair.js +13 -0
- package/src/global/ink/cockpit-changes.js +124 -0
- package/src/global/ink/cockpit-control-center.js +106 -0
- package/src/global/ink/cockpit-controller.js +17 -0
- package/src/global/ink/cockpit-enter.js +27 -0
- package/src/global/ink/cockpit-focus.js +7 -1
- package/src/global/ink/cockpit-models.js +98 -46
- package/src/global/ink/cockpit-proposals.js +82 -0
- package/src/global/ink/cockpit-recovery.js +120 -0
- package/src/global/ink/cockpit-runs.js +47 -0
- package/src/global/ink/cockpit-scan.js +60 -0
- package/src/global/ink/cockpit-views.js +144 -74
- package/src/global/ink/orchestrator-app.js +156 -42
- package/src/global/ink/orchestrator-state.js +22 -1
- package/src/global/ink/use-orchestrator-data.js +239 -17
- package/src/global/integrations/engram-apply.js +105 -0
- package/src/global/integrations/engram-evidence.js +218 -0
- package/src/global/integrations/engram-exec.js +107 -0
- package/src/global/integrations/engram-plan.js +60 -0
- package/src/global/integrations/engram-provider.js +22 -0
- package/src/global/integrations/engram-receipts.js +116 -0
- package/src/global/integrations/engram-rollback.js +131 -0
- package/src/global/integrations/index.js +12 -0
- package/src/global/integrations/provider-contract.js +83 -0
- package/src/global/integrations/provider-registry.js +47 -0
- package/src/global/integrations/sdd-apply.js +190 -0
- package/src/global/integrations/sdd-destinations.js +95 -0
- package/src/global/integrations/sdd-evidence.js +89 -0
- package/src/global/integrations/sdd-fs-guard.js +183 -0
- package/src/global/integrations/sdd-lifecycle.js +84 -0
- package/src/global/integrations/sdd-persona.js +71 -0
- package/src/global/integrations/sdd-plan.js +87 -0
- package/src/global/integrations/sdd-provider.js +21 -0
- package/src/global/integrations/sdd-receipts.js +44 -0
- package/src/global/integrations/sdd-rollback.js +269 -0
- package/src/global/integrations/sdd-skill-files.js +61 -0
- package/src/global/integrations/sdd-state.js +175 -0
- package/src/global/integrations/sdd-verify.js +122 -0
- package/src/global/intelligence/backends/opencode-catalog.js +212 -0
- package/src/global/intelligence/backends/opencode-cli.js +247 -0
- package/src/global/intelligence/backends/opencode-evidence.js +129 -0
- package/src/global/intelligence/backends/opencode-http.js +152 -0
- package/src/global/intelligence/backends/opencode-invoke.js +83 -0
- package/src/global/intelligence/backends/opencode-providers.js +67 -0
- package/src/global/intelligence/backends/opencode-runtime.js +242 -0
- package/src/global/intelligence/context-compiler.js +95 -50
- package/src/global/intelligence/index.js +38 -1
- package/src/global/intelligence/orchestrate.js +4 -2
- package/src/global/intelligence/registry.js +62 -6
- package/src/global/intelligence/router.js +188 -70
- package/src/global/intelligence/transport-registry.js +147 -0
- package/src/global/intelligence/types.js +21 -2
- package/src/global/intelligence-cli.js +48 -17
- package/src/global/intelligence-session.js +37 -0
- package/src/global/json-output.js +1 -0
- package/src/global/load-component-catalog.js +10 -28
- package/src/global/load-workspace-component-catalog.js +22 -63
- package/src/global/state-migration.js +21 -1
- package/src/global/state.js +13 -8
- package/src/global/status.js +4 -17
- package/src/global/sync.js +2 -5
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BACKEND_IDS,
|
|
3
|
+
OPENCODE_GO_DEFAULT_MODEL,
|
|
4
|
+
OPENCODE_ZEN_DEFAULT_FREE_MODEL,
|
|
3
5
|
OPENROUTER_FREE_MODEL,
|
|
4
6
|
PRIVACY_CLASSES,
|
|
5
7
|
ROUTING_MODES,
|
|
@@ -7,36 +9,42 @@ import {
|
|
|
7
9
|
} from "./types.js";
|
|
8
10
|
import { estimateTokens } from "./context-compiler.js";
|
|
9
11
|
|
|
10
|
-
const TASK_WEIGHTS = {
|
|
11
|
-
architecture: "heavy",
|
|
12
|
-
security: "heavy",
|
|
13
|
-
review: "heavy",
|
|
14
|
-
diagnose: "light",
|
|
15
|
-
explain: "light",
|
|
16
|
-
scaffold: "light",
|
|
17
|
-
test: "light",
|
|
18
|
-
default: "light"
|
|
19
|
-
};
|
|
20
|
-
|
|
21
12
|
export function classifyTaskWeight(task = "") {
|
|
22
13
|
const text = String(task).toLowerCase();
|
|
23
14
|
if (/architect|adr|design system|security|threat/.test(text)) return "heavy";
|
|
24
15
|
if (/review|refactor complex|debug complex/.test(text)) return "heavy";
|
|
25
16
|
if (/test|scaffold|explain|status|diagnose|lint|format/.test(text)) return "light";
|
|
26
|
-
return
|
|
17
|
+
return "light";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Direct Go/Zen HTTP is eligible only with key + proven auth + available. */
|
|
21
|
+
export function isDirectProviderEligible(backend) {
|
|
22
|
+
return Boolean(backend?.hasApiKey && backend?.authenticated && backend?.available);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function diagnosticsDecision(reason, estimatedTokens) {
|
|
26
|
+
return createRoutingDecision({
|
|
27
|
+
backendId: null,
|
|
28
|
+
model: null,
|
|
29
|
+
reason,
|
|
30
|
+
estimatedTokens,
|
|
31
|
+
privacyImpact: PRIVACY_CLASSES.UNKNOWN,
|
|
32
|
+
mode: ROUTING_MODES.DIAGNOSTICS,
|
|
33
|
+
requiresCloudConsent: false,
|
|
34
|
+
canInvoke: false,
|
|
35
|
+
fallback: null
|
|
36
|
+
});
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
/**
|
|
30
|
-
* Resolve which backend/model to use.
|
|
31
|
-
* Precedence: user override > Ollama local > OpenRouter free (consent) > diagnostics.
|
|
32
|
-
*/
|
|
39
|
+
/** Precedence: CLI → profile → Ollama → Go → Zen → OpenRouter → diagnostics. Go has no Zen invoke fallback. */
|
|
33
40
|
export function resolveRoutingDecision({
|
|
34
41
|
backends = [],
|
|
35
42
|
profile = {},
|
|
36
43
|
contextPack = null,
|
|
37
44
|
task = null,
|
|
38
45
|
cloudConsent = false,
|
|
39
|
-
tokenBudget = null
|
|
46
|
+
tokenBudget = null,
|
|
47
|
+
sessionOverride = null
|
|
40
48
|
} = {}) {
|
|
41
49
|
const estimatedTokens = contextPack?.estimatedTokens
|
|
42
50
|
?? estimateTokens(contextPack?.systemPrompt ?? "")
|
|
@@ -44,24 +52,24 @@ export function resolveRoutingDecision({
|
|
|
44
52
|
|
|
45
53
|
const budget = tokenBudget ?? profile.tokenBudget ?? null;
|
|
46
54
|
if (budget != null && estimatedTokens > budget) {
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
estimatedTokens,
|
|
52
|
-
privacyImpact: PRIVACY_CLASSES.UNKNOWN,
|
|
53
|
-
mode: ROUTING_MODES.DIAGNOSTICS,
|
|
54
|
-
requiresCloudConsent: false,
|
|
55
|
-
canInvoke: false
|
|
56
|
-
});
|
|
55
|
+
return diagnosticsDecision(
|
|
56
|
+
`Estimated tokens (${estimatedTokens}) exceed budget (${budget}). Compact context or raise tokenBudget.`,
|
|
57
|
+
estimatedTokens
|
|
58
|
+
);
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
const
|
|
61
|
+
const effectiveProfile = applySessionOverride(profile, sessionOverride);
|
|
62
|
+
const override = resolveUserOverride(effectiveProfile, backends, sessionOverride);
|
|
63
|
+
if (override?.ineligible) {
|
|
64
|
+
return diagnosticsDecision(override.reason, estimatedTokens);
|
|
65
|
+
}
|
|
60
66
|
if (override) {
|
|
61
67
|
return createRoutingDecision({
|
|
62
68
|
backendId: override.backend.id,
|
|
63
69
|
model: override.model,
|
|
64
|
-
reason:
|
|
70
|
+
reason: override.source === "cli"
|
|
71
|
+
? `CLI override: ${override.backend.id}/${override.model.modelId}`
|
|
72
|
+
: `User override: ${override.backend.id}/${override.model.modelId}`,
|
|
65
73
|
estimatedTokens,
|
|
66
74
|
privacyImpact: override.model.privacyClass,
|
|
67
75
|
mode: ROUTING_MODES.USER_OVERRIDE,
|
|
@@ -87,18 +95,60 @@ export function resolveRoutingDecision({
|
|
|
87
95
|
});
|
|
88
96
|
}
|
|
89
97
|
|
|
98
|
+
const go = backends.find((entry) => entry.id === BACKEND_IDS.OPENCODE_GO);
|
|
99
|
+
if (isDirectProviderEligible(go)) {
|
|
100
|
+
const model = selectOpencodeModel(go.models, OPENCODE_GO_DEFAULT_MODEL, BACKEND_IDS.OPENCODE_GO);
|
|
101
|
+
return createRoutingDecision({
|
|
102
|
+
backendId: BACKEND_IDS.OPENCODE_GO,
|
|
103
|
+
model,
|
|
104
|
+
reason: cloudConsent
|
|
105
|
+
? `OpenCode Go approved: ${model.modelId}`
|
|
106
|
+
: `OpenCode Go available (${model.modelId}) but cloud consent required before invoke`,
|
|
107
|
+
estimatedTokens,
|
|
108
|
+
privacyImpact: PRIVACY_CLASSES.CLOUD,
|
|
109
|
+
mode: ROUTING_MODES.CLOUD_CONSENT,
|
|
110
|
+
requiresCloudConsent: true,
|
|
111
|
+
canInvoke: cloudConsent,
|
|
112
|
+
fallback: null
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// CLI-configured or broken-key Go must not silently spend Zen/OpenRouter.
|
|
116
|
+
if (go?.configured || go?.hasApiKey) {
|
|
117
|
+
return diagnosticsDecision(
|
|
118
|
+
describeIneligibleCloudStop(go, "OpenCode Go", "Zen/OpenRouter"),
|
|
119
|
+
estimatedTokens
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const zen = backends.find((entry) => entry.id === BACKEND_IDS.OPENCODE_ZEN);
|
|
124
|
+
if (isDirectProviderEligible(zen)) {
|
|
125
|
+
const model = selectOpencodeModel(zen.models, OPENCODE_ZEN_DEFAULT_FREE_MODEL, BACKEND_IDS.OPENCODE_ZEN);
|
|
126
|
+
return createRoutingDecision({
|
|
127
|
+
backendId: BACKEND_IDS.OPENCODE_ZEN,
|
|
128
|
+
model,
|
|
129
|
+
reason: cloudConsent
|
|
130
|
+
? `OpenCode Zen approved: ${model.modelId}`
|
|
131
|
+
: `OpenCode Zen available (${model.modelId}) but cloud consent required before invoke`,
|
|
132
|
+
estimatedTokens,
|
|
133
|
+
privacyImpact: PRIVACY_CLASSES.CLOUD,
|
|
134
|
+
mode: ROUTING_MODES.CLOUD_CONSENT,
|
|
135
|
+
requiresCloudConsent: true,
|
|
136
|
+
canInvoke: cloudConsent,
|
|
137
|
+
fallback: buildOpenRouterFallback(backends, cloudConsent)
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (zen?.configured || zen?.hasApiKey) {
|
|
141
|
+
return diagnosticsDecision(
|
|
142
|
+
describeIneligibleCloudStop(zen, "OpenCode Zen", "OpenRouter"),
|
|
143
|
+
estimatedTokens
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
90
147
|
const openrouter = backends.find((entry) => entry.id === BACKEND_IDS.OPENROUTER);
|
|
91
148
|
if (openrouter?.hasApiKey) {
|
|
92
149
|
const model = openrouter.models?.find((entry) => entry.modelId === OPENROUTER_FREE_MODEL)
|
|
93
150
|
?? openrouter.models?.[0]
|
|
94
|
-
??
|
|
95
|
-
provider: BACKEND_IDS.OPENROUTER,
|
|
96
|
-
modelId: OPENROUTER_FREE_MODEL,
|
|
97
|
-
local: false,
|
|
98
|
-
privacyClass: PRIVACY_CLASSES.CLOUD,
|
|
99
|
-
costClass: "free",
|
|
100
|
-
opaque: true
|
|
101
|
-
};
|
|
151
|
+
?? opaqueCloudModel(BACKEND_IDS.OPENROUTER, OPENROUTER_FREE_MODEL, "free");
|
|
102
152
|
|
|
103
153
|
return createRoutingDecision({
|
|
104
154
|
backendId: BACKEND_IDS.OPENROUTER,
|
|
@@ -115,52 +165,95 @@ export function resolveRoutingDecision({
|
|
|
115
165
|
});
|
|
116
166
|
}
|
|
117
167
|
|
|
118
|
-
return
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
168
|
+
return diagnosticsDecision(
|
|
169
|
+
"No intelligence backend available. Remaining in diagnostics/configuration mode.",
|
|
170
|
+
estimatedTokens
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function applySessionOverride(profile, sessionOverride) {
|
|
175
|
+
if (!sessionOverride) return profile;
|
|
176
|
+
return {
|
|
177
|
+
...profile,
|
|
178
|
+
preferredBackend: sessionOverride.preferredBackend ?? profile.preferredBackend,
|
|
179
|
+
preferredModel: sessionOverride.preferredModel ?? profile.preferredModel
|
|
180
|
+
};
|
|
128
181
|
}
|
|
129
182
|
|
|
130
|
-
function resolveUserOverride(profile, backends) {
|
|
183
|
+
function resolveUserOverride(profile, backends, sessionOverride) {
|
|
131
184
|
const preferredBackend = profile.preferredBackend ?? null;
|
|
132
185
|
const preferredModel = profile.preferredModel ?? null;
|
|
133
186
|
if (!preferredBackend && !preferredModel) return null;
|
|
134
187
|
|
|
188
|
+
const source = sessionOverride?.preferredBackend || sessionOverride?.preferredModel
|
|
189
|
+
? "cli"
|
|
190
|
+
: "profile";
|
|
191
|
+
const label = source === "cli" ? "CLI" : "User";
|
|
192
|
+
const reject = (reason) => ({
|
|
193
|
+
ineligible: true,
|
|
194
|
+
reason: `${reason} Remaining in diagnostics; automatic routing was not applied.`
|
|
195
|
+
});
|
|
196
|
+
|
|
135
197
|
if (preferredBackend) {
|
|
136
|
-
const backend = backends.find((entry) => entry.id === preferredBackend);
|
|
137
|
-
if (!
|
|
138
|
-
return
|
|
198
|
+
const backend = backends.find((entry) => entry.id === preferredBackend) ?? null;
|
|
199
|
+
if (!isOverrideEligible(backend)) {
|
|
200
|
+
return reject(`${label} override ${preferredBackend} is not eligible for invoke.`);
|
|
139
201
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (!model) return null;
|
|
153
|
-
return { backend, model };
|
|
202
|
+
if (preferredModel) {
|
|
203
|
+
const exact = (backend.models ?? []).find((entry) => entry.modelId === preferredModel);
|
|
204
|
+
if (!exact) {
|
|
205
|
+
return reject(
|
|
206
|
+
`${label} override model ${preferredModel} was not found on ${preferredBackend}.`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return { backend, model: exact, source };
|
|
210
|
+
}
|
|
211
|
+
const model = backend.models?.[0] ?? null;
|
|
212
|
+
if (!model) return reject(`${label} override ${preferredBackend} has no usable model.`);
|
|
213
|
+
return { backend, model, source };
|
|
154
214
|
}
|
|
155
215
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
216
|
+
for (const backend of backends) {
|
|
217
|
+
const model = (backend.models ?? []).find((entry) => entry.modelId === preferredModel);
|
|
218
|
+
if (!model) continue;
|
|
219
|
+
if (!isOverrideEligible(backend)) {
|
|
220
|
+
return reject(
|
|
221
|
+
`${label} override model ${preferredModel} resolved to ineligible backend ${backend.id}.`
|
|
222
|
+
);
|
|
160
223
|
}
|
|
224
|
+
return { backend, model, source };
|
|
161
225
|
}
|
|
226
|
+
return reject(`${label} override model ${preferredModel} was not found.`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Runtime is exempt from /models auth; Go/Zen require proven authentication. */
|
|
230
|
+
function isOverrideEligible(backend) {
|
|
231
|
+
if (!backend) return false;
|
|
232
|
+
if (backend.id === BACKEND_IDS.OPENCODE) return Boolean(backend.available || backend.detected);
|
|
233
|
+
if (backend.id === BACKEND_IDS.OLLAMA) return Boolean(backend.available);
|
|
234
|
+
if (backend.id === BACKEND_IDS.OPENCODE_GO || backend.id === BACKEND_IDS.OPENCODE_ZEN) {
|
|
235
|
+
return isDirectProviderEligible(backend);
|
|
236
|
+
}
|
|
237
|
+
if (backend.id === BACKEND_IDS.OPENROUTER) return Boolean(backend.hasApiKey);
|
|
238
|
+
return Boolean(backend.available || backend.hasApiKey || backend.detected);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function describeIneligibleCloudStop(backend, label, blockedFallback) {
|
|
242
|
+
const detail = backend?.error
|
|
243
|
+
?? backend?.recommendation
|
|
244
|
+
?? "configured but not authenticated/available";
|
|
245
|
+
return `${label} ${detail}. Invoke blocked (canInvoke=false); ${blockedFallback} fallback suppressed.`;
|
|
246
|
+
}
|
|
162
247
|
|
|
163
|
-
|
|
248
|
+
function opaqueCloudModel(provider, modelId, costClass) {
|
|
249
|
+
return {
|
|
250
|
+
provider,
|
|
251
|
+
modelId,
|
|
252
|
+
local: false,
|
|
253
|
+
privacyClass: PRIVACY_CLASSES.CLOUD,
|
|
254
|
+
costClass,
|
|
255
|
+
opaque: true
|
|
256
|
+
};
|
|
164
257
|
}
|
|
165
258
|
|
|
166
259
|
function selectLocalModel(models, task) {
|
|
@@ -171,6 +264,14 @@ function selectLocalModel(models, task) {
|
|
|
171
264
|
return models[0];
|
|
172
265
|
}
|
|
173
266
|
|
|
267
|
+
function selectOpencodeModel(models, preferredId, provider) {
|
|
268
|
+
const list = Array.isArray(models) ? models : [];
|
|
269
|
+
return list.find((entry) => entry.modelId === preferredId)
|
|
270
|
+
?? list.find((entry) => entry.costClass === "free")
|
|
271
|
+
?? list[0]
|
|
272
|
+
?? opaqueCloudModel(provider, preferredId, provider === BACKEND_IDS.OPENCODE_GO ? "paid" : "unknown");
|
|
273
|
+
}
|
|
274
|
+
|
|
174
275
|
function buildLocalFallback(backends) {
|
|
175
276
|
const ollama = backends.find((entry) => entry.id === BACKEND_IDS.OLLAMA && entry.available);
|
|
176
277
|
if (!ollama?.models?.length) return null;
|
|
@@ -181,6 +282,23 @@ function buildLocalFallback(backends) {
|
|
|
181
282
|
}
|
|
182
283
|
|
|
183
284
|
function buildCloudFallback(backends, cloudConsent) {
|
|
285
|
+
const go = backends.find((entry) => entry.id === BACKEND_IDS.OPENCODE_GO);
|
|
286
|
+
if (isDirectProviderEligible(go)) {
|
|
287
|
+
return {
|
|
288
|
+
backendId: BACKEND_IDS.OPENCODE_GO,
|
|
289
|
+
modelId: go.models?.[0]?.modelId ?? OPENCODE_GO_DEFAULT_MODEL,
|
|
290
|
+
requiresConsent: !cloudConsent
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
if (go?.configured || go?.hasApiKey) return null;
|
|
294
|
+
const zen = backends.find((entry) => entry.id === BACKEND_IDS.OPENCODE_ZEN);
|
|
295
|
+
if (zen?.configured || zen?.hasApiKey) {
|
|
296
|
+
if (!isDirectProviderEligible(zen)) return null;
|
|
297
|
+
}
|
|
298
|
+
return buildOpenRouterFallback(backends, cloudConsent);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function buildOpenRouterFallback(backends, cloudConsent) {
|
|
184
302
|
const openrouter = backends.find((entry) => entry.id === BACKEND_IDS.OPENROUTER && entry.hasApiKey);
|
|
185
303
|
if (!openrouter) return null;
|
|
186
304
|
return {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { TRANSPORT_KINDS } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Explicit transport compatibility for OpenCode Go / Zen.
|
|
5
|
+
* Never infer protocol from model name — only this registry decides.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const GO_DIRECT = {
|
|
9
|
+
"glm-5.2": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
10
|
+
"glm-5.1": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
11
|
+
"kimi-k2.7-code": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
12
|
+
"kimi-k2.6": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
13
|
+
"deepseek-v4-pro": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
14
|
+
"deepseek-v4-flash": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
15
|
+
"mimo-v2.5": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
16
|
+
"mimo-v2.5-pro": TRANSPORT_KINDS.CHAT_COMPLETIONS
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const GO_RUNTIME = {
|
|
20
|
+
"minimax-m3": TRANSPORT_KINDS.RUNTIME,
|
|
21
|
+
"minimax-m2.7": TRANSPORT_KINDS.RUNTIME,
|
|
22
|
+
"minimax-m2.5": TRANSPORT_KINDS.RUNTIME,
|
|
23
|
+
"qwen3.7-max": TRANSPORT_KINDS.RUNTIME,
|
|
24
|
+
"qwen3.7-plus": TRANSPORT_KINDS.RUNTIME,
|
|
25
|
+
"qwen3.6-plus": TRANSPORT_KINDS.RUNTIME
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const ZEN_RESPONSES = {
|
|
29
|
+
"gpt-5.6-sol": TRANSPORT_KINDS.RESPONSES,
|
|
30
|
+
"gpt-5.6-terra": TRANSPORT_KINDS.RESPONSES,
|
|
31
|
+
"gpt-5.6-luna": TRANSPORT_KINDS.RESPONSES,
|
|
32
|
+
"gpt-5.5": TRANSPORT_KINDS.RESPONSES,
|
|
33
|
+
"gpt-5.5-pro": TRANSPORT_KINDS.RESPONSES,
|
|
34
|
+
"gpt-5.4": TRANSPORT_KINDS.RESPONSES,
|
|
35
|
+
"gpt-5.4-pro": TRANSPORT_KINDS.RESPONSES,
|
|
36
|
+
"gpt-5.4-mini": TRANSPORT_KINDS.RESPONSES,
|
|
37
|
+
"gpt-5.4-nano": TRANSPORT_KINDS.RESPONSES,
|
|
38
|
+
"gpt-5.3-codex": TRANSPORT_KINDS.RESPONSES,
|
|
39
|
+
"gpt-5.3-codex-spark": TRANSPORT_KINDS.RESPONSES,
|
|
40
|
+
"gpt-5.2": TRANSPORT_KINDS.RESPONSES,
|
|
41
|
+
"gpt-5.2-codex": TRANSPORT_KINDS.RESPONSES,
|
|
42
|
+
"gpt-5.1": TRANSPORT_KINDS.RESPONSES,
|
|
43
|
+
"gpt-5.1-codex": TRANSPORT_KINDS.RESPONSES,
|
|
44
|
+
"gpt-5.1-codex-max": TRANSPORT_KINDS.RESPONSES,
|
|
45
|
+
"gpt-5.1-codex-mini": TRANSPORT_KINDS.RESPONSES,
|
|
46
|
+
"gpt-5": TRANSPORT_KINDS.RESPONSES,
|
|
47
|
+
"gpt-5-codex": TRANSPORT_KINDS.RESPONSES,
|
|
48
|
+
"gpt-5-nano": TRANSPORT_KINDS.RESPONSES
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const ZEN_CHAT = {
|
|
52
|
+
"deepseek-v4-pro": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
53
|
+
"deepseek-v4-flash": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
54
|
+
"minimax-m3": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
55
|
+
"minimax-m2.7": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
56
|
+
"minimax-m2.5": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
57
|
+
"glm-5.2": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
58
|
+
"glm-5.1": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
59
|
+
"glm-5": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
60
|
+
"kimi-k2.5": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
61
|
+
"kimi-k2.6": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
62
|
+
"kimi-k2.7-code": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
63
|
+
"grok-4.5": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
64
|
+
"grok-build-0.1": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
65
|
+
"big-pickle": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
66
|
+
"mimo-v2.5-free": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
67
|
+
"north-mini-code-free": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
68
|
+
"nemotron-3-ultra-free": TRANSPORT_KINDS.CHAT_COMPLETIONS,
|
|
69
|
+
"deepseek-v4-flash-free": TRANSPORT_KINDS.CHAT_COMPLETIONS
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const ZEN_RUNTIME = {
|
|
73
|
+
"claude-fable-5": TRANSPORT_KINDS.RUNTIME,
|
|
74
|
+
"claude-opus-4-8": TRANSPORT_KINDS.RUNTIME,
|
|
75
|
+
"claude-opus-4-7": TRANSPORT_KINDS.RUNTIME,
|
|
76
|
+
"claude-opus-4-6": TRANSPORT_KINDS.RUNTIME,
|
|
77
|
+
"claude-opus-4-5": TRANSPORT_KINDS.RUNTIME,
|
|
78
|
+
"claude-sonnet-5": TRANSPORT_KINDS.RUNTIME,
|
|
79
|
+
"claude-sonnet-4-6": TRANSPORT_KINDS.RUNTIME,
|
|
80
|
+
"claude-sonnet-4-5": TRANSPORT_KINDS.RUNTIME,
|
|
81
|
+
"claude-haiku-4-5": TRANSPORT_KINDS.RUNTIME,
|
|
82
|
+
"gemini-3.5-flash": TRANSPORT_KINDS.RUNTIME,
|
|
83
|
+
"gemini-3.1-pro": TRANSPORT_KINDS.RUNTIME,
|
|
84
|
+
"gemini-3-flash": TRANSPORT_KINDS.RUNTIME,
|
|
85
|
+
"qwen3.7-max": TRANSPORT_KINDS.RUNTIME,
|
|
86
|
+
"qwen3.7-plus": TRANSPORT_KINDS.RUNTIME,
|
|
87
|
+
"qwen3.6-plus": TRANSPORT_KINDS.RUNTIME,
|
|
88
|
+
"qwen3.5-plus": TRANSPORT_KINDS.RUNTIME
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const OPENCODE_GO_TRANSPORTS = Object.freeze({ ...GO_DIRECT, ...GO_RUNTIME });
|
|
92
|
+
export const OPENCODE_ZEN_TRANSPORTS = Object.freeze({
|
|
93
|
+
...ZEN_RESPONSES,
|
|
94
|
+
...ZEN_CHAT,
|
|
95
|
+
...ZEN_RUNTIME
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
export function resolveOpencodeTransport(product, modelId) {
|
|
99
|
+
const id = normalizeModelId(modelId);
|
|
100
|
+
if (!id) return null;
|
|
101
|
+
if (product === "go") return OPENCODE_GO_TRANSPORTS[id] ?? null;
|
|
102
|
+
if (product === "zen") return OPENCODE_ZEN_TRANSPORTS[id] ?? null;
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function isDirectTransport(transport) {
|
|
107
|
+
return transport === TRANSPORT_KINDS.CHAT_COMPLETIONS
|
|
108
|
+
|| transport === TRANSPORT_KINDS.RESPONSES;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function listRegisteredModelIds(product, { directOnly = false, runtimeOnly = false } = {}) {
|
|
112
|
+
const table = product === "go" ? OPENCODE_GO_TRANSPORTS : OPENCODE_ZEN_TRANSPORTS;
|
|
113
|
+
return Object.entries(table)
|
|
114
|
+
.filter(([, transport]) => {
|
|
115
|
+
if (directOnly) return isDirectTransport(transport);
|
|
116
|
+
if (runtimeOnly) return transport === TRANSPORT_KINDS.RUNTIME;
|
|
117
|
+
return true;
|
|
118
|
+
})
|
|
119
|
+
.map(([modelId]) => modelId);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function normalizeModelId(modelId) {
|
|
123
|
+
if (!modelId) return null;
|
|
124
|
+
const raw = String(modelId).trim();
|
|
125
|
+
if (!raw) return null;
|
|
126
|
+
const slash = raw.lastIndexOf("/");
|
|
127
|
+
return slash >= 0 ? raw.slice(slash + 1) : raw;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function toRuntimeModelRef(product, modelId) {
|
|
131
|
+
const id = normalizeModelId(modelId);
|
|
132
|
+
if (!id) return null;
|
|
133
|
+
return product === "go" ? `opencode-go/${id}` : `opencode/${id}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function resolveRuntimeProduct(modelId) {
|
|
137
|
+
const raw = String(modelId ?? "");
|
|
138
|
+
if (raw.startsWith("opencode-go/")) return "go";
|
|
139
|
+
if (raw.startsWith("opencode/")) return "zen";
|
|
140
|
+
if (OPENCODE_GO_TRANSPORTS[normalizeModelId(raw)] === TRANSPORT_KINDS.RUNTIME) {
|
|
141
|
+
return OPENCODE_ZEN_TRANSPORTS[normalizeModelId(raw)] === TRANSPORT_KINDS.RUNTIME
|
|
142
|
+
? "zen"
|
|
143
|
+
: "go";
|
|
144
|
+
}
|
|
145
|
+
if (OPENCODE_ZEN_TRANSPORTS[normalizeModelId(raw)] === TRANSPORT_KINDS.RUNTIME) return "zen";
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
export const BACKEND_IDS = {
|
|
2
2
|
OLLAMA: "ollama",
|
|
3
3
|
OPENROUTER: "openrouter",
|
|
4
|
+
OPENCODE_GO: "opencode-go",
|
|
5
|
+
OPENCODE_ZEN: "opencode-zen",
|
|
6
|
+
OPENCODE: "opencode",
|
|
4
7
|
CUSTOM: "custom"
|
|
5
8
|
};
|
|
6
9
|
|
|
10
|
+
export const TRANSPORT_KINDS = {
|
|
11
|
+
CHAT_COMPLETIONS: "chat_completions",
|
|
12
|
+
RESPONSES: "responses",
|
|
13
|
+
RUNTIME: "runtime"
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const OPENCODE_API_KEY_ENV = "OPENCODE_API_KEY";
|
|
17
|
+
|
|
18
|
+
export const OPENCODE_GO_BASE_URL = "https://opencode.ai/zen/go/v1";
|
|
19
|
+
export const OPENCODE_ZEN_BASE_URL = "https://opencode.ai/zen/v1";
|
|
20
|
+
|
|
21
|
+
export const OPENCODE_GO_DEFAULT_MODEL = "kimi-k2.7-code";
|
|
22
|
+
export const OPENCODE_ZEN_DEFAULT_FREE_MODEL = "big-pickle";
|
|
23
|
+
|
|
7
24
|
export const COST_CLASSES = {
|
|
8
25
|
FREE: "free",
|
|
9
26
|
LOCAL: "local",
|
|
@@ -38,7 +55,8 @@ export function createModelDescriptor({
|
|
|
38
55
|
tools = false,
|
|
39
56
|
reasoning = false,
|
|
40
57
|
rateLimits = null,
|
|
41
|
-
opaque = false
|
|
58
|
+
opaque = false,
|
|
59
|
+
transport = null
|
|
42
60
|
}) {
|
|
43
61
|
return {
|
|
44
62
|
provider,
|
|
@@ -50,7 +68,8 @@ export function createModelDescriptor({
|
|
|
50
68
|
tools,
|
|
51
69
|
reasoning,
|
|
52
70
|
rateLimits,
|
|
53
|
-
opaque
|
|
71
|
+
opaque,
|
|
72
|
+
transport
|
|
54
73
|
};
|
|
55
74
|
}
|
|
56
75
|
|