smoltalk 0.0.37 → 0.0.39

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 (40) hide show
  1. package/dist/classes/message/AssistantMessage.d.ts +9 -1
  2. package/dist/classes/message/AssistantMessage.js +14 -0
  3. package/dist/classes/message/index.d.ts +3 -1
  4. package/dist/client.js +3 -4
  5. package/dist/clients/anthropic.d.ts +2 -1
  6. package/dist/clients/anthropic.js +34 -15
  7. package/dist/clients/baseClient.d.ts +6 -0
  8. package/dist/clients/baseClient.js +131 -7
  9. package/dist/clients/google.d.ts +2 -1
  10. package/dist/clients/google.js +29 -7
  11. package/dist/clients/ollama.d.ts +2 -1
  12. package/dist/clients/ollama.js +30 -8
  13. package/dist/clients/openai.d.ts +2 -1
  14. package/dist/clients/openai.js +14 -9
  15. package/dist/clients/openaiResponses.d.ts +2 -1
  16. package/dist/clients/openaiResponses.js +16 -9
  17. package/dist/functions.js +24 -3
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +2 -0
  20. package/dist/model.d.ts +33 -0
  21. package/dist/model.js +132 -0
  22. package/dist/models.d.ts +5 -26
  23. package/dist/models.js +0 -102
  24. package/dist/smolError.d.ts +6 -0
  25. package/dist/smolError.js +12 -0
  26. package/dist/statelogClient.d.ts +2 -1
  27. package/dist/strategies/baseStrategy.d.ts +10 -0
  28. package/dist/strategies/baseStrategy.js +20 -0
  29. package/dist/strategies/fallbackStrategy.d.ts +10 -0
  30. package/dist/strategies/fallbackStrategy.js +48 -0
  31. package/dist/strategies/idStrategy.d.ts +10 -0
  32. package/dist/strategies/idStrategy.js +22 -0
  33. package/dist/strategies/index.d.ts +11 -0
  34. package/dist/strategies/index.js +40 -0
  35. package/dist/strategies/raceStrategy.d.ts +9 -0
  36. package/dist/strategies/raceStrategy.js +37 -0
  37. package/dist/strategies/types.d.ts +31 -0
  38. package/dist/strategies/types.js +1 -0
  39. package/dist/types.d.ts +27 -1
  40. package/package.json +1 -1
@@ -4,7 +4,7 @@ import { getLogger } from "../logger.js";
4
4
  import { success, } from "../types.js";
5
5
  import { zodToGoogleTool } from "../util/tool.js";
6
6
  import { BaseClient } from "./baseClient.js";
7
- import { calculateCost } from "../models.js";
7
+ import { Model } from "../model.js";
8
8
  export const DEFAULT_OLLAMA_HOST = "http://localhost:11434";
