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.
Files changed (47) hide show
  1. package/README.md +139 -0
  2. package/dist/classes/ToolCall.js +4 -3
  3. package/dist/classes/message/AssistantMessage.d.ts +1 -0
  4. package/dist/classes/message/AssistantMessage.js +5 -3
  5. package/dist/classes/message/DeveloperMessage.js +5 -3
  6. package/dist/classes/message/SystemMessage.js +5 -3
  7. package/dist/classes/message/ToolMessage.js +4 -3
  8. package/dist/classes/message/UserMessage.js +5 -3
  9. package/dist/client.js +5 -3
  10. package/dist/clients/anthropic.d.ts +3 -1
  11. package/dist/clients/anthropic.js +71 -13
  12. package/dist/clients/baseClient.js +11 -1
  13. package/dist/clients/google.d.ts +4 -0
  14. package/dist/clients/google.js +92 -24
  15. package/dist/clients/ollama.d.ts +1 -0
  16. package/dist/clients/ollama.js +18 -7
  17. package/dist/clients/openai.js +7 -4
  18. package/dist/clients/openaiResponses.d.ts +3 -0
  19. package/dist/clients/openaiResponses.js +60 -7
  20. package/dist/embed/openai.js +3 -3
  21. package/dist/embed.d.ts +5 -2
  22. package/dist/embed.js +14 -3
  23. package/dist/image/google.js +3 -3
  24. package/dist/image/openai.js +6 -6
  25. package/dist/image.d.ts +5 -2
  26. package/dist/image.js +14 -3
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.js +1 -0
  29. package/dist/model.d.ts +4 -2
  30. package/dist/model.js +7 -5
  31. package/dist/modelData.d.ts +40 -0
  32. package/dist/modelData.js +222 -0
  33. package/dist/models.d.ts +658 -760
  34. package/dist/models.js +836 -60
  35. package/dist/smolError.d.ts +78 -5
  36. package/dist/smolError.js +134 -9
  37. package/dist/types/costEstimate.d.ts +2 -0
  38. package/dist/types/costEstimate.js +1 -0
  39. package/dist/types.d.ts +30 -1
  40. package/dist/types.js +2 -1
  41. package/dist/util/hostedTools.d.ts +19 -0
  42. package/dist/util/hostedTools.js +104 -0
  43. package/dist/util/httpError.d.ts +23 -0
  44. package/dist/util/httpError.js +237 -0
  45. package/dist/util/provider.d.ts +2 -1
  46. package/dist/util/provider.js +2 -2
  47. package/package.json +7 -2
@@ -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
- constructor(message: string);
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
- constructor(message) {
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>;
@@ -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
+ }
@@ -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;