smoltalk 0.17.0 → 0.18.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.
@@ -2,8 +2,12 @@ import { Content } from "@google/genai";
2
2
  import { Message } from "ollama";
3
3
  import { ChatCompletionMessageParam } from "openai/resources";
4
4
  import type { ResponseInputItem } from "openai/resources/responses/responses.js";
5
+ /** The text a provider sees for a message's content: a string as is, any
6
+ * other value as its JSON text, nothing for null. Exported so a consumer
7
+ * that compares saved messages can apply the same rule. */
8
+ export declare function contentToString(content: unknown): string;
5
9
  export declare class BaseMessage {
6
- protected contentToString(content: string | unknown[] | null | undefined): string;
10
+ protected contentToString(content: unknown): string;
7
11
  }
8
12
  export interface MessageClass {
9
13
  get content(): string;
@@ -1,7 +1,13 @@
1
+ /** The text a provider sees for a message's content: a string as is, any
2
+ * other value as its JSON text, nothing for null. Exported so a consumer
3
+ * that compares saved messages can apply the same rule. */
4
+ export function contentToString(content) {
5
+ if (content === null || content === undefined)
6
+ return "";
7
+ return typeof content === "string" ? content : JSON.stringify(content);
8
+ }
1
9
  export class BaseMessage {
2
10
  contentToString(content) {
3
- if (content === null || content === undefined)
4
- return "";
5
- return typeof content === "string" ? content : JSON.stringify(content);
11
+ return contentToString(content);
6
12
  }
7
13
  }
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
2
  import { BaseMessage, MessageClass } from "./BaseMessage.js";
3
- import { TextPart } from "../../types.js";
4
3
  import { ChatCompletionMessageParam } from "openai/resources";
5
4
  import { Content } from "@google/genai";
6
5
  import { Message } from "ollama";
@@ -21,11 +20,11 @@ export type ToolMessageJSON = {
21
20
  };
22
21
  export declare class ToolMessage extends BaseMessage implements MessageClass {
23
22
  _role: "tool";
24
- _content: string | Array<TextPart>;
23
+ _content: unknown;
25
24
  _tool_call_id: string;
26
25
  _rawData?: any;
27
26
  _name: string;
28
- constructor(content: string | Array<TextPart>, options: {
27
+ constructor(content: unknown, options: {
29
28
  tool_call_id: string;
30
29
  rawData?: any;
31
30
  name: string;
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
2
  import { BaseMessage } from "./BaseMessage.js";
3
- import { TextPartSchema } from "../../types.js";
4
3
  import { getLogger } from "../../util/logger.js";
5
4
  export const ToolMessageJSONSchema = z.object({
6
5
  role: z.literal("tool"),
@@ -12,6 +11,9 @@ export const ToolMessageJSONSchema = z.object({
12
11
  });
13
12
  export class ToolMessage extends BaseMessage {
14
13
  _role = "tool";
14
+ // A tool result is whatever the tool returned: text, text parts, or a
15
+ // plain value such as an array of records. Every provider conversion
16
+ // reads it through the `content` getter, which turns any value into text.
15
17
  _content;
16
18
  _tool_call_id;
17
19
  _rawData;
@@ -59,18 +61,10 @@ export class ToolMessage extends BaseMessage {
59
61
  logger.debug("ToolMessage payload that failed to parse:", JSON.stringify(json, null, 2));
60
62
  throw new Error("Failed to parse ToolMessage");
61
63
  }
62
- const TextPartArraySchema = z.array(TextPartSchema);
63
- const textPartArrayResult = TextPartArraySchema.safeParse(result.data.content);
64
- if (textPartArrayResult.success) {
65
- result.data.content = textPartArrayResult.data;
66
- }
67
- else if (typeof result.data.content === "string") {
68
- // do nothing, it's already a string
69
- }
70
- else {
71
- getLogger().debug("ToolMessage content is neither a string nor an array of TextParts. Converting to string using JSON.stringify.");
72
- result.data.content = JSON.stringify(result.data.content);
73
- }
64
+ // Content is kept exactly as saved. Rebuilding a non-text value as its
65
+ // JSON text here would make a saved-and-rebuilt thread differ from one
66
+ // that was never saved, while what a provider sees is the same either
67
+ // way (the `content` getter stringifies both).
74
68
  return new ToolMessage(result.data.content, {
75
69
  tool_call_id: result.data.tool_call_id,
76
70
  name: result.data.name,
@@ -51,7 +51,7 @@ export declare function systemMessage(content: string | Array<TextPart>, options
51
51
  name?: string;
52
52
  rawData?: any;
53
53
  }): SystemMessage;
54
- export declare function toolMessage(content: string | Array<TextPart>, options: {
54
+ export declare function toolMessage(content: unknown, options: {
55
55
  tool_call_id: string;
56
56
  rawData?: any;
57
57
  name: string;
@@ -223,9 +223,12 @@ export class SmolGoogle extends BaseClient {
223
223
  let cost;
224
224
  if (usageMetadata) {
225
225
  const cached = usageMetadata.cachedContentTokenCount ?? 0;
226
+ // Thinking is reported in thoughtsTokenCount, separate from
227
+ // candidatesTokenCount, and billed at the output rate.
228
+ const thoughts = usageMetadata.thoughtsTokenCount ?? 0;
226
229
  usage = {
227
230
  inputTokens: Math.max(0, (usageMetadata.promptTokenCount || 0) - cached),
228
- outputTokens: usageMetadata.candidatesTokenCount || 0,
231
+ outputTokens: (usageMetadata.candidatesTokenCount || 0) + thoughts,
229
232
  totalTokens: usageMetadata.totalTokenCount,
230
233
  };
231
234
  if (cached > 0) {
@@ -1,4 +1,4 @@
1
- import { Ollama } from "ollama";
1
+ import { Ollama } from "#ollama";
2
2
  import { success, failure } from "../types/result.js";
3
3
  import { DEFAULT_OLLAMA_HOST } from "../clients/ollama.js";
4
4
  export async function ollamaEmbed(inputs, config, apiKey, ollamaHost) {
package/dist/model.js CHANGED
@@ -43,7 +43,27 @@ export class Model {
43
43
  return null;
44
44
  }
45
45
  // BaseModel token-rate fields, read structurally across the model union.
46
- const rates = model;
46
+ const baseRates = model;
47
+ const rates = { ...baseRates };
48
+ // A prompt over the long-context threshold is billed at the long-context
49
+ // rates for the whole request, input and output alike. The prompt size
50
+ // counts every input bucket, cached ones included.
51
+ const promptTokens = usage.inputTokens +
52
+ (usage.cachedInputTokens ?? 0) +
53
+ (usage.cacheCreationInputTokens ?? 0) +
54
+ (usage.inputAudioTokens ?? 0);
55
+ const longContext = baseRates.longContext;
56
+ if (longContext !== undefined && promptTokens > longContext.thresholdTokens) {
57
+ if (longContext.inputTokenCost !== undefined) {
58
+ rates.inputTokenCost = longContext.inputTokenCost;
59
+ }
60
+ if (longContext.outputTokenCost !== undefined) {
61
+ rates.outputTokenCost = longContext.outputTokenCost;
62
+ }
63
+ if (longContext.cachedInputTokenCost !== undefined) {
64
+ rates.cachedInputTokenCost = longContext.cachedInputTokenCost;
65
+ }
66
+ }
47
67
  // Price only models that carry at least one token rate; those without
48
68
  // (per-minute STT, per-char TTS) return null so their dedicated helpers apply.
49
69
  if (rates.inputTokenCost === undefined &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoltalk",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "A common interface for LLM APIs",
5
5
  "homepage": "https://github.com/egonSchiele/smoltalk",
6
6
  "files": [