bare-agent 0.10.4 → 0.12.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/bin/cli.d.ts +4 -0
- package/bin/cli.js +70 -12
- package/bin/test-provider.d.ts +2 -0
- package/bin/test-provider.js +5 -1
- package/index.d.ts +20 -0
- package/package.json +44 -10
- package/src/bareguard-adapter.d.ts +118 -0
- package/src/bareguard-adapter.js +75 -3
- package/src/checkpoint.d.ts +61 -0
- package/src/checkpoint.js +17 -8
- package/src/circuit-breaker.d.ts +70 -0
- package/src/circuit-breaker.js +20 -4
- package/src/errors.d.ts +106 -0
- package/src/errors.js +50 -1
- package/src/loop.d.ts +135 -0
- package/src/loop.js +80 -18
- package/src/mcp-bridge.d.ts +133 -0
- package/src/mcp-bridge.js +199 -26
- package/src/mcp.d.ts +4 -0
- package/src/memory.d.ts +50 -0
- package/src/memory.js +22 -2
- package/src/planner.d.ts +62 -0
- package/src/planner.js +26 -7
- package/src/provider-anthropic.d.ts +55 -0
- package/src/provider-anthropic.js +34 -10
- package/src/provider-clipipe.d.ts +86 -0
- package/src/provider-clipipe.js +28 -18
- package/src/provider-fallback.d.ts +44 -0
- package/src/provider-fallback.js +18 -8
- package/src/provider-ollama.d.ts +41 -0
- package/src/provider-ollama.js +29 -7
- package/src/provider-openai.d.ts +57 -0
- package/src/provider-openai.js +34 -7
- package/src/providers.d.ts +6 -0
- package/src/retry.d.ts +44 -0
- package/src/retry.js +15 -1
- package/src/run-plan.d.ts +126 -0
- package/src/run-plan.js +46 -13
- package/src/scheduler.d.ts +102 -0
- package/src/scheduler.js +32 -4
- package/src/state.d.ts +45 -0
- package/src/state.js +18 -2
- package/src/store-jsonfile.d.ts +85 -0
- package/src/store-jsonfile.js +50 -8
- package/src/store-sqlite.d.ts +90 -0
- package/src/store-sqlite.js +31 -7
- package/src/stores.d.ts +3 -0
- package/src/stream.d.ts +79 -0
- package/src/stream.js +32 -0
- package/src/tools.d.ts +8 -0
- package/src/transport-jsonl.d.ts +30 -0
- package/src/transport-jsonl.js +13 -0
- package/src/transports.d.ts +2 -0
- package/tools/browse.d.ts +10 -0
- package/tools/browse.js +2 -0
- package/tools/defer.d.ts +33 -0
- package/tools/defer.js +12 -3
- package/tools/mobile.d.ts +34 -0
- package/tools/mobile.js +28 -15
- package/tools/shell.d.ts +31 -0
- package/tools/shell.js +83 -6
- package/tools/spawn.d.ts +107 -0
- package/tools/spawn.js +24 -5
- package/types/index.d.ts +66 -0
- package/types/shims.d.ts +16 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export type Message = import("../types").Message;
|
|
2
|
+
export type ToolDef = import("../types").ToolDef;
|
|
3
|
+
export type GenerateResult = import("../types").GenerateResult;
|
|
4
|
+
export type AnthropicOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* - Anthropic API key (required).
|
|
7
|
+
*/
|
|
8
|
+
apiKey?: string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* - Model ID.
|
|
11
|
+
*/
|
|
12
|
+
model?: string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* - Attach the full upstream response to `err.body` on HTTP errors (off by default to avoid leaking unexpected fields through error logs; `err.message` still carries the API error).
|
|
15
|
+
*/
|
|
16
|
+
exposeErrorBody?: boolean | undefined;
|
|
17
|
+
};
|
|
18
|
+
/** @typedef {import('../types').Message} Message */
|
|
19
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
20
|
+
/** @typedef {import('../types').GenerateResult} GenerateResult */
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {object} AnthropicOptions
|
|
23
|
+
* @property {string} [apiKey] - Anthropic API key (required).
|
|
24
|
+
* @property {string} [model='claude-haiku-4-5-20251001'] - Model ID.
|
|
25
|
+
* @property {boolean} [exposeErrorBody=false] - Attach the full upstream response to `err.body` on HTTP errors (off by default to avoid leaking unexpected fields through error logs; `err.message` still carries the API error).
|
|
26
|
+
*/
|
|
27
|
+
export class AnthropicProvider {
|
|
28
|
+
/**
|
|
29
|
+
* @param {AnthropicOptions} [options]
|
|
30
|
+
* @throws {Error} `[AnthropicProvider] requires apiKey` — when apiKey is missing.
|
|
31
|
+
*/
|
|
32
|
+
constructor(options?: AnthropicOptions);
|
|
33
|
+
apiKey: string;
|
|
34
|
+
model: string;
|
|
35
|
+
exposeErrorBody: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Generate a response from the Anthropic API.
|
|
38
|
+
* @param {Message[]} messages - Conversation messages (OpenAI format, auto-converted).
|
|
39
|
+
* @param {ToolDef[]} [tools=[]] - Tool definitions.
|
|
40
|
+
* @param {Record<string, any>} [options={}] - Options (temperature, maxTokens, system).
|
|
41
|
+
* @returns {Promise<GenerateResult>}
|
|
42
|
+
* @throws {Error} `[AnthropicProvider] ...` — on HTTP errors (4xx/5xx) or invalid JSON response.
|
|
43
|
+
*/
|
|
44
|
+
generate(messages: Message[], tools?: ToolDef[], options?: Record<string, any>): Promise<GenerateResult>;
|
|
45
|
+
/**
|
|
46
|
+
* @param {Message} msg
|
|
47
|
+
* @returns {any}
|
|
48
|
+
*/
|
|
49
|
+
_toAnthropicMessage(msg: Message): any;
|
|
50
|
+
/**
|
|
51
|
+
* @param {Record<string, any>} body
|
|
52
|
+
* @returns {Promise<any>}
|
|
53
|
+
*/
|
|
54
|
+
_request(body: Record<string, any>): Promise<any>;
|
|
55
|
+
}
|
|
@@ -3,30 +3,43 @@
|
|
|
3
3
|
const https = require('https');
|
|
4
4
|
const { ProviderError } = require('./errors');
|
|
5
5
|
|
|
6
|
+
/** @typedef {import('../types').Message} Message */
|
|
7
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
8
|
+
/** @typedef {import('../types').GenerateResult} GenerateResult */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} AnthropicOptions
|
|
12
|
+
* @property {string} [apiKey] - Anthropic API key (required).
|
|
13
|
+
* @property {string} [model='claude-haiku-4-5-20251001'] - Model ID.
|
|
14
|
+
* @property {boolean} [exposeErrorBody=false] - Attach the full upstream response to `err.body` on HTTP errors (off by default to avoid leaking unexpected fields through error logs; `err.message` still carries the API error).
|
|
15
|
+
*/
|
|
16
|
+
|
|
6
17
|
class AnthropicProvider {
|
|
7
18
|
/**
|
|
8
|
-
* @param {
|
|
9
|
-
* @param {string} options.apiKey - Anthropic API key (required).
|
|
10
|
-
* @param {string} [options.model='claude-haiku-4-5-20251001'] - Model ID.
|
|
19
|
+
* @param {AnthropicOptions} [options]
|
|
11
20
|
* @throws {Error} `[AnthropicProvider] requires apiKey` — when apiKey is missing.
|
|
12
21
|
*/
|
|
13
22
|
constructor(options = {}) {
|
|
14
23
|
if (!options.apiKey) throw new Error('[AnthropicProvider] requires apiKey');
|
|
15
24
|
this.apiKey = options.apiKey.trim();
|
|
16
25
|
this.model = options.model || 'claude-haiku-4-5-20251001';
|
|
26
|
+
// See OpenAIProvider: attach full upstream body to err.body only on opt-in.
|
|
27
|
+
this.exposeErrorBody = options.exposeErrorBody === true;
|
|
17
28
|
}
|
|
18
29
|
|
|
19
30
|
/**
|
|
20
31
|
* Generate a response from the Anthropic API.
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {
|
|
23
|
-
* @param {
|
|
24
|
-
* @returns {Promise<
|
|
32
|
+
* @param {Message[]} messages - Conversation messages (OpenAI format, auto-converted).
|
|
33
|
+
* @param {ToolDef[]} [tools=[]] - Tool definitions.
|
|
34
|
+
* @param {Record<string, any>} [options={}] - Options (temperature, maxTokens, system).
|
|
35
|
+
* @returns {Promise<GenerateResult>}
|
|
25
36
|
* @throws {Error} `[AnthropicProvider] ...` — on HTTP errors (4xx/5xx) or invalid JSON response.
|
|
26
37
|
*/
|
|
27
38
|
async generate(messages, tools = [], options = {}) {
|
|
28
39
|
// Separate system message from conversation messages
|
|
40
|
+
/** @type {any} */
|
|
29
41
|
let system;
|
|
42
|
+
/** @type {any[]} */
|
|
30
43
|
const msgs = [];
|
|
31
44
|
for (const m of messages) {
|
|
32
45
|
if (m.role === 'system') {
|
|
@@ -39,6 +52,7 @@ class AnthropicProvider {
|
|
|
39
52
|
// Override with options.system if provided
|
|
40
53
|
if (options.system) system = options.system;
|
|
41
54
|
|
|
55
|
+
/** @type {Record<string, any>} */
|
|
42
56
|
const body = {
|
|
43
57
|
model: this.model,
|
|
44
58
|
max_tokens: options.maxTokens || 4096,
|
|
@@ -57,6 +71,7 @@ class AnthropicProvider {
|
|
|
57
71
|
const data = await this._request(body);
|
|
58
72
|
|
|
59
73
|
let text = '';
|
|
74
|
+
/** @type {import('../types').ToolCall[]} */
|
|
60
75
|
const toolCalls = [];
|
|
61
76
|
for (const block of data.content) {
|
|
62
77
|
if (block.type === 'text') text += block.text;
|
|
@@ -75,6 +90,10 @@ class AnthropicProvider {
|
|
|
75
90
|
};
|
|
76
91
|
}
|
|
77
92
|
|
|
93
|
+
/**
|
|
94
|
+
* @param {Message} msg
|
|
95
|
+
* @returns {any}
|
|
96
|
+
*/
|
|
78
97
|
_toAnthropicMessage(msg) {
|
|
79
98
|
// Convert OpenAI-format tool results → Anthropic tool_result blocks
|
|
80
99
|
if (msg.role === 'tool') {
|
|
@@ -88,7 +107,8 @@ class AnthropicProvider {
|
|
|
88
107
|
};
|
|
89
108
|
}
|
|
90
109
|
// Convert OpenAI-format assistant tool_calls → Anthropic tool_use content blocks
|
|
91
|
-
if (msg.role === 'assistant' && msg.tool_calls
|
|
110
|
+
if (msg.role === 'assistant' && msg.tool_calls && msg.tool_calls.length > 0) {
|
|
111
|
+
/** @type {any[]} */
|
|
92
112
|
const content = [];
|
|
93
113
|
if (msg.content) content.push({ type: 'text', text: msg.content });
|
|
94
114
|
for (const tc of msg.tool_calls) {
|
|
@@ -106,6 +126,10 @@ class AnthropicProvider {
|
|
|
106
126
|
return { role: msg.role, content: msg.content };
|
|
107
127
|
}
|
|
108
128
|
|
|
129
|
+
/**
|
|
130
|
+
* @param {Record<string, any>} body
|
|
131
|
+
* @returns {Promise<any>}
|
|
132
|
+
*/
|
|
109
133
|
_request(body) {
|
|
110
134
|
return new Promise((resolve, reject) => {
|
|
111
135
|
const payload = JSON.stringify(body);
|
|
@@ -123,10 +147,10 @@ class AnthropicProvider {
|
|
|
123
147
|
res.on('end', () => {
|
|
124
148
|
try {
|
|
125
149
|
const parsed = JSON.parse(chunks);
|
|
126
|
-
if (res.statusCode >= 400) {
|
|
150
|
+
if ((res.statusCode ?? 0) >= 400) {
|
|
127
151
|
return reject(new ProviderError(
|
|
128
152
|
`[AnthropicProvider] ${parsed.error?.message || `HTTP ${res.statusCode}`}`,
|
|
129
|
-
{ status: res.statusCode, body: parsed }
|
|
153
|
+
/** @type {any} */ ({ status: res.statusCode, body: this.exposeErrorBody ? parsed : undefined })
|
|
130
154
|
));
|
|
131
155
|
}
|
|
132
156
|
resolve(parsed);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export type Message = import("../types").Message;
|
|
2
|
+
export type ToolDef = import("../types").ToolDef;
|
|
3
|
+
export type GenerateResult = import("../types").GenerateResult;
|
|
4
|
+
export type CLIPipeOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* - CLI command to spawn (required).
|
|
7
|
+
*/
|
|
8
|
+
command?: string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* - Arguments to pass to the command.
|
|
11
|
+
*/
|
|
12
|
+
args?: string[] | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* - Working directory for the child process.
|
|
15
|
+
*/
|
|
16
|
+
cwd?: string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* - Environment variables for the child process.
|
|
19
|
+
*/
|
|
20
|
+
env?: Record<string, string> | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* - Timeout in milliseconds.
|
|
23
|
+
*/
|
|
24
|
+
timeout?: number | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* - CLI flag for system prompt (e.g. '--system'). When set, system messages are extracted and passed via this flag instead of stdin.
|
|
27
|
+
*/
|
|
28
|
+
systemPromptFlag?: string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* - Called with each stdout chunk as it streams.
|
|
31
|
+
*/
|
|
32
|
+
onChunk?: ((chunk: string) => void) | undefined;
|
|
33
|
+
};
|
|
34
|
+
/** @typedef {import('../types').Message} Message */
|
|
35
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
36
|
+
/** @typedef {import('../types').GenerateResult} GenerateResult */
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {object} CLIPipeOptions
|
|
39
|
+
* @property {string} [command] - CLI command to spawn (required).
|
|
40
|
+
* @property {string[]} [args=[]] - Arguments to pass to the command.
|
|
41
|
+
* @property {string} [cwd] - Working directory for the child process.
|
|
42
|
+
* @property {Record<string, string>} [env] - Environment variables for the child process.
|
|
43
|
+
* @property {number} [timeout=30000] - Timeout in milliseconds.
|
|
44
|
+
* @property {string} [systemPromptFlag] - CLI flag for system prompt (e.g. '--system'). When set, system messages are extracted and passed via this flag instead of stdin.
|
|
45
|
+
* @property {(chunk: string) => void} [onChunk] - Called with each stdout chunk as it streams.
|
|
46
|
+
*/
|
|
47
|
+
export class CLIPipeProvider {
|
|
48
|
+
/**
|
|
49
|
+
* Provider that pipes prompts to a CLI command via stdin and reads stdout.
|
|
50
|
+
* @param {CLIPipeOptions} [options]
|
|
51
|
+
* @throws {Error} `[CLIPipeProvider] requires command` — when options.command is missing.
|
|
52
|
+
*/
|
|
53
|
+
constructor(options?: CLIPipeOptions);
|
|
54
|
+
command: string;
|
|
55
|
+
args: string[];
|
|
56
|
+
cwd: string | undefined;
|
|
57
|
+
env: Record<string, string> | undefined;
|
|
58
|
+
timeout: number;
|
|
59
|
+
systemPromptFlag: string | null;
|
|
60
|
+
onChunk: ((chunk: string) => void) | null;
|
|
61
|
+
/**
|
|
62
|
+
* Generate a response by piping messages to the CLI command.
|
|
63
|
+
* @param {Message[]} messages - Conversation messages in OpenAI format.
|
|
64
|
+
* @param {ToolDef[]} [tools=[]] - Unused (CLI commands don't support tools).
|
|
65
|
+
* @param {Record<string, any>} [options={}] - Unused.
|
|
66
|
+
* @returns {Promise<GenerateResult>}
|
|
67
|
+
* @throws {Error} `[CLIPipeProvider] failed to spawn "cmd": ...` — when the command cannot be found or executed.
|
|
68
|
+
* @throws {Error} `[CLIPipeProvider] process exited with code N: ...` — on non-zero exit.
|
|
69
|
+
* @throws {Error} `[CLIPipeProvider] timed out after Nms` — when the process exceeds timeout.
|
|
70
|
+
* @throws {Error} `[CLIPipeProvider] process produced no output` — when stdout is empty.
|
|
71
|
+
*/
|
|
72
|
+
generate(messages: Message[], tools?: ToolDef[], options?: Record<string, any>): Promise<GenerateResult>;
|
|
73
|
+
/**
|
|
74
|
+
* Convert OpenAI-format messages to a plain text prompt.
|
|
75
|
+
* @param {Message[]} messages
|
|
76
|
+
* @returns {string}
|
|
77
|
+
*/
|
|
78
|
+
_formatPrompt(messages: Message[]): string;
|
|
79
|
+
/**
|
|
80
|
+
* Spawn the CLI process, pipe prompt to stdin, collect stdout.
|
|
81
|
+
* @param {string} prompt
|
|
82
|
+
* @param {string[]} [extraArgs=[]] - Additional args appended after this.args.
|
|
83
|
+
* @returns {Promise<string>}
|
|
84
|
+
*/
|
|
85
|
+
_spawn(prompt: string, extraArgs?: string[]): Promise<string>;
|
|
86
|
+
}
|
package/src/provider-clipipe.js
CHANGED
|
@@ -3,16 +3,25 @@
|
|
|
3
3
|
const { spawn } = require('child_process');
|
|
4
4
|
const { ProviderError } = require('./errors');
|
|
5
5
|
|
|
6
|
+
/** @typedef {import('../types').Message} Message */
|
|
7
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
8
|
+
/** @typedef {import('../types').GenerateResult} GenerateResult */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} CLIPipeOptions
|
|
12
|
+
* @property {string} [command] - CLI command to spawn (required).
|
|
13
|
+
* @property {string[]} [args=[]] - Arguments to pass to the command.
|
|
14
|
+
* @property {string} [cwd] - Working directory for the child process.
|
|
15
|
+
* @property {Record<string, string>} [env] - Environment variables for the child process.
|
|
16
|
+
* @property {number} [timeout=30000] - Timeout in milliseconds.
|
|
17
|
+
* @property {string} [systemPromptFlag] - CLI flag for system prompt (e.g. '--system'). When set, system messages are extracted and passed via this flag instead of stdin.
|
|
18
|
+
* @property {(chunk: string) => void} [onChunk] - Called with each stdout chunk as it streams.
|
|
19
|
+
*/
|
|
20
|
+
|
|
6
21
|
class CLIPipeProvider {
|
|
7
22
|
/**
|
|
8
23
|
* Provider that pipes prompts to a CLI command via stdin and reads stdout.
|
|
9
|
-
* @param {
|
|
10
|
-
* @param {string} options.command - CLI command to spawn (required).
|
|
11
|
-
* @param {string[]} [options.args=[]] - Arguments to pass to the command.
|
|
12
|
-
* @param {string} [options.cwd] - Working directory for the child process.
|
|
13
|
-
* @param {object} [options.env] - Environment variables for the child process.
|
|
14
|
-
* @param {number} [options.timeout=30000] - Timeout in milliseconds.
|
|
15
|
-
* @param {string} [options.systemPromptFlag] - CLI flag for system prompt (e.g. '--system'). When set, system messages are extracted and passed via this flag instead of stdin.
|
|
24
|
+
* @param {CLIPipeOptions} [options]
|
|
16
25
|
* @throws {Error} `[CLIPipeProvider] requires command` — when options.command is missing.
|
|
17
26
|
*/
|
|
18
27
|
constructor(options = {}) {
|
|
@@ -28,16 +37,17 @@ class CLIPipeProvider {
|
|
|
28
37
|
|
|
29
38
|
/**
|
|
30
39
|
* Generate a response by piping messages to the CLI command.
|
|
31
|
-
* @param {
|
|
32
|
-
* @param {
|
|
33
|
-
* @param {
|
|
34
|
-
* @returns {Promise<
|
|
40
|
+
* @param {Message[]} messages - Conversation messages in OpenAI format.
|
|
41
|
+
* @param {ToolDef[]} [tools=[]] - Unused (CLI commands don't support tools).
|
|
42
|
+
* @param {Record<string, any>} [options={}] - Unused.
|
|
43
|
+
* @returns {Promise<GenerateResult>}
|
|
35
44
|
* @throws {Error} `[CLIPipeProvider] failed to spawn "cmd": ...` — when the command cannot be found or executed.
|
|
36
45
|
* @throws {Error} `[CLIPipeProvider] process exited with code N: ...` — on non-zero exit.
|
|
37
46
|
* @throws {Error} `[CLIPipeProvider] timed out after Nms` — when the process exceeds timeout.
|
|
38
47
|
* @throws {Error} `[CLIPipeProvider] process produced no output` — when stdout is empty.
|
|
39
48
|
*/
|
|
40
49
|
async generate(messages, tools = [], options = {}) {
|
|
50
|
+
/** @type {string[]} */
|
|
41
51
|
let extraArgs = [];
|
|
42
52
|
let promptMessages = messages;
|
|
43
53
|
|
|
@@ -61,7 +71,7 @@ class CLIPipeProvider {
|
|
|
61
71
|
|
|
62
72
|
/**
|
|
63
73
|
* Convert OpenAI-format messages to a plain text prompt.
|
|
64
|
-
* @param {
|
|
74
|
+
* @param {Message[]} messages
|
|
65
75
|
* @returns {string}
|
|
66
76
|
*/
|
|
67
77
|
_formatPrompt(messages) {
|
|
@@ -79,11 +89,11 @@ class CLIPipeProvider {
|
|
|
79
89
|
*/
|
|
80
90
|
_spawn(prompt, extraArgs = []) {
|
|
81
91
|
return new Promise((resolve, reject) => {
|
|
82
|
-
const child = spawn(this.command, [...this.args, ...extraArgs], {
|
|
92
|
+
const child = spawn(this.command, [...this.args, ...extraArgs], /** @type {any} */ ({
|
|
83
93
|
cwd: this.cwd,
|
|
84
94
|
env: this.env,
|
|
85
95
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
86
|
-
});
|
|
96
|
+
}));
|
|
87
97
|
|
|
88
98
|
let stdout = '';
|
|
89
99
|
let stderr = '';
|
|
@@ -93,17 +103,17 @@ class CLIPipeProvider {
|
|
|
93
103
|
child.stderr.on('data', d => { stderr += d; });
|
|
94
104
|
|
|
95
105
|
child.on('error', err => {
|
|
96
|
-
reject(new ProviderError(`[CLIPipeProvider] failed to spawn "${this.command}": ${err.message}`, { status: 0 }));
|
|
106
|
+
reject(new ProviderError(`[CLIPipeProvider] failed to spawn "${this.command}": ${err.message}`, /** @type {any} */ ({ status: 0 })));
|
|
97
107
|
});
|
|
98
108
|
|
|
99
109
|
child.on('close', code => {
|
|
100
110
|
if (killed) return; // timeout already rejected
|
|
101
111
|
if (code !== 0) {
|
|
102
|
-
return reject(new ProviderError(`[CLIPipeProvider] process exited with code ${code}: ${stderr.trim()}`, { status: code }));
|
|
112
|
+
return reject(new ProviderError(`[CLIPipeProvider] process exited with code ${code}: ${stderr.trim()}`, /** @type {any} */ ({ status: code })));
|
|
103
113
|
}
|
|
104
114
|
const text = stdout.trim();
|
|
105
115
|
if (!text) {
|
|
106
|
-
return reject(new ProviderError('[CLIPipeProvider] process produced no output', { status: 0 }));
|
|
116
|
+
return reject(new ProviderError('[CLIPipeProvider] process produced no output', /** @type {any} */ ({ status: 0 })));
|
|
107
117
|
}
|
|
108
118
|
resolve(text);
|
|
109
119
|
});
|
|
@@ -115,7 +125,7 @@ class CLIPipeProvider {
|
|
|
115
125
|
setTimeout(() => {
|
|
116
126
|
try { child.kill('SIGKILL'); } catch (_) {}
|
|
117
127
|
}, 1000);
|
|
118
|
-
reject(new ProviderError(`[CLIPipeProvider] timed out after ${this.timeout}ms`, { status: 0 }));
|
|
128
|
+
reject(new ProviderError(`[CLIPipeProvider] timed out after ${this.timeout}ms`, /** @type {any} */ ({ status: 0 })));
|
|
119
129
|
}, this.timeout);
|
|
120
130
|
|
|
121
131
|
child.on('close', () => clearTimeout(timer));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type Provider = import("../types").Provider;
|
|
2
|
+
export type Message = import("../types").Message;
|
|
3
|
+
export type ToolDef = import("../types").ToolDef;
|
|
4
|
+
export type GenerateResult = import("../types").GenerateResult;
|
|
5
|
+
export type FallbackOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* - Return false to stop.
|
|
8
|
+
*/
|
|
9
|
+
shouldFallback?: ((error: any, index: number) => boolean) | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* - Callback.
|
|
12
|
+
*/
|
|
13
|
+
onFallback?: ((error: any, fromIndex: number, toIndex: number) => void) | undefined;
|
|
14
|
+
};
|
|
15
|
+
/** @typedef {import('../types').Provider} Provider */
|
|
16
|
+
/** @typedef {import('../types').Message} Message */
|
|
17
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
18
|
+
/** @typedef {import('../types').GenerateResult} GenerateResult */
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} FallbackOptions
|
|
21
|
+
* @property {(error: any, index: number) => boolean} [shouldFallback] - Return false to stop.
|
|
22
|
+
* @property {(error: any, fromIndex: number, toIndex: number) => void} [onFallback] - Callback.
|
|
23
|
+
*/
|
|
24
|
+
export class FallbackProvider {
|
|
25
|
+
/**
|
|
26
|
+
* Provider that tries multiple providers in order.
|
|
27
|
+
* @param {Provider[]} providers - Ordered list of providers with generate().
|
|
28
|
+
* @param {FallbackOptions} [options={}]
|
|
29
|
+
* @throws {Error} `[FallbackProvider] requires at least one provider` — when providers is empty.
|
|
30
|
+
*/
|
|
31
|
+
constructor(providers: Provider[], options?: FallbackOptions);
|
|
32
|
+
providers: import("../types").Provider[];
|
|
33
|
+
shouldFallback: (error: any, index: number) => boolean;
|
|
34
|
+
onFallback: ((error: any, fromIndex: number, toIndex: number) => void) | null;
|
|
35
|
+
/**
|
|
36
|
+
* Generate using first available provider.
|
|
37
|
+
* @param {Message[]} messages
|
|
38
|
+
* @param {ToolDef[]} [tools=[]]
|
|
39
|
+
* @param {Record<string, any>} [options={}]
|
|
40
|
+
* @returns {Promise<GenerateResult>}
|
|
41
|
+
* @throws {AggregateError} When all providers fail.
|
|
42
|
+
*/
|
|
43
|
+
generate(messages: Message[], tools?: ToolDef[], options?: Record<string, any>): Promise<GenerateResult>;
|
|
44
|
+
}
|
package/src/provider-fallback.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/** @typedef {import('../types').Provider} Provider */
|
|
4
|
+
/** @typedef {import('../types').Message} Message */
|
|
5
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
6
|
+
/** @typedef {import('../types').GenerateResult} GenerateResult */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} FallbackOptions
|
|
10
|
+
* @property {(error: any, index: number) => boolean} [shouldFallback] - Return false to stop.
|
|
11
|
+
* @property {(error: any, fromIndex: number, toIndex: number) => void} [onFallback] - Callback.
|
|
12
|
+
*/
|
|
13
|
+
|
|
3
14
|
class FallbackProvider {
|
|
4
15
|
/**
|
|
5
16
|
* Provider that tries multiple providers in order.
|
|
6
|
-
* @param {
|
|
7
|
-
* @param {
|
|
8
|
-
* @param {function} [options.shouldFallback] - (error, index) => boolean. Return false to stop.
|
|
9
|
-
* @param {function} [options.onFallback] - (error, fromIndex, toIndex) callback.
|
|
17
|
+
* @param {Provider[]} providers - Ordered list of providers with generate().
|
|
18
|
+
* @param {FallbackOptions} [options={}]
|
|
10
19
|
* @throws {Error} `[FallbackProvider] requires at least one provider` — when providers is empty.
|
|
11
20
|
*/
|
|
12
21
|
constructor(providers, options = {}) {
|
|
@@ -20,13 +29,14 @@ class FallbackProvider {
|
|
|
20
29
|
|
|
21
30
|
/**
|
|
22
31
|
* Generate using first available provider.
|
|
23
|
-
* @param {
|
|
24
|
-
* @param {
|
|
25
|
-
* @param {
|
|
26
|
-
* @returns {Promise<
|
|
32
|
+
* @param {Message[]} messages
|
|
33
|
+
* @param {ToolDef[]} [tools=[]]
|
|
34
|
+
* @param {Record<string, any>} [options={}]
|
|
35
|
+
* @returns {Promise<GenerateResult>}
|
|
27
36
|
* @throws {AggregateError} When all providers fail.
|
|
28
37
|
*/
|
|
29
38
|
async generate(messages, tools = [], options = {}) {
|
|
39
|
+
/** @type {any[]} */
|
|
30
40
|
const errors = [];
|
|
31
41
|
|
|
32
42
|
for (let i = 0; i < this.providers.length; i++) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type Message = import("../types").Message;
|
|
2
|
+
export type ToolDef = import("../types").ToolDef;
|
|
3
|
+
export type GenerateResult = import("../types").GenerateResult;
|
|
4
|
+
export type OllamaOptions = {
|
|
5
|
+
model?: string | undefined;
|
|
6
|
+
url?: string | undefined;
|
|
7
|
+
exposeErrorBody?: boolean | undefined;
|
|
8
|
+
};
|
|
9
|
+
/** @typedef {import('../types').Message} Message */
|
|
10
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
11
|
+
/** @typedef {import('../types').GenerateResult} GenerateResult */
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} OllamaOptions
|
|
14
|
+
* @property {string} [model='llama3.2']
|
|
15
|
+
* @property {string} [url='http://localhost:11434']
|
|
16
|
+
* @property {boolean} [exposeErrorBody=false]
|
|
17
|
+
*/
|
|
18
|
+
export class OllamaProvider {
|
|
19
|
+
/**
|
|
20
|
+
* @param {OllamaOptions} [options]
|
|
21
|
+
*/
|
|
22
|
+
constructor(options?: OllamaOptions);
|
|
23
|
+
model: string;
|
|
24
|
+
url: string;
|
|
25
|
+
exposeErrorBody: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Generate a response from a local Ollama instance.
|
|
28
|
+
* @param {Message[]} messages - Conversation messages.
|
|
29
|
+
* @param {ToolDef[]} [tools=[]] - Tool definitions.
|
|
30
|
+
* @param {Record<string, any>} [options={}] - Options (temperature).
|
|
31
|
+
* @returns {Promise<GenerateResult>}
|
|
32
|
+
* @throws {Error} `[OllamaProvider] ...` — on HTTP errors or invalid JSON response.
|
|
33
|
+
*/
|
|
34
|
+
generate(messages: Message[], tools?: ToolDef[], options?: Record<string, any>): Promise<GenerateResult>;
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} path
|
|
37
|
+
* @param {Record<string, any>} body
|
|
38
|
+
* @returns {Promise<any>}
|
|
39
|
+
*/
|
|
40
|
+
_request(path: string, body: Record<string, any>): Promise<any>;
|
|
41
|
+
}
|
package/src/provider-ollama.js
CHANGED
|
@@ -3,21 +3,38 @@
|
|
|
3
3
|
const http = require('http');
|
|
4
4
|
const { ProviderError } = require('./errors');
|
|
5
5
|
|
|
6
|
+
/** @typedef {import('../types').Message} Message */
|
|
7
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
8
|
+
/** @typedef {import('../types').GenerateResult} GenerateResult */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} OllamaOptions
|
|
12
|
+
* @property {string} [model='llama3.2']
|
|
13
|
+
* @property {string} [url='http://localhost:11434']
|
|
14
|
+
* @property {boolean} [exposeErrorBody=false]
|
|
15
|
+
*/
|
|
16
|
+
|
|
6
17
|
class OllamaProvider {
|
|
18
|
+
/**
|
|
19
|
+
* @param {OllamaOptions} [options]
|
|
20
|
+
*/
|
|
7
21
|
constructor(options = {}) {
|
|
8
22
|
this.model = options.model || 'llama3.2';
|
|
9
23
|
this.url = options.url || 'http://localhost:11434';
|
|
24
|
+
// See OpenAIProvider: attach full upstream body to err.body only on opt-in.
|
|
25
|
+
this.exposeErrorBody = options.exposeErrorBody === true;
|
|
10
26
|
}
|
|
11
27
|
|
|
12
28
|
/**
|
|
13
29
|
* Generate a response from a local Ollama instance.
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {
|
|
16
|
-
* @param {
|
|
17
|
-
* @returns {Promise<
|
|
30
|
+
* @param {Message[]} messages - Conversation messages.
|
|
31
|
+
* @param {ToolDef[]} [tools=[]] - Tool definitions.
|
|
32
|
+
* @param {Record<string, any>} [options={}] - Options (temperature).
|
|
33
|
+
* @returns {Promise<GenerateResult>}
|
|
18
34
|
* @throws {Error} `[OllamaProvider] ...` — on HTTP errors or invalid JSON response.
|
|
19
35
|
*/
|
|
20
36
|
async generate(messages, tools = [], options = {}) {
|
|
37
|
+
/** @type {Record<string, any>} */
|
|
21
38
|
const body = {
|
|
22
39
|
model: this.model,
|
|
23
40
|
messages,
|
|
@@ -36,7 +53,7 @@ class OllamaProvider {
|
|
|
36
53
|
|
|
37
54
|
return {
|
|
38
55
|
text: msg.content || '',
|
|
39
|
-
toolCalls: (msg.tool_calls || []).map(tc => ({
|
|
56
|
+
toolCalls: (msg.tool_calls || []).map((/** @type {any} */ tc) => ({
|
|
40
57
|
id: tc.id || `call_${Date.now()}`,
|
|
41
58
|
name: tc.function.name,
|
|
42
59
|
arguments: typeof tc.function.arguments === 'string'
|
|
@@ -50,6 +67,11 @@ class OllamaProvider {
|
|
|
50
67
|
};
|
|
51
68
|
}
|
|
52
69
|
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} path
|
|
72
|
+
* @param {Record<string, any>} body
|
|
73
|
+
* @returns {Promise<any>}
|
|
74
|
+
*/
|
|
53
75
|
_request(path, body) {
|
|
54
76
|
return new Promise((resolve, reject) => {
|
|
55
77
|
const url = new URL(this.url + path);
|
|
@@ -67,10 +89,10 @@ class OllamaProvider {
|
|
|
67
89
|
res.on('end', () => {
|
|
68
90
|
try {
|
|
69
91
|
const parsed = JSON.parse(chunks);
|
|
70
|
-
if (res.statusCode >= 400) {
|
|
92
|
+
if ((res.statusCode ?? 0) >= 400) {
|
|
71
93
|
return reject(new ProviderError(
|
|
72
94
|
`[OllamaProvider] ${parsed.error || `HTTP ${res.statusCode}`}`,
|
|
73
|
-
{ status: res.statusCode, body: parsed }
|
|
95
|
+
/** @type {any} */ ({ status: res.statusCode, body: this.exposeErrorBody ? parsed : undefined })
|
|
74
96
|
));
|
|
75
97
|
}
|
|
76
98
|
resolve(parsed);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type Message = import("../types").Message;
|
|
2
|
+
export type ToolDef = import("../types").ToolDef;
|
|
3
|
+
export type ToolCall = import("../types").ToolCall;
|
|
4
|
+
export type GenerateResult = import("../types").GenerateResult;
|
|
5
|
+
export type OpenAIOptions = {
|
|
6
|
+
apiKey?: string | undefined;
|
|
7
|
+
model?: string | undefined;
|
|
8
|
+
baseUrl?: string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* - Attach the full upstream
|
|
11
|
+
* response to `err.body` on HTTP errors. Off by default so an unexpected
|
|
12
|
+
* field in an error payload can't leak through logs that dump the error
|
|
13
|
+
* object; `err.message` still carries the API's error message. Turn on for
|
|
14
|
+
* debugging only.
|
|
15
|
+
*/
|
|
16
|
+
exposeErrorBody?: boolean | undefined;
|
|
17
|
+
};
|
|
18
|
+
/** @typedef {import('../types').Message} Message */
|
|
19
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
20
|
+
/** @typedef {import('../types').ToolCall} ToolCall */
|
|
21
|
+
/** @typedef {import('../types').GenerateResult} GenerateResult */
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} OpenAIOptions
|
|
24
|
+
* @property {string} [apiKey]
|
|
25
|
+
* @property {string} [model='gpt-4o-mini']
|
|
26
|
+
* @property {string} [baseUrl='https://api.openai.com/v1']
|
|
27
|
+
* @property {boolean} [exposeErrorBody=false] - Attach the full upstream
|
|
28
|
+
* response to `err.body` on HTTP errors. Off by default so an unexpected
|
|
29
|
+
* field in an error payload can't leak through logs that dump the error
|
|
30
|
+
* object; `err.message` still carries the API's error message. Turn on for
|
|
31
|
+
* debugging only.
|
|
32
|
+
*/
|
|
33
|
+
export class OpenAIProvider {
|
|
34
|
+
/**
|
|
35
|
+
* @param {OpenAIOptions} [options]
|
|
36
|
+
*/
|
|
37
|
+
constructor(options?: OpenAIOptions);
|
|
38
|
+
apiKey: string | undefined;
|
|
39
|
+
model: string;
|
|
40
|
+
baseUrl: string;
|
|
41
|
+
exposeErrorBody: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Generate a response from the OpenAI API.
|
|
44
|
+
* @param {Message[]} messages - Conversation messages.
|
|
45
|
+
* @param {ToolDef[]} [tools=[]] - Tool definitions.
|
|
46
|
+
* @param {Record<string, any>} [options={}] - Options (temperature, maxTokens).
|
|
47
|
+
* @returns {Promise<GenerateResult>}
|
|
48
|
+
* @throws {Error} `[OpenAIProvider] ...` — on HTTP errors (4xx/5xx) or invalid JSON response.
|
|
49
|
+
*/
|
|
50
|
+
generate(messages: Message[], tools?: ToolDef[], options?: Record<string, any>): Promise<GenerateResult>;
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} path
|
|
53
|
+
* @param {Record<string, any>} body
|
|
54
|
+
* @returns {Promise<any>}
|
|
55
|
+
*/
|
|
56
|
+
_request(path: string, body: Record<string, any>): Promise<any>;
|
|
57
|
+
}
|