mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 14:52:09 +03:00
fix(qoder): route PAT tokens to Qoder native API instead of DashScope (#2559)
Integrated into release/v3.8.2
This commit is contained in:
committed by
GitHub
parent
c63080489e
commit
e62e7564be
@@ -1,11 +1,15 @@
|
||||
import {
|
||||
BaseExecutor,
|
||||
mergeUpstreamExtraHeaders,
|
||||
setUserAgentHeader,
|
||||
type ExecuteInput,
|
||||
type ProviderCredentials,
|
||||
} from "./base.ts";
|
||||
import { PROVIDERS } from "../config/constants.ts";
|
||||
import { getQoderDashscopeCompatHeaders } from "../config/providerHeaderProfiles.ts";
|
||||
import {
|
||||
getQoderDashscopeCompatHeaders,
|
||||
QODER_DEFAULT_USER_AGENT,
|
||||
} from "../config/providerHeaderProfiles.ts";
|
||||
import { sanitizeQwenThinkingToolChoice } from "../services/qwenThinking.ts";
|
||||
|
||||
function getAuthToken(credentials: ProviderCredentials): string {
|
||||
@@ -29,6 +33,17 @@ export class QoderExecutor extends BaseExecutor {
|
||||
super("qoder", PROVIDERS.qoder);
|
||||
}
|
||||
|
||||
buildHeaders(
|
||||
credentials: ProviderCredentials,
|
||||
stream = true,
|
||||
clientHeaders?: Record<string, string> | null,
|
||||
model?: string
|
||||
): Record<string, string> {
|
||||
const headers = super.buildHeaders(credentials, stream, clientHeaders, model);
|
||||
setUserAgentHeader(headers, QODER_DEFAULT_USER_AGENT);
|
||||
return headers;
|
||||
}
|
||||
|
||||
transformRequest(model: string, body: unknown): Record<string, unknown> {
|
||||
const payload = {
|
||||
...(typeof body === "object" && body !== null ? body : {}),
|
||||
@@ -61,20 +76,24 @@ export class QoderExecutor extends BaseExecutor {
|
||||
|
||||
const resolvedModel = model || "qwen3-coder-plus";
|
||||
|
||||
// Check if it's a model-alias matching QwenCode
|
||||
// Detect token type: PAT (Personal Access Token) starts with "pt-"
|
||||
const isPatToken = token.startsWith("pt-");
|
||||
|
||||
let mappedModel = resolvedModel;
|
||||
if (resolvedModel === "qwen3.5-plus" || resolvedModel === "qwen3.6-plus") {
|
||||
mappedModel = "coder-model"; // Translate alias to what DashScope compatible endpoint accepts via QwenCode tokens
|
||||
} else if (resolvedModel === "vision-model") {
|
||||
mappedModel = "qwen3-vl-plus";
|
||||
let endpointUrl: string;
|
||||
|
||||
if (isPatToken) {
|
||||
endpointUrl = "https://api.qoder.com/v1/chat/completions";
|
||||
} else {
|
||||
if (resolvedModel === "qwen3.5-plus" || resolvedModel === "qwen3.6-plus") {
|
||||
mappedModel = "coder-model";
|
||||
} else if (resolvedModel === "vision-model") {
|
||||
mappedModel = "qwen3-vl-plus";
|
||||
}
|
||||
endpointUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
|
||||
}
|
||||
|
||||
// Determine the resource URL: Qwen CLI tokens usually target portal.qwen.ai natively,
|
||||
// but the DashScope compatible endpoint works out of the box when authtype is set.
|
||||
// If the token was mapped to a custom `resource_url`, we should use it. Otherwise default to dashscope Aliyun.
|
||||
let endpointUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
|
||||
|
||||
// We allow setting custom API base via credentials
|
||||
// Check for custom API base via credentials (overrides the default)
|
||||
let credentialsApiBase: unknown;
|
||||
if (typeof credentials === "object" && credentials !== null) {
|
||||
const credsObj = credentials as Record<string, unknown>;
|
||||
@@ -90,7 +109,7 @@ export class QoderExecutor extends BaseExecutor {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
...getQoderDashscopeCompatHeaders(),
|
||||
...(isPatToken ? {} : getQoderDashscopeCompatHeaders()),
|
||||
};
|
||||
|
||||
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
|
||||
|
||||
@@ -34,6 +34,24 @@ test("QoderExecutor: buildHeaders inherits configured user agent, auth and strea
|
||||
});
|
||||
});
|
||||
|
||||
test("QoderExecutor: buildHeaders for PAT token includes User-Agent and Accept headers", () => {
|
||||
const executor = new QoderExecutor();
|
||||
|
||||
// PAT tokens (pt- prefix) must include standard headers for native Qoder API compatibility
|
||||
assert.deepEqual(executor.buildHeaders({ apiKey: "pt-test-token" }, true), {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Qoder-Cli",
|
||||
Authorization: "Bearer pt-test-token",
|
||||
Accept: "text/event-stream",
|
||||
});
|
||||
assert.deepEqual(executor.buildHeaders({ apiKey: "pt-test-token" }, false), {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Qoder-Cli",
|
||||
Authorization: "Bearer pt-test-token",
|
||||
Accept: "application/json",
|
||||
});
|
||||
});
|
||||
|
||||
test("QoderExecutor: buildUrl uses the live qoder.com API base", () => {
|
||||
const executor = new QoderExecutor();
|
||||
assert.equal(
|
||||
@@ -166,13 +184,59 @@ test("QoderExecutor: missing tokens return an authentication error response", as
|
||||
assert.equal(payload.error.code, "token_required");
|
||||
});
|
||||
|
||||
test("QoderExecutor: non-stream calls target DashScope and map alias models", async () => {
|
||||
test("QoderExecutor: non-stream calls target Qoder native API for PAT tokens", async () => {
|
||||
const executor = new QoderExecutor();
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (url, options) => {
|
||||
assert.equal(String(url), "https://api.qoder.com/v1/chat/completions");
|
||||
assert.equal(options.method, "POST");
|
||||
assert.equal(options.headers.Authorization, "Bearer pt-0pUI-test-token");
|
||||
assert.equal(options.headers["x-dashscope-authtype"], undefined);
|
||||
const parsedBody = JSON.parse(String(options.body));
|
||||
assert.equal(parsedBody.model, "qwen3.5-plus");
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
id: "chatcmpl-qoder",
|
||||
object: "chat.completion",
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: { role: "assistant", content: "OK" },
|
||||
finish_reason: "stop",
|
||||
},
|
||||
],
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
const { response, url, transformedBody } = await executor.execute({
|
||||
model: "qwen3.5-plus",
|
||||
body: { messages: [{ role: "user", content: "Reply with OK only." }] },
|
||||
stream: false,
|
||||
credentials: { apiKey: "pt-0pUI-test-token" },
|
||||
});
|
||||
|
||||
assert.equal(url, "https://api.qoder.com/v1/chat/completions");
|
||||
assert.equal((transformedBody as any).model, "qwen3.5-plus");
|
||||
assert.equal(response.status, 200);
|
||||
const payload = (await response.json()) as any;
|
||||
assert.equal(payload.object, "chat.completion");
|
||||
assert.equal(payload.choices[0].message.role, "assistant");
|
||||
assert.equal(payload.choices[0].message.content, "OK");
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test("QoderExecutor: non-stream calls target DashScope for non-PAT tokens and map alias models", async () => {
|
||||
const executor = new QoderExecutor();
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (url, options) => {
|
||||
assert.equal(String(url), "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions");
|
||||
assert.equal(options.method, "POST");
|
||||
assert.equal(options.headers.Authorization, "Bearer pat_test");
|
||||
assert.equal(options.headers.Authorization, "Bearer sk_test");
|
||||
assert.equal(options.headers["x-dashscope-authtype"], "qwen-oauth");
|
||||
assert.equal(options.headers["user-agent"], getQwenCliUserAgent());
|
||||
assert.equal(options.headers["x-dashscope-useragent"], getQwenCliUserAgent());
|
||||
@@ -199,7 +263,7 @@ test("QoderExecutor: non-stream calls target DashScope and map alias models", as
|
||||
model: "qwen3.5-plus",
|
||||
body: { messages: [{ role: "user", content: "Reply with OK only." }] },
|
||||
stream: false,
|
||||
credentials: { apiKey: "pat_test" },
|
||||
credentials: { apiKey: "sk_test" },
|
||||
});
|
||||
|
||||
assert.equal(url, "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions");
|
||||
|
||||
Reference in New Issue
Block a user