@mcpbridgeapp/cli 0.0.0-stage → 0.1.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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +345 -2
  3. package/dist/agents/broken.d.ts +24 -0
  4. package/dist/agents/broken.js +31 -0
  5. package/dist/agents/broken.js.map +1 -0
  6. package/dist/agents/cli.d.ts +47 -0
  7. package/dist/agents/cli.js +270 -0
  8. package/dist/agents/cli.js.map +1 -0
  9. package/dist/agents/health.d.ts +38 -0
  10. package/dist/agents/health.js +53 -0
  11. package/dist/agents/health.js.map +1 -0
  12. package/dist/agents/ollama.d.ts +27 -0
  13. package/dist/agents/ollama.js +118 -0
  14. package/dist/agents/ollama.js.map +1 -0
  15. package/dist/agents/openai.d.ts +35 -0
  16. package/dist/agents/openai.js +168 -0
  17. package/dist/agents/openai.js.map +1 -0
  18. package/dist/agents/outcome.d.ts +34 -0
  19. package/dist/agents/outcome.js +96 -0
  20. package/dist/agents/outcome.js.map +1 -0
  21. package/dist/agents/permissions.d.ts +44 -0
  22. package/dist/agents/permissions.js +68 -0
  23. package/dist/agents/permissions.js.map +1 -0
  24. package/dist/agents/registry.d.ts +56 -0
  25. package/dist/agents/registry.js +167 -0
  26. package/dist/agents/registry.js.map +1 -0
  27. package/dist/agents/shim.d.ts +35 -0
  28. package/dist/agents/shim.js +53 -0
  29. package/dist/agents/shim.js.map +1 -0
  30. package/dist/agents/types.d.ts +69 -0
  31. package/dist/agents/types.js +28 -0
  32. package/dist/agents/types.js.map +1 -0
  33. package/dist/config.d.ts +99 -0
  34. package/dist/config.js +78 -0
  35. package/dist/config.js.map +1 -0
  36. package/dist/connect.d.ts +35 -0
  37. package/dist/connect.js +124 -0
  38. package/dist/connect.js.map +1 -0
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +269 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/limit.d.ts +14 -0
  43. package/dist/limit.js +33 -0
  44. package/dist/limit.js.map +1 -0
  45. package/dist/protocol.d.ts +175 -0
  46. package/dist/protocol.js +86 -0
  47. package/dist/protocol.js.map +1 -0
  48. package/dist/secrets.d.ts +27 -0
  49. package/dist/secrets.js +53 -0
  50. package/dist/secrets.js.map +1 -0
  51. package/dist/server.d.ts +5 -0
  52. package/dist/server.js +97 -0
  53. package/dist/server.js.map +1 -0
  54. package/dist/tasks.d.ts +48 -0
  55. package/dist/tasks.js +103 -0
  56. package/dist/tasks.js.map +1 -0
  57. package/dist/tools.d.ts +25 -0
  58. package/dist/tools.js +71 -0
  59. package/dist/tools.js.map +1 -0
  60. package/dist/ui/page.d.ts +7 -0
  61. package/dist/ui/page.js +428 -0
  62. package/dist/ui/page.js.map +1 -0
  63. package/dist/ui/presets.d.ts +22 -0
  64. package/dist/ui/presets.js +57 -0
  65. package/dist/ui/presets.js.map +1 -0
  66. package/dist/ui/server.d.ts +38 -0
  67. package/dist/ui/server.js +567 -0
  68. package/dist/ui/server.js.map +1 -0
  69. package/package.json +93 -4
