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/smolError.d.ts
CHANGED
|
@@ -1,15 +1,88 @@
|
|
|
1
|
+
export interface SmolErrorOptions {
|
|
2
|
+
/** HTTP status code from the provider response, when the error came from an HTTP call. */
|
|
3
|
+
status?: number;
|
|
4
|
+
/**
|
|
5
|
+
* Curated, safe-to-log subset of the provider response headers (see
|
|
6
|
+
* `extractHttpErrorFields` for the allowlist). The full, unredacted headers
|
|
7
|
+
* live on the raw provider error at `cause`.
|
|
8
|
+
*/
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Suggested wait before retrying, in milliseconds, parsed from the
|
|
12
|
+
* `retry-after-ms` / `retry-after` response headers (when available).
|
|
13
|
+
*/
|
|
14
|
+
retryAfterMs?: number;
|
|
15
|
+
/** Provider request id (`x-request-id` / `request-id`), useful for support tickets. */
|
|
16
|
+
requestId?: string;
|
|
17
|
+
/** The underlying provider error this was derived from, if any. */
|
|
18
|
+
cause?: unknown;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Base error type for all smoltalk failures. Carries a curated, safe-to-log
|
|
22
|
+
* snapshot of the HTTP response on `status` / `headers` / `retryAfterMs` /
|
|
23
|
+
* `requestId`.
|
|
24
|
+
*
|
|
25
|
+
* **Security note on `cause`:** the raw provider error is attached at `cause`
|
|
26
|
+
* as a non-enumerable property (using the ES2022 `Error` `{ cause }` option),
|
|
27
|
+
* so `JSON.stringify(err)` and most error serializers (Sentry, pino, Bunyan)
|
|
28
|
+
* will NOT walk it. The custom `toJSON()` below similarly omits both `cause`
|
|
29
|
+
* and `stack`. The provider error can still contain `authorization` headers,
|
|
30
|
+
* cookies, and echoed prompt bodies — direct access (`err.cause`) and Node's
|
|
31
|
+
* native `console.error` chain still work, so handle deliberately.
|
|
32
|
+
*/
|
|
1
33
|
export declare class SmolError extends Error {
|
|
2
|
-
|
|
34
|
+
/** HTTP status code from the provider response, when available. */
|
|
35
|
+
readonly status?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Curated, safe-to-log subset of the provider response headers. For the full,
|
|
38
|
+
* unredacted set (including any session cookies the provider sent), reach for
|
|
39
|
+
* the raw provider error on `cause` — but note that `cause` is non-enumerable
|
|
40
|
+
* specifically to keep it out of casual logging.
|
|
41
|
+
*/
|
|
42
|
+
readonly headers?: Record<string, string>;
|
|
43
|
+
/**
|
|
44
|
+
* Suggested wait before retrying, in milliseconds, parsed from the
|
|
45
|
+
* `retry-after-ms` / `retry-after` response headers (when available).
|
|
46
|
+
* Always non-negative and capped at a sane ceiling (see `httpError.ts`).
|
|
47
|
+
*/
|
|
48
|
+
readonly retryAfterMs?: number;
|
|
49
|
+
/** Provider request id (`x-request-id` / `request-id`), useful for support tickets. */
|
|
50
|
+
readonly requestId?: string;
|
|
51
|
+
constructor(message: string, options?: SmolErrorOptions);
|
|
52
|
+
/**
|
|
53
|
+
* Custom JSON serializer. Returns only the curated/safe fields — never
|
|
54
|
+
* `cause` or `stack`, both of which can leak secrets (auth headers in
|
|
55
|
+
* `cause`, file system paths and source snippets in `stack`).
|
|
56
|
+
*/
|
|
57
|
+
toJSON(): Record<string, unknown>;
|
|
3
58
|
}
|
|
4
59
|
export declare class SmolStructuredOutputError extends SmolError {
|
|
5
|
-
constructor(message: string);
|
|
60
|
+
constructor(message: string, options?: SmolErrorOptions);
|
|
6
61
|
}
|
|
7
62
|
export declare class SmolTimeoutError extends SmolError {
|
|
8
|
-
constructor(message: string);
|
|
63
|
+
constructor(message: string, options?: SmolErrorOptions);
|
|
9
64
|
}
|
|
10
65
|
export declare class SmolContentPolicyError extends SmolError {
|
|
11
|
-
constructor(message: string);
|
|
66
|
+
constructor(message: string, options?: SmolErrorOptions);
|
|
12
67
|
}
|
|
13
68
|
export declare class SmolContextWindowExceededError extends SmolError {
|
|
14
|
-
constructor(message: string);
|
|
69
|
+
constructor(message: string, options?: SmolErrorOptions);
|
|
70
|
+
}
|
|
71
|
+
/** Rate limited (HTTP 429). Inspect `retryAfterMs` to back off. */
|
|
72
|
+
export declare class SmolRateLimitError extends SmolError {
|
|
73
|
+
constructor(message: string, options?: SmolErrorOptions);
|
|
74
|
+
}
|
|
75
|
+
/** Provider temporarily overloaded/unavailable (HTTP 503, or Anthropic's 529). */
|
|
76
|
+
export declare class SmolOverloadedError extends SmolError {
|
|
77
|
+
constructor(message: string, options?: SmolErrorOptions);
|
|
78
|
+
}
|
|
79
|
+
/** Authentication or permission failure (HTTP 401/403). */
|
|
80
|
+
export declare class SmolAuthError extends SmolError {
|
|
81
|
+
constructor(message: string, options?: SmolErrorOptions);
|
|
15
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Pick the most specific SmolError subclass for an HTTP status code, so retry
|
|
85
|
+
* code can `instanceof`-dispatch instead of sniffing magic status numbers.
|
|
86
|
+
* Falls back to the base `SmolError` for unclassified statuses.
|
|
87
|
+
*/
|
|
88
|
+
export declare function smolErrorForStatus(message: string, options?: SmolErrorOptions): SmolError;
|
package/dist/smolError.js
CHANGED
|
@@ -1,30 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error type for all smoltalk failures. Carries a curated, safe-to-log
|
|
3
|
+
* snapshot of the HTTP response on `status` / `headers` / `retryAfterMs` /
|
|
4
|
+
* `requestId`.
|
|
5
|
+
*
|
|
6
|
+
* **Security note on `cause`:** the raw provider error is attached at `cause`
|
|
7
|
+
* as a non-enumerable property (using the ES2022 `Error` `{ cause }` option),
|
|
8
|
+
* so `JSON.stringify(err)` and most error serializers (Sentry, pino, Bunyan)
|
|
9
|
+
* will NOT walk it. The custom `toJSON()` below similarly omits both `cause`
|
|
10
|
+
* and `stack`. The provider error can still contain `authorization` headers,
|
|
11
|
+
* cookies, and echoed prompt bodies — direct access (`err.cause`) and Node's
|
|
12
|
+
* native `console.error` chain still work, so handle deliberately.
|
|
13
|
+
*/
|
|
1
14
|
export class SmolError extends Error {
|
|
2
|
-
|
|
15
|
+
/** HTTP status code from the provider response, when available. */
|
|
16
|
+
status;
|
|
17
|
+
/**
|
|
18
|
+
* Curated, safe-to-log subset of the provider response headers. For the full,
|
|
19
|
+
* unredacted set (including any session cookies the provider sent), reach for
|
|
20
|
+
* the raw provider error on `cause` — but note that `cause` is non-enumerable
|
|
21
|
+
* specifically to keep it out of casual logging.
|
|
22
|
+
*/
|
|
23
|
+
headers;
|
|
24
|
+
/**
|
|
25
|
+
* Suggested wait before retrying, in milliseconds, parsed from the
|
|
26
|
+
* `retry-after-ms` / `retry-after` response headers (when available).
|
|
27
|
+
* Always non-negative and capped at a sane ceiling (see `httpError.ts`).
|
|
28
|
+
*/
|
|
29
|
+
retryAfterMs;
|
|
30
|
+
/** Provider request id (`x-request-id` / `request-id`), useful for support tickets. */
|
|
31
|
+
requestId;
|
|
32
|
+
constructor(message, options = {}) {
|
|
3
33
|
super(message);
|
|
4
34
|
this.name = "SmolTalkError";
|
|
35
|
+
this.status = options.status;
|
|
36
|
+
this.headers = options.headers;
|
|
37
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
38
|
+
this.requestId = options.requestId;
|
|
39
|
+
if (options.cause !== undefined) {
|
|
40
|
+
// Explicit non-enumerable install. A plain `this.cause = options.cause`
|
|
41
|
+
// would create an *enumerable* own property — `JSON.stringify(err)` and
|
|
42
|
+
// structured loggers (`logger.error({ err })`) would then walk `cause`
|
|
43
|
+
// and re-leak the raw provider error (including the unredacted
|
|
44
|
+
// `set-cookie` / `authorization` headers the allowlist stripped from
|
|
45
|
+
// `headers`). This restores the behavior the spec intends for
|
|
46
|
+
// `new Error(msg, { cause })`: `cause` is the escape hatch, not the
|
|
47
|
+
// default-serialized field.
|
|
48
|
+
Object.defineProperty(this, "cause", {
|
|
49
|
+
value: options.cause,
|
|
50
|
+
enumerable: false,
|
|
51
|
+
writable: true,
|
|
52
|
+
configurable: true,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Custom JSON serializer. Returns only the curated/safe fields — never
|
|
58
|
+
* `cause` or `stack`, both of which can leak secrets (auth headers in
|
|
59
|
+
* `cause`, file system paths and source snippets in `stack`).
|
|
60
|
+
*/
|
|
61
|
+
toJSON() {
|
|
62
|
+
return {
|
|
63
|
+
name: this.name,
|
|
64
|
+
message: this.message,
|
|
65
|
+
status: this.status,
|
|
66
|
+
headers: this.headers,
|
|
67
|
+
retryAfterMs: this.retryAfterMs,
|
|
68
|
+
requestId: this.requestId,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Custom Node.js inspect. Node's default error formatter walks `cause` and
|
|
73
|
+
* prints it even when the property is non-enumerable, which would re-leak
|
|
74
|
+
* the raw provider error through `console.error(err)` / `util.inspect(err)`.
|
|
75
|
+
* Override that path to show a placeholder; callers who want the full chain
|
|
76
|
+
* can still reach for `err.cause` explicitly.
|
|
77
|
+
*/
|
|
78
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
79
|
+
const fields = [`${this.name}: ${this.message}`];
|
|
80
|
+
if (this.status !== undefined)
|
|
81
|
+
fields.push(`status: ${this.status}`);
|
|
82
|
+
if (this.requestId !== undefined)
|
|
83
|
+
fields.push(`requestId: ${JSON.stringify(this.requestId)}`);
|
|
84
|
+
if (this.retryAfterMs !== undefined)
|
|
85
|
+
fields.push(`retryAfterMs: ${this.retryAfterMs}`);
|
|
86
|
+
if (this.headers !== undefined)
|
|
87
|
+
fields.push(`headers: ${JSON.stringify(this.headers)}`);
|
|
88
|
+
if (this.cause !== undefined)
|
|
89
|
+
fields.push("cause: [hidden — access err.cause directly]");
|
|
90
|
+
return fields.join("\n ");
|
|
5
91
|
}
|
|
6
92
|
}
|
|
7
93
|
export class SmolStructuredOutputError extends SmolError {
|
|
8
|
-
constructor(message) {
|
|
9
|
-
super(message);
|
|
94
|
+
constructor(message, options = {}) {
|
|
95
|
+
super(message, options);
|
|
10
96
|
this.name = "SmolStructuredOutputError";
|
|
11
97
|
}
|
|
12
98
|
}
|
|
13
99
|
export class SmolTimeoutError extends SmolError {
|
|
14
|
-
constructor(message) {
|
|
15
|
-
super(message);
|
|
100
|
+
constructor(message, options = {}) {
|
|
101
|
+
super(message, options);
|
|
16
102
|
this.name = "SmolTimeoutError";
|
|
17
103
|
}
|
|
18
104
|
}
|
|
19
105
|
export class SmolContentPolicyError extends SmolError {
|
|
20
|
-
constructor(message) {
|
|
21
|
-
super(message);
|
|
106
|
+
constructor(message, options = {}) {
|
|
107
|
+
super(message, options);
|
|
22
108
|
this.name = "SmolContentPolicyError";
|
|
23
109
|
}
|
|
24
110
|
}
|
|
25
111
|
export class SmolContextWindowExceededError extends SmolError {
|
|
26
|
-
constructor(message) {
|
|
27
|
-
super(message);
|
|
112
|
+
constructor(message, options = {}) {
|
|
113
|
+
super(message, options);
|
|
28
114
|
this.name = "SmolContextWindowExceededError";
|
|
29
115
|
}
|
|
30
116
|
}
|
|
117
|
+
/** Rate limited (HTTP 429). Inspect `retryAfterMs` to back off. */
|
|
118
|
+
export class SmolRateLimitError extends SmolError {
|
|
119
|
+
constructor(message, options = {}) {
|
|
120
|
+
super(message, options);
|
|
121
|
+
this.name = "SmolRateLimitError";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** Provider temporarily overloaded/unavailable (HTTP 503, or Anthropic's 529). */
|
|
125
|
+
export class SmolOverloadedError extends SmolError {
|
|
126
|
+
constructor(message, options = {}) {
|
|
127
|
+
super(message, options);
|
|
128
|
+
this.name = "SmolOverloadedError";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** Authentication or permission failure (HTTP 401/403). */
|
|
132
|
+
export class SmolAuthError extends SmolError {
|
|
133
|
+
constructor(message, options = {}) {
|
|
134
|
+
super(message, options);
|
|
135
|
+
this.name = "SmolAuthError";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Pick the most specific SmolError subclass for an HTTP status code, so retry
|
|
140
|
+
* code can `instanceof`-dispatch instead of sniffing magic status numbers.
|
|
141
|
+
* Falls back to the base `SmolError` for unclassified statuses.
|
|
142
|
+
*/
|
|
143
|
+
export function smolErrorForStatus(message, options = {}) {
|
|
144
|
+
const status = options.status;
|
|
145
|
+
if (status === 429) {
|
|
146
|
+
return new SmolRateLimitError(message, options);
|
|
147
|
+
}
|
|
148
|
+
if (status === 503 || status === 529) {
|
|
149
|
+
return new SmolOverloadedError(message, options);
|
|
150
|
+
}
|
|
151
|
+
if (status === 401 || status === 403) {
|
|
152
|
+
return new SmolAuthError(message, options);
|
|
153
|
+
}
|
|
154
|
+
return new SmolError(message, options);
|
|
155
|
+
}
|
|
@@ -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>;
|
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
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort extraction of HTTP status and headers from a provider SDK error.
|
|
3
|
+
*
|
|
4
|
+
* Each provider SDK shapes its errors slightly differently:
|
|
5
|
+
* - OpenAI / Anthropic: `status` (number) + `headers` (web `Headers`)
|
|
6
|
+
* - Google Gemini (`@google/genai`): `status` (number), no headers
|
|
7
|
+
* - Ollama: `status_code` (number), no headers
|
|
8
|
+
*/
|
|
9
|
+
export interface HttpErrorFields {
|
|
10
|
+
status?: number;
|
|
11
|
+
/** Allowlisted, safe-to-log subset of response headers (see `ALLOWED_HEADERS`). */
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
/**
|
|
14
|
+
* Suggested wait before retrying, in milliseconds, parsed from the
|
|
15
|
+
* `retry-after-ms` / `retry-after` response headers. Only ever populated for
|
|
16
|
+
* OpenAI/Anthropic — Google and Ollama errors carry no headers to parse.
|
|
17
|
+
* Always non-negative and capped at `MAX_RETRY_AFTER_MS`.
|
|
18
|
+
*/
|
|
19
|
+
retryAfterMs?: number;
|
|
20
|
+
/** Provider request id (`x-request-id` / `request-id`), useful for support tickets. */
|
|
21
|
+
requestId?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function extractHttpErrorFields(error: unknown): HttpErrorFields;
|