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.
Files changed (65) hide show
  1. package/bin/cli.d.ts +4 -0
  2. package/bin/cli.js +70 -12
  3. package/bin/test-provider.d.ts +2 -0
  4. package/bin/test-provider.js +5 -1
  5. package/index.d.ts +20 -0
  6. package/package.json +44 -10
  7. package/src/bareguard-adapter.d.ts +118 -0
  8. package/src/bareguard-adapter.js +75 -3
  9. package/src/checkpoint.d.ts +61 -0
  10. package/src/checkpoint.js +17 -8
  11. package/src/circuit-breaker.d.ts +70 -0
  12. package/src/circuit-breaker.js +20 -4
  13. package/src/errors.d.ts +106 -0
  14. package/src/errors.js +50 -1
  15. package/src/loop.d.ts +135 -0
  16. package/src/loop.js +80 -18
  17. package/src/mcp-bridge.d.ts +133 -0
  18. package/src/mcp-bridge.js +199 -26
  19. package/src/mcp.d.ts +4 -0
  20. package/src/memory.d.ts +50 -0
  21. package/src/memory.js +22 -2
  22. package/src/planner.d.ts +62 -0
  23. package/src/planner.js +26 -7
  24. package/src/provider-anthropic.d.ts +55 -0
  25. package/src/provider-anthropic.js +34 -10
  26. package/src/provider-clipipe.d.ts +86 -0
  27. package/src/provider-clipipe.js +28 -18
  28. package/src/provider-fallback.d.ts +44 -0
  29. package/src/provider-fallback.js +18 -8
  30. package/src/provider-ollama.d.ts +41 -0
  31. package/src/provider-ollama.js +29 -7
  32. package/src/provider-openai.d.ts +57 -0
  33. package/src/provider-openai.js +34 -7
  34. package/src/providers.d.ts +6 -0
  35. package/src/retry.d.ts +44 -0
  36. package/src/retry.js +15 -1
  37. package/src/run-plan.d.ts +126 -0
  38. package/src/run-plan.js +46 -13
  39. package/src/scheduler.d.ts +102 -0
  40. package/src/scheduler.js +32 -4
  41. package/src/state.d.ts +45 -0
  42. package/src/state.js +18 -2
  43. package/src/store-jsonfile.d.ts +85 -0
  44. package/src/store-jsonfile.js +50 -8
  45. package/src/store-sqlite.d.ts +90 -0
  46. package/src/store-sqlite.js +31 -7
  47. package/src/stores.d.ts +3 -0
  48. package/src/stream.d.ts +79 -0
  49. package/src/stream.js +32 -0
  50. package/src/tools.d.ts +8 -0
  51. package/src/transport-jsonl.d.ts +30 -0
  52. package/src/transport-jsonl.js +13 -0
  53. package/src/transports.d.ts +2 -0
  54. package/tools/browse.d.ts +10 -0
  55. package/tools/browse.js +2 -0
  56. package/tools/defer.d.ts +33 -0
  57. package/tools/defer.js +12 -3
  58. package/tools/mobile.d.ts +34 -0
  59. package/tools/mobile.js +28 -15
  60. package/tools/shell.d.ts +31 -0
  61. package/tools/shell.js +83 -6
  62. package/tools/spawn.d.ts +107 -0
  63. package/tools/spawn.js +24 -5
  64. package/types/index.d.ts +66 -0
  65. 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 {object} options
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 {Array<object>} messages - Conversation messages (OpenAI format, auto-converted).
22
- * @param {Array<object>} [tools=[]] - Tool definitions.
23
- * @param {object} [options={}] - Options (temperature, maxTokens, system).
24
- * @returns {Promise<{text: string, toolCalls: Array, usage: object}>}
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?.length > 0) {
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
+ }
@@ -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 {object} options
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 {Array<object>} messages - Conversation messages in OpenAI format.
32
- * @param {Array<object>} [tools=[]] - Unused (CLI commands don't support tools).
33
- * @param {object} [options={}] - Unused.
34
- * @returns {Promise<{text: string, toolCalls: Array, usage: object}>}
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 {Array<object>} messages
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
+ }
@@ -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 {Array<object>} providers - Ordered list of providers with generate().
7
- * @param {object} [options={}]
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 {Array<object>} messages
24
- * @param {Array<object>} [tools=[]]
25
- * @param {object} [options={}]
26
- * @returns {Promise<{text: string, toolCalls: Array, usage: object}>}
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
+ }
@@ -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 {Array<object>} messages - Conversation messages.
15
- * @param {Array<object>} [tools=[]] - Tool definitions.
16
- * @param {object} [options={}] - Options (temperature).
17
- * @returns {Promise<{text: string, toolCalls: Array, usage: object}>}
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
+ }