@namzu/sdk 30.1.0 → 30.2.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/CHANGELOG.md +20 -0
- package/dist/provider/collect-chat-completion.d.ts +7 -0
- package/dist/provider/collect-chat-completion.d.ts.map +1 -1
- package/dist/provider/collect-chat-completion.js +31 -0
- package/dist/provider/collect-chat-completion.js.map +1 -1
- package/package.json +1 -1
- package/src/pricing/rates.source.json +315 -204
- package/src/provider/collect-chat-completion.ts +33 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 30.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7050dd4: Add `@namzu/deepseek`, and stop dropping reasoning when a stream is collected.
|
|
8
|
+
|
|
9
|
+
**A new driver, and a separate package on purpose.** DeepSeek's endpoint is OpenAI's Chat Completions shape, so pointing `@namzu/openai` at it with a `baseURL` looks like it should work. It does not, and the reason is thinking mode: it is **on by default**, the chain of thought comes back in a `reasoning_content` field that wire has no concept of, and the vendor requires that field replayed on every later turn once tool calls are in play. A driver that does not know about it drops the model's reasoning on every call.
|
|
10
|
+
|
|
11
|
+
`@namzu/deepseek` maps `ThinkingConfig` one-to-one onto the vendor's own `adaptive | enabled | disabled`, streams reasoning through `delta.reasoning` — the same channel `@namzu/anthropic` uses, so a host that renders one renders the other — and replays it automatically. Callers pass the assistant message back and the field goes with it.
|
|
12
|
+
|
|
13
|
+
It **refuses** two things the vendor accepts and applies to nothing: `effort` (this wire validates `thinking.type` and ignores any effort beside it) and the sampling parameters while thinking is on. Both were measured against the live API rather than read off the documentation. `samplingInThinkingMode: 'ignore'` opts out of the second.
|
|
14
|
+
|
|
15
|
+
It carries no price rows, deliberately: the vendor charges twice as much during peak UTC hours, and a static table has no hour in it.
|
|
16
|
+
|
|
17
|
+
**`collectChatCompletion` dropped reasoning blocks** (`@namzu/sdk`). `delta.reasoning` existed, `AssistantMessage.reasoning` is documented as replayed verbatim, and the run loop assembled it correctly — but this helper, which every non-streaming caller goes through, threw it away. So the same stream produced a message with reasoning through one route and without it through the other, and a vendor that needs the blocks back was sent a message that had lost them. It now buckets them by index exactly as the run loop does. This affects `@namzu/anthropic` users too.
|
|
18
|
+
|
|
19
|
+
**The CLI ships the driver** (`@namzu/cli`), so `namzu --provider deepseek` works on a fresh install with `DEEPSEEK_API_KEY` set. That is a fifth bundled driver and a slightly larger install.
|
|
20
|
+
|
|
21
|
+
Models are `deepseek-v4-flash` and `deepseek-v4-pro`. `deepseek-chat` and `deepseek-reasoner` were discontinued on 2026-07-24 and resolve to nothing.
|
|
22
|
+
|
|
3
23
|
## 30.1.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
|
@@ -14,6 +14,13 @@ import type { StreamChunk } from '../types/provider/stream.js';
|
|
|
14
14
|
* - text content is concatenated in delta order;
|
|
15
15
|
* - tool calls are bucketed by `index` into the existing
|
|
16
16
|
* `Array<{ id, function: { name, arguments } }>` shape;
|
|
17
|
+
* - reasoning blocks are bucketed by `index` the same way, because the
|
|
18
|
+
* assembled message is the thing a caller replays and
|
|
19
|
+
* {@link ReasoningBlock} is documented as replayed verbatim. This was
|
|
20
|
+
* missing: `delta.reasoning` was dropped on the floor, so a run collected
|
|
21
|
+
* through this helper came back with no reasoning even when the driver had
|
|
22
|
+
* streamed it — and a vendor that requires the blocks back on the next turn
|
|
23
|
+
* would then be sent a message that had lost them;
|
|
17
24
|
* - usage and finishReason fall back to safe defaults when the provider
|
|
18
25
|
* omits them (defensive — a known vendor-SDK failure mode
|
|
19
26
|
* where `message_stop` is occasionally dropped on connection close).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collect-chat-completion.d.ts","sourceRoot":"","sources":["../../src/provider/collect-chat-completion.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"collect-chat-completion.d.ts","sourceRoot":"","sources":["../../src/provider/collect-chat-completion.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,qBAAqB,CAC1C,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,GAChC,OAAO,CAAC,sBAAsB,CAAC,CAuFjC"}
|
|
@@ -13,6 +13,13 @@ import { mergeTokenUsage } from '../types/common/index.js';
|
|
|
13
13
|
* - text content is concatenated in delta order;
|
|
14
14
|
* - tool calls are bucketed by `index` into the existing
|
|
15
15
|
* `Array<{ id, function: { name, arguments } }>` shape;
|
|
16
|
+
* - reasoning blocks are bucketed by `index` the same way, because the
|
|
17
|
+
* assembled message is the thing a caller replays and
|
|
18
|
+
* {@link ReasoningBlock} is documented as replayed verbatim. This was
|
|
19
|
+
* missing: `delta.reasoning` was dropped on the floor, so a run collected
|
|
20
|
+
* through this helper came back with no reasoning even when the driver had
|
|
21
|
+
* streamed it — and a vendor that requires the blocks back on the next turn
|
|
22
|
+
* would then be sent a message that had lost them;
|
|
16
23
|
* - usage and finishReason fall back to safe defaults when the provider
|
|
17
24
|
* omits them (defensive — a known vendor-SDK failure mode
|
|
18
25
|
* where `message_stop` is occasionally dropped on connection close).
|
|
@@ -33,6 +40,10 @@ export async function collectChatCompletion(stream) {
|
|
|
33
40
|
cacheWriteTokens: 0,
|
|
34
41
|
};
|
|
35
42
|
const toolBuckets = new Map();
|
|
43
|
+
// Same bucketing rule the run loop uses (`runtime/query/iteration/
|
|
44
|
+
// stream-turn.ts`), so a message assembled here and a message assembled
|
|
45
|
+
// there carry the same blocks in the same order.
|
|
46
|
+
const reasoningBuckets = new Map();
|
|
36
47
|
for await (const chunk of stream) {
|
|
37
48
|
if (chunk.error) {
|
|
38
49
|
throw new Error(chunk.error);
|
|
@@ -42,6 +53,22 @@ export async function collectChatCompletion(stream) {
|
|
|
42
53
|
if (chunk.delta.content) {
|
|
43
54
|
content += chunk.delta.content;
|
|
44
55
|
}
|
|
56
|
+
const reasoning = chunk.delta.reasoning;
|
|
57
|
+
if (reasoning) {
|
|
58
|
+
const bucket = reasoningBuckets.get(reasoning.index) ?? {
|
|
59
|
+
type: reasoning.type ?? 'thinking',
|
|
60
|
+
text: '',
|
|
61
|
+
};
|
|
62
|
+
if (reasoning.type)
|
|
63
|
+
bucket.type = reasoning.type;
|
|
64
|
+
if (reasoning.text)
|
|
65
|
+
bucket.text += reasoning.text;
|
|
66
|
+
if (reasoning.signature)
|
|
67
|
+
bucket.signature = reasoning.signature;
|
|
68
|
+
if (reasoning.encrypted)
|
|
69
|
+
bucket.encrypted = reasoning.encrypted;
|
|
70
|
+
reasoningBuckets.set(reasoning.index, bucket);
|
|
71
|
+
}
|
|
45
72
|
for (const tc of chunk.delta.toolCalls ?? []) {
|
|
46
73
|
const bucket = toolBuckets.get(tc.index) ?? {
|
|
47
74
|
id: '',
|
|
@@ -70,6 +97,9 @@ export async function collectChatCompletion(stream) {
|
|
|
70
97
|
type: 'function',
|
|
71
98
|
function: { name: b.name, arguments: b.argsBuf },
|
|
72
99
|
}));
|
|
100
|
+
const reasoningBlocks = [...reasoningBuckets.entries()]
|
|
101
|
+
.sort(([a], [b]) => a - b)
|
|
102
|
+
.map(([, b]) => b);
|
|
73
103
|
return {
|
|
74
104
|
id,
|
|
75
105
|
model,
|
|
@@ -77,6 +107,7 @@ export async function collectChatCompletion(stream) {
|
|
|
77
107
|
role: 'assistant',
|
|
78
108
|
content: content.length > 0 ? content : null,
|
|
79
109
|
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
110
|
+
...(reasoningBlocks.length > 0 ? { reasoning: reasoningBlocks } : {}),
|
|
80
111
|
},
|
|
81
112
|
finishReason,
|
|
82
113
|
usage,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collect-chat-completion.js","sourceRoot":"","sources":["../../src/provider/collect-chat-completion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"collect-chat-completion.js","sourceRoot":"","sources":["../../src/provider/collect-chat-completion.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,206 +1,317 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
2
|
+
"about": [
|
|
3
|
+
"The reviewed input to scripts/generate-model-prices.mjs. Edit THIS file, never",
|
|
4
|
+
"catalogue.generated.ts — the generator overwrites that file and a CI gate",
|
|
5
|
+
"re-derives it from this one and fails on any difference.",
|
|
6
|
+
"",
|
|
7
|
+
"A rate here is a commercial fact about a vendor's published list price. It is",
|
|
8
|
+
"entered by a human who read that vendor's own pricing page, and `verified` is",
|
|
9
|
+
"the date they read it. It is NOT fetched at build time: an auto-refreshing",
|
|
10
|
+
"generator would change every consumer's reported cost AND every consumer's",
|
|
11
|
+
"costLimitUsd behaviour on an unrelated commit, with no review and no diff.",
|
|
12
|
+
"",
|
|
13
|
+
"Absent is a real answer. A model with no row here reports UNKNOWN cost, not",
|
|
14
|
+
"zero, and a run that sets runConfig.costLimitUsd against it is refused rather",
|
|
15
|
+
"than run without a budget. So the correct move when a rate cannot be",
|
|
16
|
+
"established is to leave the row out, never to approximate it.",
|
|
17
|
+
"",
|
|
18
|
+
"unmetered: true means this driver bills nothing by construction — local",
|
|
19
|
+
"inference. Its runs are priced at zero, which is KNOWN-free and therefore",
|
|
20
|
+
"distinct from unknown. Such a vendor needs no model rows.",
|
|
21
|
+
"",
|
|
22
|
+
"Matching is EXACT on a normalised id (lowercased, one trailing -YYYYMMDD or",
|
|
23
|
+
"@YYYYMMDD snapshot suffix removed). Deliberately not prefix or substring:",
|
|
24
|
+
"within one vendor here, ids one character apart differ in price by 4x, so a",
|
|
25
|
+
"near-miss would misprice silently. A miss reports unknown, which is safe."
|
|
26
|
+
],
|
|
27
|
+
"cacheTokensAre": [
|
|
28
|
+
"Two of the four rates only mean anything alongside a fact about the DRIVER,",
|
|
29
|
+
"not about the model: whether the prompt-token count it reports already",
|
|
30
|
+
"contains the tokens it read from cache.",
|
|
31
|
+
"",
|
|
32
|
+
"The drivers in this repository disagree, and it is measured, not assumed:",
|
|
33
|
+
" packages/providers/anthropic/src/client.ts parseUsage — promptTokens is",
|
|
34
|
+
" input_tokens, which EXCLUDES cache reads and cache writes.",
|
|
35
|
+
" packages/providers/bedrock/src/client.ts parseBedrockUsage — same.",
|
|
36
|
+
" packages/providers/openai/src/client.ts parseUsage — promptTokens is",
|
|
37
|
+
" prompt_tokens, which INCLUDES cached_tokens.",
|
|
38
|
+
"",
|
|
39
|
+
"So the same TokenUsage shape carries two incompatible meanings, and a rate",
|
|
40
|
+
"applied without knowing which one is off by the whole cache-read volume. The",
|
|
41
|
+
"fact is a property of the driver, and the driver's identity is in hand at the",
|
|
42
|
+
"moment cost is accumulated — so it is declared here, per vendor, once.",
|
|
43
|
+
"",
|
|
44
|
+
"promptIncludesCacheReads true => billable input is promptTokens minus",
|
|
45
|
+
" cachedTokens.",
|
|
46
|
+
" false => billable input is promptTokens, and",
|
|
47
|
+
" cachedTokens are charged on top.",
|
|
48
|
+
"",
|
|
49
|
+
"reportsCacheWrites declares whether the driver ever produces a non-zero",
|
|
50
|
+
"cacheWriteTokens. When false, model rows carry no write rate — a rate for a",
|
|
51
|
+
"quantity nothing produces is a declaration nothing drives. If such a driver",
|
|
52
|
+
"later does report writes, those tokens are counted as unpriced rather than",
|
|
53
|
+
"quietly charged at some other rate, so the contradiction surfaces."
|
|
54
|
+
],
|
|
55
|
+
"vendors": [
|
|
56
|
+
{
|
|
57
|
+
"providerId": "anthropic",
|
|
58
|
+
"unmetered": false,
|
|
59
|
+
"verified": "2026-08-10",
|
|
60
|
+
"promptIncludesCacheReads": false,
|
|
61
|
+
"reportsCacheWrites": true,
|
|
62
|
+
"note": "The last two rows exist because the driver's own offline catalogue offers those models and this table did not price them — a lookup-key gap that reads as `cost unknown` and is invisible to the generator's own check, which only proves the module matches this file. `providers/anthropic` asserts the two lists agree. Vendor list prices. Cache read is the vendor's published 0.1x of the input rate; cache write is its published 1.25x for the five-minute entry, which is the default TTL and the one the driver's `cacheControl: { type: 'auto' }` asks for. A time-limited introductory rate applied to one model at the time of entry; this table has no time dimension and records the list rate, so a run inside a promotional window is over-reported rather than mis-modelled.",
|
|
63
|
+
"models": [
|
|
64
|
+
{
|
|
65
|
+
"id": "claude-fable-5",
|
|
66
|
+
"inputPer1M": 10,
|
|
67
|
+
"outputPer1M": 50,
|
|
68
|
+
"cacheReadPer1M": 1,
|
|
69
|
+
"cacheWritePer1M": 12.5
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "claude-mythos-5",
|
|
73
|
+
"inputPer1M": 10,
|
|
74
|
+
"outputPer1M": 50,
|
|
75
|
+
"cacheReadPer1M": 1,
|
|
76
|
+
"cacheWritePer1M": 12.5
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "claude-opus-5",
|
|
80
|
+
"inputPer1M": 5,
|
|
81
|
+
"outputPer1M": 25,
|
|
82
|
+
"cacheReadPer1M": 0.5,
|
|
83
|
+
"cacheWritePer1M": 6.25
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "claude-opus-4-8",
|
|
87
|
+
"inputPer1M": 5,
|
|
88
|
+
"outputPer1M": 25,
|
|
89
|
+
"cacheReadPer1M": 0.5,
|
|
90
|
+
"cacheWritePer1M": 6.25
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "claude-opus-4-7",
|
|
94
|
+
"inputPer1M": 5,
|
|
95
|
+
"outputPer1M": 25,
|
|
96
|
+
"cacheReadPer1M": 0.5,
|
|
97
|
+
"cacheWritePer1M": 6.25
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": "claude-opus-4-6",
|
|
101
|
+
"inputPer1M": 5,
|
|
102
|
+
"outputPer1M": 25,
|
|
103
|
+
"cacheReadPer1M": 0.5,
|
|
104
|
+
"cacheWritePer1M": 6.25
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "claude-sonnet-5",
|
|
108
|
+
"inputPer1M": 3,
|
|
109
|
+
"outputPer1M": 15,
|
|
110
|
+
"cacheReadPer1M": 0.3,
|
|
111
|
+
"cacheWritePer1M": 3.75
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"id": "claude-sonnet-4-6",
|
|
115
|
+
"inputPer1M": 3,
|
|
116
|
+
"outputPer1M": 15,
|
|
117
|
+
"cacheReadPer1M": 0.3,
|
|
118
|
+
"cacheWritePer1M": 3.75
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "claude-haiku-4-5",
|
|
122
|
+
"inputPer1M": 1,
|
|
123
|
+
"outputPer1M": 5,
|
|
124
|
+
"cacheReadPer1M": 0.1,
|
|
125
|
+
"cacheWritePer1M": 1.25
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"id": "claude-sonnet-4-5",
|
|
129
|
+
"inputPer1M": 3,
|
|
130
|
+
"outputPer1M": 15,
|
|
131
|
+
"cacheReadPer1M": 0.3,
|
|
132
|
+
"cacheWritePer1M": 3.75
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"id": "claude-opus-4-1",
|
|
136
|
+
"inputPer1M": 15,
|
|
137
|
+
"outputPer1M": 75,
|
|
138
|
+
"cacheReadPer1M": 1.5,
|
|
139
|
+
"cacheWritePer1M": 18.75
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"providerId": "openai",
|
|
145
|
+
"unmetered": false,
|
|
146
|
+
"verified": "2026-08-10",
|
|
147
|
+
"promptIncludesCacheReads": true,
|
|
148
|
+
"reportsCacheWrites": false,
|
|
149
|
+
"note": "Vendor list prices for text models, cached-input rate included. The driver hard-codes cacheWriteTokens to zero, so no write rate is carried. Embedding models are omitted (no output rate, and a chat run never reaches one), as are models whose cached-input rate was not published alongside the others — an unestablished rate is left out rather than approximated, which is the rule this whole file follows.",
|
|
150
|
+
"models": [
|
|
151
|
+
{
|
|
152
|
+
"id": "gpt-5.6-sol",
|
|
153
|
+
"inputPer1M": 5,
|
|
154
|
+
"outputPer1M": 30,
|
|
155
|
+
"cacheReadPer1M": 0.5
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"id": "gpt-5.6-terra",
|
|
159
|
+
"inputPer1M": 2,
|
|
160
|
+
"outputPer1M": 12,
|
|
161
|
+
"cacheReadPer1M": 0.2
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"id": "gpt-5.6-luna",
|
|
165
|
+
"inputPer1M": 0.2,
|
|
166
|
+
"outputPer1M": 1.2,
|
|
167
|
+
"cacheReadPer1M": 0.02
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"id": "gpt-5.5",
|
|
171
|
+
"inputPer1M": 5,
|
|
172
|
+
"outputPer1M": 30,
|
|
173
|
+
"cacheReadPer1M": 0.5
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"id": "gpt-5.4",
|
|
177
|
+
"inputPer1M": 2.5,
|
|
178
|
+
"outputPer1M": 15,
|
|
179
|
+
"cacheReadPer1M": 0.25
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"id": "gpt-5.4-mini",
|
|
183
|
+
"inputPer1M": 0.75,
|
|
184
|
+
"outputPer1M": 4.5,
|
|
185
|
+
"cacheReadPer1M": 0.075
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"id": "gpt-5.4-nano",
|
|
189
|
+
"inputPer1M": 0.2,
|
|
190
|
+
"outputPer1M": 1.25,
|
|
191
|
+
"cacheReadPer1M": 0.02
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"id": "gpt-5.2",
|
|
195
|
+
"inputPer1M": 1.75,
|
|
196
|
+
"outputPer1M": 14,
|
|
197
|
+
"cacheReadPer1M": 0.175
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"id": "gpt-5.1",
|
|
201
|
+
"inputPer1M": 1.25,
|
|
202
|
+
"outputPer1M": 10,
|
|
203
|
+
"cacheReadPer1M": 0.125
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"id": "gpt-5",
|
|
207
|
+
"inputPer1M": 1.25,
|
|
208
|
+
"outputPer1M": 10,
|
|
209
|
+
"cacheReadPer1M": 0.125
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"id": "gpt-5-mini",
|
|
213
|
+
"inputPer1M": 0.25,
|
|
214
|
+
"outputPer1M": 2,
|
|
215
|
+
"cacheReadPer1M": 0.025
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"id": "gpt-5-nano",
|
|
219
|
+
"inputPer1M": 0.05,
|
|
220
|
+
"outputPer1M": 0.4,
|
|
221
|
+
"cacheReadPer1M": 0.005
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"id": "gpt-4.1",
|
|
225
|
+
"inputPer1M": 2,
|
|
226
|
+
"outputPer1M": 8,
|
|
227
|
+
"cacheReadPer1M": 0.5
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"id": "gpt-4.1-mini",
|
|
231
|
+
"inputPer1M": 0.4,
|
|
232
|
+
"outputPer1M": 1.6,
|
|
233
|
+
"cacheReadPer1M": 0.1
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"id": "gpt-4.1-nano",
|
|
237
|
+
"inputPer1M": 0.1,
|
|
238
|
+
"outputPer1M": 0.4,
|
|
239
|
+
"cacheReadPer1M": 0.025
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"id": "gpt-4o",
|
|
243
|
+
"inputPer1M": 2.5,
|
|
244
|
+
"outputPer1M": 10,
|
|
245
|
+
"cacheReadPer1M": 1.25
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"id": "gpt-4o-mini",
|
|
249
|
+
"inputPer1M": 0.15,
|
|
250
|
+
"outputPer1M": 0.6,
|
|
251
|
+
"cacheReadPer1M": 0.075
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"id": "o1",
|
|
255
|
+
"inputPer1M": 15,
|
|
256
|
+
"outputPer1M": 60,
|
|
257
|
+
"cacheReadPer1M": 7.5
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"id": "o3",
|
|
261
|
+
"inputPer1M": 2,
|
|
262
|
+
"outputPer1M": 8,
|
|
263
|
+
"cacheReadPer1M": 0.5
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"id": "o3-mini",
|
|
267
|
+
"inputPer1M": 1.1,
|
|
268
|
+
"outputPer1M": 4.4,
|
|
269
|
+
"cacheReadPer1M": 0.55
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"id": "o4-mini",
|
|
273
|
+
"inputPer1M": 1.1,
|
|
274
|
+
"outputPer1M": 4.4,
|
|
275
|
+
"cacheReadPer1M": 0.275
|
|
276
|
+
}
|
|
277
|
+
]
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"providerId": "ollama",
|
|
281
|
+
"unmetered": true,
|
|
282
|
+
"verified": "2026-08-10",
|
|
283
|
+
"note": "Local inference. Nothing is billed for a token, whatever the model, so there is no model list to keep current.",
|
|
284
|
+
"models": []
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"providerId": "lmstudio",
|
|
288
|
+
"unmetered": true,
|
|
289
|
+
"verified": "2026-08-10",
|
|
290
|
+
"note": "Local inference. Same as above.",
|
|
291
|
+
"models": []
|
|
292
|
+
}
|
|
293
|
+
],
|
|
294
|
+
"absent": [
|
|
295
|
+
"Drivers deliberately carrying no rows, so that a run through them reports",
|
|
296
|
+
"unknown rather than a number nobody checked:",
|
|
297
|
+
"",
|
|
298
|
+
" bedrock — partner-operated, on a separate rate card from the vendor whose",
|
|
299
|
+
" models it serves. Entering the vendor's own rates against it",
|
|
300
|
+
" would be the exact class of quiet wrongness this file exists to",
|
|
301
|
+
" stop.",
|
|
302
|
+
" openrouter — routes to arbitrary upstreams and publishes per-model rates from",
|
|
303
|
+
" its own catalogue endpoint at run time. That is a live source",
|
|
304
|
+
" and a static table cannot be one.",
|
|
305
|
+
" http — a driver for whatever endpoint the host points it at. There is",
|
|
306
|
+
" no vendor to have a price.",
|
|
307
|
+
"",
|
|
308
|
+
" deepseek — publishes two list prices per model and picks between them by the",
|
|
309
|
+
" CLOCK: peak (01:00-04:00 and 06:00-10:00 UTC) costs exactly twice",
|
|
310
|
+
" off-peak, on both input and output. A static table has no hour, so",
|
|
311
|
+
" whichever of the two numbers were entered here would be wrong for",
|
|
312
|
+
" half of every day — and wrong in the direction that reads as a",
|
|
313
|
+
" correct, verified figure. Read 2026-08-17.",
|
|
314
|
+
"Adding one is a normal change: add the vendor here with the rows and the date",
|
|
315
|
+
"they were read, run the generator, commit both files."
|
|
316
|
+
]
|
|
206
317
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mergeTokenUsage } from '../types/common/index.js'
|
|
2
|
+
import type { ReasoningBlock } from '../types/message/index.js'
|
|
2
3
|
import type { ChatCompletionResponse } from '../types/provider/chat.js'
|
|
3
4
|
import type { StreamChunk } from '../types/provider/stream.js'
|
|
4
5
|
|
|
@@ -16,6 +17,13 @@ import type { StreamChunk } from '../types/provider/stream.js'
|
|
|
16
17
|
* - text content is concatenated in delta order;
|
|
17
18
|
* - tool calls are bucketed by `index` into the existing
|
|
18
19
|
* `Array<{ id, function: { name, arguments } }>` shape;
|
|
20
|
+
* - reasoning blocks are bucketed by `index` the same way, because the
|
|
21
|
+
* assembled message is the thing a caller replays and
|
|
22
|
+
* {@link ReasoningBlock} is documented as replayed verbatim. This was
|
|
23
|
+
* missing: `delta.reasoning` was dropped on the floor, so a run collected
|
|
24
|
+
* through this helper came back with no reasoning even when the driver had
|
|
25
|
+
* streamed it — and a vendor that requires the blocks back on the next turn
|
|
26
|
+
* would then be sent a message that had lost them;
|
|
19
27
|
* - usage and finishReason fall back to safe defaults when the provider
|
|
20
28
|
* omits them (defensive — a known vendor-SDK failure mode
|
|
21
29
|
* where `message_stop` is occasionally dropped on connection close).
|
|
@@ -39,6 +47,13 @@ export async function collectChatCompletion(
|
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
const toolBuckets = new Map<number, { id: string; name: string; argsBuf: string }>()
|
|
50
|
+
// Same bucketing rule the run loop uses (`runtime/query/iteration/
|
|
51
|
+
// stream-turn.ts`), so a message assembled here and a message assembled
|
|
52
|
+
// there carry the same blocks in the same order.
|
|
53
|
+
const reasoningBuckets = new Map<
|
|
54
|
+
number,
|
|
55
|
+
{ -readonly [K in keyof ReasoningBlock]: ReasoningBlock[K] } & { text: string }
|
|
56
|
+
>()
|
|
42
57
|
|
|
43
58
|
for await (const chunk of stream) {
|
|
44
59
|
if (chunk.error) {
|
|
@@ -50,6 +65,19 @@ export async function collectChatCompletion(
|
|
|
50
65
|
content += chunk.delta.content
|
|
51
66
|
}
|
|
52
67
|
|
|
68
|
+
const reasoning = chunk.delta.reasoning
|
|
69
|
+
if (reasoning) {
|
|
70
|
+
const bucket = reasoningBuckets.get(reasoning.index) ?? {
|
|
71
|
+
type: reasoning.type ?? 'thinking',
|
|
72
|
+
text: '',
|
|
73
|
+
}
|
|
74
|
+
if (reasoning.type) bucket.type = reasoning.type
|
|
75
|
+
if (reasoning.text) bucket.text += reasoning.text
|
|
76
|
+
if (reasoning.signature) bucket.signature = reasoning.signature
|
|
77
|
+
if (reasoning.encrypted) bucket.encrypted = reasoning.encrypted
|
|
78
|
+
reasoningBuckets.set(reasoning.index, bucket)
|
|
79
|
+
}
|
|
80
|
+
|
|
53
81
|
for (const tc of chunk.delta.toolCalls ?? []) {
|
|
54
82
|
const bucket = toolBuckets.get(tc.index) ?? {
|
|
55
83
|
id: '',
|
|
@@ -76,6 +104,10 @@ export async function collectChatCompletion(
|
|
|
76
104
|
function: { name: b.name, arguments: b.argsBuf },
|
|
77
105
|
}))
|
|
78
106
|
|
|
107
|
+
const reasoningBlocks: ReasoningBlock[] = [...reasoningBuckets.entries()]
|
|
108
|
+
.sort(([a], [b]) => a - b)
|
|
109
|
+
.map(([, b]) => b)
|
|
110
|
+
|
|
79
111
|
return {
|
|
80
112
|
id,
|
|
81
113
|
model,
|
|
@@ -83,6 +115,7 @@ export async function collectChatCompletion(
|
|
|
83
115
|
role: 'assistant',
|
|
84
116
|
content: content.length > 0 ? content : null,
|
|
85
117
|
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
118
|
+
...(reasoningBlocks.length > 0 ? { reasoning: reasoningBlocks } : {}),
|
|
86
119
|
},
|
|
87
120
|
finishReason,
|
|
88
121
|
usage,
|