smoltalk 0.4.1 → 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.
- package/README.md +139 -0
- package/dist/classes/ToolCall.js +4 -3
- package/dist/classes/message/AssistantMessage.d.ts +1 -0
- package/dist/classes/message/AssistantMessage.js +5 -3
- package/dist/classes/message/DeveloperMessage.js +5 -3
- package/dist/classes/message/SystemMessage.js +5 -3
- package/dist/classes/message/ToolMessage.js +4 -3
- package/dist/classes/message/UserMessage.js +5 -3
- package/dist/client.js +5 -3
- package/dist/clients/anthropic.d.ts +3 -1
- package/dist/clients/anthropic.js +71 -13
- package/dist/clients/baseClient.js +11 -1
- package/dist/clients/google.d.ts +4 -0
- package/dist/clients/google.js +92 -24
- package/dist/clients/ollama.d.ts +1 -0
- package/dist/clients/ollama.js +18 -7
- package/dist/clients/openai.js +7 -4
- package/dist/clients/openaiResponses.d.ts +3 -0
- package/dist/clients/openaiResponses.js +60 -7
- package/dist/embed/openai.js +3 -3
- package/dist/embed.d.ts +5 -2
- package/dist/embed.js +14 -3
- package/dist/image/google.js +3 -3
- package/dist/image/openai.js +6 -6
- package/dist/image.d.ts +5 -2
- package/dist/image.js +14 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/model.d.ts +4 -2
- package/dist/model.js +7 -5
- package/dist/modelData.d.ts +40 -0
- package/dist/modelData.js +222 -0
- package/dist/models.d.ts +658 -760
- package/dist/models.js +836 -60
- package/dist/smolError.d.ts +78 -5
- package/dist/smolError.js +134 -9
- package/dist/types/costEstimate.d.ts +2 -0
- package/dist/types/costEstimate.js +1 -0
- package/dist/types.d.ts +30 -1
- package/dist/types.js +2 -1
- package/dist/util/hostedTools.d.ts +19 -0
- package/dist/util/hostedTools.js +104 -0
- package/dist/util/httpError.d.ts +23 -0
- package/dist/util/httpError.js +237 -0
- package/dist/util/provider.d.ts +2 -1
- package/dist/util/provider.js +2 -2
- package/package.json +7 -2
package/dist/clients/google.js
CHANGED
|
@@ -3,11 +3,65 @@ import { ToolCall } from "../classes/ToolCall.js";
|
|
|
3
3
|
import { getLogger } from "../util/logger.js";
|
|
4
4
|
import { addCosts, addTokenUsage, success, } from "../types.js";
|
|
5
5
|
import { zodToGoogleTool } from "../util/tool.js";
|
|
6
|
-
import { SmolContentPolicyError, SmolContextWindowExceededError, } from "../smolError.js";
|
|
6
|
+
import { SmolContentPolicyError, SmolContextWindowExceededError, smolErrorForStatus, } from "../smolError.js";
|
|
7
|
+
import { extractHttpErrorFields } from "../util/httpError.js";
|
|
7
8
|
import { sanitizeAttributes } from "../util/util.js";
|
|
8
9
|
import { BaseClient } from "./baseClient.js";
|
|
10
|
+
import { WEB_SEARCH, webSearchResult, applyHostedToolCost } from "../util/hostedTools.js";
|
|
9
11
|
import { Model } from "../model.js";
|
|
10
12
|
import { userMessage } from "../classes/message/index.js";
|
|
13
|
+
export function googleWebSearchEntries(hostedTools) {
|
|
14
|
+
if (hostedTools && hostedTools.includes(WEB_SEARCH)) {
|
|
15
|
+
return [{ googleSearch: {} }];
|
|
16
|
+
}
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
export function parseGoogleHostedTools(result, provider, model) {
|
|
20
|
+
const queries = [];
|
|
21
|
+
const sources = [];
|
|
22
|
+
const citations = [];
|
|
23
|
+
const raw = [];
|
|
24
|
+
for (const candidate of result.candidates || []) {
|
|
25
|
+
const gm = candidate.groundingMetadata;
|
|
26
|
+
// A candidate without groundingMetadata simply didn't ground — nothing to
|
|
27
|
+
// extract. If no candidate grounded, this returns [] overall.
|
|
28
|
+
if (!gm) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
raw.push(gm);
|
|
32
|
+
for (const q of gm.webSearchQueries || []) {
|
|
33
|
+
queries.push(q);
|
|
34
|
+
}
|
|
35
|
+
const chunks = gm.groundingChunks || [];
|
|
36
|
+
for (const c of chunks) {
|
|
37
|
+
if (c.web && typeof c.web.uri === "string") {
|
|
38
|
+
sources.push({ url: c.web.uri, title: c.web.title });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const s of gm.groundingSupports || []) {
|
|
42
|
+
for (const idx of s.groundingChunkIndices || []) {
|
|
43
|
+
const chunk = chunks[idx];
|
|
44
|
+
if (chunk && chunk.web && typeof chunk.web.uri === "string") {
|
|
45
|
+
citations.push({
|
|
46
|
+
url: chunk.web.uri,
|
|
47
|
+
title: chunk.web.title,
|
|
48
|
+
startIndex: s.segment?.startIndex,
|
|
49
|
+
endIndex: s.segment?.endIndex,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (queries.length === 0 && sources.length === 0) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
// Gemini 2.5 bills per prompt (1), Gemini 3+ per query.
|
|
59
|
+
let callCount = queries.length;
|
|
60
|
+
if (model.startsWith("gemini-2.5")) {
|
|
61
|
+
callCount = 1;
|
|
62
|
+
}
|
|
63
|
+
return [webSearchResult(provider, { queries, sources, citations, callCount, raw })];
|
|
64
|
+
}
|
|
11
65
|
export class SmolGoogle extends BaseClient {
|
|
12
66
|
client;
|
|
13
67
|
logger;
|
|
@@ -19,7 +73,7 @@ export class SmolGoogle extends BaseClient {
|
|
|
19
73
|
}
|
|
20
74
|
this.client = new GoogleGenAI({ apiKey: config.googleApiKey });
|
|
21
75
|
this.logger = getLogger();
|
|
22
|
-
this.model = new Model(config.model);
|
|
76
|
+
this.model = new Model(config.model, undefined, config.modelData);
|
|
23
77
|
}
|
|
24
78
|
getClient() {
|
|
25
79
|
return this.client;
|
|
@@ -68,8 +122,16 @@ export class SmolGoogle extends BaseClient {
|
|
|
68
122
|
if (systemParts.length > 0) {
|
|
69
123
|
genConfig.systemInstruction = systemParts.join("\n");
|
|
70
124
|
}
|
|
71
|
-
|
|
72
|
-
|
|
125
|
+
const hostedEntries = googleWebSearchEntries(config.hostedTools);
|
|
126
|
+
if (tools.length > 0 || hostedEntries.length > 0) {
|
|
127
|
+
const toolGroups = [];
|
|
128
|
+
if (tools.length > 0) {
|
|
129
|
+
toolGroups.push({ functionDeclarations: tools });
|
|
130
|
+
}
|
|
131
|
+
for (const entry of hostedEntries) {
|
|
132
|
+
toolGroups.push(entry);
|
|
133
|
+
}
|
|
134
|
+
genConfig.tools = toolGroups;
|
|
73
135
|
}
|
|
74
136
|
if (config.responseFormat) {
|
|
75
137
|
genConfig.responseMimeType = "application/json";
|
|
@@ -88,6 +150,18 @@ export class SmolGoogle extends BaseClient {
|
|
|
88
150
|
...sanitizeAttributes(config.rawAttributes),
|
|
89
151
|
};
|
|
90
152
|
}
|
|
153
|
+
rethrowAsSmolError(error) {
|
|
154
|
+
const http = { ...extractHttpErrorFields(error), cause: error };
|
|
155
|
+
const msg = (error.message || "").toLowerCase();
|
|
156
|
+
if (msg.includes("token") &&
|
|
157
|
+
(msg.includes("exceed") || msg.includes("too long") || msg.includes("limit"))) {
|
|
158
|
+
throw new SmolContextWindowExceededError(error.message, http);
|
|
159
|
+
}
|
|
160
|
+
if (http.status !== undefined) {
|
|
161
|
+
throw smolErrorForStatus(error.message, http);
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
91
165
|
async _textSync(config) {
|
|
92
166
|
const signal = this.getAbortSignal(config);
|
|
93
167
|
const request = {
|
|
@@ -180,14 +254,7 @@ export class SmolGoogle extends BaseClient {
|
|
|
180
254
|
result = await this.client.models.generateContent(request);
|
|
181
255
|
}
|
|
182
256
|
catch (error) {
|
|
183
|
-
|
|
184
|
-
if (msg.includes("token") &&
|
|
185
|
-
(msg.includes("exceed") ||
|
|
186
|
-
msg.includes("too long") ||
|
|
187
|
-
msg.includes("limit"))) {
|
|
188
|
-
throw new SmolContextWindowExceededError(error.message);
|
|
189
|
-
}
|
|
190
|
-
throw error;
|
|
257
|
+
this.rethrowAsSmolError(error);
|
|
191
258
|
}
|
|
192
259
|
this.logger.debug("Response from Google Gemini:", JSON.stringify(result, null, 2));
|
|
193
260
|
this.statelogClient?.promptResponse(result);
|
|
@@ -226,15 +293,23 @@ export class SmolGoogle extends BaseClient {
|
|
|
226
293
|
const output = textContent || null;
|
|
227
294
|
// Extract usage and calculate cost
|
|
228
295
|
const { usage, cost } = this.calculateUsageAndCost(result.usageMetadata);
|
|
296
|
+
const parsed = parseGoogleHostedTools(result, "google", request.model);
|
|
297
|
+
const { results: hostedToolResults, cost: finalCost } = applyHostedToolCost(parsed, cost, request.model, this.config.modelData);
|
|
229
298
|
// Return the response, updating the chat history
|
|
230
|
-
|
|
299
|
+
const promptResult = {
|
|
231
300
|
output,
|
|
232
301
|
toolCalls,
|
|
233
|
-
...(thinkingBlocks.length > 0 && { thinkingBlocks }),
|
|
234
302
|
usage,
|
|
235
|
-
cost,
|
|
303
|
+
cost: finalCost,
|
|
236
304
|
model: request.model,
|
|
237
|
-
}
|
|
305
|
+
};
|
|
306
|
+
if (thinkingBlocks.length > 0) {
|
|
307
|
+
promptResult.thinkingBlocks = thinkingBlocks;
|
|
308
|
+
}
|
|
309
|
+
if (hostedToolResults.length > 0) {
|
|
310
|
+
promptResult.hostedToolResults = hostedToolResults;
|
|
311
|
+
}
|
|
312
|
+
return success(promptResult);
|
|
238
313
|
}
|
|
239
314
|
async *_textStream(config) {
|
|
240
315
|
const signal = this.getAbortSignal(config);
|
|
@@ -257,14 +332,7 @@ export class SmolGoogle extends BaseClient {
|
|
|
257
332
|
stream = await this.client.models.generateContentStream(request);
|
|
258
333
|
}
|
|
259
334
|
catch (error) {
|
|
260
|
-
|
|
261
|
-
if (msg.includes("token") &&
|
|
262
|
-
(msg.includes("exceed") ||
|
|
263
|
-
msg.includes("too long") ||
|
|
264
|
-
msg.includes("limit"))) {
|
|
265
|
-
throw new SmolContextWindowExceededError(error.message);
|
|
266
|
-
}
|
|
267
|
-
throw error;
|
|
335
|
+
this.rethrowAsSmolError(error);
|
|
268
336
|
}
|
|
269
337
|
let content = "";
|
|
270
338
|
const toolCallsMap = new Map();
|
package/dist/clients/ollama.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare class SmolOllama extends BaseClient implements SmolClient {
|
|
|
12
12
|
getClient(): Ollama;
|
|
13
13
|
getModel(): ModelName;
|
|
14
14
|
private calculateUsageAndCost;
|
|
15
|
+
private rethrowAsSmolError;
|
|
15
16
|
_textSync(config: SmolConfig): Promise<Result<PromptResult>>;
|
|
16
17
|
_textStream(config: SmolConfig): AsyncGenerator<StreamChunk>;
|
|
17
18
|
}
|
package/dist/clients/ollama.js
CHANGED
|
@@ -5,7 +5,8 @@ import { success, } from "../types.js";
|
|
|
5
5
|
import { zodToGoogleTool } from "../util/tool.js";
|
|
6
6
|
import { sanitizeAttributes } from "../util/util.js";
|
|
7
7
|
import { BaseClient } from "./baseClient.js";
|
|
8
|
-
import { SmolContextWindowExceededError } from "../smolError.js";
|
|
8
|
+
import { SmolContextWindowExceededError, smolErrorForStatus, } from "../smolError.js";
|
|
9
|
+
import { extractHttpErrorFields } from "../util/httpError.js";
|
|
9
10
|
import { Model } from "../model.js";
|
|
10
11
|
export const DEFAULT_OLLAMA_HOST = "http://localhost:11434";
|
|
11
12
|
export class SmolOllama extends BaseClient {
|
|
@@ -15,7 +16,7 @@ export class SmolOllama extends BaseClient {
|
|
|
15
16
|
constructor(config) {
|
|
16
17
|
super(config);
|
|
17
18
|
this.logger = getLogger();
|
|
18
|
-
this.model = new Model(config.model);
|
|
19
|
+
this.model = new Model(config.model, undefined, config.modelData);
|
|
19
20
|
if (config.ollamaApiKey) {
|
|
20
21
|
this.client = new Ollama({
|
|
21
22
|
host: "https://cloud.ollama.com",
|
|
@@ -51,6 +52,17 @@ export class SmolOllama extends BaseClient {
|
|
|
51
52
|
}
|
|
52
53
|
return { usage, cost };
|
|
53
54
|
}
|
|
55
|
+
rethrowAsSmolError(error) {
|
|
56
|
+
const http = { ...extractHttpErrorFields(error), cause: error };
|
|
57
|
+
const msg = (error.message || "").toLowerCase();
|
|
58
|
+
if (msg.includes("context length") || msg.includes("context window")) {
|
|
59
|
+
throw new SmolContextWindowExceededError(error.message, http);
|
|
60
|
+
}
|
|
61
|
+
if (http.status !== undefined) {
|
|
62
|
+
throw smolErrorForStatus(error.message, http);
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
54
66
|
async _textSync(config) {
|
|
55
67
|
const messages = config.messages.map((msg) => msg.toOpenAIMessage());
|
|
56
68
|
const tools = (config.tools || []).map((tool) => {
|
|
@@ -82,11 +94,7 @@ export class SmolOllama extends BaseClient {
|
|
|
82
94
|
result = await this.client.chat(request);
|
|
83
95
|
}
|
|
84
96
|
catch (error) {
|
|
85
|
-
|
|
86
|
-
if (msg.includes("context length") || msg.includes("context window")) {
|
|
87
|
-
throw new SmolContextWindowExceededError(error.message);
|
|
88
|
-
}
|
|
89
|
-
throw error;
|
|
97
|
+
this.rethrowAsSmolError(error);
|
|
90
98
|
}
|
|
91
99
|
finally {
|
|
92
100
|
if (signal && abortHandler) {
|
|
@@ -201,6 +209,9 @@ export class SmolOllama extends BaseClient {
|
|
|
201
209
|
},
|
|
202
210
|
};
|
|
203
211
|
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
this.rethrowAsSmolError(error);
|
|
214
|
+
}
|
|
204
215
|
finally {
|
|
205
216
|
if (signal && abortHandler) {
|
|
206
217
|
signal.removeEventListener("abort", abortHandler);
|
package/dist/clients/openai.js
CHANGED
|
@@ -4,7 +4,8 @@ import { ToolCall } from "../classes/ToolCall.js";
|
|
|
4
4
|
import { isFunctionToolCall, sanitizeAttributes } from "../util/util.js";
|
|
5
5
|
import { getLogger } from "../util/logger.js";
|
|
6
6
|
import { BaseClient } from "./baseClient.js";
|
|
7
|
-
import { SmolContentPolicyError, SmolContextWindowExceededError, } from "../smolError.js";
|
|
7
|
+
import { SmolContentPolicyError, SmolContextWindowExceededError, smolErrorForStatus, } from "../smolError.js";
|
|
8
|
+
import { extractHttpErrorFields } from "../util/httpError.js";
|
|
8
9
|
import { zodToOpenAITool } from "../util/tool.js";
|
|
9
10
|
import { Model } from "../model.js";
|
|
10
11
|
export class SmolOpenAi extends BaseClient {
|
|
@@ -18,7 +19,7 @@ export class SmolOpenAi extends BaseClient {
|
|
|
18
19
|
}
|
|
19
20
|
this.client = new OpenAI({ apiKey: config.openAiApiKey });
|
|
20
21
|
this.logger = getLogger();
|
|
21
|
-
this.model = new Model(config.model);
|
|
22
|
+
this.model = new Model(config.model, undefined, config.modelData);
|
|
22
23
|
}
|
|
23
24
|
getClient() {
|
|
24
25
|
return this.client;
|
|
@@ -74,12 +75,14 @@ export class SmolOpenAi extends BaseClient {
|
|
|
74
75
|
}
|
|
75
76
|
rethrowAsSmolError(error) {
|
|
76
77
|
if (error instanceof OpenAI.APIError) {
|
|
78
|
+
const http = { ...extractHttpErrorFields(error), cause: error };
|
|
77
79
|
if (error.code === "context_length_exceeded") {
|
|
78
|
-
throw new SmolContextWindowExceededError(error.message);
|
|
80
|
+
throw new SmolContextWindowExceededError(error.message, http);
|
|
79
81
|
}
|
|
80
82
|
if (error.code === "content_policy_violation") {
|
|
81
|
-
throw new SmolContentPolicyError(error.message);
|
|
83
|
+
throw new SmolContentPolicyError(error.message, http);
|
|
82
84
|
}
|
|
85
|
+
throw smolErrorForStatus(error.message, http);
|
|
83
86
|
}
|
|
84
87
|
throw error;
|
|
85
88
|
}
|
|
@@ -2,7 +2,10 @@ import OpenAI from "openai";
|
|
|
2
2
|
import { PromptResult, Result, SmolClient, SmolConfig, StreamChunk } from "../types.js";
|
|
3
3
|
import { BaseClient } from "./baseClient.js";
|
|
4
4
|
import { ModelName } from "../models.js";
|
|
5
|
+
import { HostedToolResult } from "../types.js";
|
|
5
6
|
export type SmolOpenAiResponsesConfig = SmolConfig;
|
|
7
|
+
export declare function openaiResponsesWebSearchEntries(hostedTools?: string[]): any[];
|
|
8
|
+
export declare function parseOpenAIResponsesHostedTools(response: any, provider: string): HostedToolResult[];
|
|
6
9
|
export declare class SmolOpenAiResponses extends BaseClient implements SmolClient {
|
|
7
10
|
private client;
|
|
8
11
|
private logger;
|
|
@@ -5,8 +5,48 @@ import { getLogger } from "../util/logger.js";
|
|
|
5
5
|
import { BaseClient } from "./baseClient.js";
|
|
6
6
|
import { zodToOpenAIResponsesTool } from "../util/tool.js";
|
|
7
7
|
import { sanitizeAttributes } from "../util/util.js";
|
|
8
|
+
import { WEB_SEARCH, webSearchResult, applyHostedToolCost } from "../util/hostedTools.js";
|
|
8
9
|
import { Model } from "../model.js";
|
|
9
|
-
import { SmolContentPolicyError, SmolContextWindowExceededError, } from "../smolError.js";
|
|
10
|
+
import { SmolContentPolicyError, SmolContextWindowExceededError, smolErrorForStatus, } from "../smolError.js";
|
|
11
|
+
import { extractHttpErrorFields } from "../util/httpError.js";
|
|
12
|
+
export function openaiResponsesWebSearchEntries(hostedTools) {
|
|
13
|
+
if (hostedTools && hostedTools.includes(WEB_SEARCH)) {
|
|
14
|
+
return [{ type: "web_search" }];
|
|
15
|
+
}
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
export function parseOpenAIResponsesHostedTools(response, provider) {
|
|
19
|
+
const queries = [];
|
|
20
|
+
const sources = [];
|
|
21
|
+
const citations = [];
|
|
22
|
+
const raw = [];
|
|
23
|
+
let callCount = 0;
|
|
24
|
+
for (const item of response.output || []) {
|
|
25
|
+
if (item.type === "web_search_call") {
|
|
26
|
+
raw.push(item);
|
|
27
|
+
callCount += 1;
|
|
28
|
+
// action.query is present for `search` actions but not always (e.g. open_page).
|
|
29
|
+
const query = item.action?.query;
|
|
30
|
+
if (typeof query === "string") {
|
|
31
|
+
queries.push(query);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else if (item.type === "message") {
|
|
35
|
+
for (const part of item.content || []) {
|
|
36
|
+
for (const ann of part.annotations || []) {
|
|
37
|
+
if (ann.type === "url_citation" && typeof ann.url === "string") {
|
|
38
|
+
citations.push({ url: ann.url, title: ann.title, startIndex: ann.start_index, endIndex: ann.end_index });
|
|
39
|
+
sources.push({ url: ann.url, title: ann.title });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (callCount === 0 && citations.length === 0) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
return [webSearchResult(provider, { queries, sources, citations, callCount, raw })];
|
|
49
|
+
}
|
|
10
50
|
export class SmolOpenAiResponses extends BaseClient {
|
|
11
51
|
client;
|
|
12
52
|
logger;
|
|
@@ -18,7 +58,7 @@ export class SmolOpenAiResponses extends BaseClient {
|
|
|
18
58
|
}
|
|
19
59
|
this.client = new OpenAI({ apiKey: config.openAiApiKey });
|
|
20
60
|
this.logger = getLogger();
|
|
21
|
-
this.model = new Model(config.model);
|
|
61
|
+
this.model = new Model(config.model, undefined, config.modelData);
|
|
22
62
|
}
|
|
23
63
|
getClient() {
|
|
24
64
|
return this.client;
|
|
@@ -61,6 +101,11 @@ export class SmolOpenAiResponses extends BaseClient {
|
|
|
61
101
|
description: tool.description,
|
|
62
102
|
}));
|
|
63
103
|
}
|
|
104
|
+
const hostedEntries = openaiResponsesWebSearchEntries(config.hostedTools);
|
|
105
|
+
if (hostedEntries.length > 0) {
|
|
106
|
+
const existing = Array.isArray(request.tools) ? request.tools : [];
|
|
107
|
+
request.tools = [...existing, ...hostedEntries];
|
|
108
|
+
}
|
|
64
109
|
if (config.temperature !== undefined) {
|
|
65
110
|
request.temperature = config.temperature;
|
|
66
111
|
}
|
|
@@ -107,12 +152,14 @@ export class SmolOpenAiResponses extends BaseClient {
|
|
|
107
152
|
}
|
|
108
153
|
rethrowAsSmolError(error) {
|
|
109
154
|
if (error instanceof OpenAI.APIError) {
|
|
155
|
+
const http = { ...extractHttpErrorFields(error), cause: error };
|
|
110
156
|
if (error.code === "context_length_exceeded") {
|
|
111
|
-
throw new SmolContextWindowExceededError(error.message);
|
|
157
|
+
throw new SmolContextWindowExceededError(error.message, http);
|
|
112
158
|
}
|
|
113
159
|
if (error.code === "content_policy_violation") {
|
|
114
|
-
throw new SmolContentPolicyError(error.message);
|
|
160
|
+
throw new SmolContentPolicyError(error.message, http);
|
|
115
161
|
}
|
|
162
|
+
throw smolErrorForStatus(error.message, http);
|
|
116
163
|
}
|
|
117
164
|
throw error;
|
|
118
165
|
}
|
|
@@ -141,13 +188,19 @@ export class SmolOpenAiResponses extends BaseClient {
|
|
|
141
188
|
}
|
|
142
189
|
}
|
|
143
190
|
const { usage, cost } = this.calculateUsageAndCost(response.usage);
|
|
144
|
-
|
|
191
|
+
const parsed = parseOpenAIResponsesHostedTools(response, "openai-responses");
|
|
192
|
+
const { results: hostedToolResults, cost: finalCost } = applyHostedToolCost(parsed, cost, this.getModel(), this.config.modelData);
|
|
193
|
+
const result = {
|
|
145
194
|
output,
|
|
146
195
|
toolCalls,
|
|
147
196
|
usage,
|
|
148
|
-
cost,
|
|
197
|
+
cost: finalCost,
|
|
149
198
|
model: this.getModel(),
|
|
150
|
-
}
|
|
199
|
+
};
|
|
200
|
+
if (hostedToolResults.length > 0) {
|
|
201
|
+
result.hostedToolResults = hostedToolResults;
|
|
202
|
+
}
|
|
203
|
+
return success(result);
|
|
151
204
|
}
|
|
152
205
|
async *_textStream(config) {
|
|
153
206
|
const request = this.buildRequest(config);
|
package/dist/embed/openai.js
CHANGED
|
@@ -16,7 +16,7 @@ export async function openaiEmbed(inputs, config, apiKey) {
|
|
|
16
16
|
.sort((a, b) => a.index - b.index)
|
|
17
17
|
.map((d) => d.embedding);
|
|
18
18
|
const inputTokens = response.usage.prompt_tokens;
|
|
19
|
-
const costEstimate = calculateEmbeddingCost(config.model, inputTokens);
|
|
19
|
+
const costEstimate = calculateEmbeddingCost(config.model, inputTokens, config.modelData);
|
|
20
20
|
return success({
|
|
21
21
|
embeddings,
|
|
22
22
|
model: response.model,
|
|
@@ -28,8 +28,8 @@ export async function openaiEmbed(inputs, config, apiKey) {
|
|
|
28
28
|
return failure(err instanceof Error ? err.message : "OpenAI embedding request failed");
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
function calculateEmbeddingCost(modelName, inputTokens) {
|
|
32
|
-
const model = getModel(modelName);
|
|
31
|
+
function calculateEmbeddingCost(modelName, inputTokens, modelData) {
|
|
32
|
+
const model = getModel(modelName, modelData);
|
|
33
33
|
if (!model || !isEmbeddingsModel(model) || !model.tokenCost)
|
|
34
34
|
return undefined;
|
|
35
35
|
const inputCost = round((inputTokens * model.tokenCost) / 1_000_000, 6);
|
package/dist/embed.d.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ModelDataBlob } from "./modelData.js";
|
|
2
2
|
import { Result } from "./types/result.js";
|
|
3
3
|
import { TokenUsage } from "./types/tokenUsage.js";
|
|
4
4
|
import { CostEstimate } from "./types/costEstimate.js";
|
|
5
5
|
export type EmbedConfig = {
|
|
6
6
|
model: string;
|
|
7
|
-
provider?:
|
|
7
|
+
provider?: string;
|
|
8
8
|
dimensions?: number;
|
|
9
9
|
openAiApiKey?: string;
|
|
10
10
|
googleApiKey?: string;
|
|
11
11
|
ollamaApiKey?: string;
|
|
12
12
|
ollamaHost?: string;
|
|
13
13
|
metadata?: Record<string, unknown>;
|
|
14
|
+
modelData?: ModelDataBlob;
|
|
14
15
|
};
|
|
15
16
|
export type EmbedResult = {
|
|
16
17
|
embeddings: number[][];
|
|
@@ -18,4 +19,6 @@ export type EmbedResult = {
|
|
|
18
19
|
tokenUsage?: TokenUsage;
|
|
19
20
|
costEstimate?: CostEstimate;
|
|
20
21
|
};
|
|
22
|
+
export type EmbedProvider = (inputs: string[], config: EmbedConfig) => Promise<Result<EmbedResult>>;
|
|
23
|
+
export declare function registerEmbeddingProvider(name: string, fn: EmbedProvider): void;
|
|
21
24
|
export declare function embed(input: string | string[], config: EmbedConfig): Promise<Result<EmbedResult>>;
|
package/dist/embed.js
CHANGED
|
@@ -3,11 +3,17 @@ import { resolveProvider, resolveApiKey } from "./util/provider.js";
|
|
|
3
3
|
import { openaiEmbed } from "./embed/openai.js";
|
|
4
4
|
import { googleEmbed } from "./embed/google.js";
|
|
5
5
|
import { ollamaEmbed } from "./embed/ollama.js";
|
|
6
|
+
// Null-prototype so provider names like "toString"/"__proto__" can't collide
|
|
7
|
+
// with Object.prototype or pollute the registry.
|
|
8
|
+
const registeredEmbedProviders = Object.create(null);
|
|
9
|
+
export function registerEmbeddingProvider(name, fn) {
|
|
10
|
+
registeredEmbedProviders[name] = fn;
|
|
11
|
+
}
|
|
6
12
|
export async function embed(input, config) {
|
|
7
13
|
const inputs = Array.isArray(input) ? input : [input];
|
|
8
14
|
let provider;
|
|
9
15
|
try {
|
|
10
|
-
provider = resolveProvider(config.model, config.provider);
|
|
16
|
+
provider = resolveProvider(config.model, config.provider, config.modelData);
|
|
11
17
|
}
|
|
12
18
|
catch (err) {
|
|
13
19
|
return failure(err instanceof Error ? err.message : "Failed to resolve provider");
|
|
@@ -29,7 +35,12 @@ export async function embed(input, config) {
|
|
|
29
35
|
}
|
|
30
36
|
case "ollama":
|
|
31
37
|
return ollamaEmbed(inputs, config, apiKey, config.ollamaHost);
|
|
32
|
-
default:
|
|
33
|
-
|
|
38
|
+
default: {
|
|
39
|
+
const custom = registeredEmbedProviders[provider];
|
|
40
|
+
if (custom) {
|
|
41
|
+
return custom(inputs, config);
|
|
42
|
+
}
|
|
43
|
+
return failure(`Provider "${provider}" does not support embeddings. Register one with registerEmbeddingProvider(name, fn).`);
|
|
44
|
+
}
|
|
34
45
|
}
|
|
35
46
|
}
|
package/dist/image/google.js
CHANGED
|
@@ -41,15 +41,15 @@ export async function googleImage(input, config, apiKey) {
|
|
|
41
41
|
return success({
|
|
42
42
|
images,
|
|
43
43
|
model: config.model,
|
|
44
|
-
costEstimate: calculateGoogleImageCost(config.model, images.length),
|
|
44
|
+
costEstimate: calculateGoogleImageCost(config.model, images.length, config.modelData),
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
catch (err) {
|
|
48
48
|
return failure(err instanceof Error ? err.message : "Google image request failed");
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
function calculateGoogleImageCost(modelName, imageCount) {
|
|
52
|
-
const model = getModel(modelName);
|
|
51
|
+
function calculateGoogleImageCost(modelName, imageCount, modelData) {
|
|
52
|
+
const model = getModel(modelName, modelData);
|
|
53
53
|
if (!model || !isImageModel(model) || !model.costPerImage)
|
|
54
54
|
return undefined;
|
|
55
55
|
const totalCost = round(model.costPerImage * imageCount, COST_DECIMAL_PLACES);
|
package/dist/image/openai.js
CHANGED
|
@@ -24,8 +24,8 @@ export async function openaiImage(input, config, apiKey) {
|
|
|
24
24
|
}));
|
|
25
25
|
const tokenUsage = extractUsage(response);
|
|
26
26
|
const costEstimate = tokenUsage
|
|
27
|
-
? calculateImageCost(config.model, tokenUsage)
|
|
28
|
-
: calculatePerImageCost(config.model, images.length);
|
|
27
|
+
? calculateImageCost(config.model, tokenUsage, config.modelData)
|
|
28
|
+
: calculatePerImageCost(config.model, images.length, config.modelData);
|
|
29
29
|
return success({
|
|
30
30
|
images,
|
|
31
31
|
model: config.model,
|
|
@@ -97,8 +97,8 @@ function extractUsage(response) {
|
|
|
97
97
|
totalTokens: u.total_tokens,
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
|
-
function calculateImageCost(modelName, usage) {
|
|
101
|
-
const model = getModel(modelName);
|
|
100
|
+
function calculateImageCost(modelName, usage, modelData) {
|
|
101
|
+
const model = getModel(modelName, modelData);
|
|
102
102
|
if (!model || !isImageModel(model))
|
|
103
103
|
return undefined;
|
|
104
104
|
const totalIn = usage.inputTokens ?? 0;
|
|
@@ -131,8 +131,8 @@ function calculateImageCost(modelName, usage) {
|
|
|
131
131
|
currency: "USD",
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
|
-
function calculatePerImageCost(modelName, imageCount) {
|
|
135
|
-
const model = getModel(modelName);
|
|
134
|
+
function calculatePerImageCost(modelName, imageCount, modelData) {
|
|
135
|
+
const model = getModel(modelName, modelData);
|
|
136
136
|
if (!model || !isImageModel(model) || !model.costPerImage)
|
|
137
137
|
return undefined;
|
|
138
138
|
const totalCost = round(model.costPerImage * imageCount, COST_DECIMAL_PLACES);
|
package/dist/image.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ModelDataBlob } from "./modelData.js";
|
|
2
2
|
import { Result } from "./types/result.js";
|
|
3
3
|
import { TokenUsage } from "./types/tokenUsage.js";
|
|
4
4
|
import { CostEstimate } from "./types/costEstimate.js";
|
|
@@ -11,7 +11,7 @@ export type ImageInput = string | {
|
|
|
11
11
|
};
|
|
12
12
|
export type ImageConfig = {
|
|
13
13
|
model: string;
|
|
14
|
-
provider?:
|
|
14
|
+
provider?: string;
|
|
15
15
|
n?: number;
|
|
16
16
|
size?: string;
|
|
17
17
|
quality?: "low" | "medium" | "high" | "auto";
|
|
@@ -20,6 +20,7 @@ export type ImageConfig = {
|
|
|
20
20
|
openAiApiKey?: string;
|
|
21
21
|
googleApiKey?: string;
|
|
22
22
|
metadata?: Record<string, unknown>;
|
|
23
|
+
modelData?: ModelDataBlob;
|
|
23
24
|
};
|
|
24
25
|
export type GeneratedImage = {
|
|
25
26
|
data: Uint8Array;
|
|
@@ -32,4 +33,6 @@ export type ImageGenResult = {
|
|
|
32
33
|
tokenUsage?: TokenUsage;
|
|
33
34
|
costEstimate?: CostEstimate;
|
|
34
35
|
};
|
|
36
|
+
export type ImageProvider = (input: ImageInput, config: ImageConfig) => Promise<Result<ImageGenResult>>;
|
|
37
|
+
export declare function registerImageProvider(name: string, fn: ImageProvider): void;
|
|
35
38
|
export declare function image(input: ImageInput, config: ImageConfig): Promise<Result<ImageGenResult>>;
|
package/dist/image.js
CHANGED
|
@@ -2,10 +2,16 @@ import { failure } from "./types/result.js";
|
|
|
2
2
|
import { resolveProvider, resolveApiKey } from "./util/provider.js";
|
|
3
3
|
import { openaiImage } from "./image/openai.js";
|
|
4
4
|
import { googleImage } from "./image/google.js";
|
|
5
|
+
// Null-prototype so provider names like "toString"/"__proto__" can't collide
|
|
6
|
+
// with Object.prototype or pollute the registry.
|
|
7
|
+
const registeredImageProviders = Object.create(null);
|
|
8
|
+
export function registerImageProvider(name, fn) {
|
|
9
|
+
registeredImageProviders[name] = fn;
|
|
10
|
+
}
|
|
5
11
|
export async function image(input, config) {
|
|
6
12
|
let provider;
|
|
7
13
|
try {
|
|
8
|
-
provider = resolveProvider(config.model, config.provider);
|
|
14
|
+
provider = resolveProvider(config.model, config.provider, config.modelData);
|
|
9
15
|
}
|
|
10
16
|
catch (err) {
|
|
11
17
|
return failure(err instanceof Error ? err.message : "Failed to resolve provider");
|
|
@@ -31,7 +37,12 @@ export async function image(input, config) {
|
|
|
31
37
|
}
|
|
32
38
|
return googleImage(input, config, apiKey);
|
|
33
39
|
}
|
|
34
|
-
default:
|
|
35
|
-
|
|
40
|
+
default: {
|
|
41
|
+
const custom = registeredImageProviders[provider];
|
|
42
|
+
if (custom) {
|
|
43
|
+
return custom(input, config);
|
|
44
|
+
}
|
|
45
|
+
return failure(`Provider "${provider}" does not support image generation. Register one with registerImageProvider(name, fn).`);
|
|
46
|
+
}
|
|
36
47
|
}
|
|
37
48
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/model.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { ModelName, Provider } from "./models.js";
|
|
2
2
|
import { ModelLike } from "./types.js";
|
|
3
|
+
import type { ModelDataBlob } from "./modelData.js";
|
|
3
4
|
export declare class Model {
|
|
4
5
|
private model;
|
|
5
6
|
private provider?;
|
|
6
|
-
|
|
7
|
+
private modelData?;
|
|
8
|
+
constructor(model: ModelName, provider?: Provider, modelData?: ModelDataBlob);
|
|
7
9
|
getModel(): ModelName;
|
|
8
10
|
getProvider(): Provider | undefined;
|
|
9
11
|
private lookupProvider;
|
|
@@ -22,5 +24,5 @@ export declare class Model {
|
|
|
22
24
|
} | null;
|
|
23
25
|
toString(): string;
|
|
24
26
|
toJSON(): ModelName;
|
|
25
|
-
static create(model: ModelLike, provider?: Provider): Model;
|
|
27
|
+
static create(model: ModelLike, provider?: Provider, modelData?: ModelDataBlob): Model;
|
|
26
28
|
}
|