bare-agent 0.51.0 → 0.52.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/README.md +1 -1
- package/bareagent.context.md +2 -2
- package/package.json +1 -1
- package/primitives.json +1 -1
- package/src/loop.d.ts +4 -2
- package/src/loop.js +30 -20
- package/src/provider-anthropic.d.ts +6 -7
- package/src/provider-anthropic.js +16 -11
- package/src/provider-clipipe.d.ts +11 -2
- package/src/provider-clipipe.js +22 -10
- package/src/provider-gemini.d.ts +6 -7
- package/src/provider-gemini.js +16 -11
- package/src/provider-ollama.d.ts +6 -7
- package/src/provider-ollama.js +16 -11
- package/src/provider-openai.d.ts +8 -9
- package/src/provider-openai.js +24 -17
- package/src/provider-temperature.d.ts +2 -2
- package/src/provider-temperature.js +5 -3
- package/src/thinking-mode.d.ts +75 -26
- package/src/thinking-mode.js +130 -71
- package/types/index.d.ts +16 -6
package/src/thinking-mode.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Normalized `thinkingMode: 'on' | 'off'` (hamr-approved design:
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// Normalized `thinkingMode: 'on' | 'off'` (hamr-approved design: callers don't care about providers —
|
|
4
|
+
// try the known shapes, ALWAYS run the call, and REPORT the outcome as `thinkingStatus`). "Not
|
|
5
|
+
// supported" is an ANSWER, not an error: the call runs without our field and says so. Callers say
|
|
6
|
+
// "on"/"off"; this module owns the per-provider/per-model request field, the retry ladder, the measured
|
|
7
|
+
// per-model facts, and the post-reply signal readers. The provider files stay thin.
|
|
7
8
|
//
|
|
8
9
|
// Every fact in MODELS below was MEASURED live (poc/thinking-mode-verify.mjs →
|
|
9
10
|
// poc/results/thinking-mode-verify.json, 2026-10-11) EXCEPT Ollama, which is shape-known but not
|
|
10
11
|
// live-verified. A model that is not in the table is 'unverified': the ladder tries the shapes at
|
|
11
|
-
// runtime
|
|
12
|
+
// runtime; if they all fail with a client validation error, ONE plain request decides (success ⇒ 'unsupported', failure ⇒ that error is thrown).
|
|
12
13
|
//
|
|
13
14
|
// The raw `thinking` passthrough options (BA-7) are a DIFFERENT, untouched field: opaque and verbatim.
|
|
14
|
-
// `thinkingMode` is the normalized switch; setting both at once is a ValidationError (no silent precedence).
|
|
15
|
+
// `thinkingMode` is the normalized switch; setting both at once is a ValidationError (a caller config mistake, no silent precedence).
|
|
15
16
|
|
|
16
17
|
const { ValidationError } = require('./errors');
|
|
17
18
|
|
|
@@ -72,8 +73,8 @@ const LADDERS = {
|
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
/**
|
|
75
|
-
* MEASURED per-model facts. Value `null` ⇒ the mode is known NOT switchable on that model (
|
|
76
|
-
*
|
|
76
|
+
* MEASURED per-model facts. Value `null` ⇒ the mode is known NOT switchable on that model (one plain
|
|
77
|
+
* request is sent with no thinking field; `thinkingStatus:'unsupported'`). A string ⇒ the first shape that worked (tried first).
|
|
77
78
|
* @type {Record<LadderKind, Record<string, { off: string|null, on: string|null }>>}
|
|
78
79
|
*/
|
|
79
80
|
const MODELS = {
|
|
@@ -108,20 +109,8 @@ const MODELS = {
|
|
|
108
109
|
ollama: {},
|
|
109
110
|
};
|
|
110
111
|
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
// say "thinking" elsewhere in a different vendor's vocabulary can't trigger a retry. No `.*`, no
|
|
114
|
-
// unbounded quantifiers (ReDoS-safe on provider-supplied text).
|
|
115
|
-
/** @type {Record<LadderKind, RegExp>} */
|
|
116
|
-
const FIELD_NAMED = {
|
|
117
|
-
anthropic: /\bthinking\b/i,
|
|
118
|
-
openai: /\b(reasoning_effort|reasoning)\b/i,
|
|
119
|
-
deepseek: /\bthinking\b/i,
|
|
120
|
-
gemini: /\b(thinkingBudget|thinkingConfig|thinking_budget|thinking_config|thinking|budget)\b/i,
|
|
121
|
-
ollama: /\b(thinking|think)\b/i,
|
|
122
|
-
};
|
|
123
|
-
// A 400 that names `temperature` is the temperature fallback's business (e.g. Anthropic's "temperature
|
|
124
|
-
// may only be set to 1 when thinking is enabled" names BOTH) — never a shape rejection.
|
|
112
|
+
// An error that names `temperature` is the temperature fallback's business (e.g. Anthropic's "temperature
|
|
113
|
+
// may only be set to 1 when thinking is enabled" names BOTH) — never a shape failure; it propagates to that path.
|
|
125
114
|
const TEMPERATURE_NAMED = /temperature/i;
|
|
126
115
|
|
|
127
116
|
/**
|
|
@@ -194,62 +183,82 @@ function resolveThinkingMode({ label, instanceMode, options, instanceThinking =
|
|
|
194
183
|
}
|
|
195
184
|
|
|
196
185
|
/**
|
|
197
|
-
*
|
|
186
|
+
* A client validation error (400, or 422 — FastAPI/pydantic servers use it for extra_forbidden) that is
|
|
187
|
+
* not the temperature fallback's. NO error-text word list decides anything beyond the temperature
|
|
188
|
+
* exclusion: whether the thinking field was the problem is decided by a plain retry, not by reading prose.
|
|
198
189
|
* @param {any} err
|
|
199
190
|
* @returns {boolean}
|
|
200
191
|
*/
|
|
201
|
-
function
|
|
202
|
-
|
|
203
|
-
return
|
|
192
|
+
function isClientValidationError(err) {
|
|
193
|
+
if (!err || (err.status !== 400 && err.status !== 422)) return false;
|
|
194
|
+
return !TEMPERATURE_NAMED.test(typeof err.message === 'string' ? err.message : '');
|
|
204
195
|
}
|
|
205
196
|
|
|
206
197
|
/**
|
|
207
|
-
* Send a request with the `thinkingMode` field applied
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
198
|
+
* Send a request with the `thinkingMode` field applied. Order: the instance cache (a shape that already
|
|
199
|
+
* worked for this mode+model), else the table's known-good shape, else the ladder's first; then the
|
|
200
|
+
* remaining shapes in ladder order (a shape whose apply() returns null is skipped).
|
|
201
|
+
*
|
|
202
|
+
* When a shape request fails with a client validation error (400/422, not temperature-named), the next
|
|
203
|
+
* shape is tried; after the last, ONE plain request (no thinking field) decides: it succeeds ⇒ the
|
|
204
|
+
* setting was the problem (`track.unsupported = true`, result kept); it fails too ⇒ it was a real error
|
|
205
|
+
* and the PLAIN request's error is thrown as-is (it is the one without our field). Any other failure
|
|
206
|
+
* (5xx, 429, timeout, transport, 401/403/404) propagates immediately — no ladder.
|
|
207
|
+
*
|
|
208
|
+
* A model the table marks not switchable gets ONE plain request, `unsupported`. Only the winning shape
|
|
209
|
+
* is cached; "unsupported" is never cached (a runtime unsupported cache poisoned on body-dependent
|
|
210
|
+
* skips and transcript/signature errors), so an unknown model that cannot switch re-pays its refused
|
|
211
|
+
* shape requests on every call.
|
|
212
212
|
*
|
|
213
213
|
* @param {object} p
|
|
214
214
|
* @param {LadderKind} p.kind
|
|
215
215
|
* @param {'on'|'off'} p.mode
|
|
216
216
|
* @param {string} p.model
|
|
217
|
-
* @param {string} p.label - provider class name
|
|
218
217
|
* @param {Record<string, any>} p.body - the base body (NOT mutated; each attempt gets a patched copy)
|
|
219
218
|
* @param {(body: Record<string, any>) => Promise<any>} p.request
|
|
220
|
-
* @param {Map<string, string>} p.cache - per provider instance
|
|
221
|
-
* @
|
|
219
|
+
* @param {Map<string, string>} p.cache - per provider instance; holds winning shape keys only
|
|
220
|
+
* @param {{ unsupported: boolean }} p.track - written: `unsupported` is set true when the call ran without our field
|
|
221
|
+
* @returns {Promise<any>} the reply data
|
|
222
222
|
*/
|
|
223
|
-
async function requestWithThinkingMode({ kind, mode, model,
|
|
223
|
+
async function requestWithThinkingMode({ kind, mode, model, body, request, cache, track }) {
|
|
224
224
|
const ladder = LADDERS[kind][mode];
|
|
225
225
|
const facts = lookupModel(kind, model);
|
|
226
|
+
const cacheKey = `${mode}|${model}`;
|
|
227
|
+
track.unsupported = false;
|
|
226
228
|
if (facts && facts[mode] === null) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
track.unsupported = true;
|
|
230
|
+
return request(body);
|
|
229
231
|
}
|
|
230
|
-
const cacheKey = `${mode}|${model}`;
|
|
231
232
|
const preferredKey = cache.get(cacheKey) || (facts && facts[mode]) || null;
|
|
232
233
|
const ordered = [...ladder].sort((a, b) => (a.key === preferredKey ? -1 : b.key === preferredKey ? 1 : 0));
|
|
233
234
|
|
|
234
|
-
/** @type {string[]} */
|
|
235
|
-
const tried = [];
|
|
236
|
-
let lastMessage = '';
|
|
237
235
|
for (const s of ordered) {
|
|
238
236
|
const patched = s.apply(body);
|
|
239
|
-
if (patched === null)
|
|
237
|
+
if (patched === null) continue; // shape cannot apply to this body (e.g. max_tokens too small)
|
|
240
238
|
try {
|
|
241
239
|
const data = await request(patched);
|
|
242
240
|
cache.set(cacheKey, s.key);
|
|
243
241
|
return data;
|
|
244
242
|
} catch (err) {
|
|
245
|
-
if (!
|
|
246
|
-
tried.push(s.id);
|
|
247
|
-
lastMessage = err.message;
|
|
243
|
+
if (!isClientValidationError(err)) throw err;
|
|
248
244
|
}
|
|
249
245
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
246
|
+
// Every shape refused (or none applied): one plain request decides. Success ⇒ the setting was the
|
|
247
|
+
// problem; failure ⇒ a real error, thrown as the plain request's own.
|
|
248
|
+
const data = await request(body);
|
|
249
|
+
track.unsupported = true;
|
|
250
|
+
return data;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* The DeepSeek body for thinking-ON + a forced tool choice: thinking-on is impossible there (the API
|
|
255
|
+
* 400s), so the request goes out with `thinking:{type:'disabled'}` — the only shape that works with a
|
|
256
|
+
* forced tool_choice (measured) — and the caller reports `unsupported`.
|
|
257
|
+
* @param {Record<string, any>} body
|
|
258
|
+
* @returns {Record<string, any>}
|
|
259
|
+
*/
|
|
260
|
+
function deepseekDisabledBody(body) {
|
|
261
|
+
return LADDERS.deepseek.off[0].apply(body) || body;
|
|
253
262
|
}
|
|
254
263
|
|
|
255
264
|
/** @param {any} v @returns {boolean} */
|
|
@@ -298,39 +307,82 @@ function readThinkingSignal(kind, data) {
|
|
|
298
307
|
}
|
|
299
308
|
}
|
|
300
309
|
|
|
310
|
+
/** @typedef {'applied'|'unsupported'|'ignored'|'unknown'} ThinkingStatus */
|
|
311
|
+
|
|
312
|
+
/** @type {Record<ThinkingStatus, boolean|null>} */
|
|
313
|
+
const APPLIED_OF = { applied: true, ignored: false, unsupported: false, unknown: null };
|
|
314
|
+
|
|
301
315
|
/**
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
|
|
316
|
+
* Derive the legacy `thinkingApplied` boolean from a status (applied⇒true, ignored/unsupported⇒false, unknown⇒null).
|
|
317
|
+
* @param {ThinkingStatus} status
|
|
318
|
+
* @returns {boolean|null}
|
|
319
|
+
*/
|
|
320
|
+
function appliedOf(status) {
|
|
321
|
+
return APPLIED_OF[status];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Warn ONCE per provider instance when the requested mode did not (fully) take effect.
|
|
326
|
+
* @param {object} p
|
|
327
|
+
* @param {any} p.owner - provider instance (holds the warn-once flag)
|
|
328
|
+
* @param {string} p.label
|
|
329
|
+
* @param {string} p.model
|
|
330
|
+
* @param {'on'|'off'} p.mode
|
|
331
|
+
* @param {ThinkingStatus} p.status
|
|
332
|
+
* @param {string} [p.reason] - 'forced-tool': the request was sent with thinking explicitly disabled
|
|
333
|
+
*/
|
|
334
|
+
function warnThinkingStatus({ owner, label, model, mode, status, reason }) {
|
|
335
|
+
if (status !== 'unsupported' && status !== 'ignored') return;
|
|
336
|
+
// One once-flag PER status: an 'unsupported' warn must not silence a later, more serious 'ignored'.
|
|
337
|
+
const flag = status === 'ignored' ? '_warnedThinkingIgnored' : '_warnedThinkingUnsupported';
|
|
338
|
+
if (owner[flag]) return;
|
|
339
|
+
owner[flag] = true;
|
|
340
|
+
const why = status === 'unsupported' && reason === 'forced-tool'
|
|
341
|
+
? 'thinking can\'t be on with a forced tool choice — the call ran with thinking disabled'
|
|
342
|
+
: status === 'unsupported'
|
|
343
|
+
? 'this provider/model cannot do the requested mode — the call ran WITHOUT a thinking field (the model\'s default behavior)'
|
|
344
|
+
: 'the switch was accepted but the reply shows thinking happened anyway';
|
|
345
|
+
console.warn(`[${label}] thinkingMode '${mode}' for ${model}: thinkingStatus '${status}' — ${why}. The result is kept. Further '${status}' occurrences from this provider instance are silent.`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* The outcome of a thinkingMode call: `{thinkingStatus, thinkingApplied}` ready to spread onto a
|
|
350
|
+
* GenerateResult. `unsupported` ⇒ the call ran without our field. Otherwise 'on' ⇒ 'applied' (an
|
|
351
|
+
* adaptive model may legitimately choose not to think, so 'on' is never flagged); 'off' ⇒ 'applied'
|
|
352
|
+
* when the reply shows zero thinking, 'ignored' when thinking happened anyway, 'unknown' when the
|
|
353
|
+
* signal is unreadable. Warns ONCE per `owner` on unsupported/ignored and never throws.
|
|
306
354
|
* @param {object} p
|
|
307
355
|
* @param {LadderKind} p.kind
|
|
308
356
|
* @param {'on'|'off'} p.mode
|
|
309
357
|
* @param {any} p.data
|
|
310
|
-
* @param {
|
|
358
|
+
* @param {boolean} [p.unsupported]
|
|
359
|
+
* @param {string} [p.reason] - why it is unsupported, when the wording differs (see warnThinkingStatus)
|
|
360
|
+
* @param {any} p.owner
|
|
311
361
|
* @param {string} p.label
|
|
312
362
|
* @param {string} p.model
|
|
313
|
-
* @returns {boolean|null}
|
|
363
|
+
* @returns {{ thinkingStatus: ThinkingStatus, thinkingApplied: boolean|null }}
|
|
314
364
|
*/
|
|
315
|
-
function
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
365
|
+
function assessThinking({ kind, mode, data, unsupported, reason, owner, label, model }) {
|
|
366
|
+
/** @type {ThinkingStatus} */
|
|
367
|
+
let status;
|
|
368
|
+
if (unsupported) status = 'unsupported';
|
|
369
|
+
else if (mode === 'on') status = 'applied';
|
|
370
|
+
else {
|
|
371
|
+
const signal = readThinkingSignal(kind, data);
|
|
372
|
+
status = signal === 'none' ? 'applied' : signal === 'thinking' ? 'ignored' : 'unknown';
|
|
323
373
|
}
|
|
324
|
-
|
|
374
|
+
warnThinkingStatus({ owner, label, model, mode, status, reason });
|
|
375
|
+
return { thinkingStatus: status, thinkingApplied: APPLIED_OF[status] };
|
|
325
376
|
}
|
|
326
377
|
|
|
327
378
|
/**
|
|
328
379
|
* What bare-agent knows about switching a model's thinking on/off, with no network. Measured models
|
|
329
380
|
* answer true/false; a model not in the measured table on a provider with a shape ladder answers
|
|
330
|
-
* 'unverified' (the ladder will try the shapes at runtime
|
|
381
|
+
* 'unverified' (the ladder will try the shapes at runtime; if they fail with client validation errors a plain request decides: OK ⇒ thinkingStatus 'unsupported', failure ⇒ that error is thrown);
|
|
331
382
|
* clipipe and unknown providers answer false.
|
|
332
383
|
* @param {'anthropic'|'openai'|'deepseek'|'gemini'|'ollama'|'clipipe'|string} provider
|
|
333
384
|
* @param {string} [model]
|
|
385
|
+
* @param {{ baseUrl?: string }} [opts] - `baseUrl` only matters for provider 'openai': an OpenAI-wire endpoint on a DeepSeek host answers with DeepSeek's shapes, exactly as the request path does (same shared `isDeepSeek`). Omitted ⇒ model-id detection only. A non-object (incl. `null`) throws ValidationError (type only).
|
|
334
386
|
* @returns {{ switchable: boolean|'unverified', off: string|null, on: string|null, note: string }}
|
|
335
387
|
* @when you need to know, before building a request, whether a provider/model can have thinking turned on or off and which request field does it — pure lookup, no network
|
|
336
388
|
* @fails returns switchable:false for clipipe and unknown providers (never throws); 'unverified' means not measured, not unsupported; Ollama's shapes are not live-verified.
|
|
@@ -338,14 +390,19 @@ function assessThinkingApplied({ kind, mode, data, owner, label, model }) {
|
|
|
338
390
|
* @example
|
|
339
391
|
* thinkingSupport('anthropic', 'claude-opus-5-5'); // { switchable: false, off: null, on: "thinking:{type:'adaptive'}", note: '...' }
|
|
340
392
|
*/
|
|
341
|
-
function thinkingSupport(provider, model) {
|
|
393
|
+
function thinkingSupport(provider, model, opts) {
|
|
394
|
+
if (opts !== undefined && (opts === null || typeof opts !== 'object' || Array.isArray(opts))) {
|
|
395
|
+
throw invalid(`thinkingSupport: opts must be an object (got ${opts === null ? 'null' : Array.isArray(opts) ? 'array' : typeof opts})`);
|
|
396
|
+
}
|
|
397
|
+
const baseUrl = opts && opts.baseUrl;
|
|
342
398
|
if (provider === 'clipipe') {
|
|
343
|
-
return { switchable: false, off: null, on: null, note: 'CLIPipe has no thinking-mode mechanism; thinkingMode
|
|
399
|
+
return { switchable: false, off: null, on: null, note: 'CLIPipe has no thinking-mode mechanism; thinkingMode runs the call unchanged and reports thinkingStatus \'unsupported\'.' };
|
|
344
400
|
}
|
|
345
|
-
const kind = /** @type {LadderKind} */ (provider);
|
|
346
401
|
if (typeof provider !== 'string' || !Object.prototype.hasOwnProperty.call(LADDERS, provider)) {
|
|
347
402
|
return { switchable: false, off: null, on: null, note: `Unknown provider '${String(provider)}'.` };
|
|
348
403
|
}
|
|
404
|
+
// The request path detects DeepSeek by model id OR baseUrl host (isDeepSeek) and sends DeepSeek's shape — the lookup must too, with the same call.
|
|
405
|
+
const kind = /** @type {LadderKind} */ (provider === 'openai' && isDeepSeek(model, baseUrl) ? 'deepseek' : provider);
|
|
349
406
|
const idOf = (/** @type {'on'|'off'} */ mode, /** @type {string|null} */ key) =>
|
|
350
407
|
key === null ? null : (LADDERS[kind][mode].find((s) => s.key === key) || LADDERS[kind][mode][0]).id;
|
|
351
408
|
const facts = lookupModel(kind, /** @type {string} */ (model));
|
|
@@ -356,7 +413,7 @@ function thinkingSupport(provider, model) {
|
|
|
356
413
|
on: LADDERS[kind].on[0].id,
|
|
357
414
|
note: kind === 'ollama'
|
|
358
415
|
? 'Shapes known (think:true/false) but NOT live-verified; the reply signal is message.thinking.'
|
|
359
|
-
: `Model not in the measured table; the shape ladder is tried at runtime (off: ${LADDERS[kind].off.map((s) => s.id).join(' → ')}; on: ${LADDERS[kind].on.map((s) => s.id).join(' → ')}) and
|
|
416
|
+
: `Model not in the measured table; the shape ladder is tried at runtime (off: ${LADDERS[kind].off.map((s) => s.id).join(' → ')}; on: ${LADDERS[kind].on.map((s) => s.id).join(' → ')}) and, if they fail with client validation errors, one plain request decides (OK ⇒ thinkingStatus 'unsupported'; failure ⇒ that error is thrown).`,
|
|
360
417
|
};
|
|
361
418
|
}
|
|
362
419
|
const switchable = facts.off !== null && facts.on !== null;
|
|
@@ -366,7 +423,7 @@ function thinkingSupport(provider, model) {
|
|
|
366
423
|
on: idOf('on', facts.on),
|
|
367
424
|
note: switchable
|
|
368
425
|
? 'Measured: both modes work.'
|
|
369
|
-
: `Measured: ${facts.off === null ? "'off'" : "'on'"} is rejected by the API for this model — requesting it
|
|
426
|
+
: `Measured: ${facts.off === null ? "'off'" : "'on'"} is rejected by the API for this model — requesting it sends the call without a thinking field and reports thinkingStatus 'unsupported'.`,
|
|
370
427
|
};
|
|
371
428
|
}
|
|
372
429
|
|
|
@@ -376,9 +433,11 @@ module.exports = {
|
|
|
376
433
|
normalizeThinkingMode,
|
|
377
434
|
resolveThinkingMode,
|
|
378
435
|
requestWithThinkingMode,
|
|
379
|
-
|
|
436
|
+
assessThinking,
|
|
437
|
+
appliedOf,
|
|
438
|
+
warnThinkingStatus,
|
|
439
|
+
deepseekDisabledBody,
|
|
380
440
|
readThinkingSignal,
|
|
381
|
-
isShapeRejection,
|
|
382
441
|
isDeepSeek,
|
|
383
442
|
lookupModel,
|
|
384
443
|
};
|
package/types/index.d.ts
CHANGED
|
@@ -135,12 +135,22 @@ export interface GenerateResult {
|
|
|
135
135
|
*/
|
|
136
136
|
temperatureDropped?: boolean;
|
|
137
137
|
/**
|
|
138
|
-
* Present ONLY when `thinkingMode` ('on'|'off') was set on the provider/call. The
|
|
139
|
-
*
|
|
140
|
-
* `
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
138
|
+
* Present ONLY when `thinkingMode` ('on'|'off') was set on the provider/call. The outcome — "not
|
|
139
|
+
* supported" is an ANSWER, never an error; the call always runs:
|
|
140
|
+
* `'applied'` — a shape was accepted and, for 'off', the reply shows zero thinking (for 'on' always
|
|
141
|
+
* `'applied'` once accepted: an adaptive model may legitimately choose not to think);
|
|
142
|
+
* `'unsupported'` — this provider/model cannot do the requested mode (measured, every shape rejected,
|
|
143
|
+
* DeepSeek 'on' + forced toolChoice, or CLIPipe): the call ran WITHOUT our thinking field (the model's
|
|
144
|
+
* default behavior; DeepSeek forced-tool case sends `thinking:{type:'disabled'}`);
|
|
145
|
+
* `'ignored'` — 'off' was sent and accepted but the reply shows thinking anyway (warns once; round kept);
|
|
146
|
+
* `'unknown'` — accepted but the reply's thinking signal is unreadable (e.g. OpenAI without `reasoning_tokens`).
|
|
147
|
+
* `'ignored'`/`'unsupported'` warn once per provider instance. `Loop.run()` surfaces the WORST status
|
|
148
|
+
* across rounds (ignored > unsupported > unknown > applied).
|
|
149
|
+
*/
|
|
150
|
+
thinkingStatus?: 'applied' | 'unsupported' | 'ignored' | 'unknown';
|
|
151
|
+
/**
|
|
152
|
+
* Legacy boolean, DERIVED from `thinkingStatus` (present with it): applied⇒`true`, ignored⇒`false`,
|
|
153
|
+
* unsupported⇒`false`, unknown⇒`null`. Prefer `thinkingStatus`. On `Loop.run()` it is derived from the worst status.
|
|
144
154
|
*/
|
|
145
155
|
thinkingApplied?: boolean | null;
|
|
146
156
|
/**
|