@kal-elsam/kairo-runtime 0.1.4 → 0.2.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 +23 -0
- package/package.json +1 -1
- package/src/cli.js +113 -4
- package/src/global/action-planner.js +230 -0
- package/src/global/agent-capabilities/create-capability-adapter.js +224 -0
- package/src/global/agent-capabilities/index.js +54 -0
- package/src/global/brand/cli.js +11 -7
- package/src/global/capability-registry.js +80 -0
- package/src/global/capability-states.js +30 -0
- package/src/global/cli-probe.js +47 -0
- package/src/global/ink/orchestrator-app.js +224 -0
- package/src/global/ink/orchestrator-state.js +107 -0
- package/src/global/ink/run-orchestrator-ink.js +29 -0
- package/src/global/intelligence/backends/custom-http.js +175 -0
- package/src/global/intelligence/backends/ollama.js +164 -0
- package/src/global/intelligence/backends/openrouter.js +198 -0
- package/src/global/intelligence/context-compiler.js +338 -0
- package/src/global/intelligence/custom-url.js +82 -0
- package/src/global/intelligence/http.js +63 -0
- package/src/global/intelligence/index.js +38 -0
- package/src/global/intelligence/orchestrate.js +189 -0
- package/src/global/intelligence/registry.js +77 -0
- package/src/global/intelligence/router.js +191 -0
- package/src/global/intelligence/types.js +99 -0
- package/src/global/intelligence-cli.js +323 -0
- package/src/global/orchestrator.js +150 -0
- package/src/global/paths.js +1 -0
- package/src/global/profile.js +297 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { BACKEND_IDS } from "./types.js";
|
|
2
|
+
import { createOllamaBackend } from "./backends/ollama.js";
|
|
3
|
+
import { createOpenRouterBackend } from "./backends/openrouter.js";
|
|
4
|
+
import { createCustomHttpBackend } from "./backends/custom-http.js";
|
|
5
|
+
import { CAPABILITY_STATES } from "../capability-states.js";
|
|
6
|
+
|
|
7
|
+
export function createDefaultBackends({
|
|
8
|
+
env = process.env,
|
|
9
|
+
fetchImpl = globalThis.fetch,
|
|
10
|
+
customProviders = []
|
|
11
|
+
} = {}) {
|
|
12
|
+
const backends = [
|
|
13
|
+
createOllamaBackend({ env, fetchImpl }),
|
|
14
|
+
createOpenRouterBackend({ env, fetchImpl })
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
for (const provider of customProviders) {
|
|
18
|
+
backends.push(createCustomHttpBackend({
|
|
19
|
+
...provider,
|
|
20
|
+
env,
|
|
21
|
+
fetchImpl
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return backends;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function inspectIntelligenceBackends({
|
|
29
|
+
env = process.env,
|
|
30
|
+
fetchImpl = globalThis.fetch,
|
|
31
|
+
customProviders = [],
|
|
32
|
+
backends = null
|
|
33
|
+
} = {}) {
|
|
34
|
+
const resolved = backends ?? createDefaultBackends({ env, fetchImpl, customProviders });
|
|
35
|
+
return Promise.all(resolved.map(async (backend) => {
|
|
36
|
+
const detection = await backend.detect();
|
|
37
|
+
const models = detection.detected || detection.available
|
|
38
|
+
? await backend.listModels()
|
|
39
|
+
: [];
|
|
40
|
+
const capabilities = await backend.capabilities();
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
...detection,
|
|
44
|
+
models,
|
|
45
|
+
capabilities
|
|
46
|
+
};
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function summarizeIntelligenceBackends(inspections) {
|
|
51
|
+
const byState = {};
|
|
52
|
+
for (const state of Object.values(CAPABILITY_STATES)) {
|
|
53
|
+
byState[state] = 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const entry of inspections) {
|
|
57
|
+
if (byState[entry.state] != null) {
|
|
58
|
+
byState[entry.state] += 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
total: inspections.length,
|
|
64
|
+
available: inspections.filter((entry) => entry.available).length,
|
|
65
|
+
localAvailable: inspections.some(
|
|
66
|
+
(entry) => entry.id === BACKEND_IDS.OLLAMA && entry.available
|
|
67
|
+
),
|
|
68
|
+
cloudAuthenticated: inspections.some(
|
|
69
|
+
(entry) => entry.id === BACKEND_IDS.OPENROUTER && entry.hasApiKey
|
|
70
|
+
),
|
|
71
|
+
byState
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function resolveBackendById(backends, backendId) {
|
|
76
|
+
return backends.find((backend) => backend.id === backendId) ?? null;
|
|
77
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BACKEND_IDS,
|
|
3
|
+
OPENROUTER_FREE_MODEL,
|
|
4
|
+
PRIVACY_CLASSES,
|
|
5
|
+
ROUTING_MODES,
|
|
6
|
+
createRoutingDecision
|
|
7
|
+
} from "./types.js";
|
|
8
|
+
import { estimateTokens } from "./context-compiler.js";
|
|
9
|
+
|
|
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
|
+
export function classifyTaskWeight(task = "") {
|
|
22
|
+
const text = String(task).toLowerCase();
|
|
23
|
+
if (/architect|adr|design system|security|threat/.test(text)) return "heavy";
|
|
24
|
+
if (/review|refactor complex|debug complex/.test(text)) return "heavy";
|
|
25
|
+
if (/test|scaffold|explain|status|diagnose|lint|format/.test(text)) return "light";
|
|
26
|
+
return TASK_WEIGHTS.default;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolve which backend/model to use.
|
|
31
|
+
* Precedence: user override > Ollama local > OpenRouter free (consent) > diagnostics.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveRoutingDecision({
|
|
34
|
+
backends = [],
|
|
35
|
+
profile = {},
|
|
36
|
+
contextPack = null,
|
|
37
|
+
task = null,
|
|
38
|
+
cloudConsent = false,
|
|
39
|
+
tokenBudget = null
|
|
40
|
+
} = {}) {
|
|
41
|
+
const estimatedTokens = contextPack?.estimatedTokens
|
|
42
|
+
?? estimateTokens(contextPack?.systemPrompt ?? "")
|
|
43
|
+
+ estimateTokens(task ?? "");
|
|
44
|
+
|
|
45
|
+
const budget = tokenBudget ?? profile.tokenBudget ?? null;
|
|
46
|
+
if (budget != null && estimatedTokens > budget) {
|
|
47
|
+
return createRoutingDecision({
|
|
48
|
+
backendId: null,
|
|
49
|
+
model: null,
|
|
50
|
+
reason: `Estimated tokens (${estimatedTokens}) exceed budget (${budget}). Compact context or raise tokenBudget.`,
|
|
51
|
+
estimatedTokens,
|
|
52
|
+
privacyImpact: PRIVACY_CLASSES.UNKNOWN,
|
|
53
|
+
mode: ROUTING_MODES.DIAGNOSTICS,
|
|
54
|
+
requiresCloudConsent: false,
|
|
55
|
+
canInvoke: false
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const override = resolveUserOverride(profile, backends);
|
|
60
|
+
if (override) {
|
|
61
|
+
return createRoutingDecision({
|
|
62
|
+
backendId: override.backend.id,
|
|
63
|
+
model: override.model,
|
|
64
|
+
reason: `User override: ${override.backend.id}/${override.model.modelId}`,
|
|
65
|
+
estimatedTokens,
|
|
66
|
+
privacyImpact: override.model.privacyClass,
|
|
67
|
+
mode: ROUTING_MODES.USER_OVERRIDE,
|
|
68
|
+
requiresCloudConsent: !override.model.local,
|
|
69
|
+
canInvoke: override.model.local || cloudConsent,
|
|
70
|
+
fallback: buildLocalFallback(backends)
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const ollama = backends.find((entry) => entry.id === BACKEND_IDS.OLLAMA);
|
|
75
|
+
if (ollama?.available && Array.isArray(ollama.models) && ollama.models.length > 0) {
|
|
76
|
+
const model = selectLocalModel(ollama.models, task);
|
|
77
|
+
return createRoutingDecision({
|
|
78
|
+
backendId: BACKEND_IDS.OLLAMA,
|
|
79
|
+
model,
|
|
80
|
+
reason: `Local-first: Ollama model ${model.modelId}`,
|
|
81
|
+
estimatedTokens,
|
|
82
|
+
privacyImpact: PRIVACY_CLASSES.LOCAL,
|
|
83
|
+
mode: ROUTING_MODES.LOCAL,
|
|
84
|
+
requiresCloudConsent: false,
|
|
85
|
+
canInvoke: true,
|
|
86
|
+
fallback: buildCloudFallback(backends, cloudConsent)
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const openrouter = backends.find((entry) => entry.id === BACKEND_IDS.OPENROUTER);
|
|
91
|
+
if (openrouter?.hasApiKey) {
|
|
92
|
+
const model = openrouter.models?.find((entry) => entry.modelId === OPENROUTER_FREE_MODEL)
|
|
93
|
+
?? 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
|
+
};
|
|
102
|
+
|
|
103
|
+
return createRoutingDecision({
|
|
104
|
+
backendId: BACKEND_IDS.OPENROUTER,
|
|
105
|
+
model,
|
|
106
|
+
reason: cloudConsent
|
|
107
|
+
? `Cloud fallback approved: ${model.modelId}`
|
|
108
|
+
: `OpenRouter available (${model.modelId}) but cloud consent required before invoke`,
|
|
109
|
+
estimatedTokens,
|
|
110
|
+
privacyImpact: PRIVACY_CLASSES.CLOUD,
|
|
111
|
+
mode: ROUTING_MODES.CLOUD_CONSENT,
|
|
112
|
+
requiresCloudConsent: true,
|
|
113
|
+
canInvoke: cloudConsent,
|
|
114
|
+
fallback: null
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return createRoutingDecision({
|
|
119
|
+
backendId: null,
|
|
120
|
+
model: null,
|
|
121
|
+
reason: "No intelligence backend available. Remaining in diagnostics/configuration mode.",
|
|
122
|
+
estimatedTokens,
|
|
123
|
+
privacyImpact: PRIVACY_CLASSES.UNKNOWN,
|
|
124
|
+
mode: ROUTING_MODES.DIAGNOSTICS,
|
|
125
|
+
requiresCloudConsent: false,
|
|
126
|
+
canInvoke: false
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function resolveUserOverride(profile, backends) {
|
|
131
|
+
const preferredBackend = profile.preferredBackend ?? null;
|
|
132
|
+
const preferredModel = profile.preferredModel ?? null;
|
|
133
|
+
if (!preferredBackend && !preferredModel) return null;
|
|
134
|
+
|
|
135
|
+
if (preferredBackend) {
|
|
136
|
+
const backend = backends.find((entry) => entry.id === preferredBackend);
|
|
137
|
+
if (!backend || (!backend.available && !backend.hasApiKey && !backend.detected)) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const model = (backend.models ?? []).find((entry) => entry.modelId === preferredModel)
|
|
141
|
+
?? backend.models?.[0]
|
|
142
|
+
?? (preferredModel
|
|
143
|
+
? {
|
|
144
|
+
provider: preferredBackend,
|
|
145
|
+
modelId: preferredModel,
|
|
146
|
+
local: backend.id === BACKEND_IDS.OLLAMA,
|
|
147
|
+
privacyClass: backend.id === BACKEND_IDS.OLLAMA ? PRIVACY_CLASSES.LOCAL : PRIVACY_CLASSES.CLOUD,
|
|
148
|
+
costClass: "unknown",
|
|
149
|
+
opaque: true
|
|
150
|
+
}
|
|
151
|
+
: null);
|
|
152
|
+
if (!model) return null;
|
|
153
|
+
return { backend, model };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (preferredModel) {
|
|
157
|
+
for (const backend of backends) {
|
|
158
|
+
const model = (backend.models ?? []).find((entry) => entry.modelId === preferredModel);
|
|
159
|
+
if (model) return { backend, model };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function selectLocalModel(models, task) {
|
|
167
|
+
const weight = classifyTaskWeight(task);
|
|
168
|
+
if (weight === "heavy" && models.length > 1) {
|
|
169
|
+
return [...models].sort((a, b) => String(b.modelId).localeCompare(String(a.modelId)))[0];
|
|
170
|
+
}
|
|
171
|
+
return models[0];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function buildLocalFallback(backends) {
|
|
175
|
+
const ollama = backends.find((entry) => entry.id === BACKEND_IDS.OLLAMA && entry.available);
|
|
176
|
+
if (!ollama?.models?.length) return null;
|
|
177
|
+
return {
|
|
178
|
+
backendId: BACKEND_IDS.OLLAMA,
|
|
179
|
+
modelId: ollama.models[0].modelId
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function buildCloudFallback(backends, cloudConsent) {
|
|
184
|
+
const openrouter = backends.find((entry) => entry.id === BACKEND_IDS.OPENROUTER && entry.hasApiKey);
|
|
185
|
+
if (!openrouter) return null;
|
|
186
|
+
return {
|
|
187
|
+
backendId: BACKEND_IDS.OPENROUTER,
|
|
188
|
+
modelId: OPENROUTER_FREE_MODEL,
|
|
189
|
+
requiresConsent: !cloudConsent
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export const BACKEND_IDS = {
|
|
2
|
+
OLLAMA: "ollama",
|
|
3
|
+
OPENROUTER: "openrouter",
|
|
4
|
+
CUSTOM: "custom"
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const COST_CLASSES = {
|
|
8
|
+
FREE: "free",
|
|
9
|
+
LOCAL: "local",
|
|
10
|
+
PAID: "paid",
|
|
11
|
+
UNKNOWN: "unknown"
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const PRIVACY_CLASSES = {
|
|
15
|
+
LOCAL: "local",
|
|
16
|
+
CLOUD: "cloud",
|
|
17
|
+
UNKNOWN: "unknown"
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const ROUTING_MODES = {
|
|
21
|
+
DIAGNOSTICS: "diagnostics",
|
|
22
|
+
LOCAL: "local",
|
|
23
|
+
CLOUD_CONSENT: "cloud_consent",
|
|
24
|
+
USER_OVERRIDE: "user_override"
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const OPENROUTER_FREE_MODEL = "openrouter/free";
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_OLLAMA_HOST = "http://127.0.0.1:11434";
|
|
30
|
+
|
|
31
|
+
export function createModelDescriptor({
|
|
32
|
+
provider,
|
|
33
|
+
modelId,
|
|
34
|
+
local = false,
|
|
35
|
+
costClass = COST_CLASSES.UNKNOWN,
|
|
36
|
+
privacyClass = PRIVACY_CLASSES.UNKNOWN,
|
|
37
|
+
contextLimit = null,
|
|
38
|
+
tools = false,
|
|
39
|
+
reasoning = false,
|
|
40
|
+
rateLimits = null,
|
|
41
|
+
opaque = false
|
|
42
|
+
}) {
|
|
43
|
+
return {
|
|
44
|
+
provider,
|
|
45
|
+
modelId,
|
|
46
|
+
local,
|
|
47
|
+
costClass,
|
|
48
|
+
privacyClass,
|
|
49
|
+
contextLimit,
|
|
50
|
+
tools,
|
|
51
|
+
reasoning,
|
|
52
|
+
rateLimits,
|
|
53
|
+
opaque
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createRoutingDecision({
|
|
58
|
+
backendId,
|
|
59
|
+
model,
|
|
60
|
+
reason,
|
|
61
|
+
estimatedTokens = null,
|
|
62
|
+
privacyImpact = PRIVACY_CLASSES.UNKNOWN,
|
|
63
|
+
fallback = null,
|
|
64
|
+
mode = ROUTING_MODES.DIAGNOSTICS,
|
|
65
|
+
requiresCloudConsent = false,
|
|
66
|
+
canInvoke = false
|
|
67
|
+
}) {
|
|
68
|
+
return {
|
|
69
|
+
backendId,
|
|
70
|
+
model,
|
|
71
|
+
reason,
|
|
72
|
+
estimatedTokens,
|
|
73
|
+
privacyImpact,
|
|
74
|
+
fallback,
|
|
75
|
+
mode,
|
|
76
|
+
requiresCloudConsent,
|
|
77
|
+
canInvoke
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createUsageTelemetry({
|
|
82
|
+
inputTokens = null,
|
|
83
|
+
outputTokens = null,
|
|
84
|
+
cachedTokens = null,
|
|
85
|
+
estimatedCost = null,
|
|
86
|
+
model = null,
|
|
87
|
+
backendId = null,
|
|
88
|
+
fallbackUsed = false
|
|
89
|
+
} = {}) {
|
|
90
|
+
return {
|
|
91
|
+
inputTokens,
|
|
92
|
+
outputTokens,
|
|
93
|
+
cachedTokens,
|
|
94
|
+
estimatedCost,
|
|
95
|
+
model,
|
|
96
|
+
backendId,
|
|
97
|
+
fallbackUsed
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { resolveHomeDir } from "./paths.js";
|
|
2
|
+
import { resolveProfile, buildProfileJson } from "./profile.js";
|
|
3
|
+
import {
|
|
4
|
+
compileContextPack,
|
|
5
|
+
inspectIntelligenceBackends,
|
|
6
|
+
resolveRoutingDecision,
|
|
7
|
+
runIntelligenceRequest,
|
|
8
|
+
summarizeIntelligenceBackends
|
|
9
|
+
} from "./intelligence/index.js";
|
|
10
|
+
import { BRAND } from "./brand/index.js";
|
|
11
|
+
import { formatCliCommand } from "./brand/cli.js";
|
|
12
|
+
|
|
13
|
+
export async function runIntelligenceCli(options, packageManifest) {
|
|
14
|
+
const action = options.intelligenceAction ?? "status";
|
|
15
|
+
const homeDir = resolveHomeDir();
|
|
16
|
+
const workspaceRoot = options.cwd;
|
|
17
|
+
const { profile, sources } = await resolveProfile({ homeDir, workspaceRoot });
|
|
18
|
+
|
|
19
|
+
switch (action) {
|
|
20
|
+
case "status":
|
|
21
|
+
return printIntelligenceStatus({
|
|
22
|
+
homeDir,
|
|
23
|
+
workspaceRoot,
|
|
24
|
+
profile,
|
|
25
|
+
sources,
|
|
26
|
+
json: options.json,
|
|
27
|
+
env: process.env
|
|
28
|
+
});
|
|
29
|
+
case "models":
|
|
30
|
+
return printIntelligenceModels({
|
|
31
|
+
profile,
|
|
32
|
+
json: options.json,
|
|
33
|
+
env: process.env
|
|
34
|
+
});
|
|
35
|
+
case "context":
|
|
36
|
+
return printIntelligenceContext({
|
|
37
|
+
workspaceRoot,
|
|
38
|
+
profile,
|
|
39
|
+
task: options.intelligenceTask,
|
|
40
|
+
relevantPaths: options.intelligencePaths ?? [],
|
|
41
|
+
includePrivate: options.includePrivate,
|
|
42
|
+
confirmed: options.yes || options.confirm,
|
|
43
|
+
json: options.json
|
|
44
|
+
});
|
|
45
|
+
case "route":
|
|
46
|
+
return printIntelligenceRoute({
|
|
47
|
+
workspaceRoot,
|
|
48
|
+
profile,
|
|
49
|
+
task: options.intelligenceTask,
|
|
50
|
+
cloudConsent: options.cloudConsent,
|
|
51
|
+
json: options.json,
|
|
52
|
+
env: process.env
|
|
53
|
+
});
|
|
54
|
+
case "ask":
|
|
55
|
+
return runIntelligenceAsk({
|
|
56
|
+
workspaceRoot,
|
|
57
|
+
profile,
|
|
58
|
+
task: options.intelligenceTask,
|
|
59
|
+
prompt: options.intelligencePrompt,
|
|
60
|
+
relevantPaths: options.intelligencePaths ?? [],
|
|
61
|
+
includePrivate: options.includePrivate,
|
|
62
|
+
cloudConsent: options.cloudConsent,
|
|
63
|
+
confirmed: options.yes || options.confirm,
|
|
64
|
+
json: options.json,
|
|
65
|
+
env: process.env,
|
|
66
|
+
packageManifest
|
|
67
|
+
});
|
|
68
|
+
default:
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Unknown intelligence action "${action}". Use status, models, context, route, or ask.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function printIntelligenceStatus({ homeDir, workspaceRoot, profile, sources, json, env }) {
|
|
76
|
+
const backends = await inspectIntelligenceBackends({
|
|
77
|
+
env,
|
|
78
|
+
customProviders: profile.customProviders
|
|
79
|
+
});
|
|
80
|
+
const summary = summarizeIntelligenceBackends(backends);
|
|
81
|
+
const routing = resolveRoutingDecision({
|
|
82
|
+
backends,
|
|
83
|
+
profile,
|
|
84
|
+
cloudConsent: false
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const payload = {
|
|
88
|
+
readOnly: true,
|
|
89
|
+
homeDir,
|
|
90
|
+
workspaceRoot,
|
|
91
|
+
profile: buildProfileJson({ profile, sources }),
|
|
92
|
+
backends,
|
|
93
|
+
summary,
|
|
94
|
+
routing
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (json) {
|
|
98
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
99
|
+
return payload;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(`${BRAND.displayName} intelligence — status`);
|
|
103
|
+
console.log(`Home: ${homeDir}`);
|
|
104
|
+
console.log(`Workspace: ${workspaceRoot}`);
|
|
105
|
+
console.log("");
|
|
106
|
+
for (const backend of backends) {
|
|
107
|
+
const models = backend.models?.length ?? 0;
|
|
108
|
+
console.log(
|
|
109
|
+
` ${backend.label.padEnd(14)} ${backend.state.padEnd(14)} models=${models}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
console.log("");
|
|
113
|
+
console.log(`Routing: ${routing.reason}`);
|
|
114
|
+
console.log(`Can invoke: ${routing.canInvoke ? "yes" : "no"}`);
|
|
115
|
+
if (!summary.localAvailable && !summary.cloudAuthenticated) {
|
|
116
|
+
console.log("");
|
|
117
|
+
console.log("Diagnostics mode: configure Ollama or OPENROUTER_API_KEY to enable inference.");
|
|
118
|
+
}
|
|
119
|
+
return payload;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function printIntelligenceModels({ profile, json, env }) {
|
|
123
|
+
const backends = await inspectIntelligenceBackends({
|
|
124
|
+
env,
|
|
125
|
+
customProviders: profile.customProviders
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const models = backends.flatMap((backend) =>
|
|
129
|
+
(backend.models ?? []).map((model) => ({
|
|
130
|
+
...model,
|
|
131
|
+
backendState: backend.state,
|
|
132
|
+
available: backend.available
|
|
133
|
+
}))
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const payload = { readOnly: true, models, backends: backends.map((entry) => ({
|
|
137
|
+
id: entry.id,
|
|
138
|
+
state: entry.state,
|
|
139
|
+
available: entry.available
|
|
140
|
+
})) };
|
|
141
|
+
|
|
142
|
+
if (json) {
|
|
143
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
144
|
+
return payload;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(`${BRAND.displayName} intelligence — models`);
|
|
148
|
+
if (models.length === 0) {
|
|
149
|
+
console.log(" No models detected. Start Ollama or set OPENROUTER_API_KEY.");
|
|
150
|
+
} else {
|
|
151
|
+
for (const model of models) {
|
|
152
|
+
console.log(
|
|
153
|
+
` ${model.provider.padEnd(12)} ${model.modelId.padEnd(32)} ${model.privacyClass}/${model.costClass}`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return payload;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function printIntelligenceContext({
|
|
161
|
+
workspaceRoot,
|
|
162
|
+
profile,
|
|
163
|
+
task,
|
|
164
|
+
relevantPaths,
|
|
165
|
+
includePrivate,
|
|
166
|
+
confirmed,
|
|
167
|
+
json
|
|
168
|
+
}) {
|
|
169
|
+
const privateConfirmationRequired = includePrivate && !confirmed;
|
|
170
|
+
const pack = await compileContextPack({
|
|
171
|
+
workspaceRoot,
|
|
172
|
+
task,
|
|
173
|
+
relevantPaths,
|
|
174
|
+
includePrivate: includePrivate && confirmed,
|
|
175
|
+
stableBudgetTokens: profile.stableContextBudget ?? undefined,
|
|
176
|
+
requestBudgetTokens: profile.requestContextBudget ?? undefined
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const payload = {
|
|
180
|
+
readOnly: true,
|
|
181
|
+
estimatedTokens: pack.estimatedTokens,
|
|
182
|
+
project: pack.stable.project,
|
|
183
|
+
evidence: pack.evidence,
|
|
184
|
+
privacy: pack.privacy,
|
|
185
|
+
skills: pack.stable.skills,
|
|
186
|
+
sdd: pack.stable.sdd,
|
|
187
|
+
tdd: pack.stable.tdd,
|
|
188
|
+
graphify: pack.stable.graphify,
|
|
189
|
+
hasAgentsMd: Boolean(pack.stable.agentsMd),
|
|
190
|
+
relevantFiles: pack.perRequest.files.map((file) => file.path)
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
if (privateConfirmationRequired) {
|
|
194
|
+
payload.privateConfirmationRequired = true;
|
|
195
|
+
payload.error = "Including private context requires explicit confirmation (--include-private --yes / --confirm).";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (json) {
|
|
199
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
200
|
+
return payload;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log(`${BRAND.displayName} intelligence — context`);
|
|
204
|
+
console.log(`Estimated tokens: ${pack.estimatedTokens}`);
|
|
205
|
+
console.log(`Project: ${pack.stable.project.name} (${pack.stable.project.stack})`);
|
|
206
|
+
console.log(`Evidence files: ${pack.evidence.filter((entry) => entry.kind === "file").length}`);
|
|
207
|
+
if (privateConfirmationRequired) {
|
|
208
|
+
console.log(`Blocked: ${payload.error}`);
|
|
209
|
+
}
|
|
210
|
+
if (pack.privacy.excludedPrivate.length > 0) {
|
|
211
|
+
console.log(`Excluded private: ${pack.privacy.excludedPrivate.join(", ")}`);
|
|
212
|
+
}
|
|
213
|
+
return payload;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function printIntelligenceRoute({
|
|
217
|
+
workspaceRoot,
|
|
218
|
+
profile,
|
|
219
|
+
task,
|
|
220
|
+
cloudConsent,
|
|
221
|
+
json,
|
|
222
|
+
env
|
|
223
|
+
}) {
|
|
224
|
+
const backends = await inspectIntelligenceBackends({
|
|
225
|
+
env,
|
|
226
|
+
customProviders: profile.customProviders
|
|
227
|
+
});
|
|
228
|
+
const pack = await compileContextPack({ workspaceRoot, task });
|
|
229
|
+
const routing = resolveRoutingDecision({
|
|
230
|
+
backends,
|
|
231
|
+
profile,
|
|
232
|
+
contextPack: pack,
|
|
233
|
+
task,
|
|
234
|
+
cloudConsent: Boolean(cloudConsent)
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const payload = {
|
|
238
|
+
readOnly: true,
|
|
239
|
+
routing,
|
|
240
|
+
estimatedTokens: pack.estimatedTokens,
|
|
241
|
+
evidenceUsed: pack.evidence.filter((entry) => entry.kind === "file").map((entry) => entry.path)
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
if (json) {
|
|
245
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
246
|
+
return payload;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log(`${BRAND.displayName} intelligence — routing`);
|
|
250
|
+
console.log(`Backend: ${routing.backendId ?? "none"}`);
|
|
251
|
+
console.log(`Model: ${routing.model?.modelId ?? "none"}`);
|
|
252
|
+
console.log(`Reason: ${routing.reason}`);
|
|
253
|
+
console.log(`Estimated tokens: ${routing.estimatedTokens}`);
|
|
254
|
+
console.log(`Privacy: ${routing.privacyImpact}`);
|
|
255
|
+
console.log(`Can invoke: ${routing.canInvoke ? "yes" : "no"}`);
|
|
256
|
+
return payload;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function runIntelligenceAsk({
|
|
260
|
+
workspaceRoot,
|
|
261
|
+
profile,
|
|
262
|
+
task,
|
|
263
|
+
prompt,
|
|
264
|
+
relevantPaths,
|
|
265
|
+
includePrivate,
|
|
266
|
+
cloudConsent,
|
|
267
|
+
confirmed,
|
|
268
|
+
json,
|
|
269
|
+
env
|
|
270
|
+
}) {
|
|
271
|
+
if (!prompt && !task) {
|
|
272
|
+
throw new Error(`Missing prompt. Use: ${formatCliCommand("intelligence ask --prompt \"...\"")}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const outcome = await runIntelligenceRequest({
|
|
276
|
+
workspaceRoot,
|
|
277
|
+
profile,
|
|
278
|
+
task,
|
|
279
|
+
prompt: prompt ?? task,
|
|
280
|
+
relevantPaths,
|
|
281
|
+
includePrivate,
|
|
282
|
+
cloudConsent,
|
|
283
|
+
confirmed,
|
|
284
|
+
env
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
if (json) {
|
|
288
|
+
console.log(JSON.stringify({
|
|
289
|
+
ok: outcome.ok,
|
|
290
|
+
mode: outcome.mode,
|
|
291
|
+
diagnosticsOnly: outcome.diagnosticsOnly,
|
|
292
|
+
routing: outcome.routing,
|
|
293
|
+
explanation: outcome.explanation,
|
|
294
|
+
telemetry: outcome.telemetry,
|
|
295
|
+
content: outcome.result?.content ?? null,
|
|
296
|
+
error: outcome.error
|
|
297
|
+
}, null, 2));
|
|
298
|
+
return outcome;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
console.log(`${BRAND.displayName} intelligence — ask`);
|
|
302
|
+
console.log(`Routing: ${outcome.explanation.reason}`);
|
|
303
|
+
console.log(`Estimated tokens: ${outcome.explanation.estimatedTokens}`);
|
|
304
|
+
console.log(`Privacy: ${outcome.explanation.privacyImpact}`);
|
|
305
|
+
console.log("");
|
|
306
|
+
|
|
307
|
+
if (!outcome.ok) {
|
|
308
|
+
console.log(`Blocked: ${outcome.error}`);
|
|
309
|
+
if (outcome.routing?.requiresCloudConsent) {
|
|
310
|
+
console.log(`Retry with --cloud-consent --yes after reviewing context (${formatCliCommand("intelligence context")}).`);
|
|
311
|
+
}
|
|
312
|
+
return outcome;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
console.log(outcome.result.content);
|
|
316
|
+
if (outcome.telemetry) {
|
|
317
|
+
console.log("");
|
|
318
|
+
console.log(
|
|
319
|
+
`Usage: in=${outcome.telemetry.inputTokens ?? "?"} out=${outcome.telemetry.outputTokens ?? "?"} model=${outcome.telemetry.model}`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
return outcome;
|
|
323
|
+
}
|