@@ -0,0 +1,270 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { constants, readFileSync } from 'node:fs';
3
+ import { access } from 'node:fs/promises';
4
+ import { delimiter, dirname, join, sep } from 'node:path';
5
+ import { parseShimTarget } from './shim.js';
6
+ import { cleanDiagnostics, troubleOnSuccess } from './outcome.js';
7
+ import { permissionsFor } from './permissions.js';
8
+ import { effectiveTimeout, failed, truncate } from './types.js';
9
+ export const PROMPT_PLACEHOLDER = '{prompt}';
10
+ /** Colour and spinners in an answer are noise; most CLIs honour these. */
11
+ const QUIET_ENV = { NO_COLOR: '1', FORCE_COLOR: '0', TERM: 'dumb' };
12
+ /**
13
+ * An agent that is a command-line tool. The command comes from configuration and the
14
+ * prompt only ever fills {prompt} or goes down stdin — never through a shell, so nothing
15
+ * in a prompt can be read as a shell metacharacter.
16
+ */
17
+ export class CliAgent {
18
+ name;
19
+ config;
20
+ maxOutputChars;
21
+ requires;
22
+ readsFiles = true;
23
+ permissions;
24
+ constructor(name, config, maxOutputChars) {
25
+ this.name = name;
26
+ this.config = config;
27
+ this.maxOutputChars = maxOutputChars;
28
+ if (config.command.length === 0) {
29
+ throw new Error(`agent "${name}": command must not be empty`);
30
+ }
31
+ if (this.promptVia === 'argv' && !config.command.some((p) => p.includes(PROMPT_PLACEHOLDER))) {
32
+ throw new Error(`agent "${name}": command needs one ${PROMPT_PLACEHOLDER} element, or promptVia: "stdin"`);
33
+ }
34
+ this.requires = config.command[0];
35
+ this.permissions = permissionsFor(config);
36
+ }
37
+ get writesFiles() {
38
+ return this.permissions.writesFiles;
39
+ }
40
+ get runsCommands() {
41
+ return this.permissions.runsCommands;
42
+ }
43
+ get timeoutMs() {
44
+ return this.config.timeoutMs ?? 300_000;
45
+ }
46
+ get promptVia() {
47
+ return this.config.promptVia ?? 'argv';
48
+ }
49
+ get description() {
50
+ return this.config.description ?? `the ${this.requires} command`;
51
+ }
52
+ async probe() {
53
+ const resolved = await resolveOnPath(this.requires);
54
+ if (!resolved) {
55
+ return { available: false, detail: `${this.requires} is not on PATH — install it or fix the command` };
56
+ }
57
+ if (!needsAShell(resolved)) {
58
+ return { available: true, detail: `${this.requires} found at ${resolved}` };
59
+ }
60
+ const behindShim = readShim(resolved);
61
+ if (!behindShim) {
62
+ return { available: false, detail: batchFileAdvice(this.requires, resolved) };
63
+ }
64
+ // name the script, not the interpreter: "a shim for node.exe" says nothing about
65
+ // which program is actually going to run
66
+ const target = behindShim.prefix[0] ?? behindShim.command;
67
+ const via = behindShim.prefix.length > 0 ? `, run with ${behindShim.command}` : '';
68
+ return { available: true, detail: `${this.requires} is a shim for ${target}${via}` };
69
+ }
70
+ async run(prompt, options) {
71
+ const resolved = await resolveOnPath(this.requires);
72
+ let replacement = null;
73
+ if (resolved && needsAShell(resolved)) {
74
+ replacement = readShim(resolved);
75
+ if (!replacement)
76
+ return failed(this.name, batchFileAdvice(this.requires, resolved));
77
+ }
78
+ return this.spawnAgent(prompt, options, replacement);
79
+ }
80
+ spawnAgent(prompt, options, replacement) {
81
+ const viaStdin = this.promptVia === 'stdin';
82
+ const configured = viaStdin
83
+ ? [...this.config.command]
84
+ : this.config.command.map((part) => part.replaceAll(PROMPT_PLACEHOLDER, prompt));
85
+ // the shim's own argv[0] is replaced by what it would have run, keeping the agent's
86
+ // own flags after it
87
+ const argv = [
88
+ ...(replacement ? [replacement.command, ...replacement.prefix, ...configured.slice(1)] : configured),
89
+ // what the owner allowed it, in this CLI's own flags
90
+ ...this.permissions.args,
91
+ ];
92
+ const [command, ...args] = argv;
93
+ const timeoutMs = effectiveTimeout(this.config.timeoutMs, options);
94
+ const cwd = options.cwd ?? this.config.cwd;
95
+ const started = Date.now();
96
+ return new Promise((resolve) => {
97
+ let child;
98
+ try {
99
+ child = spawn(command, args, {
100
+ cwd,
101
+ env: { ...process.env, ...QUIET_ENV, ...this.permissions.env, ...(this.config.env ?? {}) },
102
+ // own process group, so a timeout can take the whole tree down: killing just the
103
+ // child leaves grandchildren alive, and an orphan holding the pipe keeps us
104
+ // waiting for its lifetime
105
+ detached: process.platform !== 'win32',
106
+ stdio: [viaStdin ? 'pipe' : 'ignore', 'pipe', 'pipe'],
107
+ });
108
+ }
109
+ catch (error) {
110
+ resolve(failed(this.name, this.startupAdvice(command, error, prompt)));
111
+ return;
112
+ }
113
+ let stdout = '';
114
+ let stderr = '';
115
+ let settled = false;
116
+ const finish = (result) => {
117
+ if (settled)
118
+ return;
119
+ settled = true;
120
+ clearTimeout(timer);
121
+ resolve(result);
122
+ };
123
+ // stdout and stderr are consumed as they arrive, so neither pipe can fill and stall
124
+ // the child while we are busy with the other
125
+ child.stdout?.on('data', (chunk) => {
126
+ const text = chunk.toString('utf8');
127
+ stdout += text;
128
+ options.onOutput?.(text);
129
+ });
130
+ child.stderr?.on('data', (chunk) => { stderr += chunk.toString('utf8'); });
131
+ if (viaStdin && child.stdin) {
132
+ child.stdin.on('error', () => { });
133
+ child.stdin.end(prompt, 'utf8');
134
+ }
135
+ const timer = setTimeout(() => {
136
+ killTree(child.pid);
137
+ // what it had written by then is often most of an answer; throwing it away left a
138
+ // caller with nothing after five minutes of real work
139
+ const partial = truncate(stdout, this.maxOutputChars);
140
+ finish({
141
+ ...failed(this.name, `${this.name} did not finish within ${Math.round(timeoutMs / 1000)} s and was stopped.` +
142
+ (partial.text ? ' What it had written by then is in "output"; it is incomplete.' : ' It had written nothing yet.') +
143
+ ' Give it a smaller task, or a longer timeout in its config.', Date.now() - started, true),
144
+ output: partial.text,
145
+ outputTruncated: partial.truncated,
146
+ });
147
+ }, timeoutMs);
148
+ child.on('error', (error) => {
149
+ finish(failed(this.name, this.startupAdvice(command, error, prompt), Date.now() - started));
150
+ });
151
+ child.on('close', (code) => {
152
+ const body = truncate(stdout, this.maxOutputChars);
153
+ // a CLI that failed often prints its error on stdout, not stderr
154
+ const failureText = stderr.trim() ? stderr : stdout;
155
+ const trouble = code === 0 ? troubleOnSuccess(stderr) : null;
156
+ finish({
157
+ agent: this.name,
158
+ succeeded: code === 0,
159
+ output: body.text,
160
+ diagnostics: code === 0
161
+ ? ''
162
+ : `${this.name} exited with code ${code}.\n${cleanDiagnostics(failureText, prompt)}`.trim(),
163
+ notes: trouble
164
+ ? `${this.name} exited normally but printed errors while running; a failing hook can stop it reading anything, so check the answer before trusting it:\n${trouble}`
165
+ : '',
166
+ durationMs: Date.now() - started,
167
+ timedOut: false,
168
+ outputTruncated: body.truncated,
169
+ });
170
+ });
171
+ });
172
+ }
173
+ startupAdvice(command, error, prompt) {
174
+ return startupAdvice(this.name, command, error, prompt);
175
+ }
176
+ }
177
+ /**
178
+ * Both of these mean the operating system refused an over-long argument list, which a
179
+ * prompt carrying file contents reaches easily. POSIX reports E2BIG and Windows
180
+ * ENAMETOOLONG; treating only one of them as the size problem left Windows users with
181
+ * "is it installed?", which is the wrong thing to go and check.
182
+ */
183
+ const ARG_TOO_LONG = new Set(['E2BIG', 'ENAMETOOLONG']);
184
+ /**
185
+ * Turns a spawn failure into something the caller can act on. The size limit varies by
186
+ * platform — roughly 128KB per argument on Linux, about 1MB on macOS, and as little as
187
+ * 32KB on Windows — so the advice names the setting that removes it rather than a number.
188
+ */
189
+ export function startupAdvice(agent, command, error, prompt) {
190
+ if (error.code && ARG_TOO_LONG.has(error.code)) {
191
+ return (`the prompt is too large to pass as a command-line argument (${prompt.length} characters; ` +
192
+ `the operating system's limit is as low as 32KB on Windows). Set "promptVia": "stdin" for ` +
193
+ `agent "${agent}" if ${command} reads its prompt from a pipe, or send a shorter prompt.`);
194
+ }
195
+ return `could not start ${command}. Is it installed and on PATH? (${error.message})`;
196
+ }
197
+ /**
198
+ * Reads what a Windows shim would have run. Reading it is safe where executing it is not:
199
+ * nothing from the prompt reaches the file, and the program it names is spawned directly.
200
+ */
201
+ function readShim(shimPath) {
202
+ try {
203
+ return parseShimTarget(readFileSync(shimPath, 'utf8'), dirname(shimPath), process.execPath);
204
+ }
205
+ catch {
206
+ return null;
207
+ }
208
+ }
209
+ /** Kills the process group on POSIX, the tree via taskkill on Windows. */
210
+ function killTree(pid) {
211
+ if (pid === undefined)
212
+ return;
213
+ try {
214
+ if (process.platform === 'win32') {
215
+ spawn('taskkill', ['/pid', String(pid), '/T', '/F'], { stdio: 'ignore' });
216
+ }
217
+ else {
218
+ process.kill(-pid, 'SIGKILL');
219
+ }
220
+ }
221
+ catch {
222
+ // already gone
223
+ }
224
+ }
225
+ /**
226
+ * A .cmd or .bat can only be run through a shell, and running the prompt through a shell
227
+ * is exactly what this program must never do: quoting rules for batch files are their own
228
+ * minefield (see CVE-2024-27980). So such an agent is refused with the fix, rather than
229
+ * made to work unsafely or left to fail as a bare ENOENT.
230
+ */
231
+ export function needsAShell(resolvedPath) {
232
+ if (process.platform !== 'win32')
233
+ return false;
234
+ const lower = resolvedPath.toLowerCase();
235
+ return lower.endsWith('.cmd') || lower.endsWith('.bat');
236
+ }
237
+ function batchFileAdvice(executable, resolved) {
238
+ return (`${executable} resolves to ${resolved}, a Windows batch shim whose target could not be ` +
239
+ `read, and a shim can only run through a shell — which a prompt must not touch. Point ` +
240
+ `this agent at the real executable instead, e.g. ` +
241
+ `"command": ["C:\\\\path\\\\to\\\\${executable}.exe", "{prompt}"].`);
242
+ }
243
+ export async function resolveOnPath(executable) {
244
+ if (executable.includes(sep) || executable.includes('/')) {
245
+ return (await accessible(executable)) ? executable : null;
246
+ }
247
+ const dirs = (process.env.PATH ?? '').split(delimiter).filter(Boolean);
248
+ const suffixes = process.platform === 'win32' ? ['.exe', '.com', '.cmd', '.bat', ''] : [''];
249
+ for (const dir of dirs) {
250
+ for (const suffix of suffixes) {
251
+ const candidate = join(dir, executable + suffix);
252
+ if (await accessible(candidate))
253
+ return candidate;
254
+ }
255
+ }
256
+ return null;
257
+ }
258
+ export async function onPath(executable) {
259
+ return (await resolveOnPath(executable)) !== null;
260
+ }
261
+ async function accessible(path) {
262
+ try {
263
+ await access(path, constants.X_OK);
264
+ return true;
265
+ }
266
+ catch {
267
+ return false;
268
+ }
269
+ }
270
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/agents/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { FailureKind } from './outcome.js';
2
+ import type { AgentResult } from './types.js';
3
+ /**
4
+ * What the bridge has learned about each agent by using it.
5
+ *
6
+ * <p>Probing can only say a command exists; whether its account works shows up when it is
7
+ * asked to do something. In the field report, three of four agents listed as available
8
+ * failed at call time -- an exhausted quota, an ineligible account tier, a broken hook --
9
+ * and each was found by spending a call. Quota and sign-in failures are durable: they fail
10
+ * the same way on the next call. So the last one is remembered and shown by list_agents,
11
+ * which lets a caller route around it before spending anything.
12
+ */
13
+ export interface AgentHealth {
14
+ calls: number;
15
+ failures: number;
16
+ lastSuccessAt?: number;
17
+ lastFailure?: {
18
+ kind: FailureKind;
19
+ message: string;
20
+ at: number;
21
+ retryAt?: string;
22
+ };
23
+ /**
24
+ * Which came last, by order of recording rather than by clock: two calls can finish in
25
+ * the same millisecond, and a success recorded after a failure must still clear it.
26
+ */
27
+ lastWasFailure: boolean;
28
+ }
29
+ export type AgentState = 'ready' | 'unavailable' | 'quota-exhausted' | 'sign-in-failed' | 'directory-not-trusted';
30
+ export declare class HealthStore {
31
+ private readonly byAgent;
32
+ record(name: string, result: AgentResult, now?: number): void;
33
+ get(name: string): AgentHealth | undefined;
34
+ /** Forgets an agent, for when its configuration changed and the old lessons may not hold. */
35
+ forget(name: string): void;
36
+ /** The state a caller should act on: a durable failure newer than the last success wins. */
37
+ state(name: string, probedAvailable: boolean, now?: number): AgentState;
38
+ }
@@ -0,0 +1,53 @@
1
+ import { classifyFailure } from './outcome.js';
2
+ /** How long a failure keeps an agent marked unusable when no success has cleared it. */
3
+ const REMEMBER_MS = {
4
+ quota: 60 * 60 * 1000,
5
+ auth: 6 * 60 * 60 * 1000,
6
+ trust: 6 * 60 * 60 * 1000,
7
+ other: 0,
8
+ };
9
+ export class HealthStore {
10
+ byAgent = new Map();
11
+ record(name, result, now = Date.now()) {
12
+ const health = this.byAgent.get(name) ?? { calls: 0, failures: 0, lastWasFailure: false };
13
+ health.calls += 1;
14
+ if (result.succeeded) {
15
+ health.lastSuccessAt = now;
16
+ health.lastWasFailure = false;
17
+ }
18
+ else if (!result.timedOut) {
19
+ health.lastWasFailure = true;
20
+ // a timeout says the task was big, not that the agent is broken
21
+ health.failures += 1;
22
+ const { kind, retryAt } = classifyFailure(result.diagnostics);
23
+ const firstLine = result.diagnostics.split('\n').find((l) => l.trim()) ?? result.diagnostics;
24
+ health.lastFailure = { kind, message: firstLine.slice(0, 300), at: now, ...(retryAt ? { retryAt } : {}) };
25
+ }
26
+ this.byAgent.set(name, health);
27
+ }
28
+ get(name) {
29
+ return this.byAgent.get(name);
30
+ }
31
+ /** Forgets an agent, for when its configuration changed and the old lessons may not hold. */
32
+ forget(name) {
33
+ this.byAgent.delete(name);
34
+ }
35
+ /** The state a caller should act on: a durable failure newer than the last success wins. */
36
+ state(name, probedAvailable, now = Date.now()) {
37
+ if (!probedAvailable)
38
+ return 'unavailable';
39
+ const health = this.byAgent.get(name);
40
+ const failure = health?.lastFailure;
41
+ if (!failure || !health.lastWasFailure)
42
+ return 'ready';
43
+ if (now - failure.at > REMEMBER_MS[failure.kind])
44
+ return 'ready';
45
+ switch (failure.kind) {
46
+ case 'quota': return 'quota-exhausted';
47
+ case 'auth': return 'sign-in-failed';
48
+ case 'trust': return 'directory-not-trusted';
49
+ default: return 'ready';
50
+ }
51
+ }
52
+ }
53
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/agents/health.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { OllamaAgentConfig } from '../config.js';
2
+ import { type Agent, type AgentResult, type RunOptions } from './types.js';
3
+ /**
4
+ * A model served locally by Ollama. The only agent that needs neither an account nor a
5
+ * network, so it is the one that is always awake — useful as a fallback when no vendor CLI
6
+ * is installed, and for work that should not leave the machine.
7
+ */
8
+ export declare class OllamaAgent implements Agent {
9
+ readonly name: string;
10
+ private readonly config;
11
+ private readonly maxOutputChars;
12
+ readonly requires: string;
13
+ readonly readsFiles = false;
14
+ readonly writesFiles = false;
15
+ readonly runsCommands = false;
16
+ get timeoutMs(): number;
17
+ constructor(name: string, config: OllamaAgentConfig, maxOutputChars: number);
18
+ private get baseUrl();
19
+ get description(): string;
20
+ probe(): Promise<{
21
+ available: boolean;
22
+ detail: string;
23
+ }>;
24
+ run(prompt: string, options: RunOptions): Promise<AgentResult>;
25
+ /** null means Ollama is not reachable, as opposed to reachable with nothing installed. */
26
+ private installedModels;
27
+ }
@@ -0,0 +1,118 @@
1
+ import { effectiveTimeout, failed, truncate } from './types.js';
2
+ const DEFAULT_BASE_URL = 'http://127.0.0.1:11434';
3
+ /**
4
+ * A model served locally by Ollama. The only agent that needs neither an account nor a
5
+ * network, so it is the one that is always awake — useful as a fallback when no vendor CLI
6
+ * is installed, and for work that should not leave the machine.
7
+ */
8
+ export class OllamaAgent {
9
+ name;
10
+ config;
11
+ maxOutputChars;
12
+ requires;
13
+ // an HTTP model sees only what is in the prompt; a directory means nothing to it
14
+ readsFiles = false;
15
+ writesFiles = false;
16
+ runsCommands = false;
17
+ get timeoutMs() {
18
+ return this.config.timeoutMs ?? 300_000;
19
+ }
20
+ constructor(name, config, maxOutputChars) {
21
+ this.name = name;
22
+ this.config = config;
23
+ this.maxOutputChars = maxOutputChars;
24
+ this.requires = `${this.baseUrl}`;
25
+ }
26
+ get baseUrl() {
27
+ return (this.config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
28
+ }
29
+ get description() {
30
+ const model = this.config.model ? ` (${this.config.model})` : ' (first installed model)';
31
+ return (this.config.description ?? 'A model running locally in Ollama') + model;
32
+ }
33
+ async probe() {
34
+ const models = await this.installedModels();
35
+ if (models === null) {
36
+ return { available: false, detail: `no Ollama responding at ${this.baseUrl} — start it with "ollama serve"` };
37
+ }
38
+ if (models.length === 0) {
39
+ return { available: false, detail: `Ollama is running but has no models — pull one, e.g. "ollama pull qwen3"` };
40
+ }
41
+ if (this.config.model && !models.includes(this.config.model)) {
42
+ return {
43
+ available: false,
44
+ detail: `model "${this.config.model}" is not installed. Available: ${models.join(', ')}`,
45
+ };
46
+ }
47
+ return { available: true, detail: `Ollama at ${this.baseUrl}, using ${this.config.model ?? models[0]}` };
48
+ }
49
+ async run(prompt, options) {
50
+ const started = Date.now();
51
+ // the caller may have passed a directory expecting the agent to read it; saying so
52
+ // beats letting it wonder why the answer ignores the code
53
+ const note = options.cwd
54
+ ? `workingDirectory (${options.cwd}) was ignored: ${this.name} is a model behind an HTTP ` +
55
+ `API and cannot read files. Inline the content you want it to see in the prompt, or ` +
56
+ `delegate to an agent that list_agents reports with "readsFiles": true.`
57
+ : '';
58
+ const timeoutMs = effectiveTimeout(this.config.timeoutMs, options);
59
+ let model = this.config.model;
60
+ if (!model) {
61
+ const models = await this.installedModels();
62
+ if (models === null) {
63
+ return failed(this.name, `no Ollama responding at ${this.baseUrl} — start it with "ollama serve"`, Date.now() - started);
64
+ }
65
+ if (models.length === 0) {
66
+ return failed(this.name, 'Ollama has no models installed — pull one, e.g. "ollama pull qwen3"', Date.now() - started);
67
+ }
68
+ model = models[0];
69
+ }
70
+ const controller = new AbortController();
71
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
72
+ try {
73
+ const response = await fetch(`${this.baseUrl}/api/generate`, {
74
+ method: 'POST',
75
+ headers: { 'Content-Type': 'application/json' },
76
+ body: JSON.stringify({ model, prompt, stream: false }),
77
+ signal: controller.signal,
78
+ });
79
+ if (!response.ok) {
80
+ return failed(this.name, `Ollama returned HTTP ${response.status}: ${truncate(await response.text(), 1_000).text}`, Date.now() - started);
81
+ }
82
+ const payload = (await response.json());
83
+ const body = truncate(payload.response ?? '', this.maxOutputChars);
84
+ return {
85
+ agent: this.name, succeeded: true, output: body.text, diagnostics: '', notes: note,
86
+ durationMs: Date.now() - started, timedOut: false, outputTruncated: body.truncated,
87
+ };
88
+ }
89
+ catch (error) {
90
+ const aborted = controller.signal.aborted;
91
+ return failed(this.name, aborted
92
+ ? `${this.name} did not answer within ${timeoutMs}ms. Try a smaller task or a smaller model.`
93
+ : `could not reach Ollama at ${this.baseUrl}: ${error.message}`, Date.now() - started, aborted);
94
+ }
95
+ finally {
96
+ clearTimeout(timer);
97
+ }
98
+ }
99
+ /** null means Ollama is not reachable, as opposed to reachable with nothing installed. */
100
+ async installedModels() {
101
+ const controller = new AbortController();
102
+ const timer = setTimeout(() => controller.abort(), 3_000);
103
+ try {
104
+ const response = await fetch(`${this.baseUrl}/api/tags`, { signal: controller.signal });
105
+ if (!response.ok)
106
+ return null;
107
+ const payload = (await response.json());
108
+ return (payload.models ?? []).map((m) => m.name).filter((n) => Boolean(n));
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ finally {
114
+ clearTimeout(timer);
115
+ }
116
+ }
117
+ }
118
+ //# sourceMappingURL=ollama.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/agents/ollama.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { OpenAiAgentConfig } from '../config.js';
2
+ import { type Agent, type AgentResult, type RunOptions } from './types.js';
3
+ /**
4
+ * An agent reached over an OpenAI-compatible chat-completions API.
5
+ *
6
+ * <p>This is the only door some providers leave open — xAI publishes no CLI, so Grok is
7
+ * reachable this way or not at all. One adapter serves all of them because the request
8
+ * shape is the same; only the base URL, model and key change.
9
+ *
10
+ * <p>The key is never put in a result, a note or a log: errors quote the provider's
11
+ * message, never the request.
12
+ */
13
+ export declare class OpenAiAgent implements Agent {
14
+ readonly name: string;
15
+ private readonly config;
16
+ private readonly maxOutputChars;
17
+ readonly requires: string;
18
+ readonly readsFiles = false;
19
+ readonly writesFiles = false;
20
+ readonly runsCommands = false;
21
+ get timeoutMs(): number;
22
+ constructor(name: string, config: OpenAiAgentConfig, maxOutputChars: number);
23
+ private get endpoint();
24
+ get description(): string;
25
+ /** Resolved at call time, so a key exported after startup is picked up. */
26
+ private key;
27
+ private missingKeyDetail;
28
+ probe(): Promise<{
29
+ available: boolean;
30
+ detail: string;
31
+ }>;
32
+ run(prompt: string, options: RunOptions): Promise<AgentResult>;
33
+ /** null means the endpoint does not offer a usable /models listing. */
34
+ private listModels;
35
+ }