smoltalk 0.3.1 → 0.4.1
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/dist/classes/message/AssistantMessage.d.ts +2 -0
- package/dist/clients/anthropic.d.ts +7 -0
- package/dist/clients/anthropic.js +168 -17
- package/dist/clients/google.js +5 -2
- package/dist/clients/openai.js +5 -2
- package/dist/clients/openaiResponses.js +5 -2
- package/dist/model.d.ts +2 -0
- package/dist/model.js +38 -5
- package/dist/models.d.ts +169 -34
- package/dist/models.js +83 -17
- package/dist/types/costEstimate.d.ts +2 -0
- package/dist/types/costEstimate.js +2 -0
- package/dist/types/tokenUsage.d.ts +2 -0
- package/dist/types/tokenUsage.js +2 -0
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
|
@@ -29,12 +29,14 @@ export declare const AssistantMessageJSONSchema: z.ZodObject<{
|
|
|
29
29
|
inputTokens: z.ZodNumber;
|
|
30
30
|
outputTokens: z.ZodNumber;
|
|
31
31
|
cachedInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
cacheCreationInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
32
33
|
totalTokens: z.ZodOptional<z.ZodNumber>;
|
|
33
34
|
}, z.core.$strip>>;
|
|
34
35
|
cost: z.ZodOptional<z.ZodObject<{
|
|
35
36
|
inputCost: z.ZodNumber;
|
|
36
37
|
outputCost: z.ZodNumber;
|
|
37
38
|
cachedInputCost: z.ZodOptional<z.ZodNumber>;
|
|
39
|
+
cacheCreationInputCost: z.ZodOptional<z.ZodNumber>;
|
|
38
40
|
totalCost: z.ZodNumber;
|
|
39
41
|
currency: z.ZodString;
|
|
40
42
|
}, z.core.$strip>>;
|
|
@@ -12,6 +12,13 @@ export declare class SmolAnthropic extends BaseClient implements SmolClient {
|
|
|
12
12
|
getModel(): ModelName;
|
|
13
13
|
private calculateUsageAndCost;
|
|
14
14
|
private buildRequest;
|
|
15
|
+
/**
|
|
16
|
+
* Translate `thinking` / `reasoningEffort` into the correct Anthropic
|
|
17
|
+
* parameters for this model. Callers shouldn't have to know whether a model
|
|
18
|
+
* speaks the legacy `budget_tokens` API or the newer adaptive + effort API —
|
|
19
|
+
* that's exactly the vendor difference this package smooths over.
|
|
20
|
+
*/
|
|
21
|
+
private resolveThinking;
|
|
15
22
|
private rethrowAsSmolError;
|
|
16
23
|
_textSync(config: SmolConfig): Promise<Result<PromptResult>>;
|
|
17
24
|
_textStream(config: SmolConfig): AsyncGenerator<StreamChunk>;
|
|
@@ -6,8 +6,98 @@ import { success, } from "../types.js";
|
|
|
6
6
|
import { zodToAnthropicTool } from "../util/tool.js";
|
|
7
7
|
import { SmolContentPolicyError, SmolContextWindowExceededError, } from "../smolError.js";
|
|
8
8
|
import { BaseClient } from "./baseClient.js";
|
|
9
|
+
import { getModel, isTextModel } from "../models.js";
|
|
9
10
|
import { Model } from "../model.js";
|
|
10
11
|
const DEFAULT_MAX_TOKENS = 4096;
|
|
12
|
+
/**
|
|
13
|
+
* Which thinking API a model speaks. New flagship Anthropic models (Opus 4.7+)
|
|
14
|
+
* reject the legacy `{type: "enabled", budget_tokens}` form with a 400, so we
|
|
15
|
+
* default unknown/unregistered models to "adaptive" (the forward-looking shape)
|
|
16
|
+
* rather than the form that's being phased out.
|
|
17
|
+
*/
|
|
18
|
+
function thinkingStyleFor(modelName) {
|
|
19
|
+
const model = getModel(modelName);
|
|
20
|
+
if (model && isTextModel(model) && model.reasoning?.thinkingStyle) {
|
|
21
|
+
return model.reasoning.thinkingStyle;
|
|
22
|
+
}
|
|
23
|
+
return "adaptive";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Attach ephemeral cache_control breakpoints to (up to) three places:
|
|
27
|
+
* 1. the last tool definition
|
|
28
|
+
* 2. the last system block (promoting system from string to array form)
|
|
29
|
+
* 3. the last block of the last user message
|
|
30
|
+
*
|
|
31
|
+
* Anthropic enforces minimum prefix sizes; smaller prefixes silently no-op.
|
|
32
|
+
*/
|
|
33
|
+
function applyCacheBreakpoints(req) {
|
|
34
|
+
const cc = { type: "ephemeral" };
|
|
35
|
+
// Tools: mark the last tool.
|
|
36
|
+
let tools = req.tools;
|
|
37
|
+
if (tools && tools.length > 0) {
|
|
38
|
+
const lastIdx = tools.length - 1;
|
|
39
|
+
const marked = [];
|
|
40
|
+
for (let i = 0; i < tools.length; i++) {
|
|
41
|
+
if (i === lastIdx) {
|
|
42
|
+
marked.push({ ...tools[i], cache_control: cc });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
marked.push(tools[i]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
tools = marked;
|
|
49
|
+
}
|
|
50
|
+
// System: promote string to array form so the last block can be marked.
|
|
51
|
+
let system = req.system;
|
|
52
|
+
if (typeof system === "string" && system.length > 0) {
|
|
53
|
+
system = [{ type: "text", text: system, cache_control: cc }];
|
|
54
|
+
}
|
|
55
|
+
else if (Array.isArray(system) && system.length > 0) {
|
|
56
|
+
const lastIdx = system.length - 1;
|
|
57
|
+
const marked = [];
|
|
58
|
+
for (let i = 0; i < system.length; i++) {
|
|
59
|
+
if (i === lastIdx) {
|
|
60
|
+
marked.push({ ...system[i], cache_control: cc });
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
marked.push(system[i]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
system = marked;
|
|
67
|
+
}
|
|
68
|
+
// Messages: mark the last block of the last user message.
|
|
69
|
+
let messages = req.messages;
|
|
70
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
71
|
+
const m = messages[i];
|
|
72
|
+
if (m.role !== "user")
|
|
73
|
+
continue;
|
|
74
|
+
let blocks;
|
|
75
|
+
if (typeof m.content === "string") {
|
|
76
|
+
blocks = [{ type: "text", text: m.content }];
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
blocks = [...m.content];
|
|
80
|
+
}
|
|
81
|
+
if (blocks.length === 0)
|
|
82
|
+
break;
|
|
83
|
+
blocks[blocks.length - 1] = {
|
|
84
|
+
...blocks[blocks.length - 1],
|
|
85
|
+
cache_control: cc,
|
|
86
|
+
};
|
|
87
|
+
const rebuilt = [];
|
|
88
|
+
for (let j = 0; j < messages.length; j++) {
|
|
89
|
+
if (j === i) {
|
|
90
|
+
rebuilt.push({ ...m, content: blocks });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
rebuilt.push(messages[j]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
messages = rebuilt;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
return { system, messages, tools };
|
|
100
|
+
}
|
|
11
101
|
export class SmolAnthropic extends BaseClient {
|
|
12
102
|
client;
|
|
13
103
|
logger;
|
|
@@ -22,11 +112,22 @@ export class SmolAnthropic extends BaseClient {
|
|
|
22
112
|
return this.model.getModel();
|
|
23
113
|
}
|
|
24
114
|
calculateUsageAndCost(usageData) {
|
|
115
|
+
const cacheRead = usageData.cache_read_input_tokens ?? 0;
|
|
116
|
+
const cacheCreation = usageData.cache_creation_input_tokens ?? 0;
|
|
25
117
|
const usage = {
|
|
26
118
|
inputTokens: usageData.input_tokens,
|
|
27
119
|
outputTokens: usageData.output_tokens,
|
|
28
|
-
totalTokens: usageData.input_tokens +
|
|
120
|
+
totalTokens: usageData.input_tokens +
|
|
121
|
+
cacheRead +
|
|
122
|
+
cacheCreation +
|
|
123
|
+
usageData.output_tokens,
|
|
29
124
|
};
|
|
125
|
+
if (cacheRead > 0) {
|
|
126
|
+
usage.cachedInputTokens = cacheRead;
|
|
127
|
+
}
|
|
128
|
+
if (cacheCreation > 0) {
|
|
129
|
+
usage.cacheCreationInputTokens = cacheCreation;
|
|
130
|
+
}
|
|
30
131
|
const cost = this.model.calculateCost(usage) ?? undefined;
|
|
31
132
|
return { usage, cost };
|
|
32
133
|
}
|
|
@@ -65,23 +166,49 @@ export class SmolAnthropic extends BaseClient {
|
|
|
65
166
|
description: tool.description,
|
|
66
167
|
}))
|
|
67
168
|
: undefined;
|
|
169
|
+
// Normalize the user's provider-agnostic thinking/effort config into the
|
|
170
|
+
// shape this specific model accepts. Both `thinking.enabled` and
|
|
171
|
+
// `reasoningEffort` are treated as a request to think.
|
|
172
|
+
const { thinking, outputConfig } = this.resolveThinking(config);
|
|
173
|
+
const cachingEnabled = config.caching?.enabled !== false;
|
|
174
|
+
const baseRequest = { system, messages: anthropicMessages, tools };
|
|
175
|
+
const finalRequest = cachingEnabled
|
|
176
|
+
? applyCacheBreakpoints(baseRequest)
|
|
177
|
+
: baseRequest;
|
|
178
|
+
return { ...finalRequest, thinking, outputConfig };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Translate `thinking` / `reasoningEffort` into the correct Anthropic
|
|
182
|
+
* parameters for this model. Callers shouldn't have to know whether a model
|
|
183
|
+
* speaks the legacy `budget_tokens` API or the newer adaptive + effort API —
|
|
184
|
+
* that's exactly the vendor difference this package smooths over.
|
|
185
|
+
*/
|
|
186
|
+
resolveThinking(config) {
|
|
187
|
+
const wantsThinking = config.thinking?.enabled === true;
|
|
188
|
+
const effort = config.reasoningEffort;
|
|
189
|
+
if (!wantsThinking && !effort) {
|
|
190
|
+
return { thinking: undefined, outputConfig: undefined };
|
|
191
|
+
}
|
|
192
|
+
if (thinkingStyleFor(this.getModel()) === "adaptive") {
|
|
193
|
+
// `output_config.effort` controls depth; the budget is irrelevant here.
|
|
194
|
+
return {
|
|
195
|
+
thinking: { type: "adaptive" },
|
|
196
|
+
outputConfig: effort ? { effort } : undefined,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// Legacy budget-based thinking (Haiku 4.5 and older models).
|
|
68
200
|
const reasoningBudgetMap = {
|
|
69
201
|
low: 2048,
|
|
70
202
|
medium: 5000,
|
|
71
203
|
high: 10000,
|
|
72
204
|
};
|
|
73
|
-
const
|
|
74
|
-
?
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
:
|
|
79
|
-
|
|
80
|
-
type: "enabled",
|
|
81
|
-
budget_tokens: reasoningBudgetMap[config.reasoningEffort],
|
|
82
|
-
}
|
|
83
|
-
: undefined;
|
|
84
|
-
return { system, messages: anthropicMessages, tools, thinking };
|
|
205
|
+
const budget_tokens = wantsThinking
|
|
206
|
+
? (config.thinking?.budgetTokens ?? 5000)
|
|
207
|
+
: reasoningBudgetMap[effort];
|
|
208
|
+
return {
|
|
209
|
+
thinking: { type: "enabled", budget_tokens },
|
|
210
|
+
outputConfig: undefined,
|
|
211
|
+
};
|
|
85
212
|
}
|
|
86
213
|
rethrowAsSmolError(error) {
|
|
87
214
|
if (error instanceof Anthropic.APIError) {
|
|
@@ -102,7 +229,7 @@ export class SmolAnthropic extends BaseClient {
|
|
|
102
229
|
throw error;
|
|
103
230
|
}
|
|
104
231
|
async _textSync(config) {
|
|
105
|
-
const { system, messages, tools, thinking } = this.buildRequest(config);
|
|
232
|
+
const { system, messages, tools, thinking, outputConfig } = this.buildRequest(config);
|
|
106
233
|
let debugData = {
|
|
107
234
|
model: this.getModel(),
|
|
108
235
|
max_tokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
@@ -110,6 +237,7 @@ export class SmolAnthropic extends BaseClient {
|
|
|
110
237
|
system,
|
|
111
238
|
tools,
|
|
112
239
|
thinking,
|
|
240
|
+
output_config: outputConfig,
|
|
113
241
|
};
|
|
114
242
|
this.logger.debug("Sending request to Anthropic:", debugData);
|
|
115
243
|
this.statelogClient?.promptRequest(debugData);
|
|
@@ -123,6 +251,7 @@ export class SmolAnthropic extends BaseClient {
|
|
|
123
251
|
...(system && { system }),
|
|
124
252
|
...(tools && { tools }),
|
|
125
253
|
...(thinking && { thinking }),
|
|
254
|
+
...(outputConfig && { output_config: outputConfig }),
|
|
126
255
|
...(config.temperature !== undefined && {
|
|
127
256
|
temperature: config.temperature,
|
|
128
257
|
}),
|
|
@@ -161,7 +290,7 @@ export class SmolAnthropic extends BaseClient {
|
|
|
161
290
|
});
|
|
162
291
|
}
|
|
163
292
|
async *_textStream(config) {
|
|
164
|
-
const { system, messages, tools, thinking } = this.buildRequest(config);
|
|
293
|
+
const { system, messages, tools, thinking, outputConfig } = this.buildRequest(config);
|
|
165
294
|
const streamDebugData = {
|
|
166
295
|
model: this.model,
|
|
167
296
|
max_tokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
@@ -169,6 +298,7 @@ export class SmolAnthropic extends BaseClient {
|
|
|
169
298
|
system,
|
|
170
299
|
tools,
|
|
171
300
|
thinking,
|
|
301
|
+
output_config: outputConfig,
|
|
172
302
|
};
|
|
173
303
|
this.logger.debug("Sending streaming request to Anthropic:", streamDebugData);
|
|
174
304
|
this.statelogClient?.promptRequest(streamDebugData);
|
|
@@ -182,6 +312,7 @@ export class SmolAnthropic extends BaseClient {
|
|
|
182
312
|
...(system && { system }),
|
|
183
313
|
...(tools && { tools }),
|
|
184
314
|
...(thinking && { thinking }),
|
|
315
|
+
...(outputConfig && { output_config: outputConfig }),
|
|
185
316
|
...(config.temperature !== undefined && {
|
|
186
317
|
temperature: config.temperature,
|
|
187
318
|
}),
|
|
@@ -198,10 +329,15 @@ export class SmolAnthropic extends BaseClient {
|
|
|
198
329
|
// Track thinking blocks by index: index -> { text, signature }
|
|
199
330
|
const thinkingBlockMap = new Map();
|
|
200
331
|
let inputTokens = 0;
|
|
332
|
+
let cacheReadTokens = 0;
|
|
333
|
+
let cacheCreationTokens = 0;
|
|
201
334
|
let outputTokens = 0;
|
|
202
335
|
for await (const event of stream) {
|
|
203
336
|
if (event.type === "message_start") {
|
|
204
|
-
|
|
337
|
+
const u = event.message.usage;
|
|
338
|
+
inputTokens = u.input_tokens;
|
|
339
|
+
cacheReadTokens = u.cache_read_input_tokens ?? 0;
|
|
340
|
+
cacheCreationTokens = u.cache_creation_input_tokens ?? 0;
|
|
205
341
|
}
|
|
206
342
|
else if (event.type === "content_block_start") {
|
|
207
343
|
if (event.content_block.type === "tool_use") {
|
|
@@ -252,6 +388,15 @@ export class SmolAnthropic extends BaseClient {
|
|
|
252
388
|
}
|
|
253
389
|
else if (event.type === "message_delta") {
|
|
254
390
|
outputTokens = event.usage.output_tokens;
|
|
391
|
+
// Defensive: in practice Anthropic only sends cache fields on
|
|
392
|
+
// message_start, but read them here too so we don't miss an
|
|
393
|
+
// update if the SDK changes.
|
|
394
|
+
if (event.usage.cache_read_input_tokens != null) {
|
|
395
|
+
cacheReadTokens = event.usage.cache_read_input_tokens;
|
|
396
|
+
}
|
|
397
|
+
if (event.usage.cache_creation_input_tokens != null) {
|
|
398
|
+
cacheCreationTokens = event.usage.cache_creation_input_tokens;
|
|
399
|
+
}
|
|
255
400
|
}
|
|
256
401
|
}
|
|
257
402
|
this.logger.debug("Streaming response completed from Anthropic");
|
|
@@ -269,8 +414,14 @@ export class SmolAnthropic extends BaseClient {
|
|
|
269
414
|
const usage = {
|
|
270
415
|
inputTokens,
|
|
271
416
|
outputTokens,
|
|
272
|
-
totalTokens: inputTokens + outputTokens,
|
|
417
|
+
totalTokens: inputTokens + cacheReadTokens + cacheCreationTokens + outputTokens,
|
|
273
418
|
};
|
|
419
|
+
if (cacheReadTokens > 0) {
|
|
420
|
+
usage.cachedInputTokens = cacheReadTokens;
|
|
421
|
+
}
|
|
422
|
+
if (cacheCreationTokens > 0) {
|
|
423
|
+
usage.cacheCreationInputTokens = cacheCreationTokens;
|
|
424
|
+
}
|
|
274
425
|
const cost = this.model.calculateCost(usage) ?? undefined;
|
|
275
426
|
yield {
|
|
276
427
|
type: "done",
|
package/dist/clients/google.js
CHANGED
|
@@ -31,12 +31,15 @@ export class SmolGoogle extends BaseClient {
|
|
|
31
31
|
let usage;
|
|
32
32
|
let cost;
|
|
33
33
|
if (usageMetadata) {
|
|
34
|
+
const cached = usageMetadata.cachedContentTokenCount ?? 0;
|
|
34
35
|
usage = {
|
|
35
|
-
inputTokens: usageMetadata.promptTokenCount || 0,
|
|
36
|
+
inputTokens: Math.max(0, (usageMetadata.promptTokenCount || 0) - cached),
|
|
36
37
|
outputTokens: usageMetadata.candidatesTokenCount || 0,
|
|
37
|
-
cachedInputTokens: usageMetadata.cachedContentTokenCount,
|
|
38
38
|
totalTokens: usageMetadata.totalTokenCount,
|
|
39
39
|
};
|
|
40
|
+
if (cached > 0) {
|
|
41
|
+
usage.cachedInputTokens = cached;
|
|
42
|
+
}
|
|
40
43
|
const calculatedCost = this.model.calculateCost(usage);
|
|
41
44
|
if (calculatedCost) {
|
|
42
45
|
cost = calculatedCost;
|
package/dist/clients/openai.js
CHANGED
|
@@ -30,12 +30,15 @@ export class SmolOpenAi extends BaseClient {
|
|
|
30
30
|
let usage;
|
|
31
31
|
let cost;
|
|
32
32
|
if (usageData) {
|
|
33
|
+
const cached = usageData.prompt_tokens_details?.cached_tokens ?? 0;
|
|
33
34
|
usage = {
|
|
34
|
-
inputTokens: usageData.prompt_tokens || 0,
|
|
35
|
+
inputTokens: Math.max(0, (usageData.prompt_tokens || 0) - cached),
|
|
35
36
|
outputTokens: usageData.completion_tokens || 0,
|
|
36
|
-
cachedInputTokens: usageData.prompt_tokens_details?.cached_tokens,
|
|
37
37
|
totalTokens: usageData.total_tokens,
|
|
38
38
|
};
|
|
39
|
+
if (cached > 0) {
|
|
40
|
+
usage.cachedInputTokens = cached;
|
|
41
|
+
}
|
|
39
42
|
const calculatedCost = this.model.calculateCost(usage);
|
|
40
43
|
if (calculatedCost) {
|
|
41
44
|
cost = calculatedCost;
|
|
@@ -89,12 +89,15 @@ export class SmolOpenAiResponses extends BaseClient {
|
|
|
89
89
|
let usage;
|
|
90
90
|
let cost;
|
|
91
91
|
if (usageData) {
|
|
92
|
+
const cached = usageData.input_tokens_details?.cached_tokens ?? 0;
|
|
92
93
|
usage = {
|
|
93
|
-
inputTokens: usageData.input_tokens || 0,
|
|
94
|
+
inputTokens: Math.max(0, (usageData.input_tokens || 0) - cached),
|
|
94
95
|
outputTokens: usageData.output_tokens || 0,
|
|
95
|
-
cachedInputTokens: usageData.input_tokens_details?.cached_tokens,
|
|
96
96
|
totalTokens: usageData.total_tokens,
|
|
97
97
|
};
|
|
98
|
+
if (cached > 0) {
|
|
99
|
+
usage.cachedInputTokens = cached;
|
|
100
|
+
}
|
|
98
101
|
const calculatedCost = this.model.calculateCost(usage);
|
|
99
102
|
if (calculatedCost) {
|
|
100
103
|
cost = calculatedCost;
|
package/dist/model.d.ts
CHANGED
|
@@ -11,10 +11,12 @@ export declare class Model {
|
|
|
11
11
|
inputTokens: number;
|
|
12
12
|
outputTokens: number;
|
|
13
13
|
cachedInputTokens?: number;
|
|
14
|
+
cacheCreationInputTokens?: number;
|
|
14
15
|
}): {
|
|
15
16
|
inputCost: number;
|
|
16
17
|
outputCost: number;
|
|
17
18
|
cachedInputCost?: number;
|
|
19
|
+
cacheCreationInputCost?: number;
|
|
18
20
|
totalCost: number;
|
|
19
21
|
currency: string;
|
|
20
22
|
} | null;
|
package/dist/model.js
CHANGED
|
@@ -26,16 +26,49 @@ export class Model {
|
|
|
26
26
|
if (!model || !isTextModel(model)) {
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
|
+
const cachedTokens = usage.cachedInputTokens ?? 0;
|
|
30
|
+
const cacheCreationTokens = usage.cacheCreationInputTokens ?? 0;
|
|
31
|
+
// Disjoint buckets. If a discount price isn't defined for this model,
|
|
32
|
+
// the tokens were still billed by the provider — charge them at the
|
|
33
|
+
// full input rate so totalCost stays honest.
|
|
34
|
+
const cachedRate = model.cachedInputTokenCost ?? model.inputTokenCost ?? 0;
|
|
35
|
+
const cacheCreationRate = model.cacheCreationInputTokenCost ?? model.inputTokenCost ?? 0;
|
|
29
36
|
const inputCost = round((usage.inputTokens * (model.inputTokenCost || 0)) / 1_000_000, 6);
|
|
30
37
|
const outputCost = round((usage.outputTokens * (model.outputTokenCost || 0)) / 1_000_000, 6);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
// Only expose cachedInputCost / cacheCreationInputCost when the model
|
|
39
|
+
// actually has a distinct discount price. Otherwise, fold those dollars
|
|
40
|
+
// into inputCost so the user isn't misled by a $0 cached field.
|
|
41
|
+
let cachedInputCost;
|
|
42
|
+
let cacheCreationInputCost;
|
|
43
|
+
let foldedInputDollars = 0;
|
|
44
|
+
if (cachedTokens > 0) {
|
|
45
|
+
const dollars = (cachedTokens * cachedRate) / 1_000_000;
|
|
46
|
+
if (model.cachedInputTokenCost != null) {
|
|
47
|
+
cachedInputCost = round(dollars, 6);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
foldedInputDollars += dollars;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (cacheCreationTokens > 0) {
|
|
54
|
+
const dollars = (cacheCreationTokens * cacheCreationRate) / 1_000_000;
|
|
55
|
+
if (model.cacheCreationInputTokenCost != null) {
|
|
56
|
+
cacheCreationInputCost = round(dollars, 6);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
foldedInputDollars += dollars;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const finalInputCost = round(inputCost + foldedInputDollars, 6);
|
|
63
|
+
const totalCost = round(finalInputCost +
|
|
64
|
+
outputCost +
|
|
65
|
+
(cachedInputCost || 0) +
|
|
66
|
+
(cacheCreationInputCost || 0), 6);
|
|
35
67
|
return {
|
|
36
|
-
inputCost,
|
|
68
|
+
inputCost: finalInputCost,
|
|
37
69
|
outputCost,
|
|
38
70
|
cachedInputCost,
|
|
71
|
+
cacheCreationInputCost,
|
|
39
72
|
totalCost,
|
|
40
73
|
currency: "USD",
|
|
41
74
|
};
|