smoltalk 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ export type CostEstimate = {
4
4
  outputCost: number;
5
5
  cachedInputCost?: number;
6
6
  cacheCreationInputCost?: number;
7
+ hostedToolsCost?: number;
7
8
  totalCost: number;
8
9
  currency: string;
9
10
  };
@@ -12,6 +13,7 @@ export declare const CostEstimateSchema: z.ZodObject<{
12
13
  outputCost: z.ZodNumber;
13
14
  cachedInputCost: z.ZodOptional<z.ZodNumber>;
14
15
  cacheCreationInputCost: z.ZodOptional<z.ZodNumber>;
16
+ hostedToolsCost: z.ZodOptional<z.ZodNumber>;
15
17
  totalCost: z.ZodNumber;
16
18
  currency: z.ZodString;
17
19
  }, z.core.$strip>;
@@ -4,6 +4,7 @@ export const CostEstimateSchema = z.object({
4
4
  outputCost: z.number(),
5
5
  cachedInputCost: z.number().optional(),
6
6
  cacheCreationInputCost: z.number().optional(),
7
+ hostedToolsCost: z.number().optional(),
7
8
  totalCost: z.number(),
8
9
  currency: z.string(),
9
10
  });
package/dist/types.d.ts CHANGED
@@ -5,6 +5,7 @@ import { Message } from "./classes/message/index.js";
5
5
  import { ToolCall } from "./classes/ToolCall.js";
6
6
  import { Model } from "./model.js";
7
7
  import { ModelName } from "./models.js";
8
+ import type { ModelDataBlob } from "./modelData.js";
8
9
  import { Result } from "./types/result.js";
9
10
  import { TokenUsage } from "./types/tokenUsage.js";
10
11
  import { CostEstimate } from "./types/costEstimate.js";
@@ -44,6 +45,8 @@ export type SmolConfig = {
44
45
  }>;
45
46
  /** Arbitrary metadata passed to custom model providers. */
46
47
  metadata?: Record<string, any>;
48
+ /** Refreshed model/hosted-tool data (from refreshModels) to layer over the baked-in registry for this call. */
49
+ modelData?: ModelDataBlob;
47
50
  /** The conversation messages to send to the model. */
48
51
  messages: Message[];
49
52
  /** Tools (functions) the model can call. */
@@ -52,6 +55,10 @@ export type SmolConfig = {
52
55
  description?: string;
53
56
  schema: ZodType;
54
57
  }[];
58
+ /** Provider hosted tools (server-side) to enable for this call, by capability
59
+ * name (catalog category), e.g. ["web_search"]. Distinct from `tools`
60
+ * (client functions) — hosted tools run server-side and can't be intercepted. */
61
+ hostedTools?: string[];
55
62
  /** Maximum number of tokens the model can generate in its response. */
56
63
  maxTokens?: number;
57
64
  /** Sampling temperature (0-2). (OpenAI only) */
@@ -96,6 +103,27 @@ export type ToolLoopDetection = {
96
103
  intervention?: "remove-tool" | "remove-all-tools" | "throw-error" | "halt-execution";
97
104
  excludeTools?: string[];
98
105
  };
