@namzu/zen 2.5.0 → 3.0.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 +28 -0
- package/README.md +28 -14
- package/dist/catalogue/catalogue.d.ts.map +1 -1
- package/dist/catalogue/catalogue.js +13 -1
- package/dist/catalogue/catalogue.js.map +1 -1
- package/dist/catalogue/derive.d.ts +7 -1
- package/dist/catalogue/derive.d.ts.map +1 -1
- package/dist/catalogue/derive.js +69 -6
- package/dist/catalogue/derive.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +274 -15
- package/dist/client.js.map +1 -1
- package/dist/models.d.ts +1 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +130 -53
- package/dist/models.js.map +1 -1
- package/dist/options.d.ts +2 -2
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +35 -10
- package/dist/options.js.map +1 -1
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +44 -4
- package/dist/prompt.js.map +1 -1
- package/dist/types.d.ts +8 -7
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/catalogue/catalogue.ts +15 -0
- package/src/catalogue/derive.ts +78 -6
- package/src/client.ts +304 -16
- package/src/models.review.json +5 -2
- package/src/models.ts +131 -54
- package/src/options.ts +39 -9
- package/src/prompt.ts +50 -4
- package/src/types.ts +8 -7
package/src/client.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
APICallError,
|
|
8
8
|
type LanguageModelV3,
|
|
9
9
|
type LanguageModelV3Content,
|
|
10
|
+
type LanguageModelV3Prompt,
|
|
10
11
|
type LanguageModelV3StreamPart,
|
|
11
12
|
type LanguageModelV3Usage,
|
|
12
13
|
type SharedV3ProviderMetadata,
|
|
@@ -31,9 +32,15 @@ import {
|
|
|
31
32
|
findZenCatalogueModel,
|
|
32
33
|
isUnroutedZenModel,
|
|
33
34
|
} from './catalogue/catalogue.js'
|
|
35
|
+
import { isDirectAnonymousZenId } from './catalogue/derive.js'
|
|
34
36
|
import type { ZenModel, ZenService } from './models.js'
|
|
35
37
|
import { createCallOptions } from './options.js'
|
|
36
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
createReplayState,
|
|
40
|
+
isGoDeepSeekV4ResponsesModel,
|
|
41
|
+
isMuseSparkResponsesModel,
|
|
42
|
+
toReasoningBlocks,
|
|
43
|
+
} from './prompt.js'
|
|
37
44
|
import type { ZenConfig, ZenGoConfig } from './types.js'
|
|
38
45
|
|
|
39
46
|
export const ZEN_CAPABILITIES: ProviderCapabilities = {
|
|
@@ -50,6 +57,235 @@ export const ZEN_CAPABILITIES: ProviderCapabilities = {
|
|
|
50
57
|
export const ZEN_BASE_URL = 'https://opencode.ai/zen/v1'
|
|
51
58
|
export const ZEN_GO_BASE_URL = 'https://opencode.ai/zen/go/v1'
|
|
52
59
|
|
|
60
|
+
function withoutMuseSparkEncryptedReasoning(init?: RequestInit): RequestInit {
|
|
61
|
+
// The pinned OpenAI adapter adds this include automatically whenever a
|
|
62
|
+
// reasoning model uses store:false, even if providerOptions omits it.
|
|
63
|
+
// Muse Spark cannot round-trip that gateway-issued ciphertext. Keep the
|
|
64
|
+
// request stateless and preserve every other include the adapter may add.
|
|
65
|
+
if (typeof init?.body !== 'string')
|
|
66
|
+
throw new Error('Zen Muse Spark adapter emitted a request body that cannot be checked.')
|
|
67
|
+
const body: unknown = JSON.parse(init.body)
|
|
68
|
+
if (typeof body !== 'object' || body === null || Array.isArray(body))
|
|
69
|
+
throw new Error('Zen Muse Spark adapter emitted an invalid request body.')
|
|
70
|
+
const request = body as Record<string, unknown>
|
|
71
|
+
if (request.include !== undefined) {
|
|
72
|
+
if (
|
|
73
|
+
!Array.isArray(request.include) ||
|
|
74
|
+
!request.include.every((item) => typeof item === 'string')
|
|
75
|
+
)
|
|
76
|
+
throw new Error('Zen Muse Spark adapter emitted an invalid include list.')
|
|
77
|
+
const include = request.include.filter((item) => item !== 'reasoning.encrypted_content')
|
|
78
|
+
if (include.length > 0) request.include = include
|
|
79
|
+
else request.include = undefined
|
|
80
|
+
}
|
|
81
|
+
return { ...init, body: JSON.stringify(request) }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isGoDeepSeekV4ChatModel(service: ZenService, model: string, protocol: string): boolean {
|
|
85
|
+
return (
|
|
86
|
+
service === 'go' &&
|
|
87
|
+
protocol === 'chat' &&
|
|
88
|
+
(model === 'deepseek-v4-pro' ||
|
|
89
|
+
model === 'deepseek-v4.1-flash' ||
|
|
90
|
+
model === 'deepseek-v4-flash')
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function adapterBody(init: RequestInit | undefined, model: string): Record<string, unknown> {
|
|
95
|
+
if (typeof init?.body !== 'string')
|
|
96
|
+
throw new Error(`Zen adapter emitted a non-JSON request body for ${model}.`)
|
|
97
|
+
const body: unknown = JSON.parse(init.body)
|
|
98
|
+
if (typeof body !== 'object' || body === null || Array.isArray(body))
|
|
99
|
+
throw new Error(`Zen adapter emitted an invalid request body for ${model}.`)
|
|
100
|
+
return body as Record<string, unknown>
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function withGoDeepSeekV4ChatHistory(init: RequestInit | undefined, model: string): RequestInit {
|
|
104
|
+
const body = adapterBody(init, model)
|
|
105
|
+
if (!Array.isArray(body.messages)) throw new Error('Go DeepSeek chat request has no messages.')
|
|
106
|
+
for (const value of body.messages) {
|
|
107
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
108
|
+
throw new Error('Go DeepSeek chat request has an invalid message.')
|
|
109
|
+
const message = value as Record<string, unknown>
|
|
110
|
+
if (message.role !== 'assistant') continue
|
|
111
|
+
if (message.content === null) message.content = ''
|
|
112
|
+
if (message.reasoning_content === undefined || message.reasoning_content === null)
|
|
113
|
+
message.reasoning_content = ''
|
|
114
|
+
else if (typeof message.reasoning_content !== 'string')
|
|
115
|
+
throw new Error('Go DeepSeek chat request has invalid reasoning content.')
|
|
116
|
+
}
|
|
117
|
+
return { ...init, body: JSON.stringify(body) }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function withGoDeepSeekV4ResponsesHistory(
|
|
121
|
+
init: RequestInit | undefined,
|
|
122
|
+
model: string,
|
|
123
|
+
prompt: LanguageModelV3Prompt,
|
|
124
|
+
): RequestInit {
|
|
125
|
+
const body = adapterBody(init, model)
|
|
126
|
+
if (!Array.isArray(body.input)) throw new Error('Go DeepSeek Responses request has no input.')
|
|
127
|
+
const input: Record<string, unknown>[] = []
|
|
128
|
+
let turn: Record<string, unknown>[] = []
|
|
129
|
+
const sourceTurns = prompt.filter((message) => message.role === 'assistant')
|
|
130
|
+
let sourceIndex = 0
|
|
131
|
+
const refuse = (): never => {
|
|
132
|
+
throw new ProviderRequestError({
|
|
133
|
+
providerId: 'zen-go',
|
|
134
|
+
kind: 'bad_request',
|
|
135
|
+
detail: `Go DeepSeek ${model} requires captured, nonempty reasoning_text for each previous assistant turn. This history cannot be replayed safely.`,
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
const flushTurn = () => {
|
|
139
|
+
if (turn.length === 0) return
|
|
140
|
+
const source = sourceTurns[sourceIndex++]
|
|
141
|
+
if (source?.role !== 'assistant') return refuse()
|
|
142
|
+
const messages = turn.filter((item) => item.role === 'assistant')
|
|
143
|
+
const calls = turn.filter((item) => item.type === 'function_call')
|
|
144
|
+
const sourceText = source.content
|
|
145
|
+
.filter((part) => part.type === 'text')
|
|
146
|
+
.map((part) => part.text)
|
|
147
|
+
const wireText = messages.flatMap((message) =>
|
|
148
|
+
Array.isArray(message.content)
|
|
149
|
+
? message.content
|
|
150
|
+
.filter(
|
|
151
|
+
(part) => typeof part === 'object' && part !== null && part.type === 'output_text',
|
|
152
|
+
)
|
|
153
|
+
.map((part) => part.text)
|
|
154
|
+
: [],
|
|
155
|
+
)
|
|
156
|
+
const sourceCalls = source.content
|
|
157
|
+
.filter((part) => part.type === 'tool-call')
|
|
158
|
+
.map((part) => part.toolCallId)
|
|
159
|
+
const wireCalls = calls.map((call) => call.call_id)
|
|
160
|
+
if (
|
|
161
|
+
JSON.stringify(sourceText) !== JSON.stringify(wireText) ||
|
|
162
|
+
JSON.stringify(sourceCalls) !== JSON.stringify(wireCalls)
|
|
163
|
+
)
|
|
164
|
+
refuse()
|
|
165
|
+
const captured = source.content
|
|
166
|
+
.filter((part) => part.type === 'reasoning')
|
|
167
|
+
.map((part) => part.text)
|
|
168
|
+
.filter((text) => text.trim().length > 0)
|
|
169
|
+
.join('\n')
|
|
170
|
+
if (!captured) refuse()
|
|
171
|
+
const reasoning = {
|
|
172
|
+
type: 'reasoning',
|
|
173
|
+
summary: [],
|
|
174
|
+
content: [{ type: 'reasoning_text', text: captured }],
|
|
175
|
+
}
|
|
176
|
+
// The adapter can replay a native [tool-call, text] turn as a call then
|
|
177
|
+
// an assistant message. Go rejects a message between calls and outputs.
|
|
178
|
+
input.push(reasoning, ...messages, ...calls)
|
|
179
|
+
turn = []
|
|
180
|
+
}
|
|
181
|
+
for (const value of body.input) {
|
|
182
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
183
|
+
throw new Error('Go DeepSeek Responses request has an invalid input item.')
|
|
184
|
+
const item = value as Record<string, unknown>
|
|
185
|
+
if (item.type === 'reasoning' || item.type === 'function_call' || item.role === 'assistant') {
|
|
186
|
+
turn.push(item)
|
|
187
|
+
} else {
|
|
188
|
+
flushTurn()
|
|
189
|
+
input.push(item)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
flushTurn()
|
|
193
|
+
if (sourceIndex !== sourceTurns.length) refuse()
|
|
194
|
+
body.input = input
|
|
195
|
+
return { ...init, body: JSON.stringify(body) }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function withGoDeepSeekReasoningTextEvents(response: Response): Response {
|
|
199
|
+
if (
|
|
200
|
+
!response.ok ||
|
|
201
|
+
!response.body ||
|
|
202
|
+
!response.headers.get('content-type')?.includes('text/event-stream')
|
|
203
|
+
)
|
|
204
|
+
return response
|
|
205
|
+
// @ai-sdk/openai 3.0.109 understands reasoning summary deltas but not the
|
|
206
|
+
// reasoning_text delta that Go DeepSeek streams. Translate that one event
|
|
207
|
+
// before the adapter parses SSE; the original text then enters durable replay.
|
|
208
|
+
const decoder = new TextDecoder()
|
|
209
|
+
const encoder = new TextEncoder()
|
|
210
|
+
let pending = ''
|
|
211
|
+
const rewrite = (event: string): string => {
|
|
212
|
+
const lines = event.split(/\r\n|\n|\r/)
|
|
213
|
+
const dataLines = lines.filter((line) => line.startsWith('data:'))
|
|
214
|
+
if (dataLines.length !== 1) return event
|
|
215
|
+
let value: unknown
|
|
216
|
+
try {
|
|
217
|
+
value = JSON.parse(dataLines[0]?.slice(5).trimStart() ?? '')
|
|
218
|
+
} catch {
|
|
219
|
+
return event
|
|
220
|
+
}
|
|
221
|
+
if (
|
|
222
|
+
typeof value !== 'object' ||
|
|
223
|
+
value === null ||
|
|
224
|
+
!('type' in value) ||
|
|
225
|
+
value.type !== 'response.reasoning_text.delta' ||
|
|
226
|
+
!('item_id' in value) ||
|
|
227
|
+
typeof value.item_id !== 'string' ||
|
|
228
|
+
!('delta' in value) ||
|
|
229
|
+
typeof value.delta !== 'string'
|
|
230
|
+
)
|
|
231
|
+
return event
|
|
232
|
+
const translated = {
|
|
233
|
+
...value,
|
|
234
|
+
type: 'response.reasoning_summary_text.delta',
|
|
235
|
+
summary_index: 0,
|
|
236
|
+
}
|
|
237
|
+
return lines
|
|
238
|
+
.map((line) => {
|
|
239
|
+
if (line.startsWith('data:')) return `data: ${JSON.stringify(translated)}`
|
|
240
|
+
if (line.startsWith('event:')) return 'event: response.reasoning_summary_text.delta'
|
|
241
|
+
return line
|
|
242
|
+
})
|
|
243
|
+
.join('\n')
|
|
244
|
+
}
|
|
245
|
+
const body = response.body.pipeThrough(
|
|
246
|
+
new TransformStream<Uint8Array, Uint8Array>({
|
|
247
|
+
transform(chunk, controller) {
|
|
248
|
+
pending += decoder.decode(chunk, { stream: true })
|
|
249
|
+
while (true) {
|
|
250
|
+
const boundary = /\r\n\r\n|\n\n|\r\r/.exec(pending)
|
|
251
|
+
if (!boundary) break
|
|
252
|
+
const event = pending.slice(0, boundary.index)
|
|
253
|
+
pending = pending.slice(boundary.index + boundary[0].length)
|
|
254
|
+
controller.enqueue(encoder.encode(`${rewrite(event)}\n\n`))
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
flush(controller) {
|
|
258
|
+
pending += decoder.decode()
|
|
259
|
+
if (pending) controller.enqueue(encoder.encode(pending))
|
|
260
|
+
},
|
|
261
|
+
}),
|
|
262
|
+
)
|
|
263
|
+
const headers = new Headers(response.headers)
|
|
264
|
+
headers.delete('content-length')
|
|
265
|
+
headers.delete('content-encoding')
|
|
266
|
+
return new Response(body, {
|
|
267
|
+
status: response.status,
|
|
268
|
+
statusText: response.statusText,
|
|
269
|
+
headers,
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* The words a backend's own finish reason uses, in `LanguageModelV3FinishReason.raw`,
|
|
275
|
+
* for "the model's context window, not its output-token budget, is what ran
|
|
276
|
+
* out" — Anthropic's `model_context_window_exceeded` (folded, with
|
|
277
|
+
* `max_tokens`, into `unified: 'length'` by `@ai-sdk/anthropic`'s
|
|
278
|
+
* `mapAnthropicStopReason`) and the words other OpenAI-compatible backends
|
|
279
|
+
* use (see `@namzu/http`'s `mapOpenAIFinish`), since Zen also routes through
|
|
280
|
+
* `@ai-sdk/openai-compatible`.
|
|
281
|
+
*/
|
|
282
|
+
const CONTEXT_WINDOW_RAW_FINISH_REASONS = new Set([
|
|
283
|
+
'model_context_window_exceeded',
|
|
284
|
+
'model_length',
|
|
285
|
+
'context_length',
|
|
286
|
+
'context_length_exceeded',
|
|
287
|
+
])
|
|
288
|
+
|
|
53
289
|
/** A provider instance belongs to one conversation; it never owns an agent loop. */
|
|
54
290
|
export class ZenProvider implements LLMProvider {
|
|
55
291
|
readonly id: 'zen' | 'zen-go'
|
|
@@ -119,12 +355,13 @@ export class ZenProvider implements LLMProvider {
|
|
|
119
355
|
|
|
120
356
|
async *chatStream(params: ChatCompletionParams): AsyncIterable<StreamChunk> {
|
|
121
357
|
const model =
|
|
122
|
-
params.model ||
|
|
123
|
-
this.config.model ||
|
|
124
|
-
(this.anonymous ? 'muse-spark-1.3-contributor-free' : 'glm-5.3-flash')
|
|
358
|
+
params.model || this.config.model || (this.anonymous ? 'space-bunny-free' : 'glm-5.3-flash')
|
|
125
359
|
const catalogue = this.runtimeCatalogue()
|
|
126
360
|
const info = findZenCatalogueModel(catalogue, this.service, model)
|
|
127
|
-
if (
|
|
361
|
+
if (
|
|
362
|
+
this.anonymous &&
|
|
363
|
+
(!isDirectAnonymousZenId(model) || info?.supportsAnonymousAccess !== true)
|
|
364
|
+
)
|
|
128
365
|
throw new ProviderRequestError({
|
|
129
366
|
providerId: this.id,
|
|
130
367
|
kind: 'auth',
|
|
@@ -140,7 +377,11 @@ export class ZenProvider implements LLMProvider {
|
|
|
140
377
|
? 'The service serves this model, but no source states its wire format. Configure protocol explicitly.'
|
|
141
378
|
: 'This model has no known wire format. Update the provider or configure protocol explicitly.',
|
|
142
379
|
})
|
|
143
|
-
const route = params.providerRoute ?? {
|
|
380
|
+
const route = params.providerRoute ?? {
|
|
381
|
+
providerId: this.id,
|
|
382
|
+
model,
|
|
383
|
+
chainIndex: 0,
|
|
384
|
+
}
|
|
144
385
|
const controller = new AbortController()
|
|
145
386
|
const signal = AbortSignal.any([
|
|
146
387
|
controller.signal,
|
|
@@ -152,7 +393,7 @@ export class ZenProvider implements LLMProvider {
|
|
|
152
393
|
signal.throwIfAborted()
|
|
153
394
|
let options: ReturnType<typeof createCallOptions>
|
|
154
395
|
try {
|
|
155
|
-
options = createCallOptions({ ...params, model }, route, this.service, protocol)
|
|
396
|
+
options = createCallOptions({ ...params, model }, route, this.service, protocol, info)
|
|
156
397
|
} catch (error) {
|
|
157
398
|
if (isProviderRequestError(error)) throw error
|
|
158
399
|
throw new ProviderRequestError({
|
|
@@ -177,8 +418,20 @@ export class ZenProvider implements LLMProvider {
|
|
|
177
418
|
body.generationConfig ??= {}
|
|
178
419
|
delete body.generationConfig.responseSchema
|
|
179
420
|
body.generationConfig.responseJsonSchema = params.responseFormat.json_schema.schema
|
|
180
|
-
return this.requestFetch(input, {
|
|
421
|
+
return this.requestFetch(input, {
|
|
422
|
+
...init,
|
|
423
|
+
body: JSON.stringify(body),
|
|
424
|
+
})
|
|
181
425
|
}
|
|
426
|
+
if (isMuseSparkResponsesModel(model, protocol))
|
|
427
|
+
return this.requestFetch(input, withoutMuseSparkEncryptedReasoning(init))
|
|
428
|
+
if (isGoDeepSeekV4ChatModel(this.service, model, protocol))
|
|
429
|
+
return this.requestFetch(input, withGoDeepSeekV4ChatHistory(init, model))
|
|
430
|
+
if (isGoDeepSeekV4ResponsesModel(this.service, model, protocol))
|
|
431
|
+
return this.requestFetch(
|
|
432
|
+
input,
|
|
433
|
+
withGoDeepSeekV4ResponsesHistory(init, model, options.prompt),
|
|
434
|
+
).then(withGoDeepSeekReasoningTextEvents)
|
|
182
435
|
return this.requestFetch(input, init)
|
|
183
436
|
}) as typeof fetch,
|
|
184
437
|
}
|
|
@@ -209,7 +462,10 @@ export class ZenProvider implements LLMProvider {
|
|
|
209
462
|
const texts = new Map<string, Extract<LanguageModelV3Content, { type: 'text' }>>()
|
|
210
463
|
const thoughts = new Map<
|
|
211
464
|
string,
|
|
212
|
-
{
|
|
465
|
+
{
|
|
466
|
+
index: number
|
|
467
|
+
value: Extract<LanguageModelV3Content, { type: 'reasoning' }>
|
|
468
|
+
}
|
|
213
469
|
>()
|
|
214
470
|
const tools = new Map<
|
|
215
471
|
string,
|
|
@@ -274,7 +530,10 @@ export class ZenProvider implements LLMProvider {
|
|
|
274
530
|
const index = thoughts.size
|
|
275
531
|
thoughts.set(part.id, { index, value })
|
|
276
532
|
content.push(value)
|
|
277
|
-
yield {
|
|
533
|
+
yield {
|
|
534
|
+
id,
|
|
535
|
+
delta: { reasoning: { index, type: 'thinking', text: '' } },
|
|
536
|
+
}
|
|
278
537
|
break
|
|
279
538
|
}
|
|
280
539
|
case 'reasoning-delta':
|
|
@@ -342,7 +601,9 @@ export class ZenProvider implements LLMProvider {
|
|
|
342
601
|
)
|
|
343
602
|
yield {
|
|
344
603
|
id,
|
|
345
|
-
delta: {
|
|
604
|
+
delta: {
|
|
605
|
+
toolCalls: [{ index: tool.index, function: { arguments: part.delta } }],
|
|
606
|
+
},
|
|
346
607
|
}
|
|
347
608
|
break
|
|
348
609
|
}
|
|
@@ -361,7 +622,12 @@ export class ZenProvider implements LLMProvider {
|
|
|
361
622
|
throw new Error('Provider-executed tools are not configured.')
|
|
362
623
|
let tool = tools.get(part.toolCallId)
|
|
363
624
|
if (!tool) {
|
|
364
|
-
tool = {
|
|
625
|
+
tool = {
|
|
626
|
+
index: tools.size,
|
|
627
|
+
input: '',
|
|
628
|
+
ended: false,
|
|
629
|
+
value: { ...part },
|
|
630
|
+
}
|
|
365
631
|
tools.set(part.toolCallId, tool)
|
|
366
632
|
content.push(tool.value)
|
|
367
633
|
yield {
|
|
@@ -385,7 +651,9 @@ export class ZenProvider implements LLMProvider {
|
|
|
385
651
|
if (suffix)
|
|
386
652
|
yield {
|
|
387
653
|
id,
|
|
388
|
-
delta: {
|
|
654
|
+
delta: {
|
|
655
|
+
toolCalls: [{ index: tool.index, function: { arguments: suffix } }],
|
|
656
|
+
},
|
|
389
657
|
}
|
|
390
658
|
tool.value.input = part.input
|
|
391
659
|
tool.value.providerMetadata = mergeMetadata(
|
|
@@ -393,7 +661,12 @@ export class ZenProvider implements LLMProvider {
|
|
|
393
661
|
part.providerMetadata,
|
|
394
662
|
)
|
|
395
663
|
tool.ended = true
|
|
396
|
-
yield {
|
|
664
|
+
yield {
|
|
665
|
+
id,
|
|
666
|
+
delta: {
|
|
667
|
+
toolCallEnd: { index: tool.index, id: part.toolCallId },
|
|
668
|
+
},
|
|
669
|
+
}
|
|
397
670
|
break
|
|
398
671
|
}
|
|
399
672
|
case 'finish':
|
|
@@ -423,6 +696,11 @@ export class ZenProvider implements LLMProvider {
|
|
|
423
696
|
id,
|
|
424
697
|
delta: {},
|
|
425
698
|
finishReason,
|
|
699
|
+
...(finishReason === 'length' &&
|
|
700
|
+
finish.finishReason.raw &&
|
|
701
|
+
CONTEXT_WINDOW_RAW_FINISH_REASONS.has(finish.finishReason.raw)
|
|
702
|
+
? { finishDetail: 'context_window' as const }
|
|
703
|
+
: {}),
|
|
426
704
|
usage: mapUsage(finish.usage),
|
|
427
705
|
replayState: createReplayState(
|
|
428
706
|
{ ...params, model },
|
|
@@ -472,7 +750,11 @@ export class ZenProvider implements LLMProvider {
|
|
|
472
750
|
seen.add(item.id)
|
|
473
751
|
const model = findZenCatalogueModel(catalogue, this.service, item.id)
|
|
474
752
|
if (model) {
|
|
475
|
-
if (
|
|
753
|
+
if (
|
|
754
|
+
!this.anonymous ||
|
|
755
|
+
(isDirectAnonymousZenId(item.id) && model.supportsAnonymousAccess === true)
|
|
756
|
+
)
|
|
757
|
+
result.push({ ...model })
|
|
476
758
|
continue
|
|
477
759
|
}
|
|
478
760
|
// Served, and named by the runtime catalogue as having no known wire:
|
|
@@ -582,7 +864,13 @@ export class ZenProvider implements LLMProvider {
|
|
|
582
864
|
const envelope = safeErrorEnvelope(error)
|
|
583
865
|
const body = scrub(JSON.stringify(envelope))
|
|
584
866
|
if (envelope.status !== undefined)
|
|
585
|
-
return named(
|
|
867
|
+
return named(
|
|
868
|
+
providerHttpError({
|
|
869
|
+
providerId: this.id,
|
|
870
|
+
status: envelope.status,
|
|
871
|
+
body,
|
|
872
|
+
}),
|
|
873
|
+
)
|
|
586
874
|
return named(
|
|
587
875
|
providerVendorError({
|
|
588
876
|
providerId: this.id,
|
package/src/models.review.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"omissions": {
|
|
3
3
|
"go/deepseek-flash": "Served by the Go service and named by no route row on either page; models.dev carries no entry for it either, so neither a wire nor its limits can be derived.",
|
|
4
|
-
"go/glm-5": "Served by Go and no longer routed by the Go page, which routes GLM 5.
|
|
4
|
+
"go/glm-5": "Served by Go and no longer routed by the Go page, which routes GLM 5.2, 5.3 and 5.3 Flash. The Zen page still routes this id, and one service's route is not the other's, so none is taken from it.",
|
|
5
|
+
"go/glm-5.1": "Still served by Go, but the current Go page states neither a route nor a price for it. The /models answer states no wire, so the old bundled route is not carried without current evidence.",
|
|
5
6
|
"go/grok-4.5": "Served by Go and absent from the Go page, which routes Grok 4.6. models.dev states an npm package for it, and that field is deliberately not used as a route, so none is carried.",
|
|
6
7
|
"go/hy3-preview": "Served by Go and named by no route row on either page; models.dev carries no entry for it, so neither a wire nor its limits can be derived.",
|
|
7
8
|
"go/kimi-k2.5": "Served by Go and absent from the Go page, which routes Kimi K2.6, K2.7 Code and K3. The Zen page still routes this id, and one service's route is not the other's, so none is taken from it.",
|
|
@@ -11,7 +12,9 @@
|
|
|
11
12
|
"go/mimo-v2.6-pro": "Routed by the Go page since 2026-09-21, and models.dev carries no `opencode-go` entry for it yet, so its limits cannot be derived and it is not carried.",
|
|
12
13
|
"go/minimax-m2.5": "Superseded on Go by MiniMax M2.7 and M3, which are both carried. The page still lists it.",
|
|
13
14
|
"go/omen-alpha": "Served by Go and no longer routed by the Go page, which is why this release dropped it from the catalogue. No source states a wire for it any more, so a caller that needs it must name the protocol itself.",
|
|
14
|
-
"go/qwen3.5-plus": "Served by Go and absent from the Go page, which routes Qwen 3.
|
|
15
|
+
"go/qwen3.5-plus": "Served by Go and absent from the Go page, which routes Qwen 3.7 Plus, 3.8 Flash and 3.8 Max. The Zen page routes this id on Messages, and one service's route is not the other's, so none is taken from it.",
|
|
16
|
+
"go/qwen3.6-plus": "Still served by Go, but the current Go page states neither a route nor a price for it. The /models answer states no wire, and the old bundled Messages route conflicts with the reference client's Chat route, so it is not carried by guessing.",
|
|
17
|
+
"go/qwen3.7-max": "Still served by Go, but the current Go page states neither a route nor a price for it. The /models answer states no wire, so the old bundled route is not carried without current evidence.",
|
|
15
18
|
"zen/claude-sonnet-4": "Served by Zen and named by no route row on either page: the Zen page routes Claude Sonnet 4.5 and 4.6. models.dev states an npm package for it, and that field is deliberately not used as a route, so none is carried.",
|
|
16
19
|
"zen/deepseek-v4-flash-free": "Served by Zen and absent from the Zen page, which routes DeepSeek V4 Flash. models.dev states a name and limits for this free id and no wire, so none is carried.",
|
|
17
20
|
"zen/glm-5": "Superseded by GLM 5.1, 5.2 and 5.3, which are all carried. The page still lists it.",
|