smoltalk 0.8.1 → 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.
@@ -3,6 +3,16 @@ import { PromptResult, Result, SmolClient, SmolConfig, StreamChunk, HostedToolRe
3
3
  import { BaseClient } from "./baseClient.js";
4
4
  import { ModelName } from "../models.js";
5
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;
6
16
  export declare function anthropicWebSearchEntries(hostedTools?: string[]): any[];
7
17
  export declare function parseAnthropicHostedTools(response: any, provider: string): HostedToolResult[];
8
18
  type EphemeralCacheControl = {
@@ -46,6 +46,18 @@ export function mergeConsecutiveMessages(messages) {
46
46
  }
47
47
  return merged;
48
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
+ }
49
61
  export function anthropicWebSearchEntries(hostedTools) {
50
62
  if (hostedTools && hostedTools.includes(WEB_SEARCH)) {
51
63
  return [{ type: "web_search_20250305", name: "web_search" }];
@@ -247,7 +259,22 @@ export class SmolAnthropic extends BaseClient {
247
259
  // Normalize the user's provider-agnostic thinking/effort config into the
248
260
  // shape this specific model accepts. Both `thinking.enabled` and
249
261
  // `reasoningEffort` are treated as a request to think.
250
- 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;
251
278
  const cachingEnabled = config.caching?.enabled !== false;
252
279
  const baseRequest = { system, messages: anthropicMessages, tools };
253
280
  const finalRequest = cachingEnabled
@@ -416,6 +443,18 @@ export class SmolAnthropic extends BaseClient {
416
443
  const toolBlocks = new Map();
417
444
  // Track thinking blocks by index: index -> { text, signature }
418
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;
419
458
  let inputTokens = 0;
420
459
  let cacheReadTokens = 0;
421
460
  let cacheCreationTokens = 0;
@@ -435,6 +474,25 @@ export class SmolAnthropic extends BaseClient {
435
474
  arguments: "",
436
475
  });
437
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
+ }
438
496
  else if (event.content_block.type === "thinking") {
439
497
  thinkingBlockMap.set(event.index, { text: "", signature: "" });
440
498
  }
@@ -449,6 +507,10 @@ export class SmolAnthropic extends BaseClient {
449
507
  if (block) {
450
508
  block.arguments += event.delta.partial_json;
451
509
  }
510
+ const searchBlock = webSearchBlocks.get(event.index);
511
+ if (searchBlock) {
512
+ searchBlock.arguments += event.delta.partial_json;
513
+ }
452
514
  }
453
515
  else if (event.delta.type === "thinking_delta") {
454
516
  const block = thinkingBlockMap.get(event.index);
@@ -462,6 +524,15 @@ export class SmolAnthropic extends BaseClient {
462
524
  block.signature = event.delta.signature;
463
525
  }
464
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
+ }
465
536
  }
466
537
  else if (event.type === "content_block_stop") {
467
538
  // Emit thinking chunk once the block is fully assembled
@@ -473,6 +544,29 @@ export class SmolAnthropic extends BaseClient {
473
544
  signature: thinkingBlock.signature,
474
545
  };
475
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
+ }
476
570
  }
477
571
  else if (event.type === "message_delta") {
478
572
  outputTokens = event.usage.output_tokens;
@@ -485,6 +579,10 @@ export class SmolAnthropic extends BaseClient {
485
579
  if (event.usage.cache_creation_input_tokens != null) {
486
580
  cacheCreationTokens = event.usage.cache_creation_input_tokens;
487
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
+ }
488
586
  }
489
587
  }
490
588
  this.logger.debug("Streaming response completed from Anthropic");
@@ -510,7 +608,28 @@ export class SmolAnthropic extends BaseClient {
510
608
  if (cacheCreationTokens > 0) {
511
609
  usage.cacheCreationInputTokens = cacheCreationTokens;
512
610
  }
513
- const cost = this.model.calculateCost(usage) ?? undefined;
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
+ }
514
633
  yield {
515
634
  type: "done",
516
635
  result: {
@@ -520,6 +639,7 @@ export class SmolAnthropic extends BaseClient {
520
639
  usage,
521
640
  cost,
522
641
  model: this.getModel(),
642
+ ...(hostedToolResults.length > 0 && { hostedToolResults }),
523
643
  },
524
644
  };
525
645
  }
@@ -258,7 +258,12 @@ export class BaseClient {
258
258
  retries > 0) {
259
259
  const allowExtraKeys = promptConfig.responseFormatOptions?.allowExtraKeys ?? false;
260
260
  try {
261
- const parsed = JSON.parse(output);
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,
@@ -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[];
@@ -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
- // Gemini rejects mixing built-in (server-side) tools with function calling
138
- // unless the caller opts in. Only required when both kinds coexist.
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 (!config.thinking?.enabled && config.reasoningEffort) {
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.thoughtSignature) {
295
- // Capture thought parts (thought: true indicates a thinking part)
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" && !part.thought) {
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.thoughtSignature) {
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. DEPRECATED: Will be shut down on March 31, 2026.";
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. DEPRECATED: Will be shut down on March 31, 2026. Use gemini-2.5-flash-lite instead.";
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. DEPRECATED: Will be shut down on March 31, 2026.",
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. DEPRECATED: Will be shut down on March 31, 2026. Use gemini-2.5-flash-lite instead.",
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoltalk",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "A common interface for LLM APIs",
5
5
  "homepage": "https://github.com/egonSchiele/smoltalk",
6
6
  "files": [