@bradtaylorsf/alpha-loop 1.13.1 → 1.14.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 +99 -1
- package/dist/cli.js +40 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/eval.d.ts +22 -0
- package/dist/commands/eval.js +105 -1
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/evolve-routing.d.ts +24 -0
- package/dist/commands/evolve-routing.js +320 -0
- package/dist/commands/evolve-routing.js.map +1 -0
- package/dist/commands/history.d.ts +2 -0
- package/dist/commands/history.js +95 -1
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +28 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/report.d.ts +7 -0
- package/dist/commands/report.js +27 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/run.d.ts +16 -0
- package/dist/commands/run.js +265 -36
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/scan.d.ts +1 -1
- package/dist/commands/scan.js.map +1 -1
- package/dist/engine/agents.d.ts +30 -8
- package/dist/engine/agents.js +94 -10
- package/dist/engine/agents.js.map +1 -1
- package/dist/engine/prerequisites.d.ts +40 -2
- package/dist/engine/prerequisites.js +126 -2
- package/dist/engine/prerequisites.js.map +1 -1
- package/dist/lib/agent.d.ts +39 -2
- package/dist/lib/agent.js +106 -4
- package/dist/lib/agent.js.map +1 -1
- package/dist/lib/config.d.ts +78 -1
- package/dist/lib/config.js +217 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/epics.d.ts +57 -0
- package/dist/lib/epics.js +76 -0
- package/dist/lib/epics.js.map +1 -0
- package/dist/lib/escalation.d.ts +102 -0
- package/dist/lib/escalation.js +241 -0
- package/dist/lib/escalation.js.map +1 -0
- package/dist/lib/eval-matrix.d.ts +125 -0
- package/dist/lib/eval-matrix.js +317 -0
- package/dist/lib/eval-matrix.js.map +1 -0
- package/dist/lib/eval-report.d.ts +12 -0
- package/dist/lib/eval-report.js +132 -0
- package/dist/lib/eval-report.js.map +1 -0
- package/dist/lib/eval-secret-scan.d.ts +41 -0
- package/dist/lib/eval-secret-scan.js +163 -0
- package/dist/lib/eval-secret-scan.js.map +1 -0
- package/dist/lib/eval.js +7 -4
- package/dist/lib/eval.js.map +1 -1
- package/dist/lib/github.d.ts +25 -0
- package/dist/lib/github.js +75 -0
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/hardware.d.ts +9 -0
- package/dist/lib/hardware.js +32 -0
- package/dist/lib/hardware.js.map +1 -0
- package/dist/lib/pipeline.d.ts +6 -1
- package/dist/lib/pipeline.js +223 -19
- package/dist/lib/pipeline.js.map +1 -1
- package/dist/lib/prerequisites.js +11 -3
- package/dist/lib/prerequisites.js.map +1 -1
- package/dist/lib/routing-history.d.ts +43 -0
- package/dist/lib/routing-history.js +112 -0
- package/dist/lib/routing-history.js.map +1 -0
- package/dist/lib/routing-promotion.d.ts +95 -0
- package/dist/lib/routing-promotion.js +229 -0
- package/dist/lib/routing-promotion.js.map +1 -0
- package/dist/lib/session.d.ts +10 -1
- package/dist/lib/session.js +38 -7
- package/dist/lib/session.js.map +1 -1
- package/dist/lib/telemetry.d.ts +147 -0
- package/dist/lib/telemetry.js +353 -0
- package/dist/lib/telemetry.js.map +1 -0
- package/dist/lib/verify-epic.d.ts +31 -0
- package/dist/lib/verify-epic.js +237 -0
- package/dist/lib/verify-epic.js.map +1 -0
- package/package.json +1 -1
package/dist/engine/agents.js
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
* Agent Spawn Module
|
|
3
3
|
* ==================
|
|
4
4
|
*
|
|
5
|
-
* Handles spawning different AI CLI agents (Claude, Codex, OpenCode)
|
|
5
|
+
* Handles spawning different AI CLI agents (Claude, Codex, OpenCode, LM Studio, Ollama)
|
|
6
6
|
* with the correct CLI flags per agent type.
|
|
7
7
|
*
|
|
8
|
+
* lmstudio and ollama piggy-back on existing CLIs: lmstudio uses the `claude`
|
|
9
|
+
* CLI pointed at an Anthropic-compatible local endpoint, ollama uses the
|
|
10
|
+
* `codex` CLI pointed at an OpenAI-compatible local endpoint. Endpoint
|
|
11
|
+
* selection is done via env vars (see `buildEndpointEnv`).
|
|
12
|
+
*
|
|
8
13
|
* Adding a new agent: add a case to AGENT_CLI_MAP and buildAgentArgs().
|
|
9
14
|
*/
|
|
10
15
|
import { spawn } from 'node:child_process';
|
|
11
|
-
// ============================================================================
|
|
12
|
-
// Agent CLI Mapping
|
|
13
|
-
// ============================================================================
|
|
14
16
|
/**
|
|
15
17
|
* CLI reference for each supported agent.
|
|
16
18
|
* Extend this map to add new agent types.
|
|
@@ -23,6 +25,7 @@ export const AGENT_CLI_MAP = {
|
|
|
23
25
|
permissionFlag: '--dangerously-skip-permissions',
|
|
24
26
|
supportsMaxTurns: true,
|
|
25
27
|
maxTurnsFlag: '--max-turns',
|
|
28
|
+
isSubcommand: false,
|
|
26
29
|
},
|
|
27
30
|
codex: {
|
|
28
31
|
command: 'codex',
|
|
@@ -30,12 +33,37 @@ export const AGENT_CLI_MAP = {
|
|
|
30
33
|
modelFlag: '--model',
|
|
31
34
|
permissionFlag: '--full-auto',
|
|
32
35
|
supportsMaxTurns: false,
|
|
36
|
+
isSubcommand: true,
|
|
33
37
|
},
|
|
34
38
|
opencode: {
|
|
35
39
|
command: 'opencode',
|
|
36
40
|
promptFlag: 'run',
|
|
37
41
|
modelFlag: '--model',
|
|
38
42
|
supportsMaxTurns: false,
|
|
43
|
+
isSubcommand: true,
|
|
44
|
+
},
|
|
45
|
+
// lmstudio: LM Studio 0.4.1+ exposes an Anthropic-compatible /v1/messages
|
|
46
|
+
// endpoint, so we invoke the `claude` CLI and point it at the local server
|
|
47
|
+
// via ANTHROPIC_BASE_URL / ANTHROPIC_MODEL (see buildEndpointEnv).
|
|
48
|
+
lmstudio: {
|
|
49
|
+
command: 'claude',
|
|
50
|
+
promptFlag: '-p',
|
|
51
|
+
modelFlag: '--model',
|
|
52
|
+
permissionFlag: '--dangerously-skip-permissions',
|
|
53
|
+
supportsMaxTurns: true,
|
|
54
|
+
maxTurnsFlag: '--max-turns',
|
|
55
|
+
isSubcommand: false,
|
|
56
|
+
},
|
|
57
|
+
// ollama: Ollama exposes an OpenAI-compatible /v1/chat/completions endpoint,
|
|
58
|
+
// so we invoke the `codex` CLI and point it at the local server via
|
|
59
|
+
// OPENAI_BASE_URL / OPENAI_MODEL (see buildEndpointEnv).
|
|
60
|
+
ollama: {
|
|
61
|
+
command: 'codex',
|
|
62
|
+
promptFlag: 'exec',
|
|
63
|
+
modelFlag: '--model',
|
|
64
|
+
permissionFlag: '--full-auto',
|
|
65
|
+
supportsMaxTurns: false,
|
|
66
|
+
isSubcommand: true,
|
|
39
67
|
},
|
|
40
68
|
};
|
|
41
69
|
/**
|
|
@@ -48,9 +76,7 @@ export function buildAgentArgs(config, prompt) {
|
|
|
48
76
|
throw new Error(`Unknown agent type: "${config.agent}". Supported agents: ${Object.keys(AGENT_CLI_MAP).join(', ')}`);
|
|
49
77
|
}
|
|
50
78
|
const args = [];
|
|
51
|
-
|
|
52
|
-
const isSubcommand = config.agent === 'codex' || config.agent === 'opencode';
|
|
53
|
-
if (isSubcommand) {
|
|
79
|
+
if (agentDef.isSubcommand) {
|
|
54
80
|
args.push(agentDef.promptFlag);
|
|
55
81
|
}
|
|
56
82
|
// Model flag (skip if empty — let agent CLI use its default)
|
|
@@ -66,7 +92,7 @@ export function buildAgentArgs(config, prompt) {
|
|
|
66
92
|
args.push(agentDef.maxTurnsFlag, String(config.maxTurns));
|
|
67
93
|
}
|
|
68
94
|
// Prompt: subcommand agents take it as positional arg; flag agents use promptFlag
|
|
69
|
-
if (isSubcommand) {
|
|
95
|
+
if (agentDef.isSubcommand) {
|
|
70
96
|
args.push(prompt);
|
|
71
97
|
}
|
|
72
98
|
else {
|
|
@@ -75,18 +101,76 @@ export function buildAgentArgs(config, prompt) {
|
|
|
75
101
|
return { command: agentDef.command, args };
|
|
76
102
|
}
|
|
77
103
|
// ============================================================================
|
|
104
|
+
// Endpoint Env Vars
|
|
105
|
+
// ============================================================================
|
|
106
|
+
/**
|
|
107
|
+
* Build the env-var overrides needed to point a child CLI at a specific
|
|
108
|
+
* routing endpoint. Anthropic-shaped endpoints (`anthropic`, `anthropic_compat`)
|
|
109
|
+
* set ANTHROPIC_BASE_URL / ANTHROPIC_MODEL; OpenAI-compatible endpoints set
|
|
110
|
+
* OPENAI_BASE_URL / OPENAI_MODEL.
|
|
111
|
+
*
|
|
112
|
+
* Callers MUST compute this per stage and not share envs across stages, so
|
|
113
|
+
* that a frontier stage does not inherit a local endpoint from a prior stage.
|
|
114
|
+
*/
|
|
115
|
+
export function buildEndpointEnv(endpoint, model) {
|
|
116
|
+
const env = {};
|
|
117
|
+
if (!endpoint || !endpoint.base_url)
|
|
118
|
+
return env;
|
|
119
|
+
switch (endpoint.type) {
|
|
120
|
+
case 'anthropic':
|
|
121
|
+
case 'anthropic_compat':
|
|
122
|
+
env.ANTHROPIC_BASE_URL = endpoint.base_url;
|
|
123
|
+
if (model)
|
|
124
|
+
env.ANTHROPIC_MODEL = model;
|
|
125
|
+
break;
|
|
126
|
+
case 'openai_compat':
|
|
127
|
+
env.OPENAI_BASE_URL = endpoint.base_url;
|
|
128
|
+
if (model)
|
|
129
|
+
env.OPENAI_MODEL = model;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
return env;
|
|
133
|
+
}
|
|
134
|
+
/** Default base URLs for single-agent lmstudio/ollama mode. */
|
|
135
|
+
export const DEFAULT_LMSTUDIO_BASE_URL = 'http://localhost:1234';
|
|
136
|
+
export const DEFAULT_OLLAMA_BASE_URL = 'http://localhost:11434/v1';
|
|
137
|
+
/**
|
|
138
|
+
* Auto-injected env vars for single-agent `lmstudio` / `ollama` mode, so
|
|
139
|
+
* `agent: lmstudio` actually targets the local server instead of silently
|
|
140
|
+
* hitting the real Anthropic API. Respects pre-existing env vars — users who
|
|
141
|
+
* export `ANTHROPIC_BASE_URL` themselves keep full control.
|
|
142
|
+
*/
|
|
143
|
+
function defaultLocalEnv(agent, model) {
|
|
144
|
+
const env = {};
|
|
145
|
+
if (agent === 'lmstudio') {
|
|
146
|
+
if (!process.env.ANTHROPIC_BASE_URL)
|
|
147
|
+
env.ANTHROPIC_BASE_URL = DEFAULT_LMSTUDIO_BASE_URL;
|
|
148
|
+
if (model && !process.env.ANTHROPIC_MODEL)
|
|
149
|
+
env.ANTHROPIC_MODEL = model;
|
|
150
|
+
}
|
|
151
|
+
else if (agent === 'ollama') {
|
|
152
|
+
if (!process.env.OPENAI_BASE_URL)
|
|
153
|
+
env.OPENAI_BASE_URL = DEFAULT_OLLAMA_BASE_URL;
|
|
154
|
+
if (model && !process.env.OPENAI_MODEL)
|
|
155
|
+
env.OPENAI_MODEL = model;
|
|
156
|
+
}
|
|
157
|
+
return env;
|
|
158
|
+
}
|
|
159
|
+
// ============================================================================
|
|
78
160
|
// Spawn Agent
|
|
79
161
|
// ============================================================================
|
|
80
162
|
/**
|
|
81
163
|
* Spawns an agent subprocess with the correct CLI flags.
|
|
82
164
|
* Returns the ChildProcess for the caller to manage (listen to events, pipe stdio, etc.).
|
|
83
165
|
*/
|
|
84
|
-
export function spawnAgent(config, prompt, cwd) {
|
|
166
|
+
export function spawnAgent(config, prompt, cwd, envOverrides) {
|
|
85
167
|
const { command, args } = buildAgentArgs(config, prompt);
|
|
168
|
+
// Caller overrides win > agent-default local base URLs > process.env
|
|
169
|
+
const localDefaults = defaultLocalEnv(config.agent, config.model);
|
|
86
170
|
return spawn(command, args, {
|
|
87
171
|
cwd,
|
|
88
172
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
89
|
-
env: { ...process.env },
|
|
173
|
+
env: { ...process.env, ...localDefaults, ...envOverrides },
|
|
90
174
|
});
|
|
91
175
|
}
|
|
92
176
|
//# sourceMappingURL=agents.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../../src/engine/agents.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../../src/engine/agents.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Verifies that the configured AI CLI agent is installed before starting the pipeline.
|
|
6
6
|
*/
|
|
7
|
-
import type { Config } from '../lib/config.js';
|
|
7
|
+
import type { Config, RoutingStageName } from '../lib/config.js';
|
|
8
8
|
export interface AgentCheckResult {
|
|
9
9
|
agent: string;
|
|
10
10
|
installed: boolean;
|
|
@@ -18,7 +18,9 @@ export interface PrerequisiteResult {
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function isCommandAvailable(command: string): boolean;
|
|
20
20
|
/**
|
|
21
|
-
* Checks that the configured agent CLI is installed.
|
|
21
|
+
* Checks that the configured agent CLI is installed. lmstudio/ollama
|
|
22
|
+
* piggy-back on the claude/codex CLIs, so we probe those instead of looking
|
|
23
|
+
* for a literal "lmstudio" / "ollama" binary on PATH.
|
|
22
24
|
*/
|
|
23
25
|
export declare function checkAgents(config: Config): PrerequisiteResult;
|
|
24
26
|
/**
|
|
@@ -29,3 +31,39 @@ export declare function formatCheckResults(result: PrerequisiteResult): string;
|
|
|
29
31
|
* Formats the pipeline startup summary.
|
|
30
32
|
*/
|
|
31
33
|
export declare function formatPipelineSummary(config: Config): string;
|
|
34
|
+
export interface LocalModelCheckResult {
|
|
35
|
+
ok: boolean;
|
|
36
|
+
/** List of model ids reported by the endpoint's /v1/models response. */
|
|
37
|
+
loaded: string[];
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Return true when the endpoint's base_url looks like a local/loopback server
|
|
42
|
+
* — i.e. a check against it should be cheap and safe to run on every stage
|
|
43
|
+
* start. Remote endpoints (api.anthropic.com etc.) aren't probed here.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isLocalEndpoint(baseUrl: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Verify that a local model server (LM Studio, Ollama, vLLM, etc.) is reachable
|
|
48
|
+
* at `baseUrl` and is currently serving `model`. Uses the OpenAI-compatible
|
|
49
|
+
* `/v1/models` response shape, which both LM Studio and Ollama expose even for
|
|
50
|
+
* their Anthropic-compatible endpoints.
|
|
51
|
+
*
|
|
52
|
+
* Returns `{ ok: true }` iff the model id appears in the response. Otherwise
|
|
53
|
+
* returns an actionable error describing what to do next.
|
|
54
|
+
*/
|
|
55
|
+
export declare function checkLocalModel(baseUrl: string, model: string): Promise<LocalModelCheckResult>;
|
|
56
|
+
export interface StagePrerequisiteResult {
|
|
57
|
+
ok: boolean;
|
|
58
|
+
/** Undefined when no local endpoint check was needed. */
|
|
59
|
+
checked?: boolean;
|
|
60
|
+
error?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Verify that the configured routing target for a stage is reachable before
|
|
64
|
+
* the stage runs. No-op when the stage has no routing override or when the
|
|
65
|
+
* resolved endpoint is remote. Returns an actionable error like
|
|
66
|
+
* "Start LM Studio and load model <model>" when the local server is down or
|
|
67
|
+
* the expected model isn't loaded.
|
|
68
|
+
*/
|
|
69
|
+
export declare function checkStagePrerequisites(config: Config, stage: RoutingStageName): Promise<StagePrerequisiteResult>;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Verifies that the configured AI CLI agent is installed before starting the pipeline.
|
|
6
6
|
*/
|
|
7
7
|
import { execSync } from 'node:child_process';
|
|
8
|
+
import { resolveRoutingStage } from '../lib/config.js';
|
|
8
9
|
// ============================================================================
|
|
9
10
|
// Agent Check
|
|
10
11
|
// ============================================================================
|
|
@@ -25,12 +26,17 @@ export function isCommandAvailable(command) {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
/**
|
|
28
|
-
* Checks that the configured agent CLI is installed.
|
|
29
|
+
* Checks that the configured agent CLI is installed. lmstudio/ollama
|
|
30
|
+
* piggy-back on the claude/codex CLIs, so we probe those instead of looking
|
|
31
|
+
* for a literal "lmstudio" / "ollama" binary on PATH.
|
|
29
32
|
*/
|
|
30
33
|
export function checkAgents(config) {
|
|
34
|
+
const cliCommand = config.agent === 'lmstudio' ? 'claude'
|
|
35
|
+
: config.agent === 'ollama' ? 'codex'
|
|
36
|
+
: config.agent;
|
|
31
37
|
const result = {
|
|
32
38
|
agent: config.agent,
|
|
33
|
-
installed: isCommandAvailable(
|
|
39
|
+
installed: isCommandAvailable(cliCommand),
|
|
34
40
|
};
|
|
35
41
|
return {
|
|
36
42
|
ok: result.installed,
|
|
@@ -63,4 +69,122 @@ export function formatCheckResults(result) {
|
|
|
63
69
|
export function formatPipelineSummary(config) {
|
|
64
70
|
return `Pipeline: ${config.agent}/${config.model}`;
|
|
65
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Compose the /v1/models URL for an endpoint base URL. Accepts base URLs
|
|
74
|
+
* with or without a trailing `/v1` segment.
|
|
75
|
+
*/
|
|
76
|
+
function buildModelsUrl(baseUrl) {
|
|
77
|
+
const base = baseUrl.replace(/\/+$/, '');
|
|
78
|
+
return /\/v1$/.test(base) ? `${base}/models` : `${base}/v1/models`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Return true when the endpoint's base_url looks like a local/loopback server
|
|
82
|
+
* — i.e. a check against it should be cheap and safe to run on every stage
|
|
83
|
+
* start. Remote endpoints (api.anthropic.com etc.) aren't probed here.
|
|
84
|
+
*/
|
|
85
|
+
export function isLocalEndpoint(baseUrl) {
|
|
86
|
+
try {
|
|
87
|
+
// URL.hostname wraps IPv6 addresses in brackets (e.g. "[::1]") — strip them.
|
|
88
|
+
const host = new URL(baseUrl).hostname.replace(/^\[|\]$/g, '');
|
|
89
|
+
return (host === 'localhost' ||
|
|
90
|
+
host === '127.0.0.1' ||
|
|
91
|
+
host === '::1' ||
|
|
92
|
+
host === '0.0.0.0' ||
|
|
93
|
+
host.endsWith('.local'));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Verify that a local model server (LM Studio, Ollama, vLLM, etc.) is reachable
|
|
101
|
+
* at `baseUrl` and is currently serving `model`. Uses the OpenAI-compatible
|
|
102
|
+
* `/v1/models` response shape, which both LM Studio and Ollama expose even for
|
|
103
|
+
* their Anthropic-compatible endpoints.
|
|
104
|
+
*
|
|
105
|
+
* Returns `{ ok: true }` iff the model id appears in the response. Otherwise
|
|
106
|
+
* returns an actionable error describing what to do next.
|
|
107
|
+
*/
|
|
108
|
+
export async function checkLocalModel(baseUrl, model) {
|
|
109
|
+
if (!baseUrl) {
|
|
110
|
+
return { ok: false, loaded: [], error: 'Missing base URL' };
|
|
111
|
+
}
|
|
112
|
+
const url = buildModelsUrl(baseUrl);
|
|
113
|
+
let res;
|
|
114
|
+
try {
|
|
115
|
+
res = await fetch(url);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
119
|
+
return { ok: false, loaded: [], error: `Could not reach ${url}: ${msg}` };
|
|
120
|
+
}
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
return { ok: false, loaded: [], error: `HTTP ${res.status} from ${url}` };
|
|
123
|
+
}
|
|
124
|
+
let body;
|
|
125
|
+
try {
|
|
126
|
+
body = await res.json();
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
130
|
+
return { ok: false, loaded: [], error: `Invalid JSON from ${url}: ${msg}` };
|
|
131
|
+
}
|
|
132
|
+
const data = body?.data;
|
|
133
|
+
const loaded = Array.isArray(data)
|
|
134
|
+
? data
|
|
135
|
+
.map((m) => (typeof m?.id === 'string' ? m.id : ''))
|
|
136
|
+
.filter((s) => s.length > 0)
|
|
137
|
+
: [];
|
|
138
|
+
if (!model) {
|
|
139
|
+
return { ok: false, loaded, error: 'No model specified' };
|
|
140
|
+
}
|
|
141
|
+
if (!loaded.includes(model)) {
|
|
142
|
+
const available = loaded.length > 0 ? loaded.join(', ') : '(none)';
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
loaded,
|
|
146
|
+
error: `Model "${model}" is not loaded at ${baseUrl}. Available: ${available}`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return { ok: true, loaded };
|
|
150
|
+
}
|
|
151
|
+
/** Friendly label for common local endpoints, for use in error messages. */
|
|
152
|
+
function endpointLabel(endpoint) {
|
|
153
|
+
try {
|
|
154
|
+
const port = new URL(endpoint.base_url).port;
|
|
155
|
+
if (port === '1234')
|
|
156
|
+
return 'LM Studio';
|
|
157
|
+
if (port === '11434')
|
|
158
|
+
return 'Ollama';
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// fall through
|
|
162
|
+
}
|
|
163
|
+
return 'Local model server';
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Verify that the configured routing target for a stage is reachable before
|
|
167
|
+
* the stage runs. No-op when the stage has no routing override or when the
|
|
168
|
+
* resolved endpoint is remote. Returns an actionable error like
|
|
169
|
+
* "Start LM Studio and load model <model>" when the local server is down or
|
|
170
|
+
* the expected model isn't loaded.
|
|
171
|
+
*/
|
|
172
|
+
export async function checkStagePrerequisites(config, stage) {
|
|
173
|
+
const resolved = resolveRoutingStage(config, stage);
|
|
174
|
+
if (!resolved || !resolved.endpoint)
|
|
175
|
+
return { ok: true };
|
|
176
|
+
const endpoint = resolved.endpoint;
|
|
177
|
+
if (!isLocalEndpoint(endpoint.base_url))
|
|
178
|
+
return { ok: true };
|
|
179
|
+
const result = await checkLocalModel(endpoint.base_url, resolved.model);
|
|
180
|
+
if (result.ok)
|
|
181
|
+
return { ok: true, checked: true };
|
|
182
|
+
const label = endpointLabel(endpoint);
|
|
183
|
+
const actionable = `Start ${label} and load model ${resolved.model}`;
|
|
184
|
+
return {
|
|
185
|
+
ok: false,
|
|
186
|
+
checked: true,
|
|
187
|
+
error: `${actionable} (${result.error ?? 'server did not respond'})`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
66
190
|
//# sourceMappingURL=prerequisites.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prerequisites.js","sourceRoot":"","sources":["../../src/engine/prerequisites.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"prerequisites.js","sourceRoot":"","sources":["../../src/engine/prerequisites.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/agent.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import type { RoutingEndpoint } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Supported agent CLI shapes. `lmstudio` piggy-backs on the `claude` CLI (since
|
|
4
|
+
* LM Studio exposes an Anthropic-compatible endpoint); `ollama` piggy-backs on
|
|
5
|
+
* the `codex` CLI (OpenAI-compatible).
|
|
6
|
+
*/
|
|
7
|
+
export type AgentType = 'claude' | 'codex' | 'opencode' | 'lmstudio' | 'ollama';
|
|
1
8
|
export type AgentResult = {
|
|
2
9
|
exitCode: number;
|
|
3
10
|
output: string;
|
|
@@ -10,9 +17,13 @@ export type AgentResult = {
|
|
|
10
17
|
outputTokens?: number;
|
|
11
18
|
/** Model used for the invocation. */
|
|
12
19
|
model?: string;
|
|
20
|
+
/** Number of tool_use blocks emitted during the run. */
|
|
21
|
+
toolCalls?: number;
|
|
22
|
+
/** Number of tool_result blocks with is_error === true. */
|
|
23
|
+
toolErrors?: number;
|
|
13
24
|
};
|
|
14
25
|
export type AgentOptions = {
|
|
15
|
-
agent:
|
|
26
|
+
agent: AgentType;
|
|
16
27
|
model: string;
|
|
17
28
|
prompt: string;
|
|
18
29
|
cwd: string;
|
|
@@ -24,9 +35,19 @@ export type AgentOptions = {
|
|
|
24
35
|
maxTurns?: number;
|
|
25
36
|
/** Resume the most recent agent session in the CWD instead of starting fresh. */
|
|
26
37
|
resume?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Env-var overrides merged over `process.env` when spawning the child.
|
|
40
|
+
* Callers MUST compute this per stage (see `buildEndpointEnv`) so that a
|
|
41
|
+
* frontier stage does not inherit a local-endpoint env from a prior stage.
|
|
42
|
+
*/
|
|
43
|
+
env?: Record<string, string>;
|
|
27
44
|
};
|
|
28
45
|
/**
|
|
29
46
|
* Build CLI command and args for a given agent type.
|
|
47
|
+
*
|
|
48
|
+
* `lmstudio` delegates to the claude CLI shape (Anthropic-compatible); `ollama`
|
|
49
|
+
* delegates to the codex CLI shape (OpenAI-compatible). Endpoint selection is
|
|
50
|
+
* wired via env vars (see `buildEndpointEnv`), not CLI flags.
|
|
30
51
|
*/
|
|
31
52
|
export declare function buildAgentArgs(options: AgentOptions): {
|
|
32
53
|
command: string;
|
|
@@ -36,7 +57,23 @@ export declare function buildAgentArgs(options: AgentOptions): {
|
|
|
36
57
|
* Build a shell command string for one-shot agent prompts (scan, vision).
|
|
37
58
|
* Reads prompt from stdin. Returns the command to pipe into.
|
|
38
59
|
*/
|
|
39
|
-
export declare function buildOneShotCommand(agent:
|
|
60
|
+
export declare function buildOneShotCommand(agent: AgentType, model: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Build the env-var overrides needed to point a child CLI at a specific
|
|
63
|
+
* routing endpoint. Anthropic-shaped endpoints set ANTHROPIC_BASE_URL /
|
|
64
|
+
* ANTHROPIC_MODEL; OpenAI-compatible endpoints set OPENAI_BASE_URL /
|
|
65
|
+
* OPENAI_MODEL.
|
|
66
|
+
*
|
|
67
|
+
* Callers MUST compute this per stage and not share envs across stages, so
|
|
68
|
+
* that a frontier stage does not inherit a local endpoint from a prior stage.
|
|
69
|
+
*/
|
|
70
|
+
export declare function buildEndpointEnv(endpoint: RoutingEndpoint, model: string): Record<string, string>;
|
|
71
|
+
/**
|
|
72
|
+
* Default local-server base URLs for single-agent `lmstudio` / `ollama` mode.
|
|
73
|
+
* Exported so tests and callers can reference the same constants.
|
|
74
|
+
*/
|
|
75
|
+
export declare const DEFAULT_LMSTUDIO_BASE_URL = "http://localhost:1234";
|
|
76
|
+
export declare const DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434/v1";
|
|
40
77
|
/**
|
|
41
78
|
* Spawn an AI agent with a prompt.
|
|
42
79
|
* Streams output to terminal in real-time while capturing it.
|
package/dist/lib/agent.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { createWriteStream } from 'node:fs';
|
|
6
6
|
import { log } from './logger.js';
|
|
7
|
+
import { classifyToolErrors } from './escalation.js';
|
|
7
8
|
/**
|
|
8
9
|
* Parse a Claude stream-json line into a human-readable log line.
|
|
9
10
|
* Returns null for lines that shouldn't be logged.
|
|
@@ -69,10 +70,15 @@ function formatStreamJsonLine(line) {
|
|
|
69
70
|
const DEFAULT_AGENT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
70
71
|
/**
|
|
71
72
|
* Build CLI command and args for a given agent type.
|
|
73
|
+
*
|
|
74
|
+
* `lmstudio` delegates to the claude CLI shape (Anthropic-compatible); `ollama`
|
|
75
|
+
* delegates to the codex CLI shape (OpenAI-compatible). Endpoint selection is
|
|
76
|
+
* wired via env vars (see `buildEndpointEnv`), not CLI flags.
|
|
72
77
|
*/
|
|
73
78
|
export function buildAgentArgs(options) {
|
|
74
79
|
switch (options.agent) {
|
|
75
|
-
case 'claude':
|
|
80
|
+
case 'claude':
|
|
81
|
+
case 'lmstudio': {
|
|
76
82
|
const args = [];
|
|
77
83
|
if (options.resume)
|
|
78
84
|
args.push('--continue');
|
|
@@ -85,7 +91,8 @@ export function buildAgentArgs(options) {
|
|
|
85
91
|
}
|
|
86
92
|
return { command: 'claude', args };
|
|
87
93
|
}
|
|
88
|
-
case 'codex':
|
|
94
|
+
case 'codex':
|
|
95
|
+
case 'ollama': {
|
|
89
96
|
const args = [];
|
|
90
97
|
if (options.resume) {
|
|
91
98
|
args.push('exec', 'resume', '--last');
|
|
@@ -114,14 +121,16 @@ export function buildAgentArgs(options) {
|
|
|
114
121
|
*/
|
|
115
122
|
export function buildOneShotCommand(agent, model) {
|
|
116
123
|
switch (agent) {
|
|
117
|
-
case 'claude':
|
|
124
|
+
case 'claude':
|
|
125
|
+
case 'lmstudio': {
|
|
118
126
|
const parts = ['claude', '-p'];
|
|
119
127
|
if (model)
|
|
120
128
|
parts.push('--model', model);
|
|
121
129
|
parts.push('--dangerously-skip-permissions', '--output-format', 'text');
|
|
122
130
|
return parts.join(' ');
|
|
123
131
|
}
|
|
124
|
-
case 'codex':
|
|
132
|
+
case 'codex':
|
|
133
|
+
case 'ollama': {
|
|
125
134
|
const parts = ['codex', 'exec'];
|
|
126
135
|
if (model)
|
|
127
136
|
parts.push('--model', model);
|
|
@@ -138,6 +147,65 @@ export function buildOneShotCommand(agent, model) {
|
|
|
138
147
|
throw new Error(`Unknown agent type: ${agent}`);
|
|
139
148
|
}
|
|
140
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Build the env-var overrides needed to point a child CLI at a specific
|
|
152
|
+
* routing endpoint. Anthropic-shaped endpoints set ANTHROPIC_BASE_URL /
|
|
153
|
+
* ANTHROPIC_MODEL; OpenAI-compatible endpoints set OPENAI_BASE_URL /
|
|
154
|
+
* OPENAI_MODEL.
|
|
155
|
+
*
|
|
156
|
+
* Callers MUST compute this per stage and not share envs across stages, so
|
|
157
|
+
* that a frontier stage does not inherit a local endpoint from a prior stage.
|
|
158
|
+
*/
|
|
159
|
+
export function buildEndpointEnv(endpoint, model) {
|
|
160
|
+
const env = {};
|
|
161
|
+
if (!endpoint || !endpoint.base_url)
|
|
162
|
+
return env;
|
|
163
|
+
switch (endpoint.type) {
|
|
164
|
+
case 'anthropic':
|
|
165
|
+
case 'anthropic_compat':
|
|
166
|
+
env.ANTHROPIC_BASE_URL = endpoint.base_url;
|
|
167
|
+
if (model)
|
|
168
|
+
env.ANTHROPIC_MODEL = model;
|
|
169
|
+
break;
|
|
170
|
+
case 'openai_compat':
|
|
171
|
+
env.OPENAI_BASE_URL = endpoint.base_url;
|
|
172
|
+
if (model)
|
|
173
|
+
env.OPENAI_MODEL = model;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
return env;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Default local-server base URLs for single-agent `lmstudio` / `ollama` mode.
|
|
180
|
+
* Exported so tests and callers can reference the same constants.
|
|
181
|
+
*/
|
|
182
|
+
export const DEFAULT_LMSTUDIO_BASE_URL = 'http://localhost:1234';
|
|
183
|
+
export const DEFAULT_OLLAMA_BASE_URL = 'http://localhost:11434/v1';
|
|
184
|
+
/**
|
|
185
|
+
* Auto-injected env vars for single-agent `lmstudio` / `ollama` mode.
|
|
186
|
+
*
|
|
187
|
+
* Without this, `agent: lmstudio` would spawn the claude CLI with no base URL
|
|
188
|
+
* override and silently hit the real Anthropic API. We only inject defaults
|
|
189
|
+
* when the corresponding env var isn't already set in the parent process, so
|
|
190
|
+
* users who export `ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` to point at a
|
|
191
|
+
* non-default port keep full control.
|
|
192
|
+
*/
|
|
193
|
+
function defaultLocalEnv(agent, model) {
|
|
194
|
+
const env = {};
|
|
195
|
+
if (agent === 'lmstudio') {
|
|
196
|
+
if (!process.env.ANTHROPIC_BASE_URL)
|
|
197
|
+
env.ANTHROPIC_BASE_URL = DEFAULT_LMSTUDIO_BASE_URL;
|
|
198
|
+
if (model && !process.env.ANTHROPIC_MODEL)
|
|
199
|
+
env.ANTHROPIC_MODEL = model;
|
|
200
|
+
}
|
|
201
|
+
else if (agent === 'ollama') {
|
|
202
|
+
if (!process.env.OPENAI_BASE_URL)
|
|
203
|
+
env.OPENAI_BASE_URL = DEFAULT_OLLAMA_BASE_URL;
|
|
204
|
+
if (model && !process.env.OPENAI_MODEL)
|
|
205
|
+
env.OPENAI_MODEL = model;
|
|
206
|
+
}
|
|
207
|
+
return env;
|
|
208
|
+
}
|
|
141
209
|
/**
|
|
142
210
|
* Spawn an AI agent with a prompt.
|
|
143
211
|
* Streams output to terminal in real-time while capturing it.
|
|
@@ -156,10 +224,18 @@ export async function spawnAgent(options) {
|
|
|
156
224
|
logStream = createWriteStream(options.logFile, { flags: 'w' });
|
|
157
225
|
}
|
|
158
226
|
const timeoutMs = options.timeout ?? DEFAULT_AGENT_TIMEOUT_MS;
|
|
227
|
+
// Compose env: caller overrides win > agent-default local base URLs > process.env
|
|
228
|
+
const localDefaults = defaultLocalEnv(options.agent, options.model);
|
|
229
|
+
const hasOverrides = options.env && Object.keys(options.env).length > 0;
|
|
230
|
+
const hasLocalDefaults = Object.keys(localDefaults).length > 0;
|
|
231
|
+
const spawnEnv = hasOverrides || hasLocalDefaults
|
|
232
|
+
? { ...process.env, ...localDefaults, ...(options.env ?? {}) }
|
|
233
|
+
: process.env;
|
|
159
234
|
return new Promise((resolve) => {
|
|
160
235
|
const child = spawn(command, args, {
|
|
161
236
|
cwd: options.cwd,
|
|
162
237
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
238
|
+
env: spawnEnv,
|
|
163
239
|
});
|
|
164
240
|
let resolved = false;
|
|
165
241
|
// For stream-json: accumulate partial lines, extract final result text
|
|
@@ -169,6 +245,9 @@ export async function spawnAgent(options) {
|
|
|
169
245
|
let parsedCostUsd;
|
|
170
246
|
let parsedInputTokens;
|
|
171
247
|
let parsedOutputTokens;
|
|
248
|
+
// Tool-use telemetry (per-stage metrics aggregation).
|
|
249
|
+
let toolUseCount = 0;
|
|
250
|
+
let toolErrorCount = 0;
|
|
172
251
|
// Pipe prompt via stdin (like: echo "$prompt" | claude -p)
|
|
173
252
|
child.stdin.write(options.prompt);
|
|
174
253
|
child.stdin.end();
|
|
@@ -228,6 +307,22 @@ export async function spawnAgent(options) {
|
|
|
228
307
|
parsedOutputTokens = usage.output_tokens;
|
|
229
308
|
}
|
|
230
309
|
}
|
|
310
|
+
else if (obj.type === 'assistant') {
|
|
311
|
+
const msg = obj.message;
|
|
312
|
+
const content = (msg?.content ?? []);
|
|
313
|
+
for (const block of content) {
|
|
314
|
+
if (block.type === 'tool_use')
|
|
315
|
+
toolUseCount++;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else if (obj.type === 'user') {
|
|
319
|
+
const msg = obj.message;
|
|
320
|
+
const content = (msg?.content ?? []);
|
|
321
|
+
for (const block of content) {
|
|
322
|
+
if (block.type === 'tool_result' && block.is_error === true)
|
|
323
|
+
toolErrorCount++;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
231
326
|
}
|
|
232
327
|
catch { /* not valid JSON, ignore */ }
|
|
233
328
|
const formatted = formatStreamJsonLine(line);
|
|
@@ -264,6 +359,11 @@ export async function spawnAgent(options) {
|
|
|
264
359
|
resolved = true;
|
|
265
360
|
clearTimeout(timer);
|
|
266
361
|
const duration = Date.now() - startTime;
|
|
362
|
+
// Fall back to output-string classification when we didn't see structured
|
|
363
|
+
// tool_result error blocks (e.g. non-stream-json agents like codex).
|
|
364
|
+
const toolErrorsFinal = toolErrorCount > 0
|
|
365
|
+
? toolErrorCount
|
|
366
|
+
: classifyToolErrors(output).length;
|
|
267
367
|
const result = {
|
|
268
368
|
exitCode,
|
|
269
369
|
output,
|
|
@@ -272,6 +372,8 @@ export async function spawnAgent(options) {
|
|
|
272
372
|
costUsd: parsedCostUsd,
|
|
273
373
|
inputTokens: parsedInputTokens,
|
|
274
374
|
outputTokens: parsedOutputTokens,
|
|
375
|
+
toolCalls: toolUseCount,
|
|
376
|
+
toolErrors: toolErrorsFinal,
|
|
275
377
|
};
|
|
276
378
|
if (logStream) {
|
|
277
379
|
logStream.end(() => {
|