106
+ export type WebSearchSource = {
107
+ url: string;
108
+ title?: string;
109
+ snippet?: string;
110
+ };
111
+ export type WebSearchCitation = {
112
+ url: string;
113
+ title?: string;
114
+ startIndex?: number;
115
+ endIndex?: number;
116
+ };
117
+ export type HostedToolResult = {
118
+ tool: string;
119
+ provider: string;
120
+ queries?: string[];
121
+ sources?: WebSearchSource[];
122
+ citations?: WebSearchCitation[];
123
+ callCount?: number;
124
+ estimatedCost?: number;
125
+ raw?: unknown;
126
+ };
99
127
  export type PromptResult = {
100
128
  output: string | null;
101
129
  toolCalls: ToolCall[];
@@ -103,8 +131,9 @@ export type PromptResult = {
103
131
  usage?: TokenUsage;
104
132
  cost?: CostEstimate;
105
133
  model?: ModelName;
134
+ hostedToolResults?: HostedToolResult[];
106
135
  };
107
- export declare function promptResult({ output, toolCalls, thinkingBlocks, usage, cost, model, }: Partial<PromptResult>): PromptResult;
136
+ export declare function promptResult({ output, toolCalls, thinkingBlocks, usage, cost, model, hostedToolResults, }: Partial<PromptResult>): PromptResult;
108
137
  export type StreamChunk = {
109
138
  type: "text";
110
139
  text: string;
package/dist/types.js CHANGED
@@ -2,7 +2,7 @@ export * from "./types/result.js";
2
2
  import z from "zod";
3
3
  export * from "./types/costEstimate.js";
4
4
  export * from "./types/tokenUsage.js";
5
- export function promptResult({ output, toolCalls, thinkingBlocks, usage, cost, model, }) {
5
+ export function promptResult({ output, toolCalls, thinkingBlocks, usage, cost, model, hostedToolResults, }) {
6
6
  return {
7
7
  output: output || null,
8
8
  toolCalls: toolCalls || [],
@@ -10,6 +10,7 @@ export function promptResult({ output, toolCalls, thinkingBlocks, usage, cost, m
10
10
  usage,
11
11
  cost,
12
12
  model,
13
+ hostedToolResults,
13
14
  };
14
15
  }
15
16
  export const TextPartSchema = z.object({
@@ -0,0 +1,19 @@
1
+ import type { ModelDataBlob } from "../modelData.js";
2
+ import type { CostEstimate } from "../types/costEstimate.js";
3
+ import type { HostedToolResult, WebSearchSource, WebSearchCitation } from "../types.js";
4
+ export declare const WEB_SEARCH = "web_search";
5
+ export declare const IMPLEMENTED_HOSTED_TOOLS: Set<string>;
6
+ export declare function validateHostedTools(requested: string[] | undefined, model: string, modelData?: ModelDataBlob): string | null;
7
+ export declare function estimateHostedToolCost(result: HostedToolResult, model: string, modelData?: ModelDataBlob): number | undefined;
8
+ export declare function foldHostedToolCost(cost: CostEstimate | undefined, results: HostedToolResult[]): CostEstimate | undefined;
9
+ export declare function webSearchResult(provider: string, parts: {
10
+ queries?: string[];
11
+ sources?: WebSearchSource[];
12
+ citations?: WebSearchCitation[];
13
+ callCount?: number;
14
+ raw?: unknown;
15
+ }): HostedToolResult;
16
+ export declare function applyHostedToolCost(results: HostedToolResult[], cost: CostEstimate | undefined, model: string, modelData?: ModelDataBlob): {
17
+ results: HostedToolResult[];
18
+ cost: CostEstimate | undefined;
19
+ };
@@ -0,0 +1,104 @@
1
+ import { getHostedTools, hostedToolPricingFor } from "../models.js";
2
+ import { getModel } from "../models.js";
3
+ import { round } from "./util.js";
4
+ export const WEB_SEARCH = "web_search";
5
+ // Capabilities whose runtime translation/parsing smoltalk actually implements.
6
+ export const IMPLEMENTED_HOSTED_TOOLS = new Set([WEB_SEARCH]);
7
+ // Returns an error message if any requested capability is unusable for this
8
+ // model, else null. Capabilities are matched against the catalog `category`.
9
+ export function validateHostedTools(requested, model, modelData) {
10
+ if (!requested || requested.length === 0) {
11
+ return null;
12
+ }
13
+ const allCategories = new Set(getHostedTools({ includeDisabled: true, modelData }).map((t) => t.category));
14
+ const availableCategories = new Set(getHostedTools({ model, modelData }).map((t) => t.category));
15
+ for (const name of requested) {
16
+ if (!IMPLEMENTED_HOSTED_TOOLS.has(name)) {
17
+ if (allCategories.has(name)) {
18
+ return `Hosted tool "${name}" is in the catalog but not yet supported by smoltalk at runtime.`;
19
+ }
20
+ return `Unknown hosted tool "${name}".`;
21
+ }
22
+ if (!availableCategories.has(name)) {
23
+ const provider = getModel(model, modelData)?.provider ?? "unknown";
24
+ return `${name} is a hosted capability; ${model} (${provider}) doesn't offer it — pass a search function as a tool instead.`;
25
+ }
26
+ }
27
+ return null;
28
+ }
29
+ // callCount x catalog price for the capability on this model. Only per_call
30
+ // pricing yields a number (web search); undefined otherwise.
31
+ export function estimateHostedToolCost(result, model, modelData) {
32
+ if (!result.callCount) {
33
+ return undefined;
34
+ }
35
+ const tools = getHostedTools({ model, includeDisabled: true, modelData });
36
+ const tool = tools.find((t) => t.category === result.tool && t.provider === result.provider);
37
+ if (!tool) {
38
+ return undefined;
39
+ }
40
+ const price = hostedToolPricingFor(tool, model);
41
+ if (!price || price.unit !== "per_call" || price.amount === undefined) {
42
+ return undefined;
43
+ }
44
+ return round(result.callCount * price.amount, 6);
45
+ }
46
+ // Fold per-result estimatedCost into a CostEstimate (hostedToolsCost + totalCost).
47
+ export function foldHostedToolCost(cost, results) {
48
+ let hosted = 0;
49
+ for (const r of results) {
50
+ if (r.estimatedCost) {
51
+ hosted += r.estimatedCost;
52
+ }
53
+ }
54
+ if (hosted === 0) {
55
+ return cost;
56
+ }
57
+ const hostedRounded = round(hosted, 6);
58
+ if (!cost) {
59
+ return {
60
+ inputCost: 0,
61
+ outputCost: 0,
62
+ hostedToolsCost: hostedRounded,
63
+ totalCost: hostedRounded,
64
+ currency: "USD",
65
+ };
66
+ }
67
+ return {
68
+ ...cost,
69
+ hostedToolsCost: round((cost.hostedToolsCost || 0) + hosted, 6),
70
+ totalCost: round(cost.totalCost + hosted, 6),
71
+ };
72
+ }
73
+ // Build a normalized web-search result, omitting empty fields. One place for the
74
+ // "assemble result" shape so each parser doesn't repeat it.
75
+ export function webSearchResult(provider, parts) {
76
+ const result = { tool: WEB_SEARCH, provider };
77
+ if (parts.queries && parts.queries.length > 0) {
78
+ result.queries = parts.queries;
79
+ }
80
+ if (parts.sources && parts.sources.length > 0) {
81
+ result.sources = parts.sources;
82
+ }
83
+ if (parts.citations && parts.citations.length > 0) {
84
+ result.citations = parts.citations;
85
+ }
86
+ if (parts.callCount) {
87
+ result.callCount = parts.callCount;
88
+ }
89
+ if (parts.raw !== undefined) {
90
+ result.raw = parts.raw;
91
+ }
92
+ return result;
93
+ }
94
+ // Estimate per-result cost and fold the total into the CostEstimate. One call
95
+ // for every client, so the estimate+fold sequence lives in exactly one place.
96
+ export function applyHostedToolCost(results, cost, model, modelData) {
97
+ for (const r of results) {
98
+ const est = estimateHostedToolCost(r, model, modelData);
99
+ if (est !== undefined) {
100
+ r.estimatedCost = est;
101
+ }
102
+ }
103
+ return { results, cost: foldHostedToolCost(cost, results) };
104
+ }
@@ -1,9 +1,10 @@
1
+ import type { ModelDataBlob } from "../modelData.js";
1
2
  /**
2
3
  * Resolve the provider for a given model name.
3
4
  * If an explicit provider is given, returns it directly.
4
5
  * Otherwise looks up the model in the registry.
5
6
  */
6
- export declare function resolveProvider(modelName: string, explicitProvider?: string): string;
7
+ export declare function resolveProvider(modelName: string, explicitProvider?: string, modelData?: ModelDataBlob): string;
7
8
  type ApiKeyConfig = {
8
9
  openAiApiKey?: string;
9
10
  googleApiKey?: string;
@@ -5,10 +5,10 @@ import { SmolError } from "../smolError.js";
5
5
  * If an explicit provider is given, returns it directly.
6
6
  * Otherwise looks up the model in the registry.
7
7
  */
8
- export function resolveProvider(modelName, explicitProvider) {
8
+ export function resolveProvider(modelName, explicitProvider, modelData) {
9
9
  if (explicitProvider)
10
10
  return explicitProvider;
11
- const model = getModel(modelName);
11
+ const model = getModel(modelName, modelData);
12
12
  if (model === undefined) {
13
13
  throw new SmolError(`Model ${modelName} is not recognized. Please specify a known model, or explicitly set the provider option in the config.`);
14
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoltalk",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "A common interface for LLM APIs",
5
5
  "homepage": "https://github.com/egonSchiele/smoltalk",
6
6
  "files": [
@@ -35,6 +35,9 @@
35
35
  "openai": "^6.15.0",
36
36
  "zod": "^4.3.6"
37
37
  },
38
+ "devDependencies": {
39
+ "tsx": "^4.19.2"
40
+ },
38
41
  "scripts": {
39
42
  "test": "vitest --exclude=**/*.live.test.ts",
40
43
  "test:live": "vitest run lib/clients/*.live.test.ts lib/embed/*.live.test.ts lib/image/*.live.test.ts",
@@ -42,6 +45,8 @@
42
45
  "build": "rm -rf dist && tsc",
43
46
  "start": "cd dist && node index.js",
44
47
  "doc": "typedoc --disableSources --out docs lib && prettier docs/ --write",
45
- "typecheck": "tsc --noEmit"
48
+ "typecheck": "tsc --noEmit",
49
+ "seed-data": "tsx scripts/seed-model-data.ts",
50
+ "refresh-data": "tsx scripts/refresh-from-modelsdev.ts"
46
51
  }
47
52
  }