@jxburros/ai-nugget 0.6.0 → 0.7.3
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 +92 -3
- package/UPGRADING.md +43 -1
- package/dist/adapters/engines/anthropic.js +98 -10
- package/dist/adapters/engines/google.js +8 -1
- package/dist/adapters/engines/ollama.js +6 -0
- package/dist/adapters/engines/openaiChat.d.ts +6 -0
- package/dist/adapters/engines/openaiChat.js +51 -1
- package/dist/agent/loop.d.ts +3 -1
- package/dist/agent/loop.js +4 -1
- package/dist/cjs/adapters/engines/anthropic.js +96 -8
- package/dist/cjs/adapters/engines/google.js +7 -0
- package/dist/cjs/adapters/engines/ollama.js +6 -0
- package/dist/cjs/adapters/engines/openaiChat.js +52 -1
- package/dist/cjs/agent/loop.js +4 -1
- package/dist/cjs/handler.js +28 -3
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/reasoning.js +159 -0
- package/dist/cjs/util.js +14 -0
- package/dist/handler.d.ts +6 -0
- package/dist/handler.js +28 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/reasoning.d.ts +45 -0
- package/dist/reasoning.js +154 -0
- package/dist/types.d.ts +25 -0
- package/dist/util.d.ts +7 -0
- package/dist/util.js +13 -0
- package/nugget/VERSION.txt +2 -2
- package/nugget/dist/adapters/engines/anthropic.d.ts.map +1 -1
- package/nugget/dist/adapters/engines/anthropic.js +98 -10
- package/nugget/dist/adapters/engines/anthropic.js.map +1 -1
- package/nugget/dist/adapters/engines/google.js +8 -1
- package/nugget/dist/adapters/engines/google.js.map +1 -1
- package/nugget/dist/adapters/engines/ollama.js +6 -0
- package/nugget/dist/adapters/engines/ollama.js.map +1 -1
- package/nugget/dist/adapters/engines/openaiChat.d.ts +6 -0
- package/nugget/dist/adapters/engines/openaiChat.d.ts.map +1 -1
- package/nugget/dist/adapters/engines/openaiChat.js +51 -1
- package/nugget/dist/adapters/engines/openaiChat.js.map +1 -1
- package/nugget/dist/agent/loop.d.ts +3 -1
- package/nugget/dist/agent/loop.d.ts.map +1 -1
- package/nugget/dist/agent/loop.js +4 -1
- package/nugget/dist/agent/loop.js.map +1 -1
- package/nugget/dist/cjs/adapters/engines/anthropic.js +96 -8
- package/nugget/dist/cjs/adapters/engines/google.js +7 -0
- package/nugget/dist/cjs/adapters/engines/ollama.js +6 -0
- package/nugget/dist/cjs/adapters/engines/openaiChat.js +52 -1
- package/nugget/dist/cjs/agent/loop.js +4 -1
- package/nugget/dist/cjs/handler.js +28 -3
- package/nugget/dist/cjs/index.js +1 -0
- package/nugget/dist/cjs/reasoning.js +159 -0
- package/nugget/dist/cjs/util.js +14 -0
- package/nugget/dist/handler.d.ts +6 -0
- package/nugget/dist/handler.d.ts.map +1 -1
- package/nugget/dist/handler.js +28 -3
- package/nugget/dist/handler.js.map +1 -1
- package/nugget/dist/index.d.ts +1 -0
- package/nugget/dist/index.d.ts.map +1 -1
- package/nugget/dist/index.js +1 -0
- package/nugget/dist/index.js.map +1 -1
- package/nugget/dist/reasoning.d.ts +45 -0
- package/nugget/dist/reasoning.d.ts.map +1 -0
- package/nugget/dist/reasoning.js +154 -0
- package/nugget/dist/reasoning.js.map +1 -0
- package/nugget/dist/types.d.ts +25 -0
- package/nugget/dist/types.d.ts.map +1 -1
- package/nugget/dist/types.js.map +1 -1
- package/nugget/dist/util.d.ts +7 -0
- package/nugget/dist/util.d.ts.map +1 -1
- package/nugget/dist/util.js +13 -0
- package/nugget/dist/util.js.map +1 -1
- package/nugget/src/adapters/engines/anthropic.ts +87 -10
- package/nugget/src/adapters/engines/google.ts +8 -1
- package/nugget/src/adapters/engines/ollama.ts +5 -0
- package/nugget/src/adapters/engines/openaiChat.ts +46 -1
- package/nugget/src/agent/loop.ts +7 -2
- package/nugget/src/handler.ts +30 -3
- package/nugget/src/index.ts +1 -0
- package/nugget/src/reasoning.ts +174 -0
- package/nugget/src/types.ts +27 -0
- package/nugget/src/util.ts +14 -0
- package/package.json +12 -5
|
@@ -26,7 +26,8 @@ class AnthropicAdapter {
|
|
|
26
26
|
}
|
|
27
27
|
async *stream(conn, req) {
|
|
28
28
|
const started = Date.now();
|
|
29
|
-
const
|
|
29
|
+
const nativeJson = modernModel(req.model);
|
|
30
|
+
const jsonMode = req.responseFormat?.type === 'json' && !nativeJson && !req.tools?.length;
|
|
30
31
|
let firstTokenMs = null;
|
|
31
32
|
let text = '';
|
|
32
33
|
let inputTokens;
|
|
@@ -34,6 +35,7 @@ class AnthropicAdapter {
|
|
|
34
35
|
let stopReason;
|
|
35
36
|
let sawTerminal = false;
|
|
36
37
|
const emittedTools = [];
|
|
38
|
+
const responseBlocks = new Map();
|
|
37
39
|
// Partial tool_use blocks keyed by content-block index (input_json_delta accumulation).
|
|
38
40
|
const blocks = new Map();
|
|
39
41
|
const timeout = (0, base_js_1.streamTimeout)(conn, req.signal);
|
|
@@ -41,19 +43,29 @@ class AnthropicAdapter {
|
|
|
41
43
|
// Anthropic JSON mode is implemented as a forced `json_output` tool, which
|
|
42
44
|
// cannot coexist with the caller's own tools — same silent-downgrade shape
|
|
43
45
|
// as Gemini, so it gets the same signal rather than quietly dropping.
|
|
44
|
-
if (req.responseFormat?.type === 'json' && req.tools?.length) {
|
|
46
|
+
if (req.responseFormat?.type === 'json' && !nativeJson && req.tools?.length) {
|
|
45
47
|
yield {
|
|
46
48
|
type: 'context',
|
|
47
49
|
kind: 'json_mode_downgraded',
|
|
48
50
|
data: { reason: 'Anthropic JSON mode uses a forced tool and cannot be combined with caller tools; the request was sent without JSON mode', provider: conn.provider },
|
|
49
51
|
};
|
|
50
52
|
}
|
|
53
|
+
if (req.responseFormat?.type === 'json' && nativeJson && !req.responseFormat.schema) {
|
|
54
|
+
yield { type: 'context', kind: 'json_mode_downgraded', data: { provider: conn.provider, reason: 'Claude native JSON outputs require a schema; using a JSON instruction without constrained decoding.' } };
|
|
55
|
+
}
|
|
51
56
|
try {
|
|
52
57
|
const res = await (0, transport_js_1.postResponse)(`${conn.baseUrl}/v1/messages`, body(req, jsonMode), conn.headers, timeout.signal, conn.provider);
|
|
53
58
|
const contentType = res.headers.get('content-type') ?? '';
|
|
54
59
|
if (!contentType.includes('text/event-stream')) {
|
|
55
60
|
const data = await res.json();
|
|
56
61
|
const parsed = parseResponse(data);
|
|
62
|
+
const content = (0, util_js_1.asRecord)(data)?.content;
|
|
63
|
+
if (Array.isArray(content))
|
|
64
|
+
content.forEach((block, i) => {
|
|
65
|
+
const record = (0, util_js_1.asRecord)(block);
|
|
66
|
+
if (record)
|
|
67
|
+
responseBlocks.set(i, record);
|
|
68
|
+
});
|
|
57
69
|
inputTokens = parsed.inputTokens;
|
|
58
70
|
outputTokens = parsed.outputTokens;
|
|
59
71
|
stopReason = parsed.stopReason;
|
|
@@ -80,7 +92,12 @@ class AnthropicAdapter {
|
|
|
80
92
|
if (!record)
|
|
81
93
|
continue;
|
|
82
94
|
const type = (0, util_js_1.asString)(record.type);
|
|
83
|
-
if (type === '
|
|
95
|
+
if (type === 'error') {
|
|
96
|
+
const error = (0, util_js_1.asRecord)(record.error);
|
|
97
|
+
const statuses = { overloaded_error: 529, rate_limit_error: 429, authentication_error: 401, permission_error: 403, invalid_request_error: 400, not_found_error: 404, api_error: 500, billing_error: 402, timeout_error: 504, request_too_large: 413 };
|
|
98
|
+
throw (0, errors_js_1.classify)(statuses[(0, util_js_1.asString)(error?.type) ?? ''] ?? 500, JSON.stringify(record), conn.provider);
|
|
99
|
+
}
|
|
100
|
+
else if (type === 'message_start') {
|
|
84
101
|
const usage = (0, util_js_1.asRecord)((0, util_js_1.asRecord)(record.message)?.usage);
|
|
85
102
|
inputTokens = (0, util_js_1.asNumber)(usage?.input_tokens) ?? inputTokens;
|
|
86
103
|
outputTokens = (0, util_js_1.asNumber)(usage?.output_tokens) ?? outputTokens;
|
|
@@ -88,6 +105,8 @@ class AnthropicAdapter {
|
|
|
88
105
|
else if (type === 'content_block_start') {
|
|
89
106
|
const index = (0, util_js_1.asNumber)(record.index) ?? 0;
|
|
90
107
|
const block = (0, util_js_1.asRecord)(record.content_block);
|
|
108
|
+
if (block)
|
|
109
|
+
responseBlocks.set(index, { ...block });
|
|
91
110
|
if (block?.type === 'tool_use') {
|
|
92
111
|
blocks.set(index, { id: (0, util_js_1.asString)(block.id) ?? (0, base_js_1.randomId)(), name: (0, util_js_1.asString)(block.name) ?? 'unknown', raw: '' });
|
|
93
112
|
}
|
|
@@ -95,6 +114,14 @@ class AnthropicAdapter {
|
|
|
95
114
|
else if (type === 'content_block_delta') {
|
|
96
115
|
const index = (0, util_js_1.asNumber)(record.index) ?? 0;
|
|
97
116
|
const delta = (0, util_js_1.asRecord)(record.delta);
|
|
117
|
+
const block = responseBlocks.get(index);
|
|
118
|
+
if (block && delta) {
|
|
119
|
+
const field = delta.type === 'text_delta' ? 'text' : delta.type === 'thinking_delta' ? 'thinking' : delta.type === 'signature_delta' ? 'signature' : undefined;
|
|
120
|
+
if (field)
|
|
121
|
+
block[field] = String(block[field] ?? '') + String(delta[field] ?? '');
|
|
122
|
+
if (delta.type === 'citations_delta')
|
|
123
|
+
block.citations = [...(Array.isArray(block.citations) ? block.citations : []), delta.citation];
|
|
124
|
+
}
|
|
98
125
|
if (delta?.type === 'text_delta') {
|
|
99
126
|
const piece = (0, util_js_1.asString)(delta.text) ?? '';
|
|
100
127
|
if (piece) {
|
|
@@ -121,6 +148,9 @@ class AnthropicAdapter {
|
|
|
121
148
|
const partial = blocks.get(index);
|
|
122
149
|
if (partial) {
|
|
123
150
|
const call = { id: partial.id, name: partial.name, raw: partial.raw, arguments: (0, base_js_1.parseArgs)(partial.raw) };
|
|
151
|
+
const block = responseBlocks.get(index);
|
|
152
|
+
if (block)
|
|
153
|
+
block.input = call.arguments;
|
|
124
154
|
blocks.delete(index);
|
|
125
155
|
if (jsonMode && call.name === JSON_MODE_TOOL) {
|
|
126
156
|
text = jsonTextFrom([call], text);
|
|
@@ -152,6 +182,7 @@ class AnthropicAdapter {
|
|
|
152
182
|
const hasTools = emittedTools.length > 0;
|
|
153
183
|
yield { type: 'done', result: {
|
|
154
184
|
text,
|
|
185
|
+
providerState: { provider: conn.provider, model: req.model, content: [...responseBlocks.entries()].sort(([a], [b]) => a - b).map(([, block]) => block) },
|
|
155
186
|
toolCalls: hasTools ? emittedTools : undefined,
|
|
156
187
|
finishReason: mapStop(stopReason, hasTools),
|
|
157
188
|
usage: inputTokens !== undefined || outputTokens !== undefined ? { inputTokens, outputTokens, estimated: false } : (0, tokens_js_1.estimatedUsage)((0, util_js_1.textFromMessages)(req.messages), text),
|
|
@@ -196,9 +227,15 @@ function body(req, jsonMode) {
|
|
|
196
227
|
top_p: req.topP,
|
|
197
228
|
stop_sequences: req.stopSequences,
|
|
198
229
|
system,
|
|
199
|
-
messages: toAnthropicMessages(req.messages.filter((m) => m.role !== 'system')),
|
|
230
|
+
messages: toAnthropicMessages(req.messages.filter((m) => m.role !== 'system'), req.model),
|
|
200
231
|
stream: true,
|
|
201
232
|
};
|
|
233
|
+
if (req.responseFormat?.type === 'json' && modernModel(req.model) && req.responseFormat.schema) {
|
|
234
|
+
base.output_config = { format: { type: 'json_schema', schema: schemaFor(req) } };
|
|
235
|
+
}
|
|
236
|
+
if (req.responseFormat?.type === 'json' && modernModel(req.model) && !req.responseFormat.schema) {
|
|
237
|
+
base.system = [system, 'Return only valid JSON, without markdown fences or a preamble.'].filter(Boolean).join('\n\n');
|
|
238
|
+
}
|
|
202
239
|
if (jsonMode) {
|
|
203
240
|
// Forced-tool JSON mode: Anthropic has no response_format, so we make the
|
|
204
241
|
// model answer through a single required tool whose input is the JSON we want.
|
|
@@ -214,16 +251,51 @@ function body(req, jsonMode) {
|
|
|
214
251
|
else if (req.toolChoice === 'none')
|
|
215
252
|
base.tool_choice = { type: 'none' };
|
|
216
253
|
}
|
|
254
|
+
if (req.reasoningEffort !== undefined) {
|
|
255
|
+
// First-class reasoning effort → extended thinking. Anthropic requires
|
|
256
|
+
// `budget_tokens < max_tokens` and rejects temperature/top_p alongside
|
|
257
|
+
// thinking, so the budget is fitted under max_tokens (raising max_tokens
|
|
258
|
+
// when the caller's value leaves no room) and the samplers are dropped.
|
|
259
|
+
if (modernModel(req.model)) {
|
|
260
|
+
const none = req.reasoningEffort === 'none';
|
|
261
|
+
if (none && alwaysThinking(req.model))
|
|
262
|
+
throw new errors_js_1.AIError('This Claude model cannot disable thinking; choose a reasoning effort instead.', { kind: 'invalid_request', provider: 'anthropic' });
|
|
263
|
+
base.thinking = { type: none ? (/^claude-sonnet-5-5(?:-|$)/.test(req.model) ? 'between_tools' : 'disabled') : 'adaptive' };
|
|
264
|
+
base.output_config = { ...(0, util_js_1.asRecord)(base.output_config), effort: none ? 'low' : req.reasoningEffort === 'minimal' ? 'low' : req.reasoningEffort };
|
|
265
|
+
}
|
|
266
|
+
else if (req.reasoningEffort === 'none') {
|
|
267
|
+
base.thinking = { type: 'disabled' };
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
const budget = util_js_1.REASONING_BUDGET_TOKENS[req.reasoningEffort];
|
|
271
|
+
const maxTokens = typeof base.max_tokens === 'number' ? base.max_tokens : 4096;
|
|
272
|
+
if (maxTokens <= budget)
|
|
273
|
+
base.max_tokens = budget + 1024;
|
|
274
|
+
base.thinking = { type: 'enabled', budget_tokens: budget };
|
|
275
|
+
delete base.temperature;
|
|
276
|
+
delete base.top_p;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
217
279
|
// providerOptions carries Anthropic-native fields (`thinking`, `metadata`,
|
|
218
280
|
// top-level `cache_control` extras, `service_tier`, …) without a release.
|
|
219
|
-
|
|
281
|
+
const result = (0, util_js_1.applyProviderOptions)(base, req.providerOptions, ['output_config']);
|
|
282
|
+
if (modernModel(req.model)) {
|
|
283
|
+
delete result.temperature;
|
|
284
|
+
delete result.top_p;
|
|
285
|
+
delete result.top_k;
|
|
286
|
+
}
|
|
287
|
+
const choice = (0, util_js_1.asRecord)(result.tool_choice);
|
|
288
|
+
if (noForcedTools(req.model) && (choice?.type === 'tool' || choice?.type === 'any')) {
|
|
289
|
+
throw new errors_js_1.AIError('This Claude model does not support forced tool choice; use auto or structured outputs.', { kind: 'invalid_request', provider: 'anthropic' });
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
220
292
|
}
|
|
221
|
-
function toAnthropicMessages(messages) {
|
|
293
|
+
function toAnthropicMessages(messages, model) {
|
|
222
294
|
const out = [];
|
|
223
295
|
for (let index = 0; index < messages.length; index += 1) {
|
|
224
296
|
const message = messages[index];
|
|
225
297
|
if (message.role !== 'tool') {
|
|
226
|
-
out.push(toAnthropicMessage(message));
|
|
298
|
+
out.push(toAnthropicMessage(message, model));
|
|
227
299
|
continue;
|
|
228
300
|
}
|
|
229
301
|
const content = [];
|
|
@@ -237,7 +309,10 @@ function toAnthropicMessages(messages) {
|
|
|
237
309
|
}
|
|
238
310
|
return out;
|
|
239
311
|
}
|
|
240
|
-
function toAnthropicMessage(m) {
|
|
312
|
+
function toAnthropicMessage(m, model) {
|
|
313
|
+
if (m.role === 'assistant' && m.providerState?.provider === 'anthropic' && m.providerState.model === model) {
|
|
314
|
+
return { role: 'assistant', content: m.providerState.content };
|
|
315
|
+
}
|
|
241
316
|
// Tool-role messages are handled by toAnthropicMessages' batching loop and
|
|
242
317
|
// never reach here, so no tool branch is needed.
|
|
243
318
|
// assistant turns that carried tool calls replay them as tool_use blocks,
|
|
@@ -302,7 +377,20 @@ function jsonTextFrom(toolCalls, fallback) {
|
|
|
302
377
|
function mapStop(stop, hasTools) {
|
|
303
378
|
if (stop === 'tool_use' && hasTools)
|
|
304
379
|
return 'tool';
|
|
380
|
+
if (stop === 'refusal')
|
|
381
|
+
return 'content_filter';
|
|
305
382
|
if (stop === 'max_tokens')
|
|
306
383
|
return 'length';
|
|
307
384
|
return 'stop';
|
|
308
385
|
}
|
|
386
|
+
/** Models released after Opus 4.6 use adaptive effort and reject sampling knobs.
|
|
387
|
+
* Unknown/custom IDs retain legacy behavior rather than guessing capabilities. */
|
|
388
|
+
function modernModel(model) {
|
|
389
|
+
return /^claude-(?:opus-(?:4-[78]|5(?:-5)?)|sonnet-5(?:-5)?|haiku-5-5|(?:fable|mythos)-5(?:-1)?|mythos-preview)(?:-\d{8}|-latest)?$/.test(model);
|
|
390
|
+
}
|
|
391
|
+
function alwaysThinking(model) {
|
|
392
|
+
return /^claude-(?:fable-|mythos-|opus-5-5(?:-|$))/.test(model);
|
|
393
|
+
}
|
|
394
|
+
function noForcedTools(model) {
|
|
395
|
+
return /^claude-(?:(?:opus|sonnet)-5-5(?:-|$)|(?:fable|mythos)-5-1(?:-|$))/.test(model);
|
|
396
|
+
}
|
|
@@ -188,6 +188,13 @@ function body(req) {
|
|
|
188
188
|
config.allowedFunctionNames = [req.toolChoice.name];
|
|
189
189
|
payload.toolConfig = { functionCallingConfig: config };
|
|
190
190
|
}
|
|
191
|
+
if (req.reasoningEffort !== undefined) {
|
|
192
|
+
// First-class reasoning effort → Gemini's thinking budget. 0 disables
|
|
193
|
+
// thinking on models that allow it; others treat it as a minimum.
|
|
194
|
+
payload.generationConfig.thinkingConfig = {
|
|
195
|
+
thinkingBudget: util_js_1.REASONING_BUDGET_TOKENS[req.reasoningEffort],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
191
198
|
// providerOptions reaches Google-native fields: top-level `safetySettings`,
|
|
192
199
|
// `cachedContent`, and `generationConfig` extras (`thinkingConfig`,
|
|
193
200
|
// `responseModalities`, …) merged one level deep into generationConfig.
|
|
@@ -176,6 +176,12 @@ function body(req) {
|
|
|
176
176
|
format: req.responseFormat?.type === 'json' ? (req.responseFormat.schema ?? 'json') : undefined,
|
|
177
177
|
tools: req.tools?.map((tool) => ({ type: 'function', function: { name: tool.name, description: tool.description, parameters: tool.parameters } })),
|
|
178
178
|
};
|
|
179
|
+
// First-class reasoning effort → Ollama's `think` flag (reasoning arrives in
|
|
180
|
+
// `message.thinking`, which this engine already routes to `reasoning` events).
|
|
181
|
+
// Ollama has no graded effort for most models, so anything but `'none'` is
|
|
182
|
+
// `true`. Only sent when asked for: a model without thinking support rejects it.
|
|
183
|
+
if (req.reasoningEffort !== undefined)
|
|
184
|
+
base.think = req.reasoningEffort !== 'none';
|
|
179
185
|
// providerOptions reaches Ollama's real request fields — `options.num_ctx`,
|
|
180
186
|
// `options.num_keep`, top-level `keep_alive`, `think`, etc. `options` is
|
|
181
187
|
// merged one level deep so num_ctx joins the samplers above rather than
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OpenAIChatAdapter = void 0;
|
|
4
|
+
exports.shouldRetryWithoutReasoningEffort = shouldRetryWithoutReasoningEffort;
|
|
4
5
|
const errors_js_1 = require("../../errors.js");
|
|
5
6
|
const tokens_js_1 = require("../../tokens.js");
|
|
6
7
|
const transport_js_1 = require("../../transport.js");
|
|
@@ -37,7 +38,34 @@ class OpenAIChatAdapter {
|
|
|
37
38
|
const timeout = (0, base_js_1.streamTimeout)(conn, req.signal);
|
|
38
39
|
yield { type: 'start', callId: '', provider: conn.provider, model: req.model };
|
|
39
40
|
try {
|
|
40
|
-
|
|
41
|
+
let res;
|
|
42
|
+
try {
|
|
43
|
+
res = await (0, transport_js_1.postResponse)(urlFor(conn, this.profile, req), openAiBody(req, this.profile), conn.headers, timeout.signal, conn.provider);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
// OpenAI's `/chat/completions` refuses function tools on a reasoning model
|
|
47
|
+
// unless `reasoning_effort` is `'none'` ("Function tools with reasoning_effort
|
|
48
|
+
// are not supported … use /v1/responses or set reasoning_effort to 'none'").
|
|
49
|
+
// The nugget never sets `reasoning_effort` itself, so the provider default
|
|
50
|
+
// applies and every tool-using turn 400s. Rather than make each app learn
|
|
51
|
+
// this the hard way, retry the one request once with the effort disabled
|
|
52
|
+
// and say so on the stream. Reactive on purpose: sending
|
|
53
|
+
// `reasoning_effort` up front would 400 on non-reasoning models instead.
|
|
54
|
+
// A caller-requested effort is overridden too — the alternative is a hard
|
|
55
|
+
// 400 with no tool call at all — and the context event says so.
|
|
56
|
+
if (!shouldRetryWithoutReasoningEffort(error, req))
|
|
57
|
+
throw error;
|
|
58
|
+
yield {
|
|
59
|
+
type: 'context',
|
|
60
|
+
kind: 'reasoning_effort_disabled_for_tools',
|
|
61
|
+
data: {
|
|
62
|
+
reason: 'provider rejected function tools with reasoning_effort; retried with reasoning_effort: none',
|
|
63
|
+
requested: req.providerOptions?.reasoning_effort ?? req.reasoningEffort ?? null,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const retryReq = { ...req, providerOptions: { ...req.providerOptions, reasoning_effort: 'none' } };
|
|
67
|
+
res = await (0, transport_js_1.postResponse)(urlFor(conn, this.profile, retryReq), openAiBody(retryReq, this.profile), conn.headers, timeout.signal, conn.provider);
|
|
68
|
+
}
|
|
41
69
|
const contentType = res.headers.get('content-type') ?? '';
|
|
42
70
|
if (!contentType.includes('text/event-stream')) {
|
|
43
71
|
// Server ignored stream:true (or is a buffered gateway) — recover the whole body.
|
|
@@ -173,6 +201,10 @@ function openAiBody(req, profile) {
|
|
|
173
201
|
};
|
|
174
202
|
if (req.maxTokens !== undefined)
|
|
175
203
|
body[profile.quirks?.maxTokensParam ?? 'max_tokens'] = req.maxTokens;
|
|
204
|
+
// First-class reasoning effort → OpenAI's `reasoning_effort`. Only sent when
|
|
205
|
+
// asked for: non-reasoning models reject the parameter outright.
|
|
206
|
+
if (req.reasoningEffort !== undefined)
|
|
207
|
+
body.reasoning_effort = req.reasoningEffort;
|
|
176
208
|
// providerOptions carries OpenAI-native fields the nugget doesn't model
|
|
177
209
|
// (`reasoning_effort`, `parallel_tool_calls`, `seed`, `logprobs`, …). `apiVersion`
|
|
178
210
|
// is consumed by urlFor for Azure and stripped here so it never hits the body.
|
|
@@ -259,3 +291,22 @@ function mapFinish(finish, hasToolCalls) {
|
|
|
259
291
|
return 'content_filter';
|
|
260
292
|
return 'stop';
|
|
261
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* True when a `/chat/completions` 400 is the "function tools with reasoning_effort
|
|
296
|
+
* are not supported" refusal AND the request actually carried tools AND the caller
|
|
297
|
+
* did not already pin `reasoning_effort` (in which case the choice is theirs).
|
|
298
|
+
*/
|
|
299
|
+
function shouldRetryWithoutReasoningEffort(error, req) {
|
|
300
|
+
if (!(error instanceof errors_js_1.AIError) || error.status !== 400)
|
|
301
|
+
return false;
|
|
302
|
+
if (!req.tools || req.tools.length === 0)
|
|
303
|
+
return false;
|
|
304
|
+
// Already `'none'` (from either knob) — the refusal is about something else.
|
|
305
|
+
const effective = req.providerOptions && 'reasoning_effort' in req.providerOptions
|
|
306
|
+
? req.providerOptions.reasoning_effort
|
|
307
|
+
: req.reasoningEffort;
|
|
308
|
+
if (effective === 'none')
|
|
309
|
+
return false;
|
|
310
|
+
const text = `${error.raw ?? ''} ${error.message}`.toLowerCase();
|
|
311
|
+
return text.includes('reasoning_effort') && text.includes('tool');
|
|
312
|
+
}
|
|
@@ -35,6 +35,7 @@ async function* run(opts, resolveResult) {
|
|
|
35
35
|
yield emit(opts, { type: 'step_start', step });
|
|
36
36
|
const calls = [];
|
|
37
37
|
let stepText = '';
|
|
38
|
+
let providerState;
|
|
38
39
|
let streamFailure;
|
|
39
40
|
// In promptJson mode a tool directive must not leak into the visible
|
|
40
41
|
// stream, so directive-looking text is withheld from `delta` events (still
|
|
@@ -49,6 +50,7 @@ async function* run(opts, resolveResult) {
|
|
|
49
50
|
maxTokens: opts.maxTokens,
|
|
50
51
|
topP: opts.topP,
|
|
51
52
|
stopSequences: opts.stopSequences,
|
|
53
|
+
reasoningEffort: opts.reasoningEffort,
|
|
52
54
|
providerOptions: opts.providerOptions,
|
|
53
55
|
signal: agentSignal.signal,
|
|
54
56
|
metadata: { ...opts.metadata, agentStep: step },
|
|
@@ -67,6 +69,7 @@ async function* run(opts, resolveResult) {
|
|
|
67
69
|
if (event.type === 'error')
|
|
68
70
|
streamFailure = event.error;
|
|
69
71
|
if (event.type === 'done') {
|
|
72
|
+
providerState = event.result.providerState;
|
|
70
73
|
usage = usage ? (0, tokens_js_1.mergeUsage)(usage, event.result.usage) : event.result.usage;
|
|
71
74
|
if (toolMode === 'promptJson' && calls.length === 0)
|
|
72
75
|
calls.push(...callsFromPromptJson(stepText));
|
|
@@ -87,7 +90,7 @@ async function* run(opts, resolveResult) {
|
|
|
87
90
|
finalText = stepText;
|
|
88
91
|
messages.push(toolMode === 'promptJson'
|
|
89
92
|
? { role: 'assistant', content: stepText }
|
|
90
|
-
: { role: 'assistant', content: stepText, toolCalls: calls.length ? calls : undefined });
|
|
93
|
+
: { role: 'assistant', content: stepText, providerState, toolCalls: calls.length ? calls : undefined });
|
|
91
94
|
if (calls.length === 0)
|
|
92
95
|
return yield* yieldDone('finished');
|
|
93
96
|
for (const call of calls) {
|
|
@@ -6,6 +6,7 @@ const index_js_1 = require("./adapters/index.js");
|
|
|
6
6
|
const profiles_js_1 = require("./adapters/profiles.js");
|
|
7
7
|
const errors_js_1 = require("./errors.js");
|
|
8
8
|
const json_js_1 = require("./json.js");
|
|
9
|
+
const reasoning_js_1 = require("./reasoning.js");
|
|
9
10
|
const policy_js_1 = require("./policy.js");
|
|
10
11
|
const redact_js_1 = require("./redact.js");
|
|
11
12
|
const util_js_1 = require("./util.js");
|
|
@@ -71,8 +72,11 @@ class AIHandler {
|
|
|
71
72
|
const maxAttempts = this.opts.retry?.maxAttempts ?? 3;
|
|
72
73
|
let emittedStart = false;
|
|
73
74
|
let emittedOutput = false;
|
|
75
|
+
const stripInline = this.opts.stripInlineReasoning !== false;
|
|
74
76
|
for (;;) {
|
|
75
77
|
attempt += 1;
|
|
78
|
+
// One stripper per attempt: a retried stream starts its tag state over.
|
|
79
|
+
const stripper = stripInline ? (0, reasoning_js_1.createReasoningStripper)() : null;
|
|
76
80
|
try {
|
|
77
81
|
for await (const event of adapter.stream(resolved, req)) {
|
|
78
82
|
if (event.type === 'start') {
|
|
@@ -82,12 +86,33 @@ class AIHandler {
|
|
|
82
86
|
}
|
|
83
87
|
continue;
|
|
84
88
|
}
|
|
85
|
-
if (event.type === 'delta' || event.type === 'tool_call')
|
|
89
|
+
if (event.type === 'delta' || event.type === 'tool_call' || event.type === 'reasoning')
|
|
86
90
|
emittedOutput = true;
|
|
91
|
+
if (event.type === 'delta' && stripper) {
|
|
92
|
+
// Inline reasoning never reaches `delta`; it rides the reasoning channel like a
|
|
93
|
+
// provider-native thinking field would.
|
|
94
|
+
const piece = stripper.push(event.text);
|
|
95
|
+
if (piece.reasoning)
|
|
96
|
+
yield { type: 'reasoning', text: piece.reasoning };
|
|
97
|
+
if (piece.visible)
|
|
98
|
+
yield { type: 'delta', text: piece.visible };
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
87
101
|
if (event.type === 'done') {
|
|
88
|
-
|
|
102
|
+
let result = event.result;
|
|
103
|
+
if (stripper) {
|
|
104
|
+
const tail = stripper.end();
|
|
105
|
+
if (tail.reasoning)
|
|
106
|
+
yield { type: 'reasoning', text: tail.reasoning };
|
|
107
|
+
if (tail.visible)
|
|
108
|
+
yield { type: 'delta', text: tail.visible };
|
|
109
|
+
if ((0, reasoning_js_1.containsReasoningBlock)(result.text)) {
|
|
110
|
+
result = { ...result, text: (0, reasoning_js_1.stripReasoningBlocks)(result.text) };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
await this.recordSuccess(callId, conn, req, startedAt, attempt, result);
|
|
89
114
|
recorded = true;
|
|
90
|
-
yield event;
|
|
115
|
+
yield { ...event, result };
|
|
91
116
|
return;
|
|
92
117
|
}
|
|
93
118
|
yield event;
|
package/nugget/dist/cjs/index.js
CHANGED
|
@@ -18,6 +18,7 @@ __exportStar(require("./types.js"), exports);
|
|
|
18
18
|
__exportStar(require("./errors.js"), exports);
|
|
19
19
|
__exportStar(require("./transport.js"), exports);
|
|
20
20
|
__exportStar(require("./json.js"), exports);
|
|
21
|
+
__exportStar(require("./reasoning.js"), exports);
|
|
21
22
|
__exportStar(require("./tokens.js"), exports);
|
|
22
23
|
__exportStar(require("./keys.js"), exports);
|
|
23
24
|
__exportStar(require("./connect.js"), exports);
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Inline reasoning stripper — keeps a reasoning model's chain of thought out of `delta` events
|
|
4
|
+
* and `ChatResult.text`, routing it to the `reasoning` stream event instead.
|
|
5
|
+
*
|
|
6
|
+
* Reasoning models (deepseek-r1, phi4-reasoning, qwq, …) emit their thinking either in the
|
|
7
|
+
* provider's dedicated field — which every engine already maps to `{ type: 'reasoning' }` — or
|
|
8
|
+
* INLINE in the answer as `<think>…</think>`-style blocks. Inline reasoning routinely quotes the
|
|
9
|
+
* system prompt and tool instructions back verbatim, so an app that streams `delta` straight to
|
|
10
|
+
* a screen, a database or a JSON parser leaks it. Every consumer would otherwise write this
|
|
11
|
+
* state machine, and most would get the split-tag and template-opened cases wrong; the handler
|
|
12
|
+
* runs it once, on every engine, unless `HandlerOptions.stripInlineReasoning` is `false`.
|
|
13
|
+
*
|
|
14
|
+
* Ported from AI Server Studio's `services/reasoningFilter.ts` (2026-09-05), which had to exist
|
|
15
|
+
* because the nugget did not do this.
|
|
16
|
+
*
|
|
17
|
+
* Two subtleties the tests pin:
|
|
18
|
+
* - Tags may be split across chunks, so a trailing partial `<thi` is held back until the next
|
|
19
|
+
* chunk decides whether it opens a tag. An unterminated block is reasoning to the end.
|
|
20
|
+
* - The **orphan close**: a closing tag with no opening one at the head of the stream, because
|
|
21
|
+
* the runtime's chat template emitted `<think>` itself. Handled once per stream, only before
|
|
22
|
+
* any visible text has gone out, and only when the closing tag starts its own line — a
|
|
23
|
+
* `</think>` quoted mid-sentence in a real answer is left alone.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.createReasoningStripper = createReasoningStripper;
|
|
27
|
+
exports.stripReasoningBlocksDetailed = stripReasoningBlocksDetailed;
|
|
28
|
+
exports.stripReasoningBlocks = stripReasoningBlocks;
|
|
29
|
+
exports.containsReasoningBlock = containsReasoningBlock;
|
|
30
|
+
const TAG_PAIRS = [
|
|
31
|
+
{ open: '<think>', close: '</think>' },
|
|
32
|
+
{ open: '<thinking>', close: '</thinking>' },
|
|
33
|
+
{ open: '<reasoning>', close: '</reasoning>' },
|
|
34
|
+
{ open: '<|begin_of_thought|>', close: '<|end_of_thought|>' },
|
|
35
|
+
];
|
|
36
|
+
const LONGEST_TAG = Math.max(...TAG_PAIRS.flatMap((p) => [p.open.length, p.close.length]));
|
|
37
|
+
function partialTagSuffixLength(text) {
|
|
38
|
+
const lower = text.toLowerCase();
|
|
39
|
+
const maxLen = Math.min(LONGEST_TAG - 1, lower.length);
|
|
40
|
+
for (let len = maxLen; len >= 1; len -= 1) {
|
|
41
|
+
const tail = lower.slice(lower.length - len);
|
|
42
|
+
if (tail[0] !== '<')
|
|
43
|
+
continue;
|
|
44
|
+
for (const pair of TAG_PAIRS) {
|
|
45
|
+
if (pair.open.startsWith(tail) || pair.close.startsWith(tail))
|
|
46
|
+
return len;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
function closesOwnLine(text, idx) {
|
|
52
|
+
let i = idx - 1;
|
|
53
|
+
while (i >= 0 && (text[i] === ' ' || text[i] === '\t' || text[i] === '\r'))
|
|
54
|
+
i -= 1;
|
|
55
|
+
return i < 0 || text[i] === '\n';
|
|
56
|
+
}
|
|
57
|
+
function createReasoningStripper() {
|
|
58
|
+
let pending = '';
|
|
59
|
+
let inReasoning = false;
|
|
60
|
+
let closeTag = '';
|
|
61
|
+
let emittedVisible = false;
|
|
62
|
+
let orphanConsumed = false;
|
|
63
|
+
function process(input, flushAll) {
|
|
64
|
+
let text = pending + input;
|
|
65
|
+
pending = '';
|
|
66
|
+
let visible = '';
|
|
67
|
+
let reasoning = '';
|
|
68
|
+
while (text.length > 0) {
|
|
69
|
+
const lower = text.toLowerCase();
|
|
70
|
+
if (inReasoning) {
|
|
71
|
+
const idx = lower.indexOf(closeTag);
|
|
72
|
+
if (idx === -1) {
|
|
73
|
+
const hold = flushAll ? 0 : partialTagSuffixLength(text);
|
|
74
|
+
const consumed = text.length - hold;
|
|
75
|
+
reasoning += text.slice(0, consumed);
|
|
76
|
+
pending = text.slice(consumed);
|
|
77
|
+
text = '';
|
|
78
|
+
if (flushAll) {
|
|
79
|
+
reasoning += pending;
|
|
80
|
+
pending = '';
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
reasoning += text.slice(0, idx);
|
|
85
|
+
text = text.slice(idx + closeTag.length);
|
|
86
|
+
inReasoning = false;
|
|
87
|
+
closeTag = '';
|
|
88
|
+
if (text.startsWith('\n'))
|
|
89
|
+
text = text.slice(1);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
let openIdx = -1;
|
|
93
|
+
let pair = null;
|
|
94
|
+
for (const p of TAG_PAIRS) {
|
|
95
|
+
const i = lower.indexOf(p.open);
|
|
96
|
+
if (i !== -1 && (openIdx === -1 || i < openIdx)) {
|
|
97
|
+
openIdx = i;
|
|
98
|
+
pair = p;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
let orphanCloseIdx = -1;
|
|
102
|
+
let orphanCloseLen = 0;
|
|
103
|
+
if (!emittedVisible && !orphanConsumed) {
|
|
104
|
+
for (const p of TAG_PAIRS) {
|
|
105
|
+
const i = lower.indexOf(p.close);
|
|
106
|
+
if (i !== -1 && (orphanCloseIdx === -1 || i < orphanCloseIdx) && closesOwnLine(text, i)) {
|
|
107
|
+
orphanCloseIdx = i;
|
|
108
|
+
orphanCloseLen = p.close.length;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (orphanCloseIdx !== -1 && (openIdx === -1 || orphanCloseIdx < openIdx)) {
|
|
113
|
+
orphanConsumed = true;
|
|
114
|
+
reasoning += text.slice(0, orphanCloseIdx);
|
|
115
|
+
text = text.slice(orphanCloseIdx + orphanCloseLen).replace(/^\n+/, '');
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (openIdx === -1 || !pair) {
|
|
119
|
+
const hold = flushAll ? 0 : partialTagSuffixLength(text);
|
|
120
|
+
visible += text.slice(0, text.length - hold);
|
|
121
|
+
pending = text.slice(text.length - hold);
|
|
122
|
+
text = '';
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
visible += text.slice(0, openIdx);
|
|
126
|
+
text = text.slice(openIdx + pair.open.length);
|
|
127
|
+
inReasoning = true;
|
|
128
|
+
closeTag = pair.close;
|
|
129
|
+
}
|
|
130
|
+
if (visible.trim().length > 0)
|
|
131
|
+
emittedVisible = true;
|
|
132
|
+
return { visible, reasoning };
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
push: (chunk) => process(chunk, false),
|
|
136
|
+
end: () => process('', true),
|
|
137
|
+
get inReasoning() {
|
|
138
|
+
return inReasoning;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** Whole-string form: `{ visible, reasoning }` for already-buffered text. */
|
|
143
|
+
function stripReasoningBlocksDetailed(text) {
|
|
144
|
+
if (!text)
|
|
145
|
+
return { visible: '', reasoning: '' };
|
|
146
|
+
const stripper = createReasoningStripper();
|
|
147
|
+
const a = stripper.push(text);
|
|
148
|
+
const b = stripper.end();
|
|
149
|
+
return { visible: (a.visible + b.visible).trim(), reasoning: a.reasoning + b.reasoning };
|
|
150
|
+
}
|
|
151
|
+
/** Whole-string form returning only the visible text. */
|
|
152
|
+
function stripReasoningBlocks(text) {
|
|
153
|
+
return stripReasoningBlocksDetailed(text).visible;
|
|
154
|
+
}
|
|
155
|
+
/** Cheap pre-check: does `text` carry any inline reasoning tag (open or close)? */
|
|
156
|
+
function containsReasoningBlock(text) {
|
|
157
|
+
const lower = text.toLowerCase();
|
|
158
|
+
return TAG_PAIRS.some((p) => lower.includes(p.open) || lower.includes(p.close));
|
|
159
|
+
}
|
package/nugget/dist/cjs/util.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REASONING_BUDGET_TOKENS = void 0;
|
|
3
4
|
exports.asRecord = asRecord;
|
|
4
5
|
exports.asString = asString;
|
|
5
6
|
exports.asNumber = asNumber;
|
|
@@ -110,3 +111,16 @@ function sleep(ms, signal) {
|
|
|
110
111
|
}
|
|
111
112
|
});
|
|
112
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Token budgets behind the {@link ReasoningEffort} tiers for providers that
|
|
116
|
+
* express effort as a thinking-token budget (Anthropic `budget_tokens`, Google
|
|
117
|
+
* `thinkingBudget`). `'none'` is 0. Deliberately conservative: they are floors
|
|
118
|
+
* a model may stop under, not ceilings it fills.
|
|
119
|
+
*/
|
|
120
|
+
exports.REASONING_BUDGET_TOKENS = {
|
|
121
|
+
none: 0,
|
|
122
|
+
minimal: 1024,
|
|
123
|
+
low: 2048,
|
|
124
|
+
medium: 8192,
|
|
125
|
+
high: 16384,
|
|
126
|
+
};
|
package/nugget/dist/handler.d.ts
CHANGED
|
@@ -30,6 +30,12 @@ export interface HandlerOptions {
|
|
|
30
30
|
* explicit `policy: allowAllPolicy()` says the same thing and is clearer.
|
|
31
31
|
*/
|
|
32
32
|
silencePolicyWarning?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Strip inline `<think>…</think>`-style reasoning out of `delta` events and `ChatResult.text`,
|
|
35
|
+
* emitting it as `{ type: 'reasoning' }` events instead (default `true`; see `src/reasoning.ts`).
|
|
36
|
+
* Set `false` to receive the raw answer text exactly as the provider sent it.
|
|
37
|
+
*/
|
|
38
|
+
stripInlineReasoning?: boolean;
|
|
33
39
|
/**
|
|
34
40
|
* Optional cost estimator. Called once per successful call/embedding with the
|
|
35
41
|
* final usage; its return value (USD) is stored on the telemetry record's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"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"}
|