Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/guides/CODEX-CLI-CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,16 @@ Use a real key instead when your OmniRoute server is protected or remote.

Codex CLI deprecated `wire_api = "chat"` (Chat Completions) in February 2026 and now **requires** `wire_api = "responses"` (OpenAI Responses API). Setting `wire_api = "chat"` causes an immediate startup crash since v0.138.

DeepSeek, GLM, Kimi and others only expose a Chat Completions endpoint — not the Responses API. If you pointed Codex directly at them, it would fail.
Many providers, including GLM and Kimi, still expose only a Chat Completions endpoint. DeepSeek V4 now exposes a native Responses API as well as an Anthropic-compatible endpoint; OmniRoute uses Responses by default and lets each DeepSeek connection select Anthropic compatibility.

**OmniRoute solves this transparently:**

```
Codex CLI
→ wire_api = "responses"
→ POST /v1/responses (OmniRoute)
→ OmniRoute Responses ↔ Chat Completions transformer
→ POST /chat/completions (DeepSeek / Mistral / GLM / Kimi / any provider)
→ OmniRoute selects the provider's native protocol and translates when needed
→ POST /responses (DeepSeek V4) or /chat/completions (Mistral / GLM / Kimi / others)
```

You never need a separate translation proxy when using OmniRoute. **All models use `wire_api = "responses"`** — OmniRoute handles the rest.
Expand Down
25 changes: 20 additions & 5 deletions open-sse/config/providers/registry/deepseek/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
import type { RegistryEntry } from "../../shared.ts";
import { getAnthropicCompatHeaders, type RegistryEntry } from "../../shared.ts";

