bare-agent 0.48.1 → 0.49.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 +8 -0
- package/package.json +1 -1
- package/src/provider-anthropic.d.ts +4 -0
- package/src/provider-anthropic.js +2 -0
- package/src/provider-fallback.d.ts +5 -0
- package/src/provider-fallback.js +5 -0
- package/src/provider-openai.d.ts +25 -1
- package/src/provider-openai.js +19 -1
package/bareagent.context.md
CHANGED
|
@@ -505,6 +505,14 @@ The blocks now ride the transcript on `Message.providerBlocks` (`{provider, mode
|
|
|
505
505
|
|
|
506
506
|
`AnthropicProvider({ thinking })` is a separate, opt-in knob, forwarded to `body.thinking` verbatim (e.g. `{type:'adaptive', display:'summarized'}` to surface the reasoning; the default `display` is `'omitted'`). **It does not "turn thinking on"** — it's already on. Use it to pin the mode or reach `display`/`effort`. It is passed through unvalidated on purpose: `budget_tokens` was removed from the API and now **400s** on sonnet-5/Opus 4.7+, so a library that reshaped this parameter would need a release every time Anthropic moved.
|
|
507
507
|
|
|
508
|
+
`OpenAIProvider({ thinking })` is the OpenAI-compatible counterpart: same verbatim/unvalidated forwarding to `body.thinking`, overridable per call (`loop.run(msgs, tools, { thinking })` / `generate(..., { thinking })`), and per-call `null` suppresses the constructor default. Needed for DeepSeek, which runs thinking mode by default and 400s "Thinking mode does not support this tool_choice" on a forced `toolChoice`:
|
|
509
|
+
|
|
510
|
+
```js
|
|
511
|
+
new OpenAIProvider({ baseUrl: 'https://api.deepseek.com', model: 'deepseek-flash', legacyMaxTokens: true, thinking: { type: 'disabled' } })
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
> **Fallback caveat:** per-call options (incl. `thinking`) are forwarded to EVERY provider in a `FallbackProvider` chain. For a mixed-vendor chain set `thinking` on each provider's constructor, not per-call.
|
|
515
|
+
|
|
508
516
|
> **Honesty note, and we mean it.** This is a **protocol/data-loss fix, not a capability fix.** A head-to-head with thinking fully preserved vs. stock bare-agent produced **indistinguishable** outcomes. Do not adopt 0.27 expecting better reasoning — you will not get it, and we have the measurement.
|
|
509
517
|
|
|
510
518
|
Legacy `wrapTool` / `wrapTools` are retained as deprecation shims (one-shot console warning, removal in 1.0). Migration: replace `wrapTools(tools)` at `loop.run()` with `filterTools(tools)` once upfront + `onLlmResult` / `onToolResult` on `new Loop({...})` to pick up LLM-cost recording and `_ctx` threading.
|
package/package.json
CHANGED
|
@@ -24,6 +24,8 @@ export type AnthropicOptions = {
|
|
|
24
24
|
cacheMessages?: boolean | undefined;
|
|
25
25
|
/**
|
|
26
26
|
* - Opt-in extended thinking (BA-7), forwarded to `body.thinking` VERBATIM and unvalidated — e.g. `{ type: 'adaptive' }`, or `{ type: 'adaptive', display: 'summarized' }` to surface the reasoning (the default `display` is `'omitted'`). Deliberately opaque: this parameter has already broken once (`budget_tokens` was removed and now 400s on `claude-sonnet-5` / Opus 4.7+), and a library that reshapes it would need a release every time the API moves. Overridable per call via `generate(..., { thinking })`; pass `null` there to suppress an instance default.
|
|
27
|
+
* WARNING: per-call options (incl. `thinking`) are forwarded to EVERY provider in a `FallbackProvider` chain — for a mixed-vendor
|
|
28
|
+
* chain set `thinking` on each provider's CONSTRUCTOR instead of per-call.
|
|
27
29
|
*
|
|
28
30
|
* **MEASURED CAVEAT — this option does not "turn thinking on".** On `claude-sonnet-5` adaptive thinking is ALREADY the default: sending this changed the observed thinking rate not at all (2/10 rounds with it vs 3/10 without — `poc/ba7-adaptive-default.mjs`). Its real use is pinning the mode and reaching `display`/`effort`. The change that mattered is that thinking blocks are now PRESERVED and replayed (see `Message.providerBlocks`), which happens whether or not you ever set this.
|
|
29
31
|
*/
|
|
@@ -52,6 +54,8 @@ export type AnthropicOptions = {
|
|
|
52
54
|
* @property {boolean} [cacheSystem=false] - Opt-in prompt caching: send the system prompt with a `cache_control` breakpoint so Anthropic caches it. Anthropic does NOT auto-cache, so without this its cache tiers are always 0. Overridable per call via `generate(..., { cacheSystem })`. NOTE: on its own this rarely helps a tool loop — Anthropic's minimum cacheable prefix is 1024–4096 tokens (model-dependent) and a typical system persona is a few hundred, so it silently never caches. The transcript is where a tool loop's tokens actually live — see `cacheMessages`.
|
|
53
55
|
* @property {boolean} [cacheMessages=false] - Opt-in TRANSCRIPT caching (BA-1): roll a `cache_control` breakpoint onto the last content block of the last message, so Anthropic caches the whole conversation prefix and the loop stops re-buying it at full price every round. In a tool loop the transcript IS the tool results (file bodies from `shell_read`) and it always ENDS on one, which `_toAnthropicMessage` rebuilds from scratch — so no caller-side seam (`assemble` included) can reach it, and this has to live in the provider. Measured on `claude-sonnet-5` with a ~15k-token tool-result transcript (`poc/ba1-message-caching.mjs`): steady state **$0.0753 → $0.0110 per round, 6.8x cheaper**; round 1 pays a 1.25x cache WRITE once. Off by default — it changes the wire format, so adopters opt in. Overridable per call via `generate(..., { cacheMessages })`. **Interaction:** a destructive `trim`/stash fold that rewrites the transcript PREFIX invalidates the cache (the prefix is the cache key), so a fold must keep the head stable or you re-pay the write premium every round for nothing.
|
|
54
56
|
* @property {any} [thinking] - Opt-in extended thinking (BA-7), forwarded to `body.thinking` VERBATIM and unvalidated — e.g. `{ type: 'adaptive' }`, or `{ type: 'adaptive', display: 'summarized' }` to surface the reasoning (the default `display` is `'omitted'`). Deliberately opaque: this parameter has already broken once (`budget_tokens` was removed and now 400s on `claude-sonnet-5` / Opus 4.7+), and a library that reshapes it would need a release every time the API moves. Overridable per call via `generate(..., { thinking })`; pass `null` there to suppress an instance default.
|
|
57
|
+
* WARNING: per-call options (incl. `thinking`) are forwarded to EVERY provider in a `FallbackProvider` chain — for a mixed-vendor
|
|
58
|
+
* chain set `thinking` on each provider's CONSTRUCTOR instead of per-call.
|
|
55
59
|
*
|
|
56
60
|
* **MEASURED CAVEAT — this option does not "turn thinking on".** On `claude-sonnet-5` adaptive thinking is ALREADY the default: sending this changed the observed thinking rate not at all (2/10 rounds with it vs 3/10 without — `poc/ba7-adaptive-default.mjs`). Its real use is pinning the mode and reaching `display`/`effort`. The change that mattered is that thinking blocks are now PRESERVED and replayed (see `Message.providerBlocks`), which happens whether or not you ever set this.
|
|
57
61
|
* @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).
|
|
@@ -30,6 +30,8 @@ function isLoopbackHost(hostname) {
|
|
|
30
30
|
* @property {boolean} [cacheSystem=false] - Opt-in prompt caching: send the system prompt with a `cache_control` breakpoint so Anthropic caches it. Anthropic does NOT auto-cache, so without this its cache tiers are always 0. Overridable per call via `generate(..., { cacheSystem })`. NOTE: on its own this rarely helps a tool loop — Anthropic's minimum cacheable prefix is 1024–4096 tokens (model-dependent) and a typical system persona is a few hundred, so it silently never caches. The transcript is where a tool loop's tokens actually live — see `cacheMessages`.
|
|
31
31
|
* @property {boolean} [cacheMessages=false] - Opt-in TRANSCRIPT caching (BA-1): roll a `cache_control` breakpoint onto the last content block of the last message, so Anthropic caches the whole conversation prefix and the loop stops re-buying it at full price every round. In a tool loop the transcript IS the tool results (file bodies from `shell_read`) and it always ENDS on one, which `_toAnthropicMessage` rebuilds from scratch — so no caller-side seam (`assemble` included) can reach it, and this has to live in the provider. Measured on `claude-sonnet-5` with a ~15k-token tool-result transcript (`poc/ba1-message-caching.mjs`): steady state **$0.0753 → $0.0110 per round, 6.8x cheaper**; round 1 pays a 1.25x cache WRITE once. Off by default — it changes the wire format, so adopters opt in. Overridable per call via `generate(..., { cacheMessages })`. **Interaction:** a destructive `trim`/stash fold that rewrites the transcript PREFIX invalidates the cache (the prefix is the cache key), so a fold must keep the head stable or you re-pay the write premium every round for nothing.
|
|
32
32
|
* @property {any} [thinking] - Opt-in extended thinking (BA-7), forwarded to `body.thinking` VERBATIM and unvalidated — e.g. `{ type: 'adaptive' }`, or `{ type: 'adaptive', display: 'summarized' }` to surface the reasoning (the default `display` is `'omitted'`). Deliberately opaque: this parameter has already broken once (`budget_tokens` was removed and now 400s on `claude-sonnet-5` / Opus 4.7+), and a library that reshapes it would need a release every time the API moves. Overridable per call via `generate(..., { thinking })`; pass `null` there to suppress an instance default.
|
|
33
|
+
* WARNING: per-call options (incl. `thinking`) are forwarded to EVERY provider in a `FallbackProvider` chain — for a mixed-vendor
|
|
34
|
+
* chain set `thinking` on each provider's CONSTRUCTOR instead of per-call.
|
|
33
35
|
*
|
|
34
36
|
* **MEASURED CAVEAT — this option does not "turn thinking on".** On `claude-sonnet-5` adaptive thinking is ALREADY the default: sending this changed the observed thinking rate not at all (2/10 rounds with it vs 3/10 without — `poc/ba7-adaptive-default.mjs`). Its real use is pinning the mode and reaching `display`/`effort`. The change that mattered is that thinking blocks are now PRESERVED and replayed (see `Message.providerBlocks`), which happens whether or not you ever set this.
|
|
35
37
|
* @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).
|
|
@@ -21,6 +21,11 @@ export type FallbackOptions = {
|
|
|
21
21
|
* @property {(error: any, index: number) => boolean} [shouldFallback] - Return false to stop.
|
|
22
22
|
* @property {(error: any, fromIndex: number, toIndex: number) => void} [onFallback] - Callback.
|
|
23
23
|
*/
|
|
24
|
+
/**
|
|
25
|
+
* NOTE: per-call `generate()` options (incl. `thinking`, `toolChoice`) are forwarded UNCHANGED to EVERY provider in the chain. For a
|
|
26
|
+
* mixed-vendor chain (e.g. OpenAI-compat DeepSeek + Anthropic) set `thinking` on each provider's CONSTRUCTOR instead of per-call,
|
|
27
|
+
* or a value shaped for one vendor lands in another's `body.thinking` on fallback.
|
|
28
|
+
*/
|
|
24
29
|
export class FallbackProvider {
|
|
25
30
|
/**
|
|
26
31
|
* Provider that tries multiple providers in order.
|
package/src/provider-fallback.js
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* @property {(error: any, fromIndex: number, toIndex: number) => void} [onFallback] - Callback.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* NOTE: per-call `generate()` options (incl. `thinking`, `toolChoice`) are forwarded UNCHANGED to EVERY provider in the chain. For a
|
|
16
|
+
* mixed-vendor chain (e.g. OpenAI-compat DeepSeek + Anthropic) set `thinking` on each provider's CONSTRUCTOR instead of per-call,
|
|
17
|
+
* or a value shaped for one vendor lands in another's `body.thinking` on fallback.
|
|
18
|
+
*/
|
|
14
19
|
class FallbackProvider {
|
|
15
20
|
/**
|
|
16
21
|
* Provider that tries multiple providers in order.
|
package/src/provider-openai.d.ts
CHANGED
|
@@ -46,6 +46,19 @@ export type OpenAIOptions = {
|
|
|
46
46
|
* unexpected string into logs by default" reasoning applies. See `src/provider-toolcalls.js`.
|
|
47
47
|
*/
|
|
48
48
|
exposeMalformedArgs?: boolean | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* - Opt-in `thinking` request field (fwdloop DeepSeek ask — extends BA-7(b)'s Anthropic option), forwarded to `body.thinking`
|
|
51
|
+
* VERBATIM and unvalidated — e.g. `{ type: 'disabled' }`. Generalises the Anthropic provider's option to
|
|
52
|
+
* OpenAI-compatible endpoints. Motivating case: DeepSeek's OpenAI-compatible API (`baseUrl`
|
|
53
|
+
* `https://api.deepseek.com`, model `deepseek-flash`) runs thinking mode by default and 400s "Thinking mode
|
|
54
|
+
* does not support this tool_choice" on a named or `'required'` `toolChoice`; `{ type: 'disabled' }` makes the
|
|
55
|
+
* forced tool call return 200. Deliberately opaque (no model sniffing, no free-form extra-body field) — a
|
|
56
|
+
* library that reshapes it would need a release every time a vendor moves. Unset ⇒ request body unchanged.
|
|
57
|
+
* Overridable per call via `generate(..., { thinking })`; pass `null` there to suppress an instance default.
|
|
58
|
+
* WARNING: per-call options (incl. `thinking`) are forwarded to EVERY provider in a `FallbackProvider` chain — for a mixed-vendor
|
|
59
|
+
* chain set `thinking` on each provider's CONSTRUCTOR instead of per-call.
|
|
60
|
+
*/
|
|
61
|
+
thinking?: any;
|
|
49
62
|
};
|
|
50
63
|
/**
|
|
51
64
|
* @typedef {object} OpenAIOptions
|
|
@@ -77,6 +90,16 @@ export type OpenAIOptions = {
|
|
|
77
90
|
* when longer) to `malformedToolCall.rawArguments`. Off by default, independent of `exposeErrorBody` —
|
|
78
91
|
* the raw blob is model-generated, not an upstream error body, but the same "don't leak an unbounded
|
|
79
92
|
* unexpected string into logs by default" reasoning applies. See `src/provider-toolcalls.js`.
|
|
93
|
+
* @property {any} [thinking] - Opt-in `thinking` request field (fwdloop DeepSeek ask — extends BA-7(b)'s Anthropic option), forwarded to `body.thinking`
|
|
94
|
+
* VERBATIM and unvalidated — e.g. `{ type: 'disabled' }`. Generalises the Anthropic provider's option to
|
|
95
|
+
* OpenAI-compatible endpoints. Motivating case: DeepSeek's OpenAI-compatible API (`baseUrl`
|
|
96
|
+
* `https://api.deepseek.com`, model `deepseek-flash`) runs thinking mode by default and 400s "Thinking mode
|
|
97
|
+
* does not support this tool_choice" on a named or `'required'` `toolChoice`; `{ type: 'disabled' }` makes the
|
|
98
|
+
* forced tool call return 200. Deliberately opaque (no model sniffing, no free-form extra-body field) — a
|
|
99
|
+
* library that reshapes it would need a release every time a vendor moves. Unset ⇒ request body unchanged.
|
|
100
|
+
* Overridable per call via `generate(..., { thinking })`; pass `null` there to suppress an instance default.
|
|
101
|
+
* WARNING: per-call options (incl. `thinking`) are forwarded to EVERY provider in a `FallbackProvider` chain — for a mixed-vendor
|
|
102
|
+
* chain set `thinking` on each provider's CONSTRUCTOR instead of per-call.
|
|
80
103
|
*/
|
|
81
104
|
export class OpenAIProvider {
|
|
82
105
|
/**
|
|
@@ -95,11 +118,12 @@ export class OpenAIProvider {
|
|
|
95
118
|
deadlineMs: number | undefined;
|
|
96
119
|
legacyMaxTokens: boolean;
|
|
97
120
|
exposeMalformedArgs: boolean;
|
|
121
|
+
thinking: any;
|
|
98
122
|
/**
|
|
99
123
|
* Generate a response from the OpenAI API.
|
|
100
124
|
* @param {Message[]} messages - Conversation messages.
|
|
101
125
|
* @param {ToolDef[]} [tools=[]] - Tool definitions.
|
|
102
|
-
* @param {Record<string, any>} [options={}] - Options (temperature, maxTokens, timeoutMs — a per-call override of the constructor's `timeoutMs`, see BA-18; deadlineMs — a per-call override of the constructor's `deadlineMs`, see BA-19; toolChoice — `'auto'` | `'required'` | `{ name }`, forwarded as OpenAI `tool_choice`, applied only when `tools` are present).
|
|
126
|
+
* @param {Record<string, any>} [options={}] - Options (temperature, maxTokens, timeoutMs — a per-call override of the constructor's `timeoutMs`, see BA-18; deadlineMs — a per-call override of the constructor's `deadlineMs`, see BA-19; toolChoice — `'auto'` | `'required'` | `{ name }`, forwarded as OpenAI `tool_choice`, applied only when `tools` are present; thinking — per-call override of the constructor's `thinking`, `null` suppresses an instance default, see BA-7 (b)).
|
|
103
127
|
* @returns {Promise<GenerateResult>}
|
|
104
128
|
* @throws {Error} `[OpenAIProvider] ...` — on HTTP errors (4xx/5xx) or invalid JSON response.
|
|
105
129
|
*/
|
package/src/provider-openai.js
CHANGED
|
@@ -77,6 +77,16 @@ function isLoopbackHost(hostname) {
|
|
|
77
77
|
* when longer) to `malformedToolCall.rawArguments`. Off by default, independent of `exposeErrorBody` —
|
|
78
78
|
* the raw blob is model-generated, not an upstream error body, but the same "don't leak an unbounded
|
|
79
79
|
* unexpected string into logs by default" reasoning applies. See `src/provider-toolcalls.js`.
|
|
80
|
+
* @property {any} [thinking] - Opt-in `thinking` request field (fwdloop DeepSeek ask — extends BA-7(b)'s Anthropic option), forwarded to `body.thinking`
|
|
81
|
+
* VERBATIM and unvalidated — e.g. `{ type: 'disabled' }`. Generalises the Anthropic provider's option to
|
|
82
|
+
* OpenAI-compatible endpoints. Motivating case: DeepSeek's OpenAI-compatible API (`baseUrl`
|
|
83
|
+
* `https://api.deepseek.com`, model `deepseek-flash`) runs thinking mode by default and 400s "Thinking mode
|
|
84
|
+
* does not support this tool_choice" on a named or `'required'` `toolChoice`; `{ type: 'disabled' }` makes the
|
|
85
|
+
* forced tool call return 200. Deliberately opaque (no model sniffing, no free-form extra-body field) — a
|
|
86
|
+
* library that reshapes it would need a release every time a vendor moves. Unset ⇒ request body unchanged.
|
|
87
|
+
* Overridable per call via `generate(..., { thinking })`; pass `null` there to suppress an instance default.
|
|
88
|
+
* WARNING: per-call options (incl. `thinking`) are forwarded to EVERY provider in a `FallbackProvider` chain — for a mixed-vendor
|
|
89
|
+
* chain set `thinking` on each provider's CONSTRUCTOR instead of per-call.
|
|
80
90
|
*/
|
|
81
91
|
|
|
82
92
|
class OpenAIProvider {
|
|
@@ -100,13 +110,16 @@ class OpenAIProvider {
|
|
|
100
110
|
this.legacyMaxTokens = options.legacyMaxTokens === true;
|
|
101
111
|
// BA-27 follow-up: expose the raw malformed tool-call arguments string. Off by default.
|
|
102
112
|
this.exposeMalformedArgs = options.exposeMalformedArgs === true;
|
|
113
|
+
// BA-7 (b) / fwdloop: forwarded to `body.thinking` VERBATIM — unvalidated, un-reshaped (mirrors the
|
|
114
|
+
// Anthropic provider). Null/unset ⇒ no `thinking` key, byte-identical to the pre-option body.
|
|
115
|
+
this.thinking = options.thinking != null ? options.thinking : null;
|
|
103
116
|
}
|
|
104
117
|
|
|
105
118
|
/**
|
|
106
119
|
* Generate a response from the OpenAI API.
|
|
107
120
|
* @param {Message[]} messages - Conversation messages.
|
|
108
121
|
* @param {ToolDef[]} [tools=[]] - Tool definitions.
|
|
109
|
-
* @param {Record<string, any>} [options={}] - Options (temperature, maxTokens, timeoutMs — a per-call override of the constructor's `timeoutMs`, see BA-18; deadlineMs — a per-call override of the constructor's `deadlineMs`, see BA-19; toolChoice — `'auto'` | `'required'` | `{ name }`, forwarded as OpenAI `tool_choice`, applied only when `tools` are present).
|
|
122
|
+
* @param {Record<string, any>} [options={}] - Options (temperature, maxTokens, timeoutMs — a per-call override of the constructor's `timeoutMs`, see BA-18; deadlineMs — a per-call override of the constructor's `deadlineMs`, see BA-19; toolChoice — `'auto'` | `'required'` | `{ name }`, forwarded as OpenAI `tool_choice`, applied only when `tools` are present; thinking — per-call override of the constructor's `thinking`, `null` suppresses an instance default, see BA-7 (b)).
|
|
110
123
|
* @returns {Promise<GenerateResult>}
|
|
111
124
|
* @throws {Error} `[OpenAIProvider] ...` — on HTTP errors (4xx/5xx) or invalid JSON response.
|
|
112
125
|
*/
|
|
@@ -121,6 +134,11 @@ class OpenAIProvider {
|
|
|
121
134
|
...(options.temperature != null && { temperature: options.temperature }),
|
|
122
135
|
...(options.maxTokens && { [maxTokensKey]: options.maxTokens }),
|
|
123
136
|
};
|
|
137
|
+
// BA-7 (b) / fwdloop: DeepSeek-style compat endpoints default to thinking mode and 400 on a forced
|
|
138
|
+
// tool_choice; the caller pins it (e.g. `{type:'disabled'}`). Set on `body` before the temperature
|
|
139
|
+
// fallback so the retry (which only deletes `temperature`) keeps it. Per-call `null` suppresses.
|
|
140
|
+
const thinking = options.thinking !== undefined ? options.thinking : this.thinking;
|
|
141
|
+
if (thinking) body.thinking = thinking;
|
|
124
142
|
// Ask 4 (fwdloop): validate the toolChoice SHAPE unconditionally so an invalid value ALWAYS throws
|
|
125
143
|
// (a silently-dropped force is the exact confusion this surfaces) — even when tools happen to be
|
|
126
144
|
// empty. Attach it only when tools are present: OpenAI 400s on a tool_choice with no tools, so a
|