@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
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BACKEND_IDS,
|
|
3
|
+
COST_CLASSES,
|
|
4
|
+
PRIVACY_CLASSES,
|
|
5
|
+
TRANSPORT_KINDS,
|
|
6
|
+
createModelDescriptor
|
|
7
|
+
} from "../types.js";
|
|
8
|
+
import {
|
|
9
|
+
listRegisteredModelIds,
|
|
10
|
+
normalizeModelId,
|
|
11
|
+
resolveRuntimeProduct,
|
|
12
|
+
toRuntimeModelRef
|
|
13
|
+
} from "../transport-registry.js";
|
|
14
|
+
import { CAPABILITY_STATES } from "../../capability-states.js";
|
|
15
|
+
import { isExecutableAvailable } from "../../cli-probe.js";
|
|
16
|
+
import { parseOpencodeJsonEvents, runOpencodeJson, sanitizeCliStderr } from "./opencode-cli.js";
|
|
17
|
+
import {
|
|
18
|
+
BILLING_MODELS,
|
|
19
|
+
ENTITLEMENT_STATES,
|
|
20
|
+
collectOpencodeCliEvidence
|
|
21
|
+
} from "./opencode-evidence.js";
|
|
22
|
+
|
|
23
|
+
const PREAMBLE =
|
|
24
|
+
"Answer with analysis only. Do not modify files, run mutating tools, or change the workspace.";
|
|
25
|
+
|
|
26
|
+
/** Non-mutating OpenCode CLI runtime. Override-only; never auto-routed; never reads auth.json. */
|
|
27
|
+
export function createOpencodeRuntimeBackend({
|
|
28
|
+
env = process.env,
|
|
29
|
+
spawnImpl,
|
|
30
|
+
whichImpl = isExecutableAvailable,
|
|
31
|
+
collectCliEvidence = collectOpencodeCliEvidence
|
|
32
|
+
} = {}) {
|
|
33
|
+
return {
|
|
34
|
+
id: BACKEND_IDS.OPENCODE,
|
|
35
|
+
label: "OpenCode CLI",
|
|
36
|
+
local: false,
|
|
37
|
+
|
|
38
|
+
async detect() {
|
|
39
|
+
const cli = collectCliEvidence({ env, whichImpl });
|
|
40
|
+
if (!cli.cliInstalled) {
|
|
41
|
+
return baseDetect({
|
|
42
|
+
state: CAPABILITY_STATES.UNKNOWN,
|
|
43
|
+
detected: false,
|
|
44
|
+
available: false,
|
|
45
|
+
configured: false,
|
|
46
|
+
entitlement: ENTITLEMENT_STATES.UNKNOWN,
|
|
47
|
+
evidence: evidence(false),
|
|
48
|
+
recommendation:
|
|
49
|
+
"Install and authenticate the OpenCode CLI to use Anthropic/Google models via runtime transport (Kairo does not read OpenCode auth files)."
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const configured = cli.authProviders.length > 0;
|
|
53
|
+
return baseDetect({
|
|
54
|
+
state: CAPABILITY_STATES.DETECTED,
|
|
55
|
+
detected: true,
|
|
56
|
+
available: true,
|
|
57
|
+
configured,
|
|
58
|
+
entitlement: configured
|
|
59
|
+
? ENTITLEMENT_STATES.UNVERIFIED
|
|
60
|
+
: ENTITLEMENT_STATES.UNKNOWN,
|
|
61
|
+
evidence: evidence(true, cli),
|
|
62
|
+
error: cli.error,
|
|
63
|
+
recommendation: configured
|
|
64
|
+
? `OpenCode CLI configured providers: ${cli.authProviders.join(", ")}. Entitlement remains ${ENTITLEMENT_STATES.UNVERIFIED}. Runtime invoke needs --cloud-consent --yes.`
|
|
65
|
+
: "OpenCode CLI detected but auth list reported no providers. Run `opencode auth login` (Kairo never reads auth.json)."
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async listModels() {
|
|
70
|
+
const models = listRegisteredModelIds("zen", { runtimeOnly: true })
|
|
71
|
+
.map((modelId) => runtimeModel("zen", modelId));
|
|
72
|
+
for (const modelId of listRegisteredModelIds("go", { runtimeOnly: true })) {
|
|
73
|
+
if (models.some((entry) => normalizeModelId(entry.modelId) === modelId)) continue;
|
|
74
|
+
models.push(runtimeModel("go", modelId));
|
|
75
|
+
}
|
|
76
|
+
return models;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async capabilities() {
|
|
80
|
+
const d = await this.detect();
|
|
81
|
+
return {
|
|
82
|
+
id: BACKEND_IDS.OPENCODE,
|
|
83
|
+
local: false,
|
|
84
|
+
cloud: true,
|
|
85
|
+
requiresApiKey: false,
|
|
86
|
+
requiresConsent: true,
|
|
87
|
+
streaming: false,
|
|
88
|
+
tools: false,
|
|
89
|
+
runtime: true,
|
|
90
|
+
transports: [TRANSPORT_KINDS.RUNTIME],
|
|
91
|
+
billingModel: d.billingModel,
|
|
92
|
+
entitlement: d.entitlement,
|
|
93
|
+
configured: d.configured,
|
|
94
|
+
authenticated: false,
|
|
95
|
+
state: d.state,
|
|
96
|
+
hasApiKey: false,
|
|
97
|
+
mutating: false
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async invoke(contextPack, request = {}) {
|
|
102
|
+
const detection = await this.detect();
|
|
103
|
+
if (!detection.available) return fail(detection.recommendation);
|
|
104
|
+
const modelRef = resolveModelRef(request.modelId);
|
|
105
|
+
if (!modelRef) {
|
|
106
|
+
return fail(
|
|
107
|
+
"Runtime invoke requires --model <provider/model> (e.g. opencode/claude-haiku-4-5)."
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const timeoutMs = request.timeoutMs ?? 120_000;
|
|
111
|
+
try {
|
|
112
|
+
const result = await runOpencodeJson({
|
|
113
|
+
modelRef,
|
|
114
|
+
prompt: buildPrompt(contextPack, request),
|
|
115
|
+
cwd: request.cwd ?? contextPack?.workspaceRoot ?? process.cwd(),
|
|
116
|
+
env,
|
|
117
|
+
spawnImpl,
|
|
118
|
+
timeoutMs,
|
|
119
|
+
terminationGraceMs: request.terminationGraceMs,
|
|
120
|
+
killGraceMs: request.killGraceMs
|
|
121
|
+
});
|
|
122
|
+
const failure = describeRuntimeFailure(result, timeoutMs);
|
|
123
|
+
if (failure) return fail(failure);
|
|
124
|
+
|
|
125
|
+
const parsed = parseOpencodeJsonEvents(result.stdout);
|
|
126
|
+
if (parsed.error) return fail(parsed.error);
|
|
127
|
+
if (!parsed.content) {
|
|
128
|
+
const detail = sanitizeCliStderr(result.stderr) || "no content";
|
|
129
|
+
return fail(`OpenCode CLI produced no text output (${detail}).`);
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
ok: true,
|
|
133
|
+
backendId: BACKEND_IDS.OPENCODE,
|
|
134
|
+
model: modelRef,
|
|
135
|
+
content: parsed.content,
|
|
136
|
+
usage: {
|
|
137
|
+
...parsed.usage,
|
|
138
|
+
model: modelRef,
|
|
139
|
+
backendId: BACKEND_IDS.OPENCODE,
|
|
140
|
+
fallbackUsed: false
|
|
141
|
+
},
|
|
142
|
+
raw: {
|
|
143
|
+
events: parsed.events,
|
|
144
|
+
status: result.status,
|
|
145
|
+
signal: result.signal
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (error?.code === "ENOENT") {
|
|
150
|
+
return fail(
|
|
151
|
+
"OpenCode CLI is not installed. Install OpenCode, authenticate it, then retry with --backend opencode."
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return fail(error?.message ?? String(error));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function describeRuntimeFailure(result, timeoutMs) {
|
|
161
|
+
if (result.terminationFailed) {
|
|
162
|
+
return `OpenCode CLI process did not terminate after timeout (${timeoutMs}ms SIGTERM/SIGKILL).`;
|
|
163
|
+
}
|
|
164
|
+
if (result.timedOut) {
|
|
165
|
+
return `OpenCode CLI timed out after ${timeoutMs}ms.`;
|
|
166
|
+
}
|
|
167
|
+
if (result.status !== 0) {
|
|
168
|
+
const exitPart = result.status == null
|
|
169
|
+
? (result.signal ? `signal ${result.signal}` : "exit null")
|
|
170
|
+
: `exit ${result.status}`;
|
|
171
|
+
const detail = sanitizeCliStderr(result.stderr);
|
|
172
|
+
return detail
|
|
173
|
+
? `OpenCode CLI failed (${exitPart}): ${detail}`
|
|
174
|
+
: `OpenCode CLI failed (${exitPart}).`;
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
function evidence(cliInstalled, cli = null) {
|
|
179
|
+
return {
|
|
180
|
+
hasApiKey: false,
|
|
181
|
+
cliInstalled,
|
|
182
|
+
authListOk: cli?.authListOk ?? false,
|
|
183
|
+
authProviders: cli?.authProviders ?? [],
|
|
184
|
+
modelsHttpStatus: null
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function baseDetect(fields) {
|
|
189
|
+
return {
|
|
190
|
+
id: BACKEND_IDS.OPENCODE,
|
|
191
|
+
label: "OpenCode CLI",
|
|
192
|
+
hasApiKey: false,
|
|
193
|
+
cloud: true,
|
|
194
|
+
runtime: true,
|
|
195
|
+
authenticated: false,
|
|
196
|
+
billingModel: BILLING_MODELS.CLI_RUNTIME,
|
|
197
|
+
error: null,
|
|
198
|
+
...fields
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function runtimeModel(product, modelId) {
|
|
203
|
+
return createModelDescriptor({
|
|
204
|
+
provider: BACKEND_IDS.OPENCODE,
|
|
205
|
+
modelId: toRuntimeModelRef(product, modelId),
|
|
206
|
+
local: false,
|
|
207
|
+
costClass: COST_CLASSES.PAID,
|
|
208
|
+
privacyClass: PRIVACY_CLASSES.CLOUD,
|
|
209
|
+
transport: TRANSPORT_KINDS.RUNTIME
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function resolveModelRef(modelId) {
|
|
214
|
+
if (!modelId) return null;
|
|
215
|
+
const raw = String(modelId).trim();
|
|
216
|
+
if (raw.includes("/")) return raw;
|
|
217
|
+
const product = resolveRuntimeProduct(raw);
|
|
218
|
+
return product ? toRuntimeModelRef(product, raw) : null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function buildPrompt(contextPack, request) {
|
|
222
|
+
const chunks = [PREAMBLE];
|
|
223
|
+
if (contextPack?.systemPrompt) chunks.push(contextPack.systemPrompt);
|
|
224
|
+
if (request.prompt) chunks.push(request.prompt);
|
|
225
|
+
else if (Array.isArray(request.messages)) {
|
|
226
|
+
for (const message of request.messages) {
|
|
227
|
+
if (message?.content) chunks.push(String(message.content));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return chunks.join("\n\n");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function fail(message) {
|
|
234
|
+
return {
|
|
235
|
+
ok: false,
|
|
236
|
+
backendId: BACKEND_IDS.OPENCODE,
|
|
237
|
+
model: null,
|
|
238
|
+
content: null,
|
|
239
|
+
error: message,
|
|
240
|
+
usage: null
|
|
241
|
+
};
|
|
242
|
+
}
|
|
@@ -30,6 +30,7 @@ const STABLE_DOC_CANDIDATES = [
|
|
|
30
30
|
|
|
31
31
|
const DEFAULT_STABLE_BUDGET = 6000;
|
|
32
32
|
const DEFAULT_REQUEST_BUDGET = 4000;
|
|
33
|
+
const TRUNCATION_MARKER = "\n…[truncated]";
|
|
33
34
|
|
|
34
35
|
export async function compileContextPack({
|
|
35
36
|
workspaceRoot,
|
|
@@ -44,29 +45,54 @@ export async function compileContextPack({
|
|
|
44
45
|
const project = await detectProject(root);
|
|
45
46
|
const evidence = [];
|
|
46
47
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
const stableBudget = createBudget(stableBudgetTokens);
|
|
49
|
+
let agentsMd = null;
|
|
50
|
+
const stableDocs = [];
|
|
51
|
+
|
|
52
|
+
for (const candidate of STABLE_DOC_CANDIDATES) {
|
|
53
|
+
const content = await readEvidenceFile(
|
|
54
|
+
root,
|
|
55
|
+
workspaceRealRoot,
|
|
56
|
+
candidate,
|
|
57
|
+
evidence,
|
|
58
|
+
stableBudget,
|
|
59
|
+
{ includePrivate, category: "stable" }
|
|
60
|
+
);
|
|
61
|
+
if (!content) continue;
|
|
62
|
+
if (candidate === "AGENTS.md") {
|
|
63
|
+
agentsMd = content;
|
|
64
|
+
} else {
|
|
65
|
+
stableDocs.push({ path: candidate, content: content.text, truncated: content.truncated });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
56
69
|
const skills = await listSkillIds(root, evidence);
|
|
57
70
|
const graphify = await detectGraphify(root, evidence);
|
|
58
71
|
const engram = await detectEngramHints(root, evidence);
|
|
59
72
|
|
|
73
|
+
const requestBudget = createBudget(requestBudgetTokens);
|
|
60
74
|
const requestFiles = [];
|
|
75
|
+
const seenPaths = new Set();
|
|
76
|
+
|
|
61
77
|
for (const filePath of relevantPaths) {
|
|
62
|
-
const rel = relative(root, resolve(root, filePath));
|
|
78
|
+
const rel = relative(root, resolve(root, filePath)).replace(/\\/g, "/") || ".";
|
|
79
|
+
if (seenPaths.has(rel)) {
|
|
80
|
+
evidence.push({
|
|
81
|
+
kind: "deduped_path",
|
|
82
|
+
path: rel,
|
|
83
|
+
reason: "Duplicate path skipped"
|
|
84
|
+
});
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
seenPaths.add(rel);
|
|
88
|
+
|
|
63
89
|
const content = await readEvidenceFile(
|
|
64
90
|
root,
|
|
65
91
|
workspaceRealRoot,
|
|
66
92
|
rel,
|
|
67
93
|
evidence,
|
|
68
|
-
|
|
69
|
-
{ includePrivate }
|
|
94
|
+
requestBudget,
|
|
95
|
+
{ includePrivate, category: "request" }
|
|
70
96
|
);
|
|
71
97
|
if (content) {
|
|
72
98
|
requestFiles.push({ path: rel, content: content.text, truncated: content.truncated });
|
|
@@ -92,11 +118,7 @@ export async function compileContextPack({
|
|
|
92
118
|
engram
|
|
93
119
|
};
|
|
94
120
|
|
|
95
|
-
const perRequest = {
|
|
96
|
-
task,
|
|
97
|
-
files: requestFiles
|
|
98
|
-
};
|
|
99
|
-
|
|
121
|
+
const perRequest = { task, files: requestFiles };
|
|
100
122
|
const systemPrompt = buildSystemPrompt(stable, perRequest);
|
|
101
123
|
const estimatedTokens = estimateTokens(systemPrompt);
|
|
102
124
|
|
|
@@ -110,7 +132,9 @@ export async function compileContextPack({
|
|
|
110
132
|
estimatedTokens,
|
|
111
133
|
budgets: {
|
|
112
134
|
stableBudgetTokens,
|
|
113
|
-
requestBudgetTokens
|
|
135
|
+
requestBudgetTokens,
|
|
136
|
+
stableUsedTokens: stableBudget.used,
|
|
137
|
+
requestUsedTokens: requestBudget.used
|
|
114
138
|
},
|
|
115
139
|
privacy: {
|
|
116
140
|
includePrivate,
|
|
@@ -131,28 +155,17 @@ export function estimateTokens(text) {
|
|
|
131
155
|
return Math.ceil(text.length / 4);
|
|
132
156
|
}
|
|
133
157
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
evidence,
|
|
146
|
-
remaining,
|
|
147
|
-
{ includePrivate }
|
|
148
|
-
);
|
|
149
|
-
if (!content) continue;
|
|
150
|
-
docs.push({ path: candidate, content: content.text, truncated: content.truncated });
|
|
151
|
-
used += estimateTokens(content.text);
|
|
152
|
-
if (used >= budgetTokens) break;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return docs;
|
|
158
|
+
function createBudget(limitTokens) {
|
|
159
|
+
return {
|
|
160
|
+
limit: Math.max(0, Number(limitTokens) || 0),
|
|
161
|
+
used: 0,
|
|
162
|
+
remaining() {
|
|
163
|
+
return Math.max(0, this.limit - this.used);
|
|
164
|
+
},
|
|
165
|
+
consume(tokens) {
|
|
166
|
+
this.used += tokens;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
156
169
|
}
|
|
157
170
|
|
|
158
171
|
async function listSkillIds(root, evidence) {
|
|
@@ -204,11 +217,11 @@ async function readEvidenceFile(
|
|
|
204
217
|
workspaceRealRoot,
|
|
205
218
|
relativePath,
|
|
206
219
|
evidence,
|
|
207
|
-
|
|
208
|
-
{ includePrivate = false } = {}
|
|
220
|
+
budget,
|
|
221
|
+
{ includePrivate = false, category = "request" } = {}
|
|
209
222
|
) {
|
|
210
223
|
const requested = resolve(root, relativePath);
|
|
211
|
-
const requestedRelative = relative(root, requested) || ".";
|
|
224
|
+
const requestedRelative = relative(root, requested).replace(/\\/g, "/") || ".";
|
|
212
225
|
if (!isPathInside(root, requested)) {
|
|
213
226
|
evidence.push({
|
|
214
227
|
kind: "rejected_outside_workspace",
|
|
@@ -220,6 +233,17 @@ async function readEvidenceFile(
|
|
|
220
233
|
|
|
221
234
|
if (!existsSync(requested)) return null;
|
|
222
235
|
|
|
236
|
+
const remaining = budget.remaining();
|
|
237
|
+
if (remaining <= 0) {
|
|
238
|
+
evidence.push({
|
|
239
|
+
kind: "excluded_budget",
|
|
240
|
+
path: requestedRelative,
|
|
241
|
+
reason: `${category} budget exhausted`,
|
|
242
|
+
tokens: 0
|
|
243
|
+
});
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
223
247
|
let target;
|
|
224
248
|
try {
|
|
225
249
|
target = await realpath(requested);
|
|
@@ -237,7 +261,7 @@ async function readEvidenceFile(
|
|
|
237
261
|
return null;
|
|
238
262
|
}
|
|
239
263
|
|
|
240
|
-
const targetRelative = relative(workspaceRealRoot, target) || ".";
|
|
264
|
+
const targetRelative = relative(workspaceRealRoot, target).replace(/\\/g, "/") || ".";
|
|
241
265
|
if ((isPrivatePath(requestedRelative) || isPrivatePath(targetRelative)) && !includePrivate) {
|
|
242
266
|
evidence.push({
|
|
243
267
|
kind: "excluded_private",
|
|
@@ -249,22 +273,43 @@ async function readEvidenceFile(
|
|
|
249
273
|
|
|
250
274
|
try {
|
|
251
275
|
const raw = await readFile(target, "utf8");
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
const text = truncated ? `${raw.slice(0, maxChars)}\n…[truncated]` : raw;
|
|
276
|
+
const clipped = clipToTokenBudget(raw, remaining);
|
|
277
|
+
budget.consume(clipped.tokens);
|
|
255
278
|
evidence.push({
|
|
256
279
|
kind: "file",
|
|
257
280
|
path: requestedRelative,
|
|
258
|
-
truncated,
|
|
259
|
-
chars: text.length
|
|
281
|
+
truncated: clipped.truncated,
|
|
282
|
+
chars: clipped.text.length,
|
|
283
|
+
tokens: clipped.tokens
|
|
260
284
|
});
|
|
261
|
-
return { text, truncated };
|
|
285
|
+
return { text: clipped.text, truncated: clipped.truncated, tokens: clipped.tokens };
|
|
262
286
|
} catch {
|
|
263
287
|
evidence.push({ kind: "unreadable", path: requestedRelative });
|
|
264
288
|
return null;
|
|
265
289
|
}
|
|
266
290
|
}
|
|
267
291
|
|
|
292
|
+
function clipToTokenBudget(raw, maxTokens) {
|
|
293
|
+
const maxChars = Math.max(0, maxTokens) * 4;
|
|
294
|
+
if (raw.length <= maxChars) {
|
|
295
|
+
return { text: raw, truncated: false, tokens: estimateTokens(raw) };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const marker = TRUNCATION_MARKER;
|
|
299
|
+
if (marker.length >= maxChars) {
|
|
300
|
+
const text = marker.slice(0, maxChars);
|
|
301
|
+
return { text, truncated: true, tokens: estimateTokens(text) };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const bodyChars = maxChars - marker.length;
|
|
305
|
+
const text = `${raw.slice(0, bodyChars)}${marker}`;
|
|
306
|
+
return {
|
|
307
|
+
text,
|
|
308
|
+
truncated: true,
|
|
309
|
+
tokens: estimateTokens(text)
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
268
313
|
async function resolveWorkspaceRoot(root) {
|
|
269
314
|
try {
|
|
270
315
|
return await realpath(root);
|
|
@@ -3,16 +3,52 @@ export {
|
|
|
3
3
|
COST_CLASSES,
|
|
4
4
|
PRIVACY_CLASSES,
|
|
5
5
|
ROUTING_MODES,
|
|
6
|
+
TRANSPORT_KINDS,
|
|
6
7
|
OPENROUTER_FREE_MODEL,
|
|
8
|
+
OPENCODE_API_KEY_ENV,
|
|
9
|
+
OPENCODE_GO_BASE_URL,
|
|
10
|
+
OPENCODE_ZEN_BASE_URL,
|
|
11
|
+
OPENCODE_GO_DEFAULT_MODEL,
|
|
12
|
+
OPENCODE_ZEN_DEFAULT_FREE_MODEL,
|
|
7
13
|
DEFAULT_OLLAMA_HOST,
|
|
8
14
|
createModelDescriptor,
|
|
9
15
|
createRoutingDecision,
|
|
10
16
|
createUsageTelemetry
|
|
11
17
|
} from "./types.js";
|
|
12
18
|
|
|
19
|
+
export {
|
|
20
|
+
resolveOpencodeTransport,
|
|
21
|
+
isDirectTransport,
|
|
22
|
+
listRegisteredModelIds,
|
|
23
|
+
normalizeModelId,
|
|
24
|
+
toRuntimeModelRef,
|
|
25
|
+
resolveRuntimeProduct,
|
|
26
|
+
OPENCODE_GO_TRANSPORTS,
|
|
27
|
+
OPENCODE_ZEN_TRANSPORTS
|
|
28
|
+
} from "./transport-registry.js";
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
ENTITLEMENT_STATES,
|
|
32
|
+
BILLING_MODELS,
|
|
33
|
+
parseAuthListProviders,
|
|
34
|
+
collectOpencodeCliEvidence
|
|
35
|
+
} from "./backends/opencode-evidence.js";
|
|
36
|
+
|
|
13
37
|
export { createOllamaBackend } from "./backends/ollama.js";
|
|
14
38
|
export { createOpenRouterBackend } from "./backends/openrouter.js";
|
|
15
39
|
export { createCustomHttpBackend } from "./backends/custom-http.js";
|
|
40
|
+
export {
|
|
41
|
+
createOpencodeGoBackend,
|
|
42
|
+
createOpencodeZenBackend
|
|
43
|
+
} from "./backends/opencode-providers.js";
|
|
44
|
+
|
|
45
|
+
export { createOpencodeRuntimeBackend } from "./backends/opencode-runtime.js";
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
runOpencodeJson,
|
|
49
|
+
parseOpencodeJsonEvents,
|
|
50
|
+
sanitizeCliStderr
|
|
51
|
+
} from "./backends/opencode-cli.js";
|
|
16
52
|
|
|
17
53
|
export {
|
|
18
54
|
createDefaultBackends,
|
|
@@ -29,7 +65,8 @@ export {
|
|
|
29
65
|
|
|
30
66
|
export {
|
|
31
67
|
resolveRoutingDecision,
|
|
32
|
-
classifyTaskWeight
|
|
68
|
+
classifyTaskWeight,
|
|
69
|
+
isDirectProviderEligible
|
|
33
70
|
} from "./router.js";
|
|
34
71
|
|
|
35
72
|
export {
|
|
@@ -19,7 +19,8 @@ export async function runIntelligenceRequest({
|
|
|
19
19
|
env = process.env,
|
|
20
20
|
fetchImpl = globalThis.fetch,
|
|
21
21
|
backends = null,
|
|
22
|
-
tokenBudget = null
|
|
22
|
+
tokenBudget = null,
|
|
23
|
+
sessionOverride = null
|
|
23
24
|
} = {}) {
|
|
24
25
|
const customProviders = Array.isArray(profile.customProviders) ? profile.customProviders : [];
|
|
25
26
|
const backendInstances = backends ?? createDefaultBackends({ env, fetchImpl, customProviders });
|
|
@@ -46,7 +47,8 @@ export async function runIntelligenceRequest({
|
|
|
46
47
|
contextPack,
|
|
47
48
|
task: task ?? prompt,
|
|
48
49
|
cloudConsent: Boolean(cloudConsent),
|
|
49
|
-
tokenBudget: tokenBudget ?? profile.tokenBudget ?? null
|
|
50
|
+
tokenBudget: tokenBudget ?? profile.tokenBudget ?? null,
|
|
51
|
+
sessionOverride
|
|
50
52
|
});
|
|
51
53
|
|
|
52
54
|
const explanation = explainRouting(routing, contextPack, inspections);
|
|
@@ -2,16 +2,39 @@ import { BACKEND_IDS } from "./types.js";
|
|
|
2
2
|
import { createOllamaBackend } from "./backends/ollama.js";
|
|
3
3
|
import { createOpenRouterBackend } from "./backends/openrouter.js";
|
|
4
4
|
import { createCustomHttpBackend } from "./backends/custom-http.js";
|
|
5
|
+
import {
|
|
6
|
+
createOpencodeGoBackend,
|
|
7
|
+
createOpencodeZenBackend
|
|
8
|
+
} from "./backends/opencode-providers.js";
|
|
9
|
+
import { createOpencodeRuntimeBackend } from "./backends/opencode-runtime.js";
|
|
10
|
+
import { collectOpencodeCliEvidence } from "./backends/opencode-evidence.js";
|
|
5
11
|
import { CAPABILITY_STATES } from "../capability-states.js";
|
|
6
12
|
|
|
7
13
|
export function createDefaultBackends({
|
|
8
14
|
env = process.env,
|
|
9
15
|
fetchImpl = globalThis.fetch,
|
|
10
|
-
customProviders = []
|
|
16
|
+
customProviders = [],
|
|
17
|
+
whichImpl,
|
|
18
|
+
collectCliEvidence,
|
|
19
|
+
spawnImpl
|
|
11
20
|
} = {}) {
|
|
21
|
+
const sharedCliEvidence = createSharedCliEvidenceCollector({
|
|
22
|
+
env,
|
|
23
|
+
whichImpl,
|
|
24
|
+
collectCliEvidence
|
|
25
|
+
});
|
|
26
|
+
|
|
12
27
|
const backends = [
|
|
13
28
|
createOllamaBackend({ env, fetchImpl }),
|
|
14
|
-
|
|
29
|
+
createOpencodeGoBackend({ env, fetchImpl, collectCliEvidence: sharedCliEvidence }),
|
|
30
|
+
createOpencodeZenBackend({ env, fetchImpl, collectCliEvidence: sharedCliEvidence }),
|
|
31
|
+
createOpenRouterBackend({ env, fetchImpl }),
|
|
32
|
+
createOpencodeRuntimeBackend({
|
|
33
|
+
env,
|
|
34
|
+
whichImpl,
|
|
35
|
+
spawnImpl,
|
|
36
|
+
collectCliEvidence: sharedCliEvidence
|
|
37
|
+
})
|
|
15
38
|
];
|
|
16
39
|
|
|
17
40
|
for (const provider of customProviders) {
|
|
@@ -29,9 +52,19 @@ export async function inspectIntelligenceBackends({
|
|
|
29
52
|
env = process.env,
|
|
30
53
|
fetchImpl = globalThis.fetch,
|
|
31
54
|
customProviders = [],
|
|
32
|
-
backends = null
|
|
55
|
+
backends = null,
|
|
56
|
+
whichImpl,
|
|
57
|
+
collectCliEvidence,
|
|
58
|
+
spawnImpl
|
|
33
59
|
} = {}) {
|
|
34
|
-
const resolved = backends ?? createDefaultBackends({
|
|
60
|
+
const resolved = backends ?? createDefaultBackends({
|
|
61
|
+
env,
|
|
62
|
+
fetchImpl,
|
|
63
|
+
customProviders,
|
|
64
|
+
whichImpl,
|
|
65
|
+
collectCliEvidence,
|
|
66
|
+
spawnImpl
|
|
67
|
+
});
|
|
35
68
|
return Promise.all(resolved.map(async (backend) => {
|
|
36
69
|
const detection = await backend.detect();
|
|
37
70
|
const models = detection.detected || detection.available
|
|
@@ -59,15 +92,25 @@ export function summarizeIntelligenceBackends(inspections) {
|
|
|
59
92
|
}
|
|
60
93
|
}
|
|
61
94
|
|
|
95
|
+
const go = inspections.find((entry) => entry.id === BACKEND_IDS.OPENCODE_GO);
|
|
96
|
+
const zen = inspections.find((entry) => entry.id === BACKEND_IDS.OPENCODE_ZEN);
|
|
97
|
+
const openrouter = inspections.find((entry) => entry.id === BACKEND_IDS.OPENROUTER);
|
|
98
|
+
|
|
62
99
|
return {
|
|
63
100
|
total: inspections.length,
|
|
64
101
|
available: inspections.filter((entry) => entry.available).length,
|
|
65
102
|
localAvailable: inspections.some(
|
|
66
103
|
(entry) => entry.id === BACKEND_IDS.OLLAMA && entry.available
|
|
67
104
|
),
|
|
68
|
-
|
|
69
|
-
|
|
105
|
+
cloudConfigured: Boolean(go?.configured || zen?.configured || openrouter?.hasApiKey),
|
|
106
|
+
/** OpenRouter stays key-based; Go/Zen use proven authenticated evidence. */
|
|
107
|
+
cloudAuthenticated: Boolean(
|
|
108
|
+
go?.authenticated || zen?.authenticated || openrouter?.hasApiKey
|
|
70
109
|
),
|
|
110
|
+
opencodeGoConfigured: Boolean(go?.configured),
|
|
111
|
+
opencodeZenConfigured: Boolean(zen?.configured),
|
|
112
|
+
opencodeGoAuthenticated: Boolean(go?.authenticated),
|
|
113
|
+
opencodeZenAuthenticated: Boolean(zen?.authenticated),
|
|
71
114
|
byState
|
|
72
115
|
};
|
|
73
116
|
}
|
|
@@ -75,3 +118,16 @@ export function summarizeIntelligenceBackends(inspections) {
|
|
|
75
118
|
export function resolveBackendById(backends, backendId) {
|
|
76
119
|
return backends.find((backend) => backend.id === backendId) ?? null;
|
|
77
120
|
}
|
|
121
|
+
|
|
122
|
+
function createSharedCliEvidenceCollector({
|
|
123
|
+
env,
|
|
124
|
+
whichImpl,
|
|
125
|
+
collectCliEvidence
|
|
126
|
+
}) {
|
|
127
|
+
let cached = null;
|
|
128
|
+
const collector = collectCliEvidence ?? collectOpencodeCliEvidence;
|
|
129
|
+
return () => {
|
|
130
|
+
if (!cached) cached = collector({ env, whichImpl });
|
|
131
|
+
return cached;
|
|
132
|
+
};
|
|
133
|
+
}
|