export const deepseekProvider: RegistryEntry = {
id: "deepseek",
alias: "ds",
format: "openai",
format: "openai-responses",
executor: "default",
baseUrl: "https://api.deepseek.com/v1/chat/completions",
baseUrl: "https://api.deepseek.com/responses",
authType: "apikey",
authHeader: "bearer",
alternateFormats: [
{
format: "claude",
baseUrl: "https://api.deepseek.com/anthropic/v1/messages",
authHeader: "x-api-key",
headers: getAnthropicCompatHeaders(),
label: "Anthropic-compatible",
},
],
models: [
{
id: "deepseek-v4-pro",
name: "DeepSeek V4 Pro",
name: "DeepSeek V4 Pro (0813)",
contextLength: 1_000_000,
maxOutputTokens: 384_000,
supportsReasoning: true,
supportedThinkingEfforts: ["none", "high", "max"],
toolCalling: true,
},
{
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
name: "DeepSeek V4 Flash (0731)",
contextLength: 1_000_000,
maxOutputTokens: 384_000,
supportsReasoning: true,
supportedThinkingEfforts: ["none", "low", "high", "max"],
toolCalling: true,
},
],
};
9 changes: 9 additions & 0 deletions open-sse/config/providers/registry/grok-cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ export const grok_cliProvider: RegistryEntry = {
authHeader: "bearer",
passthroughModels: true,
models: [
{
id: "grok-4.6",
name: "Grok 4.6",
contextLength: 500000,
supportsReasoning: true,
toolCalling: true,
targetFormat: "openai-responses",
unsupportedParams: ["presencePenalty", "frequencyPenalty", "logprobs", "topLogprobs"],
},
{
id: "grok-4.5",
name: "Grok 4.5",
Expand Down
11 changes: 11 additions & 0 deletions open-sse/config/providers/registry/xai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ export const xaiProvider: RegistryEntry = {
authType: "apikey",
authHeader: "bearer",
models: [
{
id: "grok-4.6",
name: "Grok 4.6",
contextLength: 500000,
supportsReasoning: true,
supportedThinkingEfforts: ["low", "medium", "high", "xhigh"],
supportsVision: true,
supportsXHighEffort: true,
toolCalling: true,
targetFormat: "openai-responses",
},
{ id: "grok-4.3", name: "Grok 4.3" },
{ id: "grok-build-0.1", name: "Grok Build 0.1", contextLength: 256000 },
// Responses-only per upstream 9router#2439: xAI serves this id exclusively
Expand Down
14 changes: 12 additions & 2 deletions open-sse/handlers/chatCore/targetFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
* decomposition, #3501).
*
* Pure resolution of the provider alias + the upstream target format used to translate the request.
* Model/custom overrides win first. A Responses-shaped inbound request normally keeps the Responses
* wire format, except for custom OpenAI-compatible connections explicitly configured for Chat.
* Model/custom overrides win first. A declared connection-level alternate protocol wins next. A
* Responses-shaped inbound request otherwise keeps the Responses wire format, except for custom
* OpenAI-compatible connections explicitly configured for Chat.
* AgentRouter may inherit the inbound protocol when no explicit connection override exists.
* Returns both `alias` (reused by the handler when stripping the `alias/` prefix off the upstream
* model id) and `targetFormat`.
*/

import { PROVIDER_ID_TO_ALIAS, getModelTargetFormat } from "../../config/providerModels.ts";
import { getRegistryEntry } from "../../config/providerRegistry.ts";
import { resolveAlternateFormat } from "../../config/providers/alternateFormats.ts";
import { getTargetFormat } from "../../services/provider.ts";
import { FORMATS } from "../../translator/formats.ts";

Expand Down Expand Up @@ -46,15 +49,22 @@ export function resolveChatCoreTargetFormat(opts: {
? sourceFormat
: undefined;
const providerTargetFormat = getTargetFormat(provider, providerSpecificData);
const declaredConnectionAlternate = resolveAlternateFormat(
getRegistryEntry(provider),
providerSpecificData
);
const customOpenAICompatible = provider.startsWith("openai-compatible-");
// #8994: model-level targetFormat overrides (from registry or custom-model DB override)
// take precedence over apiFormat="responses" — otherwise Vertex Claude models with
// targetFormat="claude" get wrongly routed to OpenAI Responses format.
// #9161: a custom OpenAI-compatible Chat connection must likewise keep its configured
// outbound protocol when a Responses-shaped client (for example Codex) calls /responses.
// Registry-declared connection alternates are equally explicit: a DeepSeek connection set to
// Anthropic must stay on /anthropic/v1/messages even when the caller speaks Responses.
let targetFormat =
modelTargetFormat ||
customModelTargetFormat ||
declaredConnectionAlternate?.format ||
(apiFormat === "responses" && !customOpenAICompatible
? FORMATS.OPENAI_RESPONSES
: inferredAgentRouterTargetFormat || providerTargetFormat);
Expand Down
19 changes: 14 additions & 5 deletions src/shared/constants/pricing/frontier-labs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,20 +315,20 @@ export const DEFAULT_PRICING_FRONTIER = {
reasoning: 2.19,
cache_creation: 0.55,
},
// DeepSeek V4 Pro — promo until 2026-05-31, then list ($0.145 / $3.48)
// DeepSeek official API list prices, checked 2026-08-13.
"deepseek-v4-pro": {
input: 0.435,
output: 0.87,
cached: 0.0036,
cached: 0.003625,
reasoning: 0.87,
cache_creation: 0.435,
},
"deepseek-v4-flash": {
input: 0.07,
input: 0.14,
output: 0.28,
cached: 0.014,
cached: 0.0028,
reasoning: 0.28,
cache_creation: 0.07,
cache_creation: 0.14,
},
},
blackbox: {
Expand All @@ -340,6 +340,15 @@ export const DEFAULT_PRICING_FRONTIER = {
"blackboxai-pro": { input: 0, output: 0, cached: 0, reasoning: 0, cache_creation: 0 },
},
xai: {
// The static rate covers prompts below 200K tokens. xAI's provider-reported
// cost_in_usd_ticks remains authoritative for the >=200K pricing tier.
"grok-4.6": {
input: 2.0,
output: 6.0,
cached: 0.5,
reasoning: 6.0,
cache_creation: 2.0,
},
"grok-3": {
input: 3.0,
output: 15.0,
Expand Down
6 changes: 3 additions & 3 deletions tests/snapshots/provider/translate-path.json
Original file line number Diff line number Diff line change
Expand Up @@ -1604,7 +1604,7 @@
}
},
"deepseek": {
"format": "openai",
"format": "openai-responses",
"headers": {
"apiKey": {
"Accept": "text/event-stream",
Expand All @@ -1622,8 +1622,8 @@
}
},
"url": {
"nonStream": "https://api.deepseek.com/v1/chat/completions",
"stream": "https://api.deepseek.com/v1/chat/completions"
"nonStream": "https://api.deepseek.com/responses",
"stream": "https://api.deepseek.com/responses"
}
},
"deepseek-web": {
Expand Down
89 changes: 86 additions & 3 deletions tests/unit/alternate-formats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { resolveAlternateFormat } from "../../open-sse/config/providers/alternat
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
import { getTargetFormat } from "../../open-sse/services/provider.ts";
import { DefaultExecutor } from "../../open-sse/executors/default.ts";
import { FORMATS } from "../../open-sse/translator/formats.ts";
import { translateRequest } from "../../open-sse/translator/index.ts";
import { getAlternateFormats } from "../../src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts";

const ENTRY: RegistryEntry = {
Expand Down Expand Up @@ -42,7 +44,10 @@ test("retorna null quando a conexao nao tem targetFormat", () => {
});

test("retorna null quando a entry nao declara alternativas", () => {
assert.equal(resolveAlternateFormat({ ...ENTRY, alternateFormats: undefined }, { targetFormat: "claude" }), null);
assert.equal(
resolveAlternateFormat({ ...ENTRY, alternateFormats: undefined }, { targetFormat: "claude" }),
null
);
assert.equal(resolveAlternateFormat(null, { targetFormat: "claude" }), null);
});

Expand Down Expand Up @@ -103,7 +108,11 @@ test("resolveBaseUrl: baseUrl manual da conexao vence a alternativa", () => {
});

test("resolveBaseUrl: alternativa vence o baseUrl padrao", () => {
const url = precedence({ targetFormat: "claude" }, ENTRY_WITH_ALT, "https://default.example.com/v1");
const url = precedence(
{ targetFormat: "claude" },
ENTRY_WITH_ALT,
"https://default.example.com/v1"
);
assert.equal(url, "https://alt.example.com/anthropic/v1/messages");
});

Expand Down Expand Up @@ -179,7 +188,81 @@ test("getAlternateFormats: provedor com alternativas retorna a lista", () => {
});

test("getAlternateFormats: provedor sem alternativas retorna lista vazia", () => {
assert.deepEqual(getAlternateFormats("deepseek"), []);
assert.deepEqual(getAlternateFormats("xai"), []);
assert.deepEqual(getAlternateFormats(null), []);
assert.deepEqual(getAlternateFormats(undefined), []);
});

test("DeepSeek defaults to Responses and exposes the official Anthropic endpoint", () => {
assert.equal(getTargetFormat("deepseek", null), "openai-responses");
assert.equal(getTargetFormat("deepseek", { targetFormat: "claude" }), "claude");

const defaultExecutor = new DefaultExecutor("deepseek");
assert.equal(
defaultExecutor.buildUrl("deepseek-v4-pro", true, 0, { apiKey: "sk-test" } as never),
"https://api.deepseek.com/responses"
);
const defaultHeaders = defaultExecutor.buildHeaders({ apiKey: "sk-test" } as never, true);
assert.equal(defaultHeaders.Authorization, "Bearer sk-test");

const anthropicCredentials = {
apiKey: "sk-test",
providerSpecificData: { targetFormat: "claude" },
} as never;
assert.equal(
defaultExecutor.buildUrl("deepseek-v4-pro", true, 0, anthropicCredentials),
"https://api.deepseek.com/anthropic/v1/messages"
);
const anthropicHeaders = defaultExecutor.buildHeaders(anthropicCredentials, true);
assert.equal(anthropicHeaders["x-api-key"], "sk-test");
assert.equal(typeof anthropicHeaders["Anthropic-Version"], "string");

const alternates = getAlternateFormats("deepseek");
assert.equal(alternates.length, 1);
assert.equal(alternates[0].format, "claude");
});

test("DeepSeek reuses the generic Chat-to-Responses and Responses-to-Anthropic translators", () => {
const responsesBody = translateRequest(
FORMATS.OPENAI,
FORMATS.OPENAI_RESPONSES,
"deepseek-v4-pro",
{
model: "deepseek-v4-pro",
messages: [{ role: "user", content: "hello" }],
max_tokens: 123,
stream: true,
},
true,
{},
"deepseek"
) as Record<string, unknown>;
assert.equal(responsesBody.messages, undefined);
assert.equal(responsesBody.max_output_tokens, 123);
assert.deepEqual(responsesBody.input, [
{
type: "message",
role: "user",
content: [{ type: "input_text", text: "hello" }],
status: "completed",
},
]);

const anthropicBody = translateRequest(
FORMATS.OPENAI_RESPONSES,
FORMATS.CLAUDE,
"deepseek-v4-pro",
{
model: "deepseek-v4-pro",
input: [{ role: "user", content: [{ type: "input_text", text: "hello" }] }],
stream: true,
},
true,
{},
"deepseek"
) as Record<string, unknown>;
assert.equal(anthropicBody.input, undefined);
assert.deepEqual(anthropicBody.messages, [
{ role: "user", content: [{ type: "text", text: "hello" }] },
]);
});
24 changes: 23 additions & 1 deletion tests/unit/chat-route-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ test("handleChat rejects requests without a model", async () => {
test("handleChat applies task-aware routing when a semantic override is enabled", async () => {
await seedConnection("deepseek", { apiKey: "sk-deepseek-task-route" });
const seenAuthHeaders = [];
const seenRequestBodies = [];
setTaskRoutingConfig({
enabled: true,
detectionEnabled: true,
Expand All @@ -214,7 +215,26 @@ test("handleChat applies task-aware routing when a semantic override is enabled"
globalThis.fetch = async (_url, init = {}) => {
const headers = toPlainHeaders(init.headers);
seenAuthHeaders.push(headers.Authorization ?? headers.authorization);
return buildOpenAIResponse("Task-routed response", "deepseek/deepseek-chat");
seenRequestBodies.push(JSON.parse(String(init.body)));
return new Response(
JSON.stringify({
id: "resp_task_route",
object: "response",
status: "completed",
model: "deepseek-v4-flash",
output: [
{
id: "msg_task_route",
type: "message",
role: "assistant",
status: "completed",
content: [{ type: "output_text", text: "Task-routed response", annotations: [] }],
},
],
usage: { input_tokens: 4, output_tokens: 2, total_tokens: 6 },
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
};

const response = await handleChat(
Expand All @@ -230,6 +250,8 @@ test("handleChat applies task-aware routing when a semantic override is enabled"

assert.equal(response.status, 200);
assert.deepEqual(seenAuthHeaders, ["Bearer sk-deepseek-task-route"]);
assert.equal(seenRequestBodies[0].messages, undefined);
assert.equal(seenRequestBodies[0].input[0].role, "user");
assert.equal(json.choices[0].message.content, "Task-routed response");
});

Expand Down
12 changes: 12 additions & 0 deletions tests/unit/chatcore-target-format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@ test("#8994: customModelTargetFormat takes precedence over apiFormat='responses'
assert.equal(r.targetFormat, "claude", "model-level targetFormat must win over apiFormat");
});

test("a declared connection alternate overrides the inbound Responses protocol", () => {
const r = resolveChatCoreTargetFormat({
provider: "deepseek",
resolvedModel: "deepseek-v4-pro",
apiFormat: "responses",
sourceFormat: FORMATS.OPENAI_RESPONSES,
customModelTargetFormat: undefined,
providerSpecificData: { targetFormat: FORMATS.CLAUDE },
});
assert.equal(r.targetFormat, FORMATS.CLAUDE);
});

test("unmapped provider → alias falls back to the provider id", () => {
const r = resolveChatCoreTargetFormat({
provider: "some-unmapped-provider",
Expand Down
Loading
Loading