9
9
  export class SmolOllama extends BaseClient {
10
10
  logger;
@@ -13,7 +13,7 @@ export class SmolOllama extends BaseClient {
13
13
  constructor(config) {
14
14
  super(config);
15
15
  this.logger = getLogger();
16
- this.model = config.model;
16
+ this.model = new Model(config.model);
17
17
  if (config.ollamaApiKey) {
18
18
  this.client = new Ollama({
19
19
  host: "https://cloud.ollama.com",
@@ -29,7 +29,7 @@ export class SmolOllama extends BaseClient {
29
29
  return this.client;
30
30
  }
31
31
  getModel() {
32
- return this.model;
32
+ return this.model.getResolvedModel();
33
33
  }
34
34
  calculateUsageAndCost(responseData) {
35
35
  let usage;
@@ -42,7 +42,7 @@ export class SmolOllama extends BaseClient {
42
42
  outputTokens,
43
43
  totalTokens: inputTokens + outputTokens,
44
44
  };
45
- const calculatedCost = calculateCost(this.model, usage);
45
+ const calculatedCost = this.model.calculateCost(usage);
46
46
  if (calculatedCost) {
47
47
  cost = calculatedCost;
48
48
  }
@@ -58,7 +58,7 @@ export class SmolOllama extends BaseClient {
58
58
  });
59
59
  const request = {
60
60
  messages: messages,
61
- model: this.model,
61
+ model: this.getModel(),
62
62
  };
63
63
  if (tools.length > 0) {
64
64
  request.tools = tools.map((t) => ({ type: "function", function: t }));
@@ -70,8 +70,16 @@ export class SmolOllama extends BaseClient {
70
70
  Object.assign(request, config.rawAttributes);
71
71
  }
72
72
  this.logger.debug("Sending request to Ollama:", JSON.stringify(request, null, 2));
73
+ const signal = this.getAbortSignal(config);
74
+ const abortHandler = signal ? () => this.client.abort() : undefined;
75
+ if (signal && abortHandler) {
76
+ signal.addEventListener("abort", abortHandler, { once: true });
77
+ }
73
78
  // @ts-ignore
74
79
  const result = await this.client.chat(request);
80
+ if (signal && abortHandler) {
81
+ signal.removeEventListener("abort", abortHandler);
82
+ }
75
83
  this.logger.debug("Response from Ollama:", JSON.stringify(result, null, 2));
76
84
  const output = result.message?.content || null;
77
85
  const toolCalls = [];
@@ -84,7 +92,7 @@ export class SmolOllama extends BaseClient {
84
92
  // Extract usage and calculate cost
85
93
  const { usage, cost } = this.calculateUsageAndCost(result);
86
94
  // Return the response, updating the chat history
87
- return success({ output, toolCalls, usage, cost, model: this.model });
95
+ return success({ output, toolCalls, usage, cost, model: this.getModel() });
88
96
  }
89
97
  async *_textStream(config) {
90
98
  const messages = config.messages.map((msg) => msg.toOpenAIMessage());
@@ -95,7 +103,7 @@ export class SmolOllama extends BaseClient {
95
103
  });
96
104
  const request = {
97
105
  messages: messages,
98
- model: this.model,
106
+ model: this.getModel(),
99
107
  stream: true,
100
108
  };
101
109
  if (tools.length > 0) {
@@ -108,6 +116,11 @@ export class SmolOllama extends BaseClient {
108
116
  Object.assign(request, config.rawAttributes);
109
117
  }
110
118
  this.logger.debug("Sending streaming request to Ollama:", JSON.stringify(request, null, 2));
119
+ const signal = this.getAbortSignal(config);
120
+ const abortHandler = signal ? () => this.client.abort() : undefined;
121
+ if (signal && abortHandler) {
122
+ signal.addEventListener("abort", abortHandler, { once: true });
123
+ }
111
124
  // @ts-ignore
112
125
  const stream = await this.client.chat(request);
113
126
  let content = "";
@@ -148,6 +161,9 @@ export class SmolOllama extends BaseClient {
148
161
  }
149
162
  }
150
163
  }
164
+ if (signal && abortHandler) {
165
+ signal.removeEventListener("abort", abortHandler);
166
+ }
151
167
  this.logger.debug("Streaming response completed from Ollama");
152
168
  // Extract usage from the last chunk
153
169
  if (lastChunk) {
@@ -164,7 +180,13 @@ export class SmolOllama extends BaseClient {
164
180
  }
165
181
  yield {
166
182
  type: "done",
167
- result: { output: content || null, toolCalls, usage, cost, model: this.model },
183
+ result: {
184
+ output: content || null,
185
+ toolCalls,
186
+ usage,
187
+ cost,
188
+ model: this.getModel(),
189
+ },
168
190
  };
169
191
  }
170
192
  }
@@ -1,6 +1,7 @@
1
1
  import OpenAI from "openai";
2
2
  import { BaseClientConfig, PromptConfig, PromptResult, Result, SmolClient, StreamChunk } from "../types.js";
3
3
  import { BaseClient } from "./baseClient.js";
4
+ import { ModelName } from "../models.js";
4
5
  export type SmolOpenAiConfig = BaseClientConfig;
5
6
  export declare class SmolOpenAi extends BaseClient implements SmolClient {
6
7
  private client;
@@ -8,7 +9,7 @@ export declare class SmolOpenAi extends BaseClient implements SmolClient {
8
9
  private model;
9
10
  constructor(config: SmolOpenAiConfig);
10
11
  getClient(): OpenAI;
11
- getModel(): string;
12
+ getModel(): ModelName;
12
13
  private calculateUsageAndCost;
13
14
  private buildRequest;
14
15
  _textSync(config: PromptConfig): Promise<Result<PromptResult>>;
@@ -5,7 +5,7 @@ import { isFunctionToolCall } from "../util.js";
5
5
  import { getLogger } from "../logger.js";
6
6
  import { BaseClient } from "./baseClient.js";
7
7
  import { zodToOpenAITool } from "../util/tool.js";
8
- import { calculateCost } from "../models.js";
8
+ import { Model } from "../model.js";
9
9
  export class SmolOpenAi extends BaseClient {
10
10
  client;
11
11
  logger;
@@ -17,13 +17,13 @@ export class SmolOpenAi extends BaseClient {
17
17
  }
18
18
  this.client = new OpenAI({ apiKey: config.openAiApiKey });
19
19
  this.logger = getLogger();
20
- this.model = config.model;
20
+ this.model = new Model(config.model);
21
21
  }
22
22
  getClient() {
23
23
  return this.client;
24
24
  }
25
25
  getModel() {
26
- return this.model;
26
+ return this.model.getResolvedModel();
27
27
  }
28
28
  calculateUsageAndCost(usageData) {
29
29
  let usage;
@@ -35,7 +35,7 @@ export class SmolOpenAi extends BaseClient {
35
35
  cachedInputTokens: usageData.prompt_tokens_details?.cached_tokens,
36
36
  totalTokens: usageData.total_tokens,
37
37
  };
38
- const calculatedCost = calculateCost(this.model, usage);
38
+ const calculatedCost = this.model.calculateCost(usage);
39
39
  if (calculatedCost) {
40
40
  cost = calculatedCost;
41
41
  }
@@ -45,13 +45,16 @@ export class SmolOpenAi extends BaseClient {
45
45
  buildRequest(config) {
46
46
  const messages = config.messages.map((msg) => msg.toOpenAIMessage());
47
47
  const request = {
48
- model: this.model,
48
+ model: this.getModel(),
49
49
  messages,
50
50
  tools: config.tools?.map((tool) => {
51
51
  return zodToOpenAITool(tool.name, tool.schema, {
52
52
  description: tool.description,
53
53
  });
54
54
  }),
55
+ ...(config.reasoningEffort && {
56
+ reasoning_effort: config.reasoningEffort,
57
+ }),
55
58
  ...(config.rawAttributes || {}),
56
59
  };
57
60
  if (config.responseFormat) {
@@ -68,10 +71,11 @@ export class SmolOpenAi extends BaseClient {
68
71
  async _textSync(config) {
69
72
  const request = this.buildRequest(config);
70
73
  this.logger.debug("Sending request to OpenAI:", JSON.stringify(request, null, 2));
74
+ const signal = this.getAbortSignal(config);
71
75
  const completion = await this.client.chat.completions.create({
72
76
  ...request,
73
77
  stream: false,
74
- });
78
+ }, { ...(signal && { signal }) });
75
79
  this.logger.debug("Response from OpenAI:", JSON.stringify(completion, null, 2));
76
80
  const message = completion.choices[0].message;
77
81
  const output = message.content;
@@ -94,17 +98,18 @@ export class SmolOpenAi extends BaseClient {
94
98
  toolCalls,
95
99
  usage,
96
100
  cost,
97
- model: request.model,
101
+ model: this.getModel(),
98
102
  });
99
103
  }
100
104
  async *_textStream(config) {
101
105
  const request = this.buildRequest(config);
102
106
  this.logger.debug("Sending streaming request to OpenAI:", JSON.stringify(request, null, 2));
107
+ const signal = this.getAbortSignal(config);
103
108
  const completion = await this.client.chat.completions.create({
104
109
  ...request,
105
110
  stream: true,
106
111
  stream_options: { include_usage: true },
107
- });
112
+ }, { ...(signal && { signal }) });
108
113
  let content = "";
109
114
  const toolCallsMap = new Map();
110
115
  let usage;
@@ -159,7 +164,7 @@ export class SmolOpenAi extends BaseClient {
159
164
  toolCalls,
160
165
  usage,
161
166
  cost,
162
- model: request.model,
167
+ model: this.getModel(),
163
168
  },
164
169
  };
165
170
  }
@@ -1,6 +1,7 @@
1
1
  import OpenAI from "openai";
2
2
  import { BaseClientConfig, PromptConfig, PromptResult, Result, SmolClient, StreamChunk } from "../types.js";
3
3
  import { BaseClient } from "./baseClient.js";
4
+ import { ModelName } from "../models.js";
4
5
  export type SmolOpenAiResponsesConfig = BaseClientConfig;
5
6
  export declare class SmolOpenAiResponses extends BaseClient implements SmolClient {
6
7
  private client;
@@ -8,7 +9,7 @@ export declare class SmolOpenAiResponses extends BaseClient implements SmolClien
8
9
  private model;
9
10
  constructor(config: SmolOpenAiResponsesConfig);
10
11
  getClient(): OpenAI;
11
- getModel(): string;
12
+ getModel(): ModelName;
12
13
  private convertMessages;
13
14
  private buildRequest;
14
15
  private calculateUsageAndCost;
@@ -4,7 +4,7 @@ import { ToolCall } from "../classes/ToolCall.js";
4
4
  import { getLogger } from "../logger.js";
5
5
  import { BaseClient } from "./baseClient.js";
6
6
  import { zodToOpenAIResponsesTool } from "../util/tool.js";
7
- import { calculateCost } from "../models.js";
7
+ import { Model } from "../model.js";
8
8
  export class SmolOpenAiResponses extends BaseClient {
9
9
  client;
10
10
  logger;
@@ -16,13 +16,13 @@ export class SmolOpenAiResponses extends BaseClient {
16
16
  }
17
17
  this.client = new OpenAI({ apiKey: config.openAiApiKey });
18
18
  this.logger = getLogger();
19
- this.model = config.model;
19
+ this.model = new Model(config.model);
20
20
  }
21
21
  getClient() {
22
22
  return this.client;
23
23
  }
24
24
  getModel() {
25
- return this.model;
25
+ return this.model.getResolvedModel();
26
26
  }
27
27
  convertMessages(config) {
28
28
  let instructions = config.instructions;
@@ -48,7 +48,7 @@ export class SmolOpenAiResponses extends BaseClient {
48
48
  buildRequest(config) {
49
49
  const { instructions, input } = this.convertMessages(config);
50
50
  const request = {
51
- model: this.model,
51
+ model: this.getModel(),
52
52
  input,
53
53
  };
54
54
  if (instructions) {
@@ -77,6 +77,9 @@ export class SmolOpenAiResponses extends BaseClient {
77
77
  },
78
78
  };
79
79
  }
80
+ if (config.reasoningEffort) {
81
+ request.reasoning = { effort: config.reasoningEffort };
82
+ }
80
83
  if (config.rawAttributes) {
81
84
  Object.assign(request, config.rawAttributes);
82
85
  }
@@ -92,7 +95,7 @@ export class SmolOpenAiResponses extends BaseClient {
92
95
  cachedInputTokens: usageData.input_tokens_details?.cached_tokens,
93
96
  totalTokens: usageData.total_tokens,
94
97
  };
95
- const calculatedCost = calculateCost(this.model, usage);
98
+ const calculatedCost = this.model.calculateCost(usage);
96
99
  if (calculatedCost) {
97
100
  cost = calculatedCost;
98
101
  }
@@ -102,10 +105,11 @@ export class SmolOpenAiResponses extends BaseClient {
102
105
  async _textSync(config) {
103
106
  const request = this.buildRequest(config);
104
107
  this.logger.debug("Sending request to OpenAI Responses API:", JSON.stringify(request, null, 2));
108
+ const signal = this.getAbortSignal(config);
105
109
  const response = await this.client.responses.create({
106
110
  ...request,
107
111
  stream: false,
108
- });
112
+ }, { ...(signal && { signal }) });
109
113
  this.logger.debug("Response from OpenAI Responses API:", JSON.stringify(response, null, 2));
110
114
  const output = response.output_text || null;
111
115
  const toolCalls = [];
@@ -120,13 +124,16 @@ export class SmolOpenAiResponses extends BaseClient {
120
124
  toolCalls,
121
125
  usage,
122
126
  cost,
123
- model: request.model,
127
+ model: this.getModel(),
124
128
  });
125
129
  }
126
130
  async *_textStream(config) {
127
131
  const request = this.buildRequest(config);
128
132
  this.logger.debug("Sending streaming request to OpenAI Responses API:", JSON.stringify(request, null, 2));
129
- const stream = this.client.responses.stream(request);
133
+ const signal = this.getAbortSignal(config);
134
+ const stream = this.client.responses.stream(request, {
135
+ ...(signal && { signal }),
136
+ });
130
137
  let content = "";
131
138
  const functionCalls = new Map();
132
139
  let usage;
@@ -206,7 +213,7 @@ export class SmolOpenAiResponses extends BaseClient {
206
213
  toolCalls,
207
214
  usage,
208
215
  cost,
209
- model: request.model,
216
+ model: this.getModel(),
210
217
  },
211
218
  };
212
219
  }
package/dist/functions.js CHANGED
@@ -1,8 +1,17 @@
1
1
  import { getClient } from "./client.js";
2
- import { isModelConfig, pickModel } from "./models.js";
2
+ import { Model } from "./model.js";
3
+ import { BaseStrategy } from "./strategies/baseStrategy.js";
4
+ import { fromJSON } from "./strategies/index.js";
5
+ function hydrateStrategy(config) {
6
+ if (config.strategy && !(config.strategy instanceof BaseStrategy)) {
7
+ return { ...config, strategy: fromJSON(config.strategy) };
8
+ }
9
+ return config;
10
+ }
3
11
  function splitConfig(config) {
4
12
  const { openAiApiKey, googleApiKey, ollamaApiKey, anthropicApiKey, ollamaHost, model: rawModel, provider, logLevel, toolLoopDetection, statelog, ...promptConfig } = config;
5
- const model = isModelConfig(rawModel) ? pickModel(rawModel) : rawModel;
13
+ const _model = new Model(rawModel);
14
+ const model = _model.getResolvedModel();
6
15
  return {
7
16
  smolConfig: {
8
17
  openAiApiKey,
@@ -20,17 +29,29 @@ function splitConfig(config) {
20
29
  };
21
30
  }
22
31
  export function text(config) {
32
+ config = hydrateStrategy(config);
33
+ if (config.strategy) {
34
+ return config.strategy.text(config);
35
+ }
23
36
  const { smolConfig, promptConfig } = splitConfig(config);
24
37
  const client = getClient(smolConfig);
25
38
  return client.text(promptConfig);
26
39
  }
27
40
  export function textSync(config) {
41
+ config = hydrateStrategy(config);
42
+ if (config.strategy) {
43
+ return config.strategy.textSync(config);
44
+ }
28
45
  const { smolConfig, promptConfig } = splitConfig(config);
29
46
  const client = getClient(smolConfig);
30
47
  return client.textSync(promptConfig);
31
48
  }
32
49
  export function textStream(config) {
33
- const { smolConfig, promptConfig } = splitConfig(config);
50
+ config = hydrateStrategy(config);
51
+ /* if (config.strategy) {
52
+ return (config.strategy as import("./strategies/types.js").Strategy).textStream(config);
53
+ }
54
+ */ const { smolConfig, promptConfig } = splitConfig(config);
34
55
  const client = getClient(smolConfig);
35
56
  return client.textStream(promptConfig);
36
57
  }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export * from "./client.js";
2
2
  export * from "./types.js";
3
3
  export * from "./models.js";
4
+ export * from "./model.js";
4
5
  export * from "./smolError.js";
5
6
  export * from "./util.js";
6
7
  export * from "./classes/message/index.js";
7
8
  export * from "./functions.js";
8
9
  export * from "./classes/ToolCall.js";
10
+ export * from "./strategies/index.js";
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  export * from "./client.js";
2
2
  export * from "./types.js";
3
3
  export * from "./models.js";
4
+ export * from "./model.js";
4
5
  export * from "./smolError.js";
5
6
  export * from "./util.js";
6
7
  export * from "./classes/message/index.js";
7
8
  export * from "./functions.js";
8
9
  export * from "./classes/ToolCall.js";
10
+ export * from "./strategies/index.js";
@@ -0,0 +1,33 @@
1
+ import { ModelName, Provider, TextModel } from "./models.js";
2
+ import { ModelLike } from "./types.js";
3
+ export type Optimization = "speed" | "reasoning" | "cost" | "large-context";
4
+ export type ModelConfig = {
5
+ optimizeFor: Optimization[];
6
+ providers: Provider[];
7
+ limit?: {
8
+ cost?: number;
9
+ };
10
+ };
11
+ export declare class Model {
12
+ private model;
13
+ private resolvedModel;
14
+ constructor(model: ModelName | ModelConfig);
15
+ getModel(): ModelName | ModelConfig;
16
+ getResolvedModel(): ModelName;
17
+ isModelConfig(model: ModelName | ModelConfig): model is ModelConfig;
18
+ resolveModel(models?: readonly TextModel[]): ModelName;
19
+ private getRawMetric;
20
+ private isLowerBetter;
21
+ calculateCost(usage: {
22
+ inputTokens: number;
23
+ outputTokens: number;
24
+ cachedInputTokens?: number;
25
+ }): {
26
+ inputCost: number;
27
+ outputCost: number;
28
+ cachedInputCost?: number;
29
+ totalCost: number;
30
+ currency: string;
31
+ } | null;
32
+ static create(model: ModelLike): Model;
33
+ }
package/dist/model.js ADDED
@@ -0,0 +1,132 @@
1
+ import { getModel, isTextModel, textModels, } from "./models.js";
2
+ import { SmolError } from "./smolError.js";
3
+ import { round } from "./util.js";
4
+ const WEIGHTS = {
5
+ 1: [1],
6
+ 2: [0.6, 0.4],
7
+ 3: [0.5, 0.3, 0.2],
8
+ 4: [0.4, 0.3, 0.2, 0.1],
9
+ };
10
+ export class Model {
11
+ model;
12
+ resolvedModel;
13
+ constructor(model) {
14
+ this.model = model;
15
+ this.resolvedModel = this.resolveModel();
16
+ }
17
+ getModel() {
18
+ return this.model;
19
+ }
20
+ getResolvedModel() {
21
+ return this.resolvedModel;
22
+ }
23
+ isModelConfig(model) {
24
+ return typeof model === "object" && "optimizeFor" in model;
25
+ }
26
+ resolveModel(models = textModels) {
27
+ if (!this.isModelConfig(this.model)) {
28
+ const modelName = this.model;
29
+ const model = getModel(modelName);
30
+ if (!model) {
31
+ throw new SmolError(`Model ${modelName} is not recognized. Please specify a known model or a valid ModelConfig.`);
32
+ }
33
+ return modelName;
34
+ }
35
+ const model = this.model;
36
+ let candidates = models.filter((m) => model.providers.includes(m.provider) &&
37
+ !("disabled" in m && m.disabled));
38
+ if (model.limit?.cost !== undefined) {
39
+ candidates = candidates.filter((m) => {
40
+ const cost = (m.inputTokenCost ?? 0) + (m.outputTokenCost ?? 0);
41
+ return cost <= model.limit.cost;
42
+ });
43
+ }
44
+ if (candidates.length === 0) {
45
+ throw new SmolError("No models available for providers: " +
46
+ model.providers.join(", ") +
47
+ ". Check that the providers have non-disabled models.");
48
+ }
49
+ if (candidates.length === 1) {
50
+ return candidates[0].modelName;
51
+ }
52
+ const optimizations = model.optimizeFor;
53
+ const weights = WEIGHTS[optimizations.length] ?? WEIGHTS[4];
54
+ const scores = new Map();
55
+ for (const c of candidates) {
56
+ scores.set(c.modelName, 0);
57
+ }
58
+ for (let i = 0; i < optimizations.length; i++) {
59
+ const opt = optimizations[i];
60
+ const weight = weights[i];
61
+ const rawValues = candidates.map((c) => this.getRawMetric(c, opt));
62
+ const min = Math.min(...rawValues);
63
+ const max = Math.max(...rawValues);
64
+ const range = max - min;
65
+ for (let j = 0; j < candidates.length; j++) {
66
+ const raw = rawValues[j];
67
+ let normalized;
68
+ if (range === 0) {
69
+ normalized = 0;
70
+ }
71
+ else if (this.isLowerBetter(opt)) {
72
+ normalized = (raw - min) / range;
73
+ }
74
+ else {
75
+ normalized = (max - raw) / range;
76
+ }
77
+ scores.set(candidates[j].modelName, scores.get(candidates[j].modelName) + weight * normalized);
78
+ }
79
+ }
80
+ let bestModel = candidates[0];
81
+ let bestScore = scores.get(candidates[0].modelName);
82
+ for (let i = 1; i < candidates.length; i++) {
83
+ const score = scores.get(candidates[i].modelName);
84
+ if (score < bestScore) {
85
+ bestScore = score;
86
+ bestModel = candidates[i];
87
+ }
88
+ }
89
+ return bestModel.modelName;
90
+ }
91
+ getRawMetric(model, optimization) {
92
+ const m = model;
93
+ switch (optimization) {
94
+ case "cost":
95
+ return (m.inputTokenCost ?? 0) + (m.outputTokenCost ?? 0);
96
+ case "speed":
97
+ return m.outputTokensPerSecond ?? 0;
98
+ case "reasoning":
99
+ return (m.inputTokenCost ?? 0) + (m.outputTokenCost ?? 0);
100
+ case "large-context":
101
+ return m.maxInputTokens;
102
+ }
103
+ }
104
+ isLowerBetter(optimization) {
105
+ return optimization === "cost";
106
+ }
107
+ calculateCost(usage) {
108
+ const model = getModel(this.getResolvedModel());
109
+ if (!model || !isTextModel(model)) {
110
+ return null;
111
+ }
112
+ const inputCost = round((usage.inputTokens * (model.inputTokenCost || 0)) / 1_000_000, 2);
113
+ const outputCost = round((usage.outputTokens * (model.outputTokenCost || 0)) / 1_000_000, 2);
114
+ const cachedInputCost = usage.cachedInputTokens && model.cachedInputTokenCost
115
+ ? round((usage.cachedInputTokens * model.cachedInputTokenCost) / 1_000_000, 2)
116
+ : undefined;
117
+ const totalCost = round(inputCost + outputCost + (cachedInputCost || 0), 2);
118
+ return {
119
+ inputCost,
120
+ outputCost,
121
+ cachedInputCost,
122
+ totalCost,
123
+ currency: "USD",
124
+ };
125
+ }
126
+ static create(model) {
127
+ if (model instanceof Model) {
128
+ return model;
129
+ }
130
+ return new Model(model);
131
+ }
132
+ }
package/dist/models.d.ts CHANGED
@@ -30,7 +30,7 @@ export type EmbeddingsModel = {
30
30
  modelName: string;
31
31
  tokenCost?: number;
32
32
  };
33
- export type Model = SpeechToTextModel | TextModel | EmbeddingsModel | ImageModel;
33
+ export type ModelType = SpeechToTextModel | TextModel | EmbeddingsModel | ImageModel;
34
34
  export declare const speechToTextModels: readonly [{
35
35
  readonly type: "speech-to-text";
36
36
  readonly modelName: "whisper-local";
@@ -805,28 +805,7 @@ export declare function getModel(modelName: ModelName): {
805
805
  readonly description: "High-fidelity image generation with reasoning-enhanced composition. Supports legible text rendering, complex multi-turn editing, and character consistency using up to 14 reference inputs.";
806
806
  readonly costPerImage: 0.05;
807
807
  } | undefined;
808
- export declare function isImageModel(model: Model): model is ImageModel;
809
- export declare function isTextModel(model: Model): model is TextModel;
810
- export declare function isSpeechToTextModel(model: Model): model is SpeechToTextModel;
811
- export declare function isEmbeddingsModel(model: Model): model is EmbeddingsModel;
812
- export type Optimization = "speed" | "accuracy" | "cost" | "large-context";
813
- export type ModelConfig = {
814
- optimizeFor: Optimization[];
815
- providers: Provider[];
816
- limit?: {
817
- cost?: number;
818
- };
819
- };
820
- export declare function isModelConfig(model: ModelName | ModelConfig): model is ModelConfig;
821
- export declare function pickModel(config: ModelConfig, models?: readonly TextModel[]): TextModelName;
822
- export declare function calculateCost(modelName: ModelName, usage: {
823
- inputTokens: number;
824
- outputTokens: number;
825
- cachedInputTokens?: number;
826
- }): {
827
- inputCost: number;
828
- outputCost: number;
829
- cachedInputCost?: number;
830
- totalCost: number;
831
- currency: string;
832
- } | null;
808
+ export declare function isImageModel(model: ModelType): model is ImageModel;
809
+ export declare function isTextModel(model: ModelType): model is TextModel;
810
+ export declare function isSpeechToTextModel(model: ModelType): model is SpeechToTextModel;
811
+ export declare function isEmbeddingsModel(model: ModelType): model is EmbeddingsModel;