@volter/twin-deepseek 0.1.0 → 0.1.2
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/README.md +2 -2
- package/dist/src/deepseek-capabilities.js +13 -2
- package/dist/src/deepseek-conformance.js +2 -1
- package/dist/src/deepseek-twin.js +31 -7
- package/package.json +3 -3
- package/src/deepseek-capabilities.ts +15 -2
- package/src/deepseek-conformance.ts +2 -1
- package/src/deepseek-twin.ts +32 -7
package/README.md
CHANGED
|
@@ -54,9 +54,9 @@ capability with a failable negative verify, grounded in
|
|
|
54
54
|
| usage cache fields | **`prompt_cache_hit_tokens` / `prompt_cache_miss_tokens`** | `prompt_tokens_details.cached_tokens` |
|
|
55
55
|
| reasoning | **`reasoning_content`** on the message and the delta | `reasoning` on o-series responses only |
|
|
56
56
|
| finish reasons | `stop`, `length`, `content_filter`, `tool_calls`, **`insufficient_system_resource`** | no `insufficient_system_resource` |
|
|
57
|
-
| end-user id | **`user_id`** (`^[a-zA-Z0-9_-]+$`, ≤512) | `user` |
|
|
57
|
+
| end-user id | **`user_id`** (`^[a-zA-Z0-9_-]+$`, ≤512); OpenAI's `user` is accepted and ignored (the docs are silent; ruling in `deepseek-twin.ts`, `OPENAI_USER`) | `user` |
|
|
58
58
|
| structured output (chat) | `text`, `json_object` — **no `json_schema` on `/chat/completions`** (its Responses API's `text.format` does take one) | `text`, `json_object`, `json_schema` |
|
|
59
|
-
| not parameters at all | **`n`, `seed`, `logit_bias`, `top_k`, `
|
|
59
|
+
| not parameters at all | **`n`, `seed`, `logit_bias`, `top_k`, `max_completion_tokens`, `service_tier`, `parallel_tool_calls`, `functions`** — each a 422 by name | all accepted |
|
|
60
60
|
| deprecated-but-accepted | `frequency_penalty`, `presence_penalty` — **no effect, never an error** | still honoured |
|
|
61
61
|
| ignored-but-accepted | `temperature`, `top_p` while thinking is on — **no effect, never an error** | honoured |
|
|
62
62
|
| beta-only | assistant `prefix: true`, `strict: true` tools, FIM `/beta/completions` | n/a |
|
|
@@ -210,12 +210,12 @@ export const DEEPSEEK_CAPABILITIES = [
|
|
|
210
210
|
const sixteen = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ stop: Array.from({ length: 16 }, (_, i) => `s${i}`) }) });
|
|
211
211
|
return many.status === 422 && ok(sixteen);
|
|
212
212
|
})),
|
|
213
|
-
done('deepseek.chat.rejects_openai_only_params', 'chat', "The OpenAI parameters DeepSeek's closed table does not declare (n, seed, logit_bias, top_k,
|
|
213
|
+
done('deepseek.chat.rejects_openai_only_params', 'chat', "The OpenAI parameters DeepSeek's closed table does not declare (n, seed, logit_bias, top_k, max_completion_tokens, service_tier, parallel_tool_calls, functions) are refused BY NAME with 422", 'api', 'core', () => withRoot(async (h) => {
|
|
214
214
|
// THE flagship OpenAI-divergence check. A twin copied from an OpenAI-shaped exemplar serves
|
|
215
215
|
// every one of these happily, which is the inherited-permissiveness false-green
|
|
216
216
|
// ADDING_A_TWIN.md §0 names. Each must be 422 — DeepSeek's "Invalid Parameters" — not 400.
|
|
217
217
|
for (const [key, value] of [
|
|
218
|
-
['n', 2], ['seed', 42], ['logit_bias', { '1': 1 }], ['top_k', 5],
|
|
218
|
+
['n', 2], ['seed', 42], ['logit_bias', { '1': 1 }], ['top_k', 5],
|
|
219
219
|
['max_completion_tokens', 32], ['service_tier', 'flex'], ['parallel_tool_calls', false],
|
|
220
220
|
['functions', [{ name: 'f' }]], ['store', true], ['metadata', { a: 'b' }],
|
|
221
221
|
]) {
|
|
@@ -285,6 +285,17 @@ export const DEEPSEEK_CAPABILITIES = [
|
|
|
285
285
|
const atCap = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ user_id: 'a'.repeat(512) }) });
|
|
286
286
|
return ok(good) && bad.status === 422 && long.status === 422 && ok(atCap);
|
|
287
287
|
})),
|
|
288
|
+
done('deepseek.chat.openai_user_ignored', 'chat', "OpenAI's `user` (which OpenAI-compatible software such as LibreChat sends) is accepted and has no effect: it is not DeepSeek's `user_id`, and a non-string is 422", 'api', 'common', () => withRoot(async (h) => {
|
|
289
|
+
const plain = await h({ m: 'POST', p: CHAT_PATH, b: CHAT() });
|
|
290
|
+
// a value user_id's charset refuses: accepted here, so it is not being read as user_id
|
|
291
|
+
const withUser = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ user: 'sam@friends.test' }) });
|
|
292
|
+
const both = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ user: 'u1', user_id: 'bad id' }) });
|
|
293
|
+
const typed = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ user: 42 }) });
|
|
294
|
+
const answer = (r) => JSON.stringify([r.body.choices?.[0]?.message?.content, r.body.usage?.prompt_tokens]);
|
|
295
|
+
return ok(plain) && ok(withUser) && answer(withUser) === answer(plain)
|
|
296
|
+
&& both.status === 422 && msg(both).includes("'user_id'")
|
|
297
|
+
&& typed.status === 422 && msg(typed).includes("'user'");
|
|
298
|
+
})),
|
|
288
299
|
done('deepseek.chat.message_name', 'chat', 'Participant `name` is accepted on system/user/assistant turns and counted into prompt tokens', 'api', 'niche', () => withRoot(async (h) => {
|
|
289
300
|
const named = await h({ m: 'POST', p: CHAT_PATH, b: { model: MODEL, messages: [{ role: 'user', content: 'hi', name: 'customer_alpha' }] } });
|
|
290
301
|
const plain = await h({ m: 'POST', p: CHAT_PATH, b: { model: MODEL, messages: [{ role: 'user', content: 'hi' }] } });
|
|
@@ -195,7 +195,8 @@ const REJECTIONS = [
|
|
|
195
195
|
{ name: 'seed', method: 'POST', path: '/chat/completions', body: { ...CHAT, seed: 42 }, status: 422, why: "'seed' is not a DeepSeek parameter" },
|
|
196
196
|
{ name: 'logit_bias', method: 'POST', path: '/chat/completions', body: { ...CHAT, logit_bias: { '1': 1 } }, status: 422, why: "'logit_bias' is not a DeepSeek parameter" },
|
|
197
197
|
{ name: 'top_k', method: 'POST', path: '/chat/completions', body: { ...CHAT, top_k: 5 }, status: 422, why: "'top_k' is not a DeepSeek parameter" },
|
|
198
|
-
|
|
198
|
+
// OpenAI's `user` as a string is accepted and ignored (deepseek-twin.ts, OPENAI_USER); only a non-string is refused
|
|
199
|
+
{ name: 'user (not a string)', method: 'POST', path: '/chat/completions', body: { ...CHAT, user: 42 }, status: 422, why: "OpenAI's 'user' is taken and ignored, but only as a string" },
|
|
199
200
|
{ name: 'max_completion_tokens', method: 'POST', path: '/chat/completions', body: { ...CHAT, max_completion_tokens: 10 }, status: 422, why: "DeepSeek declares 'max_tokens' only" },
|
|
200
201
|
{ name: 'service_tier', method: 'POST', path: '/chat/completions', body: { ...CHAT, service_tier: 'flex' }, status: 422, why: 'DeepSeek has no service tiers' },
|
|
201
202
|
{ name: 'parallel_tool_calls', method: 'POST', path: '/chat/completions', body: { ...CHAT, parallel_tool_calls: false }, status: 422, why: 'not in DeepSeek\'s parameter table' },
|
|
@@ -25,15 +25,17 @@
|
|
|
25
25
|
// includes two OpenAI does not use: 402 Insufficient Balance and 422 Invalid Parameters
|
|
26
26
|
// (api-docs.deepseek.com/quick_start/error_codes). OpenAI 400s a bad parameter; DeepSeek 422s
|
|
27
27
|
// it. 400 is reserved for "Invalid request body format".
|
|
28
|
-
// • `n`, `seed`, `logit_bias`, `top_k`, `
|
|
28
|
+
// • `n`, `seed`, `logit_bias`, `top_k`, `max_completion_tokens`, `service_tier`,
|
|
29
29
|
// `parallel_tool_calls`, `functions` are NOT DeepSeek parameters. The accepted set is a closed
|
|
30
30
|
// documented table (api-docs.deepseek.com/api/create-chat-completion), which §6 licenses as a
|
|
31
|
-
// literal-allowlist oracle — so anything outside CHAT_PARAMS is 422 by name.
|
|
31
|
+
// literal-allowlist oracle — so anything outside CHAT_PARAMS is 422 by name. OpenAI's `user` is
|
|
32
|
+
// the one exception, accepted and ignored (OPENAI_USER below).
|
|
32
33
|
// • `response_format` accepts `text` and `json_object` ONLY. `json_schema` is an OpenAI feature;
|
|
33
34
|
// `@ai-sdk/deepseek` confirms it by INJECTING the schema into a system message instead of
|
|
34
35
|
// sending `response_format.json_schema` (src/chat/convert-to-deepseek-chat-messages.ts).
|
|
35
|
-
// • `user_id
|
|
36
|
-
// the vendor documents server-side.
|
|
36
|
+
// • `user_id` is DeepSeek's end-user identifier — with a regex and a 512-character cap the SDK
|
|
37
|
+
// enforces client-side and the vendor documents server-side. OpenAI's `user` is taken and has
|
|
38
|
+
// no effect (OPENAI_USER).
|
|
37
39
|
// • Assistant-prefix completion and strict tool calls exist ONLY under the `/beta` base URL.
|
|
38
40
|
// • `frequency_penalty` / `presence_penalty` are DEPRECATED but MUST NOT ERROR, and
|
|
39
41
|
// `temperature` / `top_p` are IGNORED (not rejected) while thinking is enabled: "setting these
|
|
@@ -226,13 +228,31 @@ function parseJson(body) {
|
|
|
226
228
|
return { error: invalidFormat('the request body is not valid JSON') };
|
|
227
229
|
}
|
|
228
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* OpenAI's `user`, taken and IGNORED — where DeepSeek's documentation stops and the twin decides
|
|
233
|
+
* (read 2026-09-28). The reference table (api-docs.deepseek.com/api/create-chat-completion) does not
|
|
234
|
+
* list `user` (its end-user field is `user_id`) and says nothing of a key it does not list. Against
|
|
235
|
+
* that silence: DeepSeek states "The DeepSeek API uses an API format compatible with OpenAI/Anthropic"
|
|
236
|
+
* and invites "the OpenAI/Anthropic SDK or softwares compatible with the OpenAI/Anthropic API"
|
|
237
|
+
* (api-docs.deepseek.com, "Your First API Call"); its Anthropic-compatible surface answers the fields
|
|
238
|
+
* it does not use as "Ignored" rather than refusing them (api-docs.deepseek.com/guides/anthropic_api:
|
|
239
|
+
* metadata's "user_id is supported, others are ignored"); and OpenAI-compatible software sends `user`
|
|
240
|
+
* to DeepSeek as a matter of course — LibreChat's DeepSeek endpoint, through LangChain's ChatOpenAI,
|
|
241
|
+
* sends it on every chat (cookbook/librechat, finding: the twin's 422 was retried into a hang), which
|
|
242
|
+
* could not be DeepSeek's supported configuration if the real API refused it. So the twin accepts a
|
|
243
|
+
* string `user` and gives it no effect: it is NOT `user_id` (no isolation, no content-safety
|
|
244
|
+
* identity) and is not validated against `user_id`'s charset. A non-string `user` is 422, as OpenAI's
|
|
245
|
+
* declared type is a string. The rest of the table stays closed: this ruling is evidence for `user`
|
|
246
|
+
* alone, and none has been found for `n`, `seed` and the others.
|
|
247
|
+
*/
|
|
248
|
+
const OPENAI_USER = 'user';
|
|
229
249
|
/**
|
|
230
250
|
* THE CLOSED PARAMETER TABLE for `POST /chat/completions`
|
|
231
251
|
* (api-docs.deepseek.com/api/create-chat-completion, read 2026-08-31) plus the two streaming keys
|
|
232
252
|
* and the top-level `reasoning_effort` `@ai-sdk/deepseek` actually sends.
|
|
233
253
|
*
|
|
234
254
|
* §6 licenses a literal allowlist as an ORACLE precisely when the vendor documents a closed set,
|
|
235
|
-
* and this is one — which is what gives `n`, `seed`, `logit_bias`, `top_k`,
|
|
255
|
+
* and this is one — which is what gives `n`, `seed`, `logit_bias`, `top_k`,
|
|
236
256
|
* `max_completion_tokens`, `service_tier` and `parallel_tool_calls` a *checkable* refusal instead
|
|
237
257
|
* of the silent acceptance an OpenAI-copied twin gives them.
|
|
238
258
|
*
|
|
@@ -244,7 +264,7 @@ function parseJson(body) {
|
|
|
244
264
|
const CHAT_PARAMS = new Set([
|
|
245
265
|
'messages', 'model', 'thinking', 'max_tokens', 'response_format', 'stop', 'stream',
|
|
246
266
|
'stream_options', 'temperature', 'top_p', 'tools', 'tool_choice', 'logprobs', 'top_logprobs',
|
|
247
|
-
'user_id', 'frequency_penalty', 'presence_penalty', 'reasoning_effort',
|
|
267
|
+
'user_id', 'frequency_penalty', 'presence_penalty', 'reasoning_effort', OPENAI_USER,
|
|
248
268
|
]);
|
|
249
269
|
/** The closed table for `POST /beta/completions` (api-docs.deepseek.com/api/create-completion). */
|
|
250
270
|
const FIM_PARAMS = new Set([
|
|
@@ -432,7 +452,11 @@ function validateChat(params, beta) {
|
|
|
432
452
|
return { error: invalidParameters("'top_logprobs' requires 'logprobs' to be true") };
|
|
433
453
|
topLogprobs = v;
|
|
434
454
|
}
|
|
435
|
-
// (10) user_id — DeepSeek's end-user identifier. NOT OpenAI's `user
|
|
455
|
+
// (10) user_id — DeepSeek's end-user identifier. NOT OpenAI's `user`, which is taken and ignored
|
|
456
|
+
// (OPENAI_USER): only its type is checked.
|
|
457
|
+
if (params[OPENAI_USER] !== undefined && params[OPENAI_USER] !== null && typeof params[OPENAI_USER] !== 'string') {
|
|
458
|
+
return { error: invalidParameters(`'${OPENAI_USER}' must be a string`) };
|
|
459
|
+
}
|
|
436
460
|
let userId;
|
|
437
461
|
if (params.user_id !== undefined && params.user_id !== null) {
|
|
438
462
|
const v = params.user_id;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/twin-deepseek",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Local DeepSeek twin — a faithful, stateful local DeepSeek Platform API your real `@ai-sdk/deepseek` talks to unmodified. The model is stubbed (deterministic), but the protocol envelope is vendor-faithful and so are DeepSeek's OpenAI-DIVERGENT REFUSALS: 422 for a bad parameter and 402 for a drained balance (not OpenAI's 400), no /v1 path segment, no json_schema/n/seed/logit_bias/user, beta-only prefix completion and strict tools, reasoning_content, and a real KV-cache-split usage ledger. Built on @volter/world-core.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twin",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"postpack": "node ../../../scripts/publish/prepare-publish.mjs postpack"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@volter/world-core": "2.0.
|
|
55
|
+
"@volter/world-core": "2.0.2"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@volter/world-core": "2.0.
|
|
58
|
+
"@volter/world-core": "2.0.2",
|
|
59
59
|
"@volter/world-tooling": "0.1.0",
|
|
60
60
|
"@ai-sdk/deepseek": "^3.0.37",
|
|
61
61
|
"@types/bun": "^1.2.20",
|
|
@@ -225,12 +225,12 @@ export const DEEPSEEK_CAPABILITIES: CapabilitySpec[] = [
|
|
|
225
225
|
return many.status === 422 && ok(sixteen);
|
|
226
226
|
})),
|
|
227
227
|
|
|
228
|
-
done('deepseek.chat.rejects_openai_only_params', 'chat', "The OpenAI parameters DeepSeek's closed table does not declare (n, seed, logit_bias, top_k,
|
|
228
|
+
done('deepseek.chat.rejects_openai_only_params', 'chat', "The OpenAI parameters DeepSeek's closed table does not declare (n, seed, logit_bias, top_k, max_completion_tokens, service_tier, parallel_tool_calls, functions) are refused BY NAME with 422", 'api', 'core', () => withRoot(async (h) => {
|
|
229
229
|
// THE flagship OpenAI-divergence check. A twin copied from an OpenAI-shaped exemplar serves
|
|
230
230
|
// every one of these happily, which is the inherited-permissiveness false-green
|
|
231
231
|
// ADDING_A_TWIN.md §0 names. Each must be 422 — DeepSeek's "Invalid Parameters" — not 400.
|
|
232
232
|
for (const [key, value] of [
|
|
233
|
-
['n', 2], ['seed', 42], ['logit_bias', { '1': 1 }], ['top_k', 5],
|
|
233
|
+
['n', 2], ['seed', 42], ['logit_bias', { '1': 1 }], ['top_k', 5],
|
|
234
234
|
['max_completion_tokens', 32], ['service_tier', 'flex'], ['parallel_tool_calls', false],
|
|
235
235
|
['functions', [{ name: 'f' }]], ['store', true], ['metadata', { a: 'b' }],
|
|
236
236
|
] as Array<[string, unknown]>) {
|
|
@@ -297,6 +297,19 @@ export const DEEPSEEK_CAPABILITIES: CapabilitySpec[] = [
|
|
|
297
297
|
return ok(good) && bad.status === 422 && long.status === 422 && ok(atCap);
|
|
298
298
|
})),
|
|
299
299
|
|
|
300
|
+
done('deepseek.chat.openai_user_ignored', 'chat', "OpenAI's `user` (which OpenAI-compatible software such as LibreChat sends) is accepted and has no effect: it is not DeepSeek's `user_id`, and a non-string is 422", 'api', 'common', () => withRoot(async (h) => {
|
|
301
|
+
const plain = await h({ m: 'POST', p: CHAT_PATH, b: CHAT() });
|
|
302
|
+
// a value user_id's charset refuses: accepted here, so it is not being read as user_id
|
|
303
|
+
const withUser = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ user: 'sam@friends.test' }) });
|
|
304
|
+
const both = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ user: 'u1', user_id: 'bad id' }) });
|
|
305
|
+
const typed = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ user: 42 }) });
|
|
306
|
+
type Chat = { choices?: Array<{ message?: { content?: string } }>; usage?: { prompt_tokens?: number } };
|
|
307
|
+
const answer = (r: { body: unknown }) => JSON.stringify([(r.body as Chat).choices?.[0]?.message?.content, (r.body as Chat).usage?.prompt_tokens]);
|
|
308
|
+
return ok(plain) && ok(withUser) && answer(withUser) === answer(plain)
|
|
309
|
+
&& both.status === 422 && msg(both).includes("'user_id'")
|
|
310
|
+
&& typed.status === 422 && msg(typed).includes("'user'");
|
|
311
|
+
})),
|
|
312
|
+
|
|
300
313
|
done('deepseek.chat.message_name', 'chat', 'Participant `name` is accepted on system/user/assistant turns and counted into prompt tokens', 'api', 'niche', () => withRoot(async (h) => {
|
|
301
314
|
const named = await h({ m: 'POST', p: CHAT_PATH, b: { model: MODEL, messages: [{ role: 'user', content: 'hi', name: 'customer_alpha' }] } });
|
|
302
315
|
const plain = await h({ m: 'POST', p: CHAT_PATH, b: { model: MODEL, messages: [{ role: 'user', content: 'hi' }] } });
|
|
@@ -251,7 +251,8 @@ const REJECTIONS: Array<{ name: string; method: string; path: string; body: unkn
|
|
|
251
251
|
{ name: 'seed', method: 'POST', path: '/chat/completions', body: { ...CHAT, seed: 42 }, status: 422, why: "'seed' is not a DeepSeek parameter" },
|
|
252
252
|
{ name: 'logit_bias', method: 'POST', path: '/chat/completions', body: { ...CHAT, logit_bias: { '1': 1 } }, status: 422, why: "'logit_bias' is not a DeepSeek parameter" },
|
|
253
253
|
{ name: 'top_k', method: 'POST', path: '/chat/completions', body: { ...CHAT, top_k: 5 }, status: 422, why: "'top_k' is not a DeepSeek parameter" },
|
|
254
|
-
|
|
254
|
+
// OpenAI's `user` as a string is accepted and ignored (deepseek-twin.ts, OPENAI_USER); only a non-string is refused
|
|
255
|
+
{ name: 'user (not a string)', method: 'POST', path: '/chat/completions', body: { ...CHAT, user: 42 }, status: 422, why: "OpenAI's 'user' is taken and ignored, but only as a string" },
|
|
255
256
|
{ name: 'max_completion_tokens', method: 'POST', path: '/chat/completions', body: { ...CHAT, max_completion_tokens: 10 }, status: 422, why: "DeepSeek declares 'max_tokens' only" },
|
|
256
257
|
{ name: 'service_tier', method: 'POST', path: '/chat/completions', body: { ...CHAT, service_tier: 'flex' }, status: 422, why: 'DeepSeek has no service tiers' },
|
|
257
258
|
{ name: 'parallel_tool_calls', method: 'POST', path: '/chat/completions', body: { ...CHAT, parallel_tool_calls: false }, status: 422, why: 'not in DeepSeek\'s parameter table' },
|
package/src/deepseek-twin.ts
CHANGED
|
@@ -25,15 +25,17 @@
|
|
|
25
25
|
// includes two OpenAI does not use: 402 Insufficient Balance and 422 Invalid Parameters
|
|
26
26
|
// (api-docs.deepseek.com/quick_start/error_codes). OpenAI 400s a bad parameter; DeepSeek 422s
|
|
27
27
|
// it. 400 is reserved for "Invalid request body format".
|
|
28
|
-
// • `n`, `seed`, `logit_bias`, `top_k`, `
|
|
28
|
+
// • `n`, `seed`, `logit_bias`, `top_k`, `max_completion_tokens`, `service_tier`,
|
|
29
29
|
// `parallel_tool_calls`, `functions` are NOT DeepSeek parameters. The accepted set is a closed
|
|
30
30
|
// documented table (api-docs.deepseek.com/api/create-chat-completion), which §6 licenses as a
|
|
31
|
-
// literal-allowlist oracle — so anything outside CHAT_PARAMS is 422 by name.
|
|
31
|
+
// literal-allowlist oracle — so anything outside CHAT_PARAMS is 422 by name. OpenAI's `user` is
|
|
32
|
+
// the one exception, accepted and ignored (OPENAI_USER below).
|
|
32
33
|
// • `response_format` accepts `text` and `json_object` ONLY. `json_schema` is an OpenAI feature;
|
|
33
34
|
// `@ai-sdk/deepseek` confirms it by INJECTING the schema into a system message instead of
|
|
34
35
|
// sending `response_format.json_schema` (src/chat/convert-to-deepseek-chat-messages.ts).
|
|
35
|
-
// • `user_id
|
|
36
|
-
// the vendor documents server-side.
|
|
36
|
+
// • `user_id` is DeepSeek's end-user identifier — with a regex and a 512-character cap the SDK
|
|
37
|
+
// enforces client-side and the vendor documents server-side. OpenAI's `user` is taken and has
|
|
38
|
+
// no effect (OPENAI_USER).
|
|
37
39
|
// • Assistant-prefix completion and strict tool calls exist ONLY under the `/beta` base URL.
|
|
38
40
|
// • `frequency_penalty` / `presence_penalty` are DEPRECATED but MUST NOT ERROR, and
|
|
39
41
|
// `temperature` / `top_p` are IGNORED (not rejected) while thinking is enabled: "setting these
|
|
@@ -283,13 +285,32 @@ type ToolChoice = 'auto' | 'none' | 'required' | { name: string };
|
|
|
283
285
|
type ResponseFormat = 'text' | 'json_object';
|
|
284
286
|
type ReasoningEffort = 'low' | 'high' | 'max';
|
|
285
287
|
|
|
288
|
+
/**
|
|
289
|
+
* OpenAI's `user`, taken and IGNORED — where DeepSeek's documentation stops and the twin decides
|
|
290
|
+
* (read 2026-09-28). The reference table (api-docs.deepseek.com/api/create-chat-completion) does not
|
|
291
|
+
* list `user` (its end-user field is `user_id`) and says nothing of a key it does not list. Against
|
|
292
|
+
* that silence: DeepSeek states "The DeepSeek API uses an API format compatible with OpenAI/Anthropic"
|
|
293
|
+
* and invites "the OpenAI/Anthropic SDK or softwares compatible with the OpenAI/Anthropic API"
|
|
294
|
+
* (api-docs.deepseek.com, "Your First API Call"); its Anthropic-compatible surface answers the fields
|
|
295
|
+
* it does not use as "Ignored" rather than refusing them (api-docs.deepseek.com/guides/anthropic_api:
|
|
296
|
+
* metadata's "user_id is supported, others are ignored"); and OpenAI-compatible software sends `user`
|
|
297
|
+
* to DeepSeek as a matter of course — LibreChat's DeepSeek endpoint, through LangChain's ChatOpenAI,
|
|
298
|
+
* sends it on every chat (cookbook/librechat, finding: the twin's 422 was retried into a hang), which
|
|
299
|
+
* could not be DeepSeek's supported configuration if the real API refused it. So the twin accepts a
|
|
300
|
+
* string `user` and gives it no effect: it is NOT `user_id` (no isolation, no content-safety
|
|
301
|
+
* identity) and is not validated against `user_id`'s charset. A non-string `user` is 422, as OpenAI's
|
|
302
|
+
* declared type is a string. The rest of the table stays closed: this ruling is evidence for `user`
|
|
303
|
+
* alone, and none has been found for `n`, `seed` and the others.
|
|
304
|
+
*/
|
|
305
|
+
const OPENAI_USER = 'user';
|
|
306
|
+
|
|
286
307
|
/**
|
|
287
308
|
* THE CLOSED PARAMETER TABLE for `POST /chat/completions`
|
|
288
309
|
* (api-docs.deepseek.com/api/create-chat-completion, read 2026-08-31) plus the two streaming keys
|
|
289
310
|
* and the top-level `reasoning_effort` `@ai-sdk/deepseek` actually sends.
|
|
290
311
|
*
|
|
291
312
|
* §6 licenses a literal allowlist as an ORACLE precisely when the vendor documents a closed set,
|
|
292
|
-
* and this is one — which is what gives `n`, `seed`, `logit_bias`, `top_k`,
|
|
313
|
+
* and this is one — which is what gives `n`, `seed`, `logit_bias`, `top_k`,
|
|
293
314
|
* `max_completion_tokens`, `service_tier` and `parallel_tool_calls` a *checkable* refusal instead
|
|
294
315
|
* of the silent acceptance an OpenAI-copied twin gives them.
|
|
295
316
|
*
|
|
@@ -301,7 +322,7 @@ type ReasoningEffort = 'low' | 'high' | 'max';
|
|
|
301
322
|
const CHAT_PARAMS = new Set([
|
|
302
323
|
'messages', 'model', 'thinking', 'max_tokens', 'response_format', 'stop', 'stream',
|
|
303
324
|
'stream_options', 'temperature', 'top_p', 'tools', 'tool_choice', 'logprobs', 'top_logprobs',
|
|
304
|
-
'user_id', 'frequency_penalty', 'presence_penalty', 'reasoning_effort',
|
|
325
|
+
'user_id', 'frequency_penalty', 'presence_penalty', 'reasoning_effort', OPENAI_USER,
|
|
305
326
|
]);
|
|
306
327
|
|
|
307
328
|
/** The closed table for `POST /beta/completions` (api-docs.deepseek.com/api/create-completion). */
|
|
@@ -497,7 +518,11 @@ function validateChat(params: Record<string, unknown>, beta: boolean): { args: C
|
|
|
497
518
|
topLogprobs = v;
|
|
498
519
|
}
|
|
499
520
|
|
|
500
|
-
// (10) user_id — DeepSeek's end-user identifier. NOT OpenAI's `user
|
|
521
|
+
// (10) user_id — DeepSeek's end-user identifier. NOT OpenAI's `user`, which is taken and ignored
|
|
522
|
+
// (OPENAI_USER): only its type is checked.
|
|
523
|
+
if (params[OPENAI_USER] !== undefined && params[OPENAI_USER] !== null && typeof params[OPENAI_USER] !== 'string') {
|
|
524
|
+
return { error: invalidParameters(`'${OPENAI_USER}' must be a string`) };
|
|
525
|
+
}
|
|
501
526
|
let userId: string | undefined;
|
|
502
527
|
if (params.user_id !== undefined && params.user_id !== null) {
|
|
503
528
|
const v = params.user_id;
|