smoltalk 0.8.0 → 0.8.2
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/dist/clients/anthropic.d.ts +11 -0
- package/dist/clients/anthropic.js +165 -20
- package/dist/clients/baseClient.js +6 -1
- package/dist/clients/google.d.ts +15 -0
- package/dist/clients/google.js +67 -10
- package/dist/models.d.ts +110 -2
- package/dist/models.js +114 -2
- package/dist/types.d.ts +8 -0
- package/package.json +1 -1
|
@@ -2,6 +2,17 @@ import type { MessageParam, Tool } from "@anthropic-ai/sdk/resources/messages.js
|
|
|
2
2
|
import { PromptResult, Result, SmolClient, SmolConfig, StreamChunk, HostedToolResult } from "../types.js";
|
|
3
3
|
import { BaseClient } from "./baseClient.js";
|
|
4
4
|
import { ModelName } from "../models.js";
|
|
5
|
+
export declare function mergeConsecutiveMessages(messages: MessageParam[]): MessageParam[];
|
|
6
|
+
/**
|
|
7
|
+
* Whether a model supports Anthropic's native structured-output request
|
|
8
|
+
* (`output_config.format` with a json_schema — the direct analog of the OpenAI
|
|
9
|
+
* client's `response_format`). GA on Claude 4.x+ (Opus 4.5+, Sonnet 4.5+, Haiku
|
|
10
|
+
* 4.5, and the 4.6/4.7/4.8 / Sonnet 5 / Fable families). The legacy claude-3.x /
|
|
11
|
+
* 2.x aliases do not support it; there we fall back to prompt-based output plus
|
|
12
|
+
* the base client's fence stripping. Unknown/future model names default to
|
|
13
|
+
* supported (forward-looking).
|
|
14
|
+
*/
|
|
15
|
+
export declare function anthropicSupportsStructuredOutput(model: string): boolean;
|
|
5
16
|
export declare function anthropicWebSearchEntries(hostedTools?: string[]): any[];
|
|
6
17
|
export declare function parseAnthropicHostedTools(response: any, provider: string): HostedToolResult[];
|
|
7
18
|
type EphemeralCacheControl = {
|
|
@@ -12,6 +12,52 @@ import { BaseClient } from "./baseClient.js";
|
|
|
12
12
|
import { getModel, isTextModel } from "../models.js";
|
|
13
13
|
import { Model } from "../model.js";
|
|
14
14
|
const DEFAULT_MAX_TOKENS = 4096;
|
|
15
|
+
// Normalize an Anthropic message's content to a block array for merging.
|
|
16
|
+
// Array content is passed through; a non-empty string becomes a single text
|
|
17
|
+
// block; an empty string (or any other unexpected value) becomes no blocks
|
|
18
|
+
// (Anthropic rejects empty text blocks). Guarding non-array values keeps the
|
|
19
|
+
// exported merge helper from throwing on a spread if a caller passes content
|
|
20
|
+
// that isn't the string | block[] the type promises.
|
|
21
|
+
function anthropicContentToBlocks(content) {
|
|
22
|
+
if (Array.isArray(content)) {
|
|
23
|
+
return content;
|
|
24
|
+
}
|
|
25
|
+
if (typeof content === "string" && content.length > 0) {
|
|
26
|
+
return [{ type: "text", text: content }];
|
|
27
|
+
}
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
// Anthropic requires strict user/assistant turn alternation. Collapse any run of
|
|
31
|
+
// consecutive messages that share a role into a single message by concatenating
|
|
32
|
+
// their content blocks. Does not mutate the input.
|
|
33
|
+
export function mergeConsecutiveMessages(messages) {
|
|
34
|
+
const merged = [];
|
|
35
|
+
for (const msg of messages) {
|
|
36
|
+
const last = merged[merged.length - 1];
|
|
37
|
+
if (last && last.role === msg.role) {
|
|
38
|
+
const content = [
|
|
39
|
+
...anthropicContentToBlocks(last.content),
|
|
40
|
+
...anthropicContentToBlocks(msg.content),
|
|
41
|
+
];
|
|
42
|
+
merged[merged.length - 1] = { ...last, content };
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
merged.push(msg);
|
|
46
|
+
}
|
|
47
|
+
return merged;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Whether a model supports Anthropic's native structured-output request
|
|
51
|
+
* (`output_config.format` with a json_schema — the direct analog of the OpenAI
|
|
52
|
+
* client's `response_format`). GA on Claude 4.x+ (Opus 4.5+, Sonnet 4.5+, Haiku
|
|
53
|
+
* 4.5, and the 4.6/4.7/4.8 / Sonnet 5 / Fable families). The legacy claude-3.x /
|
|
54
|
+
* 2.x aliases do not support it; there we fall back to prompt-based output plus
|
|
55
|
+
* the base client's fence stripping. Unknown/future model names default to
|
|
56
|
+
* supported (forward-looking).
|
|
57
|
+
*/
|
|
58
|
+
export function anthropicSupportsStructuredOutput(model) {
|
|
59
|
+
return !/^claude-(?:3(?:[-.]|$)|2(?:[-.]|$)|instant(?:[-.]|$))/i.test(model);
|
|
60
|
+
}
|
|
15
61
|
export function anthropicWebSearchEntries(hostedTools) {
|
|
16
62
|
if (hostedTools && hostedTools.includes(WEB_SEARCH)) {
|
|
17
63
|
return [{ type: "web_search_20250305", name: "web_search" }];
|
|
@@ -187,30 +233,21 @@ export class SmolAnthropic extends BaseClient {
|
|
|
187
233
|
.filter((m) => m instanceof SystemMessage || m instanceof DeveloperMessage)
|
|
188
234
|
.map((m) => m.content);
|
|
189
235
|
const system = systemParts.length > 0 ? systemParts.join("\n") : undefined;
|
|
190
|
-
// Convert remaining messages
|
|
191
|
-
const
|
|
236
|
+
// Convert remaining messages into Anthropic message params.
|
|
237
|
+
const converted = [];
|
|
192
238
|
for (const msg of config.messages) {
|
|
193
239
|
if (msg instanceof SystemMessage || msg instanceof DeveloperMessage) {
|
|
194
240
|
continue;
|
|
195
241
|
}
|
|
196
|
-
const
|
|
197
|
-
if (
|
|
242
|
+
const c = msg.toAnthropicMessage();
|
|
243
|
+
if (c === null)
|
|
198
244
|
continue;
|
|
199
|
-
|
|
200
|
-
if (converted.role === "user" &&
|
|
201
|
-
Array.isArray(converted.content) &&
|
|
202
|
-
converted.content.every((c) => c.type === "tool_result")) {
|
|
203
|
-
const last = anthropicMessages[anthropicMessages.length - 1];
|
|
204
|
-
if (last &&
|
|
205
|
-
last.role === "user" &&
|
|
206
|
-
Array.isArray(last.content) &&
|
|
207
|
-
last.content.every((c) => c.type === "tool_result")) {
|
|
208
|
-
last.content.push(...converted.content);
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
anthropicMessages.push(converted);
|
|
245
|
+
converted.push(c);
|
|
213
246
|
}
|
|
247
|
+
// Anthropic requires strict user/assistant alternation — merge any run of
|
|
248
|
+
// consecutive same-role messages into one (e.g. two user turns, or a user
|
|
249
|
+
// turn followed by tool results, which also map to role "user").
|
|
250
|
+
const anthropicMessages = mergeConsecutiveMessages(converted);
|
|
214
251
|
const functionTools = config.tools && config.tools.length > 0
|
|
215
252
|
? config.tools.map((tool) => zodToAnthropicTool(tool.name, tool.schema, {
|
|
216
253
|
description: tool.description,
|
|
@@ -222,7 +259,22 @@ export class SmolAnthropic extends BaseClient {
|
|
|
222
259
|
// Normalize the user's provider-agnostic thinking/effort config into the
|
|
223
260
|
// shape this specific model accepts. Both `thinking.enabled` and
|
|
224
261
|
// `reasoningEffort` are treated as a request to think.
|
|
225
|
-
const { thinking, outputConfig } = this.resolveThinking(config);
|
|
262
|
+
const { thinking, outputConfig: effortConfig } = this.resolveThinking(config);
|
|
263
|
+
// Provider-native structured output. Anthropic constrains the response to a
|
|
264
|
+
// JSON schema via `output_config.format` (grammar-constrained decoding) —
|
|
265
|
+
// the direct analog of the OpenAI client's `response_format` json_schema,
|
|
266
|
+
// and it composes with function tools (the model may call a tool OR emit the
|
|
267
|
+
// structured JSON). GA on Claude 4.x+; on the legacy claude-3.x/2.x aliases
|
|
268
|
+
// it isn't available, so we omit it and let the base client's fence stripper
|
|
269
|
+
// + retry recover a prompt-shaped reply.
|
|
270
|
+
const format = config.responseFormat &&
|
|
271
|
+
anthropicSupportsStructuredOutput(this.getModel())
|
|
272
|
+
? { type: "json_schema", schema: config.responseFormat.toJSONSchema() }
|
|
273
|
+
: undefined;
|
|
274
|
+
// Merge effort + format into one output_config (both optional, independent).
|
|
275
|
+
const outputConfig = effortConfig || format
|
|
276
|
+
? { ...effortConfig, ...(format && { format }) }
|
|
277
|
+
: undefined;
|
|
226
278
|
const cachingEnabled = config.caching?.enabled !== false;
|
|
227
279
|
const baseRequest = { system, messages: anthropicMessages, tools };
|
|
228
280
|
const finalRequest = cachingEnabled
|
|
@@ -391,6 +443,18 @@ export class SmolAnthropic extends BaseClient {
|
|
|
391
443
|
const toolBlocks = new Map();
|
|
392
444
|
// Track thinking blocks by index: index -> { text, signature }
|
|
393
445
|
const thinkingBlockMap = new Map();
|
|
446
|
+
// Track server-side web_search blocks by index -> partial input JSON. The
|
|
447
|
+
// query streams in as input_json_delta just like a client tool call; we
|
|
448
|
+
// parse it once the block stops and emit a live `web_search` chunk.
|
|
449
|
+
const webSearchBlocks = new Map();
|
|
450
|
+
// Accumulated hosted-tool signal for the final `done` result (parity with
|
|
451
|
+
// the non-streaming path's `hostedToolResults`): queries, sources,
|
|
452
|
+
// citations, billed count, and the raw provider blocks.
|
|
453
|
+
const webSearchQueries = [];
|
|
454
|
+
const webSearchSources = [];
|
|
455
|
+
const webSearchCitations = [];
|
|
456
|
+
const webSearchRaw = [];
|
|
457
|
+
let webSearchRequests;
|
|
394
458
|
let inputTokens = 0;
|
|
395
459
|
let cacheReadTokens = 0;
|
|
396
460
|
let cacheCreationTokens = 0;
|
|
@@ -410,6 +474,25 @@ export class SmolAnthropic extends BaseClient {
|
|
|
410
474
|
arguments: "",
|
|
411
475
|
});
|
|
412
476
|
}
|
|
477
|
+
else if (event.content_block.type === "server_tool_use" &&
|
|
478
|
+
event.content_block.name === "web_search") {
|
|
479
|
+
// Keep the start block so we can reconstruct the raw provider block
|
|
480
|
+
// (with its streamed `input`) at content_block_stop.
|
|
481
|
+
webSearchBlocks.set(event.index, {
|
|
482
|
+
arguments: "",
|
|
483
|
+
start: event.content_block,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
else if (event.content_block.type === "web_search_tool_result") {
|
|
487
|
+
// Server tool results arrive whole (no deltas). Collect sources and
|
|
488
|
+
// keep the raw block so the final `done` result matches _textSync.
|
|
489
|
+
webSearchRaw.push(event.content_block);
|
|
490
|
+
for (const r of event.content_block.content || []) {
|
|
491
|
+
if (r && typeof r.url === "string") {
|
|
492
|
+
webSearchSources.push({ url: r.url, title: r.title });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
413
496
|
else if (event.content_block.type === "thinking") {
|
|
414
497
|
thinkingBlockMap.set(event.index, { text: "", signature: "" });
|
|
415
498
|
}
|
|
@@ -424,6 +507,10 @@ export class SmolAnthropic extends BaseClient {
|
|
|
424
507
|
if (block) {
|
|
425
508
|
block.arguments += event.delta.partial_json;
|
|
426
509
|
}
|
|
510
|
+
const searchBlock = webSearchBlocks.get(event.index);
|
|
511
|
+
if (searchBlock) {
|
|
512
|
+
searchBlock.arguments += event.delta.partial_json;
|
|
513
|
+
}
|
|
427
514
|
}
|
|
428
515
|
else if (event.delta.type === "thinking_delta") {
|
|
429
516
|
const block = thinkingBlockMap.get(event.index);
|
|
@@ -437,6 +524,15 @@ export class SmolAnthropic extends BaseClient {
|
|
|
437
524
|
block.signature = event.delta.signature;
|
|
438
525
|
}
|
|
439
526
|
}
|
|
527
|
+
else if (event.delta.type === "citations_delta") {
|
|
528
|
+
// Citations stream in one per delta (whereas _textSync reads them
|
|
529
|
+
// from the assembled text block's `citations` array). Collect any
|
|
530
|
+
// with a url so the done result's hostedToolResults match sync.
|
|
531
|
+
const c = event.delta.citation;
|
|
532
|
+
if (c && typeof c.url === "string") {
|
|
533
|
+
webSearchCitations.push({ url: c.url, title: c.title });
|
|
534
|
+
}
|
|
535
|
+
}
|
|
440
536
|
}
|
|
441
537
|
else if (event.type === "content_block_stop") {
|
|
442
538
|
// Emit thinking chunk once the block is fully assembled
|
|
@@ -448,6 +544,29 @@ export class SmolAnthropic extends BaseClient {
|
|
|
448
544
|
signature: thinkingBlock.signature,
|
|
449
545
|
};
|
|
450
546
|
}
|
|
547
|
+
// Emit the web search query the moment its block completes, so
|
|
548
|
+
// consumers see the keywords live rather than only in the final result.
|
|
549
|
+
const searchBlock = webSearchBlocks.get(event.index);
|
|
550
|
+
if (searchBlock) {
|
|
551
|
+
// Delete first so a replayed/duplicate stop event can't emit the
|
|
552
|
+
// same query twice.
|
|
553
|
+
webSearchBlocks.delete(event.index);
|
|
554
|
+
let input;
|
|
555
|
+
try {
|
|
556
|
+
input = JSON.parse(searchBlock.arguments || "{}");
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
// Malformed/partial JSON — skip this block rather than throw.
|
|
560
|
+
}
|
|
561
|
+
// Reconstruct the raw provider block (start header + streamed input)
|
|
562
|
+
// so hostedToolResults.raw matches the non-streaming shape.
|
|
563
|
+
webSearchRaw.push({ ...searchBlock.start, input });
|
|
564
|
+
const query = input?.query;
|
|
565
|
+
if (typeof query === "string" && query.length > 0) {
|
|
566
|
+
webSearchQueries.push(query);
|
|
567
|
+
yield { type: "web_search", query };
|
|
568
|
+
}
|
|
569
|
+
}
|
|
451
570
|
}
|
|
452
571
|
else if (event.type === "message_delta") {
|
|
453
572
|
outputTokens = event.usage.output_tokens;
|
|
@@ -460,6 +579,10 @@ export class SmolAnthropic extends BaseClient {
|
|
|
460
579
|
if (event.usage.cache_creation_input_tokens != null) {
|
|
461
580
|
cacheCreationTokens = event.usage.cache_creation_input_tokens;
|
|
462
581
|
}
|
|
582
|
+
// Billable web-search count, for hosted-tool cost on the done result.
|
|
583
|
+
if (event.usage.server_tool_use?.web_search_requests != null) {
|
|
584
|
+
webSearchRequests = event.usage.server_tool_use.web_search_requests;
|
|
585
|
+
}
|
|
463
586
|
}
|
|
464
587
|
}
|
|
465
588
|
this.logger.debug("Streaming response completed from Anthropic");
|
|
@@ -485,7 +608,28 @@ export class SmolAnthropic extends BaseClient {
|
|
|
485
608
|
if (cacheCreationTokens > 0) {
|
|
486
609
|
usage.cacheCreationInputTokens = cacheCreationTokens;
|
|
487
610
|
}
|
|
488
|
-
|
|
611
|
+
let cost = this.model.calculateCost(usage) ?? undefined;
|
|
612
|
+
// Fold hosted-tool (web search) results into the done result so the
|
|
613
|
+
// streaming path reports the same `hostedToolResults` shape as _textSync.
|
|
614
|
+
// The live `web_search` chunks above already surfaced the queries; this is
|
|
615
|
+
// the durable record (queries + sources + citations + billed cost + raw).
|
|
616
|
+
let hostedToolResults = [];
|
|
617
|
+
const usedWebSearch = webSearchQueries.length > 0 ||
|
|
618
|
+
webSearchSources.length > 0 ||
|
|
619
|
+
(webSearchRequests ?? 0) > 0;
|
|
620
|
+
if (usedWebSearch) {
|
|
621
|
+
const applied = applyHostedToolCost([
|
|
622
|
+
webSearchResult("anthropic", {
|
|
623
|
+
queries: webSearchQueries,
|
|
624
|
+
sources: webSearchSources,
|
|
625
|
+
citations: webSearchCitations,
|
|
626
|
+
callCount: webSearchRequests,
|
|
627
|
+
raw: webSearchRaw,
|
|
628
|
+
}),
|
|
629
|
+
], cost, this.getModel(), this.config.modelData);
|
|
630
|
+
hostedToolResults = applied.results;
|
|
631
|
+
cost = applied.cost;
|
|
632
|
+
}
|
|
489
633
|
yield {
|
|
490
634
|
type: "done",
|
|
491
635
|
result: {
|
|
@@ -495,6 +639,7 @@ export class SmolAnthropic extends BaseClient {
|
|
|
495
639
|
usage,
|
|
496
640
|
cost,
|
|
497
641
|
model: this.getModel(),
|
|
642
|
+
...(hostedToolResults.length > 0 && { hostedToolResults }),
|
|
498
643
|
},
|
|
499
644
|
};
|
|
500
645
|
}
|
|
@@ -258,7 +258,12 @@ export class BaseClient {
|
|
|
258
258
|
retries > 0) {
|
|
259
259
|
const allowExtraKeys = promptConfig.responseFormatOptions?.allowExtraKeys ?? false;
|
|
260
260
|
try {
|
|
261
|
-
|
|
261
|
+
// Strip any ```json … ``` fence before parsing. Models (notably
|
|
262
|
+
// Anthropic on the prompt-based path) routinely wrap structured
|
|
263
|
+
// output in a markdown fence; feeding that straight to JSON.parse
|
|
264
|
+
// throws and burns a retry. stripCodeFence is a no-op on unfenced
|
|
265
|
+
// JSON, so this is safe for every provider.
|
|
266
|
+
const parsed = JSON.parse(stripCodeFence(output));
|
|
262
267
|
const parseResult = this.extractResponse(promptConfig, parsed, promptConfig.responseFormat);
|
|
263
268
|
return success({
|
|
264
269
|
...result.value,
|
package/dist/clients/google.d.ts
CHANGED
|
@@ -5,6 +5,21 @@ import { ModelName } from "../models.js";
|
|
|
5
5
|
import { HostedToolResult } from "../types.js";
|
|
6
6
|
export type SmolGoogleConfig = SmolConfig;
|
|
7
7
|
export declare function googleWebSearchEntries(hostedTools?: string[]): any[];
|
|
8
|
+
/**
|
|
9
|
+
* Whether a Gemini model can combine built-in tools (e.g. hosted web search)
|
|
10
|
+
* with function calling in a single request. Confirmed against the live API:
|
|
11
|
+
* - Gemini 3+ : supported, but ONLY when `toolConfig
|
|
12
|
+
* .includeServerSideToolInvocations` is set ("tool call context
|
|
13
|
+
* circulation"). Without it the API 400s asking you to enable it.
|
|
14
|
+
* - Gemini 2.5 and earlier: NOT supported by any means — the raw combination
|
|
15
|
+
* 400s with "Built-in tools and Function Calling cannot be combined", and
|
|
16
|
+
* the flag 400s with "Tool call context circulation is not enabled for
|
|
17
|
+
* <model>".
|
|
18
|
+
* Unknown / non-versioned model names default to supported (forward-looking):
|
|
19
|
+
* new models are expected to allow the combination.
|
|
20
|
+
* See egonSchiele/agency-lang#495.
|
|
21
|
+
*/
|
|
22
|
+
export declare function geminiSupportsToolCirculation(model: string): boolean;
|
|
8
23
|
export declare function parseGoogleHostedTools(result: any, provider: string, model: string): HostedToolResult[];
|
|
9
24
|
type GeneratedRequest = {
|
|
10
25
|
contents: Content[];
|
package/dist/clients/google.js
CHANGED
|
@@ -4,7 +4,7 @@ import { getLogger } from "../util/logger.js";
|
|
|
4
4
|
import { redactAttachments } from "../util/redact.js";
|
|
5
5
|
import { addCosts, addTokenUsage, success, } from "../types.js";
|
|
6
6
|
import { zodToGoogleTool } from "../util/tool.js";
|
|
7
|
-
import { SmolContentPolicyError, SmolContextWindowExceededError, smolErrorForStatus, } from "../smolError.js";
|
|
7
|
+
import { SmolError, SmolContentPolicyError, SmolContextWindowExceededError, smolErrorForStatus, } from "../smolError.js";
|
|
8
8
|
import { extractHttpErrorFields } from "../util/httpError.js";
|
|
9
9
|
import { sanitizeAttributes } from "../util/util.js";
|
|
10
10
|
import { BaseClient } from "./baseClient.js";
|
|
@@ -17,6 +17,26 @@ export function googleWebSearchEntries(hostedTools) {
|
|
|
17
17
|
}
|
|
18
18
|
return [];
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Whether a Gemini model can combine built-in tools (e.g. hosted web search)
|
|
22
|
+
* with function calling in a single request. Confirmed against the live API:
|
|
23
|
+
* - Gemini 3+ : supported, but ONLY when `toolConfig
|
|
24
|
+
* .includeServerSideToolInvocations` is set ("tool call context
|
|
25
|
+
* circulation"). Without it the API 400s asking you to enable it.
|
|
26
|
+
* - Gemini 2.5 and earlier: NOT supported by any means — the raw combination
|
|
27
|
+
* 400s with "Built-in tools and Function Calling cannot be combined", and
|
|
28
|
+
* the flag 400s with "Tool call context circulation is not enabled for
|
|
29
|
+
* <model>".
|
|
30
|
+
* Unknown / non-versioned model names default to supported (forward-looking):
|
|
31
|
+
* new models are expected to allow the combination.
|
|
32
|
+
* See egonSchiele/agency-lang#495.
|
|
33
|
+
*/
|
|
34
|
+
export function geminiSupportsToolCirculation(model) {
|
|
35
|
+
const m = /^gemini-(\d+)/.exec(model);
|
|
36
|
+
if (!m)
|
|
37
|
+
return true;
|
|
38
|
+
return parseInt(m[1], 10) >= 3;
|
|
39
|
+
}
|
|
20
40
|
export function parseGoogleHostedTools(result, provider, model) {
|
|
21
41
|
const queries = [];
|
|
22
42
|
const sources = [];
|
|
@@ -134,9 +154,22 @@ export class SmolGoogle extends BaseClient {
|
|
|
134
154
|
toolGroups.push(entry);
|
|
135
155
|
}
|
|
136
156
|
genConfig.tools = toolGroups;
|
|
137
|
-
//
|
|
138
|
-
//
|
|
157
|
+
// Combining built-in tools (hosted web search) with function calling is a
|
|
158
|
+
// Gemini feature ("tool call context circulation") that must be opted
|
|
159
|
+
// into via includeServerSideToolInvocations AND is only supported on
|
|
160
|
+
// Gemini 3+. On older models the combination is impossible: sending the
|
|
161
|
+
// flag 400s ("circulation is not enabled for <model>") and omitting it
|
|
162
|
+
// 400s ("Built-in tools and Function Calling cannot be combined"). Fail
|
|
163
|
+
// fast with an actionable message instead of a cryptic provider 400.
|
|
164
|
+
// See egonSchiele/agency-lang#495.
|
|
139
165
|
if (tools.length > 0 && hostedEntries.length > 0) {
|
|
166
|
+
if (!geminiSupportsToolCirculation(this.getModel())) {
|
|
167
|
+
throw new SmolError(`${this.getModel()} cannot use the hosted web_search tool together ` +
|
|
168
|
+
`with function tools in one request. Gemini only allows combining ` +
|
|
169
|
+
`built-in tools with function calling on Gemini 3+ models. Use a ` +
|
|
170
|
+
`Gemini 3+ model, switch to a client-side search tool instead of ` +
|
|
171
|
+
`the hosted web_search, or drop one of the two.`, { status: 400 });
|
|
172
|
+
}
|
|
140
173
|
genConfig.toolConfig = {
|
|
141
174
|
...genConfig.toolConfig,
|
|
142
175
|
includeServerSideToolInvocations: true,
|
|
@@ -147,7 +180,20 @@ export class SmolGoogle extends BaseClient {
|
|
|
147
180
|
genConfig.responseMimeType = "application/json";
|
|
148
181
|
genConfig.responseJsonSchema = config.responseFormat.toJSONSchema();
|
|
149
182
|
}
|
|
150
|
-
if (
|
|
183
|
+
if (config.thinking?.enabled) {
|
|
184
|
+
// Gemini only returns thought-summary parts (parts with `thought: true`,
|
|
185
|
+
// which populate PromptResult.thinkingBlocks) when includeThoughts is set.
|
|
186
|
+
// Without it the model still reasons, but returns only the encrypted
|
|
187
|
+
// thoughtSignature on the answer part — no visible reasoning text — so
|
|
188
|
+
// thinkingBlocks would come back empty.
|
|
189
|
+
genConfig.thinkingConfig = {
|
|
190
|
+
includeThoughts: true,
|
|
191
|
+
...(config.thinking.budgetTokens !== undefined && {
|
|
192
|
+
thinkingBudget: config.thinking.budgetTokens,
|
|
193
|
+
}),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
else if (config.reasoningEffort) {
|
|
151
197
|
const budgetMap = { low: 2048, medium: 8192, high: 16384 };
|
|
152
198
|
genConfig.thinkingConfig = {
|
|
153
199
|
thinkingBudget: budgetMap[config.reasoningEffort],
|
|
@@ -291,14 +337,19 @@ export class SmolGoogle extends BaseClient {
|
|
|
291
337
|
thoughtSignature: part.thoughtSignature,
|
|
292
338
|
}));
|
|
293
339
|
}
|
|
294
|
-
else if (part.
|
|
295
|
-
//
|
|
340
|
+
else if (part.thought) {
|
|
341
|
+
// A thinking part is identified by `thought: true` — NOT merely by
|
|
342
|
+
// the presence of a thoughtSignature. Gemini 3 also rides a
|
|
343
|
+
// thoughtSignature on the final answer part (no `thought` flag) for
|
|
344
|
+
// stateless reasoning continuity; keying on the signature alone
|
|
345
|
+
// would misfile that answer text as a thinking block and leave the
|
|
346
|
+
// output empty. See egonSchiele/agency-lang.
|
|
296
347
|
thinkingBlocks.push({
|
|
297
348
|
text: part.text || "",
|
|
298
|
-
signature: part.thoughtSignature,
|
|
349
|
+
signature: part.thoughtSignature || "",
|
|
299
350
|
});
|
|
300
351
|
}
|
|
301
|
-
else if (typeof part.text === "string"
|
|
352
|
+
else if (typeof part.text === "string") {
|
|
302
353
|
textContent += part.text;
|
|
303
354
|
}
|
|
304
355
|
});
|
|
@@ -395,10 +446,16 @@ export class SmolGoogle extends BaseClient {
|
|
|
395
446
|
}
|
|
396
447
|
}
|
|
397
448
|
}
|
|
398
|
-
else if (p.
|
|
449
|
+
else if (p.thought) {
|
|
450
|
+
// A thinking part is identified by `thought: true` — NOT merely by
|
|
451
|
+
// the presence of a thoughtSignature. Gemini 3 also rides a
|
|
452
|
+
// thoughtSignature on the final answer part (no `thought` flag) for
|
|
453
|
+
// stateless reasoning continuity; keying on the signature alone
|
|
454
|
+
// would misfile that answer text as a thinking block and drop it
|
|
455
|
+
// from the completion. See egonSchiele/agency-lang.
|
|
399
456
|
const block = {
|
|
400
457
|
text: p.text || "",
|
|
401
|
-
signature: p.thoughtSignature,
|
|
458
|
+
signature: p.thoughtSignature || "",
|
|
402
459
|
};
|
|
403
460
|
thinkingBlocks.push(block);
|
|
404
461
|
yield {
|
package/dist/models.d.ts
CHANGED
|
@@ -773,6 +773,108 @@ export declare const textModels: readonly [{
|
|
|
773
773
|
readonly structuredOutput: true;
|
|
774
774
|
readonly temperatureSupported: false;
|
|
775
775
|
readonly provider: "openai-responses";
|
|
776
|
+
}, {
|
|
777
|
+
readonly type: "text";
|
|
778
|
+
readonly modelName: "gpt-5.6-sol";
|
|
779
|
+
readonly description: "GPT-5.6 Sol is the flagship model of the GPT-5.6 family for the most complex coding and agentic tasks. 1M context window. Standard pricing for ≤272K tokens, 2x input/1.5x output for >272K. Knowledge cutoff: February 2026.";
|
|
780
|
+
readonly maxInputTokens: 1050000;
|
|
781
|
+
readonly maxOutputTokens: 128000;
|
|
782
|
+
readonly inputTokenCost: 5;
|
|
783
|
+
readonly cachedInputTokenCost: 0.5;
|
|
784
|
+
readonly outputTokenCost: 30;
|
|
785
|
+
readonly longContext: {
|
|
786
|
+
readonly inputTokenCost: 10;
|
|
787
|
+
readonly cachedInputTokenCost: 1;
|
|
788
|
+
readonly outputTokenCost: 45;
|
|
789
|
+
readonly thresholdTokens: 200000;
|
|
790
|
+
};
|
|
791
|
+
readonly reasoning: {
|
|
792
|
+
readonly levels: readonly ["none", "low", "medium", "high", "xhigh", "max"];
|
|
793
|
+
readonly defaultLevel: "medium";
|
|
794
|
+
readonly canDisable: true;
|
|
795
|
+
readonly outputsThinking: false;
|
|
796
|
+
readonly outputsSignatures: false;
|
|
797
|
+
};
|
|
798
|
+
readonly modalities: {
|
|
799
|
+
readonly input: readonly ["text", "image", "pdf"];
|
|
800
|
+
readonly output: readonly ["text"];
|
|
801
|
+
};
|
|
802
|
+
readonly knowledge: "2026-02-16";
|
|
803
|
+
readonly releaseDate: "2026-07-09";
|
|
804
|
+
readonly lastUpdated: "2026-07-09";
|
|
805
|
+
readonly family: "gpt";
|
|
806
|
+
readonly openWeights: false;
|
|
807
|
+
readonly structuredOutput: true;
|
|
808
|
+
readonly temperatureSupported: false;
|
|
809
|
+
readonly provider: "openai";
|
|
810
|
+
}, {
|
|
811
|
+
readonly type: "text";
|
|
812
|
+
readonly modelName: "gpt-5.6-terra";
|
|
813
|
+
readonly description: "GPT-5.6 Terra balances capability and cost — competitive with GPT-5.5 at roughly half the price. 1M context window. Standard pricing for ≤272K tokens, 2x input/1.5x output for >272K. Knowledge cutoff: February 2026.";
|
|
814
|
+
readonly maxInputTokens: 1050000;
|
|
815
|
+
readonly maxOutputTokens: 128000;
|
|
816
|
+
readonly inputTokenCost: 2.5;
|
|
817
|
+
readonly cachedInputTokenCost: 0.25;
|
|
818
|
+
readonly outputTokenCost: 15;
|
|
819
|
+
readonly longContext: {
|
|
820
|
+
readonly inputTokenCost: 5;
|
|
821
|
+
readonly cachedInputTokenCost: 0.5;
|
|
822
|
+
readonly outputTokenCost: 22.5;
|
|
823
|
+
readonly thresholdTokens: 200000;
|
|
824
|
+
};
|
|
825
|
+
readonly reasoning: {
|
|
826
|
+
readonly levels: readonly ["none", "low", "medium", "high", "xhigh", "max"];
|
|
827
|
+
readonly defaultLevel: "medium";
|
|
828
|
+
readonly canDisable: true;
|
|
829
|
+
readonly outputsThinking: false;
|
|
830
|
+
readonly outputsSignatures: false;
|
|
831
|
+
};
|
|
832
|
+
readonly modalities: {
|
|
833
|
+
readonly input: readonly ["text", "image", "pdf"];
|
|
834
|
+
readonly output: readonly ["text"];
|
|
835
|
+
};
|
|
836
|
+
readonly knowledge: "2026-02-16";
|
|
837
|
+
readonly releaseDate: "2026-07-09";
|
|
838
|
+
readonly lastUpdated: "2026-07-09";
|
|
839
|
+
readonly family: "gpt";
|
|
840
|
+
readonly openWeights: false;
|
|
841
|
+
readonly structuredOutput: true;
|
|
842
|
+
readonly temperatureSupported: false;
|
|
843
|
+
readonly provider: "openai";
|
|
844
|
+
}, {
|
|
845
|
+
readonly type: "text";
|
|
846
|
+
readonly modelName: "gpt-5.6-luna";
|
|
847
|
+
readonly description: "GPT-5.6 Luna is the fast, most affordable member of the GPT-5.6 family. 1M context window. Standard pricing for ≤272K tokens, 2x input/1.5x output for >272K. Knowledge cutoff: February 2026.";
|
|
848
|
+
readonly maxInputTokens: 1050000;
|
|
849
|
+
readonly maxOutputTokens: 128000;
|
|
850
|
+
readonly inputTokenCost: 1;
|
|
851
|
+
readonly cachedInputTokenCost: 0.1;
|
|
852
|
+
readonly outputTokenCost: 6;
|
|
853
|
+
readonly longContext: {
|
|
854
|
+
readonly inputTokenCost: 2;
|
|
855
|
+
readonly cachedInputTokenCost: 0.2;
|
|
856
|
+
readonly outputTokenCost: 9;
|
|
857
|
+
readonly thresholdTokens: 200000;
|
|
858
|
+
};
|
|
859
|
+
readonly reasoning: {
|
|
860
|
+
readonly levels: readonly ["none", "low", "medium", "high", "xhigh", "max"];
|
|
861
|
+
readonly defaultLevel: "medium";
|
|
862
|
+
readonly canDisable: true;
|
|
863
|
+
readonly outputsThinking: false;
|
|
864
|
+
readonly outputsSignatures: false;
|
|
865
|
+
};
|
|
866
|
+
readonly modalities: {
|
|
867
|
+
readonly input: readonly ["text", "image", "pdf"];
|
|
868
|
+
readonly output: readonly ["text"];
|
|
869
|
+
};
|
|
870
|
+
readonly knowledge: "2026-02-16";
|
|
871
|
+
readonly releaseDate: "2026-07-09";
|
|
872
|
+
readonly lastUpdated: "2026-07-09";
|
|
873
|
+
readonly family: "gpt";
|
|
874
|
+
readonly openWeights: false;
|
|
875
|
+
readonly structuredOutput: true;
|
|
876
|
+
readonly temperatureSupported: false;
|
|
877
|
+
readonly provider: "openai";
|
|
776
878
|
}, {
|
|
777
879
|
readonly type: "text";
|
|
778
880
|
readonly modelName: "gemini-3.1-pro-preview";
|
|
@@ -1040,7 +1142,7 @@ export declare const textModels: readonly [{
|
|
|
1040
1142
|
}, {
|
|
1041
1143
|
readonly type: "text";
|
|
1042
1144
|
readonly modelName: "gemini-2.0-flash";
|
|
1043
|
-
readonly description: "Workhorse model for all daily tasks. Strong overall performance and supports real-time streaming Live API. 1M context window.
|
|
1145
|
+
readonly description: "Workhorse model for all daily tasks. Strong overall performance and supports real-time streaming Live API. 1M context window. RETIRED: Shut down June 1, 2026. Use gemini-2.5-flash instead.";
|
|
1044
1146
|
readonly maxInputTokens: 1048576;
|
|
1045
1147
|
readonly maxOutputTokens: 8192;
|
|
1046
1148
|
readonly inputTokenCost: 0.1;
|
|
@@ -1073,7 +1175,7 @@ export declare const textModels: readonly [{
|
|
|
1073
1175
|
}, {
|
|
1074
1176
|
readonly type: "text";
|
|
1075
1177
|
readonly modelName: "gemini-2.0-flash-lite";
|
|
1076
|
-
readonly description: "Cost effective offering to support high throughput.
|
|
1178
|
+
readonly description: "Cost effective offering to support high throughput. RETIRED: Shut down June 1, 2026. Use gemini-2.5-flash-lite instead.";
|
|
1077
1179
|
readonly maxInputTokens: 1048576;
|
|
1078
1180
|
readonly maxOutputTokens: 8192;
|
|
1079
1181
|
readonly inputTokenCost: 0.075;
|
|
@@ -1439,6 +1541,12 @@ export declare const imageModels: readonly [{
|
|
|
1439
1541
|
readonly provider: "google";
|
|
1440
1542
|
readonly description: "Fast image generation with Gemini 3.1 Flash (GA). Supports resolutions from 512px to 4096px. ~$0.045/image at 512px, $0.067 at 1K, $0.101 at 2K, $0.151 at 4K.";
|
|
1441
1543
|
readonly costPerImage: 0.067;
|
|
1544
|
+
}, {
|
|
1545
|
+
readonly type: "image";
|
|
1546
|
+
readonly modelName: "gemini-3.1-flash-lite-image";
|
|
1547
|
+
readonly provider: "google";
|
|
1548
|
+
readonly description: "aka Nano Banana 2 Lite (GA 2026-06-30). Fastest, most cost-effective Gemini image model (~4s generation). ~$0.034/image at 1K. Recommended replacement for gemini-2.5-flash-image.";
|
|
1549
|
+
readonly costPerImage: 0.034;
|
|
1442
1550
|
}];
|
|
1443
1551
|
export declare const embeddingsModels: EmbeddingsModel[];
|
|
1444
1552
|
export type TextModelName = (typeof textModels)[number]["modelName"];
|
package/dist/models.js
CHANGED
|
@@ -733,6 +733,111 @@ export const textModels = [
|
|
|
733
733
|
temperatureSupported: false,
|
|
734
734
|
provider: "openai-responses",
|
|
735
735
|
},
|
|
736
|
+
{
|
|
737
|
+
type: "text",
|
|
738
|
+
modelName: "gpt-5.6-sol",
|
|
739
|
+
description: "GPT-5.6 Sol is the flagship model of the GPT-5.6 family for the most complex coding and agentic tasks. 1M context window. Standard pricing for ≤272K tokens, 2x input/1.5x output for >272K. Knowledge cutoff: February 2026.",
|
|
740
|
+
maxInputTokens: 1050000,
|
|
741
|
+
maxOutputTokens: 128000,
|
|
742
|
+
inputTokenCost: 5,
|
|
743
|
+
cachedInputTokenCost: 0.5,
|
|
744
|
+
outputTokenCost: 30,
|
|
745
|
+
longContext: {
|
|
746
|
+
inputTokenCost: 10,
|
|
747
|
+
cachedInputTokenCost: 1,
|
|
748
|
+
outputTokenCost: 45,
|
|
749
|
+
thresholdTokens: 200000,
|
|
750
|
+
},
|
|
751
|
+
reasoning: {
|
|
752
|
+
levels: ["none", "low", "medium", "high", "xhigh", "max"],
|
|
753
|
+
defaultLevel: "medium",
|
|
754
|
+
canDisable: true,
|
|
755
|
+
outputsThinking: false,
|
|
756
|
+
outputsSignatures: false,
|
|
757
|
+
},
|
|
758
|
+
modalities: {
|
|
759
|
+
input: ["text", "image", "pdf"],
|
|
760
|
+
output: ["text"],
|
|
761
|
+
},
|
|
762
|
+
knowledge: "2026-02-16",
|
|
763
|
+
releaseDate: "2026-07-09",
|
|
764
|
+
lastUpdated: "2026-07-09",
|
|
765
|
+
family: "gpt",
|
|
766
|
+
openWeights: false,
|
|
767
|
+
structuredOutput: true,
|
|
768
|
+
temperatureSupported: false,
|
|
769
|
+
provider: "openai",
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
type: "text",
|
|
773
|
+
modelName: "gpt-5.6-terra",
|
|
774
|
+
description: "GPT-5.6 Terra balances capability and cost — competitive with GPT-5.5 at roughly half the price. 1M context window. Standard pricing for ≤272K tokens, 2x input/1.5x output for >272K. Knowledge cutoff: February 2026.",
|
|
775
|
+
maxInputTokens: 1050000,
|
|
776
|
+
maxOutputTokens: 128000,
|
|
777
|
+
inputTokenCost: 2.5,
|
|
778
|
+
cachedInputTokenCost: 0.25,
|
|
779
|
+
outputTokenCost: 15,
|
|
780
|
+
longContext: {
|
|
781
|
+
inputTokenCost: 5,
|
|
782
|
+
cachedInputTokenCost: 0.5,
|
|
783
|
+
outputTokenCost: 22.5,
|
|
784
|
+
thresholdTokens: 200000,
|
|
785
|
+
},
|
|
786
|
+
reasoning: {
|
|
787
|
+
levels: ["none", "low", "medium", "high", "xhigh", "max"],
|
|
788
|
+
defaultLevel: "medium",
|
|
789
|
+
canDisable: true,
|
|
790
|
+
outputsThinking: false,
|
|
791
|
+
outputsSignatures: false,
|
|
792
|
+
},
|
|
793
|
+
modalities: {
|
|
794
|
+
input: ["text", "image", "pdf"],
|
|
795
|
+
output: ["text"],
|
|
796
|
+
},
|
|
797
|
+
knowledge: "2026-02-16",
|
|
798
|
+
releaseDate: "2026-07-09",
|
|
799
|
+
lastUpdated: "2026-07-09",
|
|
800
|
+
family: "gpt",
|
|
801
|
+
openWeights: false,
|
|
802
|
+
structuredOutput: true,
|
|
803
|
+
temperatureSupported: false,
|
|
804
|
+
provider: "openai",
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
type: "text",
|
|
808
|
+
modelName: "gpt-5.6-luna",
|
|
809
|
+
description: "GPT-5.6 Luna is the fast, most affordable member of the GPT-5.6 family. 1M context window. Standard pricing for ≤272K tokens, 2x input/1.5x output for >272K. Knowledge cutoff: February 2026.",
|
|
810
|
+
maxInputTokens: 1050000,
|
|
811
|
+
maxOutputTokens: 128000,
|
|
812
|
+
inputTokenCost: 1,
|
|
813
|
+
cachedInputTokenCost: 0.1,
|
|
814
|
+
outputTokenCost: 6,
|
|
815
|
+
longContext: {
|
|
816
|
+
inputTokenCost: 2,
|
|
817
|
+
cachedInputTokenCost: 0.2,
|
|
818
|
+
outputTokenCost: 9,
|
|
819
|
+
thresholdTokens: 200000,
|
|
820
|
+
},
|
|
821
|
+
reasoning: {
|
|
822
|
+
levels: ["none", "low", "medium", "high", "xhigh", "max"],
|
|
823
|
+
defaultLevel: "medium",
|
|
824
|
+
canDisable: true,
|
|
825
|
+
outputsThinking: false,
|
|
826
|
+
outputsSignatures: false,
|
|
827
|
+
},
|
|
828
|
+
modalities: {
|
|
829
|
+
input: ["text", "image", "pdf"],
|
|
830
|
+
output: ["text"],
|
|
831
|
+
},
|
|
832
|
+
knowledge: "2026-02-16",
|
|
833
|
+
releaseDate: "2026-07-09",
|
|
834
|
+
lastUpdated: "2026-07-09",
|
|
835
|
+
family: "gpt",
|
|
836
|
+
openWeights: false,
|
|
837
|
+
structuredOutput: true,
|
|
838
|
+
temperatureSupported: false,
|
|
839
|
+
provider: "openai",
|
|
840
|
+
},
|
|
736
841
|
{
|
|
737
842
|
type: "text",
|
|
738
843
|
modelName: "gemini-3.1-pro-preview",
|
|
@@ -1009,7 +1114,7 @@ export const textModels = [
|
|
|
1009
1114
|
{
|
|
1010
1115
|
type: "text",
|
|
1011
1116
|
modelName: "gemini-2.0-flash",
|
|
1012
|
-
description: "Workhorse model for all daily tasks. Strong overall performance and supports real-time streaming Live API. 1M context window.
|
|
1117
|
+
description: "Workhorse model for all daily tasks. Strong overall performance and supports real-time streaming Live API. 1M context window. RETIRED: Shut down June 1, 2026. Use gemini-2.5-flash instead.",
|
|
1013
1118
|
maxInputTokens: 1048576,
|
|
1014
1119
|
maxOutputTokens: 8192,
|
|
1015
1120
|
inputTokenCost: 0.1,
|
|
@@ -1044,7 +1149,7 @@ export const textModels = [
|
|
|
1044
1149
|
{
|
|
1045
1150
|
type: "text",
|
|
1046
1151
|
modelName: "gemini-2.0-flash-lite",
|
|
1047
|
-
description: "Cost effective offering to support high throughput.
|
|
1152
|
+
description: "Cost effective offering to support high throughput. RETIRED: Shut down June 1, 2026. Use gemini-2.5-flash-lite instead.",
|
|
1048
1153
|
maxInputTokens: 1048576,
|
|
1049
1154
|
maxOutputTokens: 8192,
|
|
1050
1155
|
inputTokenCost: 0.075,
|
|
@@ -1434,6 +1539,13 @@ export const imageModels = [
|
|
|
1434
1539
|
description: "Fast image generation with Gemini 3.1 Flash (GA). Supports resolutions from 512px to 4096px. ~$0.045/image at 512px, $0.067 at 1K, $0.101 at 2K, $0.151 at 4K.",
|
|
1435
1540
|
costPerImage: 0.067,
|
|
1436
1541
|
},
|
|
1542
|
+
{
|
|
1543
|
+
type: "image",
|
|
1544
|
+
modelName: "gemini-3.1-flash-lite-image",
|
|
1545
|
+
provider: "google",
|
|
1546
|
+
description: "aka Nano Banana 2 Lite (GA 2026-06-30). Fastest, most cost-effective Gemini image model (~4s generation). ~$0.034/image at 1K. Recommended replacement for gemini-2.5-flash-image.",
|
|
1547
|
+
costPerImage: 0.034,
|
|
1548
|
+
},
|
|
1437
1549
|
];
|
|
1438
1550
|
export const embeddingsModels = [
|
|
1439
1551
|
{
|
package/dist/types.d.ts
CHANGED
|
@@ -162,6 +162,14 @@ export type StreamChunk = {
|
|
|
162
162
|
} | {
|
|
163
163
|
type: "tool_call";
|
|
164
164
|
toolCall: ToolCall;
|
|
165
|
+
}
|
|
166
|
+
/** A provider-run web search (server-side tool). Emitted the moment a
|
|
167
|
+
* search block completes, so consumers can surface the query live. One
|
|
168
|
+
* chunk per search; a turn may emit several. The same queries also appear
|
|
169
|
+
* in the final `done` result's `hostedToolResults`. */
|
|
170
|
+
| {
|
|
171
|
+
type: "web_search";
|
|
172
|
+
query: string;
|
|
165
173
|
} | {
|
|
166
174
|
type: "done";
|
|
167
175
|
result: PromptResult;
|