bare-agent 0.49.0 → 0.50.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/bareagent.context.md +2 -0
- package/package.json +1 -1
- package/src/loop.d.ts +4 -14
- package/src/loop.js +2 -2
- package/src/provider-ollama.d.ts +4 -4
- package/src/provider-ollama.js +2 -2
- package/src/provider-openai.d.ts +6 -8
- package/src/provider-openai.js +3 -4
- package/src/provider-toolcalls.d.ts +7 -6
- package/src/provider-toolcalls.js +8 -13
- package/types/index.d.ts +7 -4
package/bareagent.context.md
CHANGED
|
@@ -976,6 +976,8 @@ All return `{ text, toolCalls, usage: { inputTokens, outputTokens }, model?, cos
|
|
|
976
976
|
|
|
977
977
|
**Error body (v0.11.0):** on an HTTP error the OpenAI/Anthropic/Ollama providers throw a `ProviderError` whose `message` carries the upstream error string. The full parsed response is **not** attached to `err.body` by default (so an unexpected field can't leak through logs that dump the error object). Pass `{ exposeErrorBody: true }` to attach it for debugging.
|
|
978
978
|
|
|
979
|
+
**Malformed tool-call arguments (BA-27):** when an OpenAI-compatible or Ollama model emits `function.arguments` that is not valid JSON, the provider returns `toolCalls: []` plus a `malformedToolCall: { name, error }` marker (round still metered; JSON never repaired); `Loop.run()` surfaces the same marker on its result. Pass `{ exposeMalformedArgs: true }` (OpenAI/Ollama; default off, independent of `exposeErrorBody`) to also get `malformedToolCall.rawArguments` — the FULL raw arguments string, verbatim and uncapped (since 0.50.0; earlier it was the first 500 chars plus `rawTruncated`, which hid the broken tail). Model-generated and possibly large or sensitive: bare-agent only returns it to you and never logs it, but anything you forward it to (logs, audit rows) is your call. `rawTruncated` is deprecated and never set.
|
|
980
|
+
|
|
979
981
|
**Request/idle timeout (BA-18, v0.34.0):** the four http(s) providers (Anthropic, OpenAI, Gemini, Ollama) accept a `timeoutMs` option — constructor default **600000 (10 min)**, overridable per call via `generate(..., { timeoutMs })`, and `0`/`Infinity` disables it. Before this they wired only `req.on('error')`, so a socket the server silently dropped — or a response that never starts — hung `generate()` until the OS TCP timeout (~2h): a hang, not an error, so retry/casualty policy above it never fired. `timeoutMs` bounds on socket **inactivity** (`req.setTimeout`), so a slow-but-streaming response is not killed — only a silent/never-answering socket trips it; the 10-min default clears any single non-streaming completion (TTFB ≈ generation time). On trip, `generate()` rejects with a retryable `TimeoutError` (`code: 'ETIMEDOUT'`, `context.bound: 'idle'`, `retryable: true`). **Retry is caller-side and already wired:** `new Loop({ provider, retry: new Retry() })` wraps `provider.generate`, and `DEFAULT_RETRY_ON` classifies `ETIMEDOUT` (and `ECONNRESET`/`ENOTFOUND`/429/5xx) as transient — so a wired `Retry` retries a timed-out request and rethrows under `retryOn: () => false`, with no extra wiring (`run-plan`'s `stepRetry` is a second consumer of the same seam). CLIPipe already bounded its child process (`timeout`, default 30000 for one-shot) and is unchanged.
|
|
980
982
|
|
|
981
983
|
**Total call-duration deadline (BA-19, v0.35.0):** `timeoutMs` bounds socket *inactivity*, and `req.setTimeout` resets on any activity by design — so a "zombie stream" that trickles a byte forever (bytes arriving, the response never completing) never trips it and hangs the caller for hours (an adopter saw one `generate()` run **274 min** and end in `ECONNRESET`, not a `TimeoutError`: the reset proves bytes *were* flowing, so the idle timer never fired). The four http(s) providers now also accept a `deadlineMs` option — an absolute, **non-resetting** wall-clock ceiling on the whole request. **Disabled by default** (a deliberately long single call — large `maxTokens`, slow model — is legitimate; a default here would kill it), overridable per call via `generate(..., { deadlineMs })`, `0`/`Infinity` disable. An *unset* deadline resolves to disabled, but an *explicitly-set* garbage value (`NaN`, a non-numeric string) throws a `ValidationError` at resolve time rather than silently disabling the bound and running unbounded — unlike `timeoutMs`, the deadline has no safe default to fall back to, so a config mistake must surface loudly. On trip, `generate()` rejects with a **terminal** `TimeoutError` distinguishable from the idle trip: `code: 'EDEADLINE'`, `context.bound: 'deadline'`, `retryable: false` — a deadline is a hard ceiling meant to STOP, so it is *not* auto-retried (retrying would re-spend up to another full `deadlineMs`); a consumer that wants retry opts in via `retryOn`. When both are armed and `timeoutMs < deadlineMs`, a silent socket trips the idle bound first; only a still-active-but-never-completing stream reaches the deadline. The idle bound (BA-18) and the deadline (BA-19) are two independent failure modes — a silent socket vs a zombie stream.
|
package/package.json
CHANGED
package/src/loop.d.ts
CHANGED
|
@@ -167,7 +167,7 @@ export class Loop {
|
|
|
167
167
|
* thunk is re-evaluated each round (D4/eval-assist F2) so a tool set that grows mid-run — e.g. a skill
|
|
168
168
|
* unlocking its tools — is offered on the next round; a static array is resolved once at wire time.
|
|
169
169
|
* @param {Record<string, any>} [options={}] - Per-run overrides (system, temperature, ctx, etc.).
|
|
170
|
-
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, malformedToolCall?:
|
|
170
|
+
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, malformedToolCall?: GenerateResult['malformedToolCall'], temperatureDropped?: boolean}>}
|
|
171
171
|
* On halt the returned `error` is `halt:<rule>` (or `halt:unknown` if the
|
|
172
172
|
* thrown HaltError carried no `rule`), and `msgs` is sanitized so any
|
|
173
173
|
* dangling assistant `tool_calls` from the halted round are paired with
|
|
@@ -205,12 +205,7 @@ export class Loop {
|
|
|
205
205
|
model: string | null;
|
|
206
206
|
msgs: Message[];
|
|
207
207
|
metrics: RunMetrics;
|
|
208
|
-
malformedToolCall?:
|
|
209
|
-
name: string | undefined;
|
|
210
|
-
error: string;
|
|
211
|
-
rawArguments?: string;
|
|
212
|
-
rawTruncated?: true;
|
|
213
|
-
};
|
|
208
|
+
malformedToolCall?: GenerateResult["malformedToolCall"];
|
|
214
209
|
temperatureDropped?: boolean;
|
|
215
210
|
}>;
|
|
216
211
|
_warnedTruncated: boolean | undefined;
|
|
@@ -240,7 +235,7 @@ export class Loop {
|
|
|
240
235
|
* @param {string} text - User message.
|
|
241
236
|
* @param {ToolDef[]} [tools=[]] - Tool definitions.
|
|
242
237
|
* @param {Record<string, any>} [options={}] - Per-run overrides.
|
|
243
|
-
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, malformedToolCall?:
|
|
238
|
+
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, malformedToolCall?: GenerateResult['malformedToolCall'], temperatureDropped?: boolean}>}
|
|
244
239
|
*/
|
|
245
240
|
chat(text: string, tools?: ToolDef[], options?: Record<string, any>): Promise<{
|
|
246
241
|
text: string;
|
|
@@ -252,12 +247,7 @@ export class Loop {
|
|
|
252
247
|
model: string | null;
|
|
253
248
|
msgs: Message[];
|
|
254
249
|
metrics: RunMetrics;
|
|
255
|
-
malformedToolCall?:
|
|
256
|
-
name: string | undefined;
|
|
257
|
-
error: string;
|
|
258
|
-
rawArguments?: string;
|
|
259
|
-
rawTruncated?: true;
|
|
260
|
-
};
|
|
250
|
+
malformedToolCall?: GenerateResult["malformedToolCall"];
|
|
261
251
|
temperatureDropped?: boolean;
|
|
262
252
|
}>;
|
|
263
253
|
/**
|
package/src/loop.js
CHANGED
|
@@ -455,7 +455,7 @@ class Loop {
|
|
|
455
455
|
* thunk is re-evaluated each round (D4/eval-assist F2) so a tool set that grows mid-run — e.g. a skill
|
|
456
456
|
* unlocking its tools — is offered on the next round; a static array is resolved once at wire time.
|
|
457
457
|
* @param {Record<string, any>} [options={}] - Per-run overrides (system, temperature, ctx, etc.).
|
|
458
|
-
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, malformedToolCall?:
|
|
458
|
+
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, malformedToolCall?: GenerateResult['malformedToolCall'], temperatureDropped?: boolean}>}
|
|
459
459
|
* On halt the returned `error` is `halt:<rule>` (or `halt:unknown` if the
|
|
460
460
|
* thrown HaltError carried no `rule`), and `msgs` is sanitized so any
|
|
461
461
|
* dangling assistant `tool_calls` from the halted round are paired with
|
|
@@ -1328,7 +1328,7 @@ class Loop {
|
|
|
1328
1328
|
* @param {string} text - User message.
|
|
1329
1329
|
* @param {ToolDef[]} [tools=[]] - Tool definitions.
|
|
1330
1330
|
* @param {Record<string, any>} [options={}] - Per-run overrides.
|
|
1331
|
-
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, malformedToolCall?:
|
|
1331
|
+
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, malformedToolCall?: GenerateResult['malformedToolCall'], temperatureDropped?: boolean}>}
|
|
1332
1332
|
*/
|
|
1333
1333
|
async chat(text, tools = [], options = {}) {
|
|
1334
1334
|
this._history.push({ role: 'user', content: text });
|
package/src/provider-ollama.d.ts
CHANGED
|
@@ -15,8 +15,8 @@ export type OllamaOptions = {
|
|
|
15
15
|
deadlineMs?: number | undefined;
|
|
16
16
|
/**
|
|
17
17
|
* - BA-27 follow-up: when a tool call's string-shaped
|
|
18
|
-
* `function.arguments` JSON fails to parse, also attach the raw string (verbatim,
|
|
19
|
-
*
|
|
18
|
+
* `function.arguments` JSON fails to parse, also attach the FULL raw string (verbatim, uncapped)
|
|
19
|
+
* to `malformedToolCall.rawArguments` (model-generated; may be large or sensitive). Off by default,
|
|
20
20
|
* independent of `exposeErrorBody`. See `src/provider-toolcalls.js`.
|
|
21
21
|
*/
|
|
22
22
|
exposeMalformedArgs?: boolean | undefined;
|
|
@@ -32,8 +32,8 @@ export type OllamaOptions = {
|
|
|
32
32
|
* @property {number} [timeoutMs=600000] - BA-18: request/idle timeout in ms. Bounds a silent or never-answering socket on inactivity so `generate()` rejects with a retryable `TimeoutError` (`code: 'ETIMEDOUT'`, `context.bound: 'idle'`) instead of hanging until the OS TCP timeout. `0`/`Infinity` disables it. Overridable per call via `generate(..., { timeoutMs })`. (A local Ollama that is loading a large model cold can be slow to first byte — raise this or disable it for very large local models.)
|
|
33
33
|
* @property {number} [deadlineMs=0] - BA-19: TOTAL call-duration deadline in ms, beside `timeoutMs`. The idle bound resets on any socket activity, so a response that trickles a byte forever never trips it and hangs for hours. This is an absolute, non-resetting ceiling; on trip, `generate()` rejects with a TERMINAL `TimeoutError` (`code: 'EDEADLINE'`, `context.bound: 'deadline'`, `retryable: false`). DISABLED by default; `0`/`Infinity` disable. Overridable per call via `generate(..., { deadlineMs })`. (A cold large-model load is a legitimate long single call — leave this disabled or set it generously for such models.)
|
|
34
34
|
* @property {boolean} [exposeMalformedArgs=false] - BA-27 follow-up: when a tool call's string-shaped
|
|
35
|
-
* `function.arguments` JSON fails to parse, also attach the raw string (verbatim,
|
|
36
|
-
*
|
|
35
|
+
* `function.arguments` JSON fails to parse, also attach the FULL raw string (verbatim, uncapped)
|
|
36
|
+
* to `malformedToolCall.rawArguments` (model-generated; may be large or sensitive). Off by default,
|
|
37
37
|
* independent of `exposeErrorBody`. See `src/provider-toolcalls.js`.
|
|
38
38
|
*/
|
|
39
39
|
export class OllamaProvider {
|
package/src/provider-ollama.js
CHANGED
|
@@ -23,8 +23,8 @@ const OLLAMA_USAGE_KEYS = ['prompt_eval_count', 'eval_count'];
|
|
|
23
23
|
* @property {number} [timeoutMs=600000] - BA-18: request/idle timeout in ms. Bounds a silent or never-answering socket on inactivity so `generate()` rejects with a retryable `TimeoutError` (`code: 'ETIMEDOUT'`, `context.bound: 'idle'`) instead of hanging until the OS TCP timeout. `0`/`Infinity` disables it. Overridable per call via `generate(..., { timeoutMs })`. (A local Ollama that is loading a large model cold can be slow to first byte — raise this or disable it for very large local models.)
|
|
24
24
|
* @property {number} [deadlineMs=0] - BA-19: TOTAL call-duration deadline in ms, beside `timeoutMs`. The idle bound resets on any socket activity, so a response that trickles a byte forever never trips it and hangs for hours. This is an absolute, non-resetting ceiling; on trip, `generate()` rejects with a TERMINAL `TimeoutError` (`code: 'EDEADLINE'`, `context.bound: 'deadline'`, `retryable: false`). DISABLED by default; `0`/`Infinity` disable. Overridable per call via `generate(..., { deadlineMs })`. (A cold large-model load is a legitimate long single call — leave this disabled or set it generously for such models.)
|
|
25
25
|
* @property {boolean} [exposeMalformedArgs=false] - BA-27 follow-up: when a tool call's string-shaped
|
|
26
|
-
* `function.arguments` JSON fails to parse, also attach the raw string (verbatim,
|
|
27
|
-
*
|
|
26
|
+
* `function.arguments` JSON fails to parse, also attach the FULL raw string (verbatim, uncapped)
|
|
27
|
+
* to `malformedToolCall.rawArguments` (model-generated; may be large or sensitive). Off by default,
|
|
28
28
|
* independent of `exposeErrorBody`. See `src/provider-toolcalls.js`.
|
|
29
29
|
*/
|
|
30
30
|
|
package/src/provider-openai.d.ts
CHANGED
|
@@ -40,10 +40,9 @@ export type OpenAIOptions = {
|
|
|
40
40
|
legacyMaxTokens?: boolean | undefined;
|
|
41
41
|
/**
|
|
42
42
|
* - BA-27 follow-up: when a tool call's `function.arguments`
|
|
43
|
-
* JSON fails to parse, also attach the raw string (verbatim,
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* unexpected string into logs by default" reasoning applies. See `src/provider-toolcalls.js`.
|
|
43
|
+
* JSON fails to parse, also attach the FULL raw string (verbatim, uncapped) to `malformedToolCall.rawArguments`.
|
|
44
|
+
* Off by default, independent of `exposeErrorBody` — the raw blob is model-generated and may be large
|
|
45
|
+
* or sensitive, so it only reaches logs/audit rows if the caller opts in. See `src/provider-toolcalls.js`.
|
|
47
46
|
*/
|
|
48
47
|
exposeMalformedArgs?: boolean | undefined;
|
|
49
48
|
/**
|
|
@@ -86,10 +85,9 @@ export type OpenAIOptions = {
|
|
|
86
85
|
* Set `true` for an OpenAI-compatible server that only understands the legacy key (e.g. some
|
|
87
86
|
* self-hosted / proxy endpoints). No model-name sniffing — the caller declares the dialect.
|
|
88
87
|
* @property {boolean} [exposeMalformedArgs=false] - BA-27 follow-up: when a tool call's `function.arguments`
|
|
89
|
-
* JSON fails to parse, also attach the raw string (verbatim,
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* unexpected string into logs by default" reasoning applies. See `src/provider-toolcalls.js`.
|
|
88
|
+
* JSON fails to parse, also attach the FULL raw string (verbatim, uncapped) to `malformedToolCall.rawArguments`.
|
|
89
|
+
* Off by default, independent of `exposeErrorBody` — the raw blob is model-generated and may be large
|
|
90
|
+
* or sensitive, so it only reaches logs/audit rows if the caller opts in. See `src/provider-toolcalls.js`.
|
|
93
91
|
* @property {any} [thinking] - Opt-in `thinking` request field (fwdloop DeepSeek ask — extends BA-7(b)'s Anthropic option), forwarded to `body.thinking`
|
|
94
92
|
* VERBATIM and unvalidated — e.g. `{ type: 'disabled' }`. Generalises the Anthropic provider's option to
|
|
95
93
|
* OpenAI-compatible endpoints. Motivating case: DeepSeek's OpenAI-compatible API (`baseUrl`
|
package/src/provider-openai.js
CHANGED
|
@@ -73,10 +73,9 @@ function isLoopbackHost(hostname) {
|
|
|
73
73
|
* Set `true` for an OpenAI-compatible server that only understands the legacy key (e.g. some
|
|
74
74
|
* self-hosted / proxy endpoints). No model-name sniffing — the caller declares the dialect.
|
|
75
75
|
* @property {boolean} [exposeMalformedArgs=false] - BA-27 follow-up: when a tool call's `function.arguments`
|
|
76
|
-
* JSON fails to parse, also attach the raw string (verbatim,
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* unexpected string into logs by default" reasoning applies. See `src/provider-toolcalls.js`.
|
|
76
|
+
* JSON fails to parse, also attach the FULL raw string (verbatim, uncapped) to `malformedToolCall.rawArguments`.
|
|
77
|
+
* Off by default, independent of `exposeErrorBody` — the raw blob is model-generated and may be large
|
|
78
|
+
* or sensitive, so it only reaches logs/audit rows if the caller opts in. See `src/provider-toolcalls.js`.
|
|
80
79
|
* @property {any} [thinking] - Opt-in `thinking` request field (fwdloop DeepSeek ask — extends BA-7(b)'s Anthropic option), forwarded to `body.thinking`
|
|
81
80
|
* VERBATIM and unvalidated — e.g. `{ type: 'disabled' }`. Generalises the Anthropic provider's option to
|
|
82
81
|
* OpenAI-compatible endpoints. Motivating case: DeepSeek's OpenAI-compatible API (`baseUrl`
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type ToolCall = import("../types").ToolCall;
|
|
2
|
+
/** @typedef {import('../types').ToolCall} ToolCall */
|
|
2
3
|
/**
|
|
3
4
|
* BA-27 — parse a round's raw tool calls into the neutral {@link ToolCall} shape WITHOUT throwing on
|
|
4
5
|
* malformed arguments.
|
|
@@ -16,17 +17,18 @@ export type ToolCall = import("../types").ToolCall;
|
|
|
16
17
|
* running half a decomposed intent, so one bad call voids the whole round's calls.
|
|
17
18
|
*
|
|
18
19
|
* `opts.exposeMalformedArgs` (default off, mirrors `exposeErrorBody`): when on, the marker also
|
|
19
|
-
* carries `rawArguments` — the raw `function.arguments` VERBATIM,
|
|
20
|
-
*
|
|
20
|
+
* carries `rawArguments` — the FULL raw `function.arguments` string VERBATIM, uncapped (malformed JSON
|
|
21
|
+
* usually breaks at the END — a stray trailing brace, a truncation — so a head-only cap hid exactly the
|
|
22
|
+
* broken part; `rawTruncated` is never set). Non-string raw arguments (e.g. Ollama's well-formed object
|
|
21
23
|
* case reaching this catch some other way) are never exposed — only a string is a model-generated
|
|
22
24
|
* blob worth surfacing for debugging; an object was already structurally valid before whatever else
|
|
23
|
-
* threw.
|
|
24
|
-
*
|
|
25
|
+
* threw. Still opt-in and off by default: the raw text is model-generated and may be large or carry
|
|
26
|
+
* sensitive content into logs/audit rows, so the caller must choose to accept that.
|
|
25
27
|
*
|
|
26
28
|
* @param {any[]} rawToolCalls - provider-native tool-call entries (may be undefined/empty)
|
|
27
29
|
* @param {(tc: any) => ToolCall} mapOne - maps one raw entry to a ToolCall; MAY throw on bad arguments
|
|
28
30
|
* @param {{ exposeMalformedArgs?: boolean }} [opts]
|
|
29
|
-
* @returns {{ toolCalls: ToolCall[], malformedToolCall?: { name: string|undefined, error: string, rawArguments?: string
|
|
31
|
+
* @returns {{ toolCalls: ToolCall[], malformedToolCall?: { name: string|undefined, error: string, rawArguments?: string } }}
|
|
30
32
|
*/
|
|
31
33
|
export function parseToolCalls(rawToolCalls: any[], mapOne: (tc: any) => ToolCall, opts?: {
|
|
32
34
|
exposeMalformedArgs?: boolean;
|
|
@@ -36,6 +38,5 @@ export function parseToolCalls(rawToolCalls: any[], mapOne: (tc: any) => ToolCal
|
|
|
36
38
|
name: string | undefined;
|
|
37
39
|
error: string;
|
|
38
40
|
rawArguments?: string;
|
|
39
|
-
rawTruncated?: true;
|
|
40
41
|
};
|
|
41
42
|
};
|
|
@@ -2,11 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
/** @typedef {import('../types').ToolCall} ToolCall */
|
|
4
4
|
|
|
5
|
-
// Raw-arguments exposure cap. Matches the sibling opt-in error-body exposure pattern
|
|
6
|
-
// (`exposeErrorBody`) elsewhere in the providers: off by default, and even when on, bounded —
|
|
7
|
-
// an arbitrarily long model-generated string reaching a log/receipt unbounded is its own risk.
|
|
8
|
-
const RAW_ARGUMENTS_MAX_CHARS = 500;
|
|
9
|
-
|
|
10
5
|
/**
|
|
11
6
|
* BA-27 — parse a round's raw tool calls into the neutral {@link ToolCall} shape WITHOUT throwing on
|
|
12
7
|
* malformed arguments.
|
|
@@ -24,17 +19,18 @@ const RAW_ARGUMENTS_MAX_CHARS = 500;
|
|
|
24
19
|
* running half a decomposed intent, so one bad call voids the whole round's calls.
|
|
25
20
|
*
|
|
26
21
|
* `opts.exposeMalformedArgs` (default off, mirrors `exposeErrorBody`): when on, the marker also
|
|
27
|
-
* carries `rawArguments` — the raw `function.arguments` VERBATIM,
|
|
28
|
-
*
|
|
22
|
+
* carries `rawArguments` — the FULL raw `function.arguments` string VERBATIM, uncapped (malformed JSON
|
|
23
|
+
* usually breaks at the END — a stray trailing brace, a truncation — so a head-only cap hid exactly the
|
|
24
|
+
* broken part; `rawTruncated` is never set). Non-string raw arguments (e.g. Ollama's well-formed object
|
|
29
25
|
* case reaching this catch some other way) are never exposed — only a string is a model-generated
|
|
30
26
|
* blob worth surfacing for debugging; an object was already structurally valid before whatever else
|
|
31
|
-
* threw.
|
|
32
|
-
*
|
|
27
|
+
* threw. Still opt-in and off by default: the raw text is model-generated and may be large or carry
|
|
28
|
+
* sensitive content into logs/audit rows, so the caller must choose to accept that.
|
|
33
29
|
*
|
|
34
30
|
* @param {any[]} rawToolCalls - provider-native tool-call entries (may be undefined/empty)
|
|
35
31
|
* @param {(tc: any) => ToolCall} mapOne - maps one raw entry to a ToolCall; MAY throw on bad arguments
|
|
36
32
|
* @param {{ exposeMalformedArgs?: boolean }} [opts]
|
|
37
|
-
* @returns {{ toolCalls: ToolCall[], malformedToolCall?: { name: string|undefined, error: string, rawArguments?: string
|
|
33
|
+
* @returns {{ toolCalls: ToolCall[], malformedToolCall?: { name: string|undefined, error: string, rawArguments?: string } }}
|
|
38
34
|
*/
|
|
39
35
|
function parseToolCalls(rawToolCalls, mapOne, opts = {}) {
|
|
40
36
|
const raw = rawToolCalls || [];
|
|
@@ -44,7 +40,7 @@ function parseToolCalls(rawToolCalls, mapOne, opts = {}) {
|
|
|
44
40
|
try {
|
|
45
41
|
toolCalls.push(mapOne(tc));
|
|
46
42
|
} catch (e) {
|
|
47
|
-
/** @type {{ name: string|undefined, error: string, rawArguments?: string
|
|
43
|
+
/** @type {{ name: string|undefined, error: string, rawArguments?: string }} */
|
|
48
44
|
const malformedToolCall = {
|
|
49
45
|
name: tc && tc.function ? tc.function.name : undefined,
|
|
50
46
|
error: e instanceof Error ? e.message : String(e),
|
|
@@ -52,8 +48,7 @@ function parseToolCalls(rawToolCalls, mapOne, opts = {}) {
|
|
|
52
48
|
if (opts.exposeMalformedArgs) {
|
|
53
49
|
const rawArgs = tc && tc.function ? tc.function.arguments : undefined;
|
|
54
50
|
if (typeof rawArgs === 'string') {
|
|
55
|
-
malformedToolCall.rawArguments = rawArgs
|
|
56
|
-
if (rawArgs.length > RAW_ARGUMENTS_MAX_CHARS) malformedToolCall.rawTruncated = true;
|
|
51
|
+
malformedToolCall.rawArguments = rawArgs;
|
|
57
52
|
}
|
|
58
53
|
}
|
|
59
54
|
return { toolCalls: [], malformedToolCall };
|
package/types/index.d.ts
CHANGED
|
@@ -143,12 +143,15 @@ export interface GenerateResult {
|
|
|
143
143
|
* repaired. Absent on a clean round. OpenAI-compatible + Ollama string-arguments only; Anthropic
|
|
144
144
|
* arrives pre-parsed and cannot hit this.
|
|
145
145
|
*
|
|
146
|
-
* `rawArguments
|
|
146
|
+
* `rawArguments` (BA-27 follow-up) is present only when the provider was
|
|
147
147
|
* constructed with `exposeMalformedArgs: true` (default off, mirrors `exposeErrorBody`) AND the
|
|
148
|
-
* raw `function.arguments` was a string: the raw value verbatim,
|
|
149
|
-
*
|
|
148
|
+
* raw `function.arguments` was a string: the FULL raw value verbatim, uncapped (model-generated; may be
|
|
149
|
+
* large or sensitive).
|
|
150
150
|
*/
|
|
151
|
-
malformedToolCall?: { name: string | undefined; error: string; rawArguments?: string;
|
|
151
|
+
malformedToolCall?: { name: string | undefined; error: string; rawArguments?: string;
|
|
152
|
+
/** @deprecated never set since 0.50.0 (rawArguments is now uncapped) */
|
|
153
|
+
rawTruncated?: true;
|
|
154
|
+
};
|
|
152
155
|
/**
|
|
153
156
|
* BA-7 — provider-native content blocks the normalized `{text, toolCalls}` shape cannot express
|
|
154
157
|
* (Anthropic `thinking` / `redacted_thinking`), captured opaquely so the Loop can put them on the
|