fix(sse): route Poe API-key traffic through DefaultExecutor (#8969) (#9014)

Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates: typecheck/complexity/cognitive/changelog/vitest — only pre-existing audit.test.ts flake). Evidence: /home/diegosouzapw/dev/proxys/OmniRoute/.claude/worktrees/merge-train-20260805-222248-suite.log
This commit is contained in:
Prudhvi Vuda
2026-08-05 21:32:02 -04:00
committed by GitHub
parent 7f3d86d01e
commit 701d60dc8c
7 changed files with 500 additions and 4 deletions

View File

@@ -1,4 +1,5 @@
import type { RegistryEntry } from "../../shared.ts";
import { normalizeBaseUrl } from "../../../../utils/urlSanitize.ts";
// Poe (creator.poe.com) — OpenAI-compatible chat/responses gateway. #8082: the
// built-in `poe` provider (NAMED_OPENAI_STYLE_PROVIDERS, passthroughModels:true)
@@ -7,19 +8,86 @@ import type { RegistryEntry } from "../../shared.ts";
// for provider" even though credentials/inference worked fine. This base URL is
// the single source of truth other Poe code paths should read from (see
// src/lib/providers/validation/audioMiscProviders.ts::validatePoeProvider).
//
// #8969: canonical `poe` is the API-key provider (DefaultExecutor → api.poe.com).
// The web-cookie GraphQL transport lives only on `poe-web` / PoeWebExecutor —
// never alias `poe` to that executor (it posts to /api/gql_POST and returns 405).
export const POE_DEFAULT_BASE_URL = "https://api.poe.com/v1";
export const POE_CHAT_COMPLETIONS_URL = `${POE_DEFAULT_BASE_URL}/chat/completions`;
export const POE_RESPONSES_URL = `${POE_DEFAULT_BASE_URL}/responses`;
export const POE_MESSAGES_URL = `${POE_DEFAULT_BASE_URL}/messages`;
/** Official Claude model ids are the only ones Poe accepts on /v1/messages. */
export function isPoeMessagesEligibleModel(model: string | null | undefined): boolean {
if (typeof model !== "string" || !model) return false;
return /(?:^|[\/._-])claude(?:[\/._-]|$)/i.test(model);
}
export type PoeUpstreamProtocol = "chat" | "responses" | "messages";
/**
* Normalize an operator-supplied or registry Poe base URL onto one of the three
* documented API surfaces. Accepts bare host, `/v1`, full chat/completions URL,
* and trailing-slash variants.
*/
export function resolvePoeUpstreamUrl(opts: {
protocol: PoeUpstreamProtocol;
configuredBaseUrl?: string | null;
responsesBaseUrl?: string | null;
messagesUrl?: string | null;
defaultChatUrl?: string | null;
}): string {
const defaultChat = opts.defaultChatUrl || POE_CHAT_COMPLETIONS_URL;
const defaultResponses = opts.responsesBaseUrl || POE_RESPONSES_URL;
const defaultMessages = opts.messagesUrl || POE_MESSAGES_URL;
if (opts.protocol === "responses" && !opts.configuredBaseUrl) {
return defaultResponses;
}
if (opts.protocol === "messages" && !opts.configuredBaseUrl) {
return defaultMessages;
}
if (opts.protocol === "chat" && !opts.configuredBaseUrl) {
return defaultChat;
}
const raw = normalizeBaseUrl(opts.configuredBaseUrl || defaultChat);
// Strip any known protocol suffix so we can re-append the requested one.
const root = raw
.replace(/\/chat\/completions\/?$/i, "")
.replace(/\/responses\/?$/i, "")
.replace(/\/messages\/?$/i, "")
.replace(/\/$/, "");
const withV1 = /\/v1$/i.test(root) ? root : `${root}/v1`;
if (opts.protocol === "responses") return `${withV1}/responses`;
if (opts.protocol === "messages") return `${withV1}/messages`;
return `${withV1}/chat/completions`;
}
export const poeProvider: RegistryEntry = {
id: "poe",
alias: "poe",
format: "openai",
executor: "default",
baseUrl: `${POE_DEFAULT_BASE_URL}/chat/completions`,
baseUrl: POE_CHAT_COMPLETIONS_URL,
responsesBaseUrl: POE_RESPONSES_URL,
// Anthropic-compatible Messages API — official Claude models only
// (https://creator.poe.com/docs/external-applications/anthropic-compatible-api).
// Routed via each claude-* model's targetFormat: "claude" below; GPT/Gemini
// stay on Chat Completions / Responses.
messagesUrl: POE_MESSAGES_URL,
authType: "apikey",
authHeader: "bearer",
models: [
{ id: "gpt-5.2", name: "GPT-5.2" },
{ id: "claude-opus-4.8", name: "Claude Opus 4.8" },
{
id: "claude-opus-4.8",
name: "Claude Opus 4.8",
targetFormat: "claude",
},
{ id: "gemini-3.0-pro", name: "Gemini 3.0 Pro" },
],
};

View File

@@ -40,6 +40,10 @@ import {
normalizeOpenAIChatUrl,
getOpenRouterConnectionPreset,
} from "./default/urlNormalizers.ts";
import {
isPoeMessagesEligibleModel,
resolvePoeUpstreamUrl,
} from "../config/providers/registry/poe/index.ts";
import { buildMaritalkChatUrl } from "../config/maritalk.ts";
import { LOCAL_PROVIDERS } from "@/shared/constants/providers";
import { isForbiddenCustomHeaderName } from "@/shared/constants/upstreamHeaders";
@@ -285,6 +289,36 @@ export class DefaultExecutor extends BaseExecutor {
case "glm-coding-apikey":
// #7364: format override extracted to zaiFormatOverride.ts (file-size ratchet).
return resolveZaiUrl(credentials, (fallback) => this.resolveBaseUrl(credentials, fallback));
case "poe": {
// #8969: Poe API-key surfaces — Chat Completions, Responses, and
// Claude-only Messages. Prefer the responses marker from
// resolveExecutionCredentials (incoming /v1/responses), then the
// registry Claude targetFormat → messagesUrl, else chat/completions.
// GPT models must never hit /v1/messages (Poe rejects non-Claude there).
const psd = credentials?.providerSpecificData;
const manualBaseUrl =
typeof psd?.baseUrl === "string" && psd.baseUrl.trim() ? psd.baseUrl.trim() : null;
const forceResponses = psd?._omnirouteForceResponsesUpstream === true;
const modelTarget = getModelTargetFormat("poe", model);
const connectionTarget =
typeof psd?.targetFormat === "string" ? (psd.targetFormat as string) : null;
const effectiveTarget = modelTarget || connectionTarget;
let protocol: "chat" | "responses" | "messages" = "chat";
if (forceResponses || effectiveTarget === "openai-responses") {
protocol = "responses";
} else if (effectiveTarget === "claude" && isPoeMessagesEligibleModel(model)) {
protocol = "messages";
}
return resolvePoeUpstreamUrl({
protocol,
configuredBaseUrl: manualBaseUrl,
responsesBaseUrl: this.config.responsesBaseUrl,
messagesUrl: this.config.messagesUrl,
defaultChatUrl: this.config.baseUrl,
});
}
case "claude":
case "glm":
case "glmt":

View File

@@ -152,7 +152,9 @@ const executors = {
"yuanbao-web": new YuanbaoWebExecutor(),
ybw: new YuanbaoWebExecutor(), // Alias
"poe-web": new PoeWebExecutor(),
poe: new PoeWebExecutor(), // Alias
// #8969: do NOT alias canonical `poe` (API-key / api.poe.com) to PoeWebExecutor.
// Registry declares executor:"default"; the hard-coded map previously won and
// routed API-key traffic to GraphQL /api/gql_POST → HTTP 405.
"venice-web": new VeniceWebExecutor(),
ven: new VeniceWebExecutor(), // Alias
"notion-web": new NotionWebExecutor(),

View File

@@ -118,6 +118,18 @@ export function resolveExecutionCredentials(opts: {
providerSpecificData._omnirouteForceResponsesUpstream = true;
}
// #8969: Poe's native /v1/responses surface — DefaultExecutor.buildUrl("poe")
// reads this marker so Responses requests do not land on chat/completions.
if (targetFormat === FORMATS.OPENAI_RESPONSES && provider === "poe") {
providerSpecificData._omnirouteForceResponsesUpstream = true;
}
// #8969: Claude-tagged Poe models speak Anthropic Messages wire format. Keep
// DefaultExecutor from injecting OpenAI stream_options onto that body.
if (targetFormat === FORMATS.CLAUDE && provider === "poe") {
providerSpecificData.disableStreamOptions = true;
}
// #7364: "zai"/"glm-coding-apikey" default to the Anthropic Messages wire format
// (registry format:"claude"), but a per-model targetFormat override (custom-model
// dropdown, #2905) can resolve targetFormat to "openai" — e.g. for a vision model

View File

@@ -105,6 +105,28 @@ test("AgentRouter threads the resolved Responses protocol only into execution cr
assert.deepEqual(credentials.providerSpecificData, { apiKeyHealth: {} });
});
test("#8969: poe + responses target sets the responses-upstream marker (no apiType)", () => {
const out = resolveExecutionCredentials({
...base,
provider: "poe",
targetFormat: RESPONSES,
}) as Record<string, unknown>;
const psd = out.providerSpecificData as Record<string, unknown>;
assert.equal(psd.apiType, undefined);
assert.equal(psd._omnirouteForceResponsesUpstream, true);
});
test("#8969: poe + claude target disables OpenAI stream_options injection", () => {
const out = resolveExecutionCredentials({
...base,
provider: "poe",
targetFormat: "claude",
}) as Record<string, unknown>;
const psd = out.providerSpecificData as Record<string, unknown>;
assert.equal(psd.disableStreamOptions, true);
assert.equal(psd._omnirouteForceResponsesUpstream, undefined);
});
test("ccSessionId is threaded into providerSpecificData when present", () => {
const out = resolveExecutionCredentials({
...base,

View File

@@ -0,0 +1,356 @@
/**
* #8969: Canonical `poe` API-key provider must use DefaultExecutor → api.poe.com,
* not the web-cookie PoeWebExecutor GraphQL path (which returns HTTP 405).
*
* Locks:
* - getExecutor("poe") is DefaultExecutor; getExecutor("poe-web") stays PoeWebExecutor
* - Chat Completions / Responses / Messages URL selection + auth headers
* - GPT models never hit Poe's Claude-only Messages endpoint
* - Operator baseUrl shapes (bare host, /v1/, trailing slash) normalize correctly
* - Upstream 405 is preserved (not swallowed)
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-poe-api-8969-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const { getExecutor, hasSpecializedExecutor } = await import("../../open-sse/executors/index.ts");
const { DefaultExecutor } = await import("../../open-sse/executors/default.ts");
const { PoeWebExecutor } = await import("../../open-sse/executors/poe-web.ts");
const { getRegistryEntry } = await import("../../open-sse/config/providerRegistry.ts");
const { resolveExecutionCredentials } = await import(
"../../open-sse/handlers/chatCore/executionCredentials.ts"
);
const { POE_DEFAULT_BASE_URL, resolvePoeUpstreamUrl } = await import(
"../../open-sse/config/providers/registry/poe/index.ts"
);
const core = await import("../../src/lib/db/core.ts");
test.after(() => {
try {
core.resetDbInstance();
} catch {
// ignore
}
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
const CHAT_URL = "https://api.poe.com/v1/chat/completions";
const RESPONSES_URL = "https://api.poe.com/v1/responses";
const MESSAGES_URL = "https://api.poe.com/v1/messages";
function headerRecord(headers: Record<string, string>): Record<string, string> {
const out: Record<string, string> = {};
for (const [k, v] of Object.entries(headers)) out[k.toLowerCase()] = v;
return out;
}
test("#8969: getExecutor(poe) selects DefaultExecutor, not PoeWebExecutor", () => {
assert.equal(hasSpecializedExecutor("poe"), false);
const executor = getExecutor("poe");
assert.ok(executor instanceof DefaultExecutor);
assert.equal(executor instanceof PoeWebExecutor, false);
assert.equal(executor.provider, "poe");
});
test("#8969: getExecutor(poe-web) still selects PoeWebExecutor", () => {
assert.equal(hasSpecializedExecutor("poe-web"), true);
assert.ok(getExecutor("poe-web") instanceof PoeWebExecutor);
});
test("#8969: registry declares API-key executor + all three Poe protocol URLs", () => {
const entry = getRegistryEntry("poe");
assert.ok(entry);
assert.equal(entry.executor, "default");
assert.equal(entry.authType, "apikey");
assert.equal(entry.authHeader, "bearer");
assert.equal(entry.baseUrl, CHAT_URL);
assert.equal(entry.responsesBaseUrl, RESPONSES_URL);
assert.equal(entry.messagesUrl, MESSAGES_URL);
assert.equal(POE_DEFAULT_BASE_URL, "https://api.poe.com/v1");
const claude = entry.models?.find((m) => m.id === "claude-opus-4.8");
assert.ok(claude, "catalog must include a Claude model");
assert.equal(claude.targetFormat, "claude");
const gpt = entry.models?.find((m) => m.id === "gpt-5.2");
assert.ok(gpt, "catalog must include a GPT model");
assert.notEqual(gpt.targetFormat, "claude");
});
test("#8969: buildUrl routes chat / responses / messages correctly", () => {
const executor = getExecutor("poe") as DefaultExecutor;
const creds = { apiKey: "poe-test-key", providerSpecificData: {} };
assert.equal(executor.buildUrl("gemma-4-31b", false, 0, creds), CHAT_URL);
assert.equal(executor.buildUrl("gemma-4-31b", true, 0, creds), CHAT_URL);
assert.equal(executor.buildUrl("gpt-5.2", false, 0, creds), CHAT_URL);
assert.equal(
executor.buildUrl("gpt-5.2", false, 0, {
...creds,
providerSpecificData: { _omnirouteForceResponsesUpstream: true },
}),
RESPONSES_URL
);
assert.equal(
executor.buildUrl("claude-opus-4.8", true, 0, {
...creds,
providerSpecificData: { _omnirouteForceResponsesUpstream: true },
}),
RESPONSES_URL
);
assert.equal(executor.buildUrl("claude-opus-4.8", false, 0, creds), MESSAGES_URL);
assert.equal(executor.buildUrl("claude-opus-4.8", true, 0, creds), MESSAGES_URL);
// GPT must never land on Poe's Claude-only Messages endpoint — even if an
// operator incorrectly stamps targetFormat=claude on the connection.
assert.equal(
executor.buildUrl("gpt-5.2", false, 0, {
...creds,
providerSpecificData: { targetFormat: "claude" },
}),
CHAT_URL
);
assert.notEqual(
executor.buildUrl("gpt-5.2", false, 0, {
...creds,
providerSpecificData: { targetFormat: "claude" },
}),
MESSAGES_URL
);
});
test("#8969: resolvePoeUpstreamUrl normalizes registry-default / bare-host /v1/ / trailing-slash bases", () => {
const cases: Array<{ base: string | null | undefined; protocol: "chat" | "responses" | "messages"; expected: string }> =
[
{ base: undefined, protocol: "chat", expected: CHAT_URL },
{ base: null, protocol: "chat", expected: CHAT_URL },
{ base: "https://api.poe.com", protocol: "chat", expected: CHAT_URL },
{ base: "https://api.poe.com/", protocol: "chat", expected: CHAT_URL },
{ base: "https://api.poe.com/v1", protocol: "chat", expected: CHAT_URL },
{ base: "https://api.poe.com/v1/", protocol: "chat", expected: CHAT_URL },
{
base: "https://api.poe.com/v1/chat/completions",
protocol: "chat",
expected: CHAT_URL,
},
{
base: "https://api.poe.com/v1/chat/completions/",
protocol: "chat",
expected: CHAT_URL,
},
{ base: "https://api.poe.com/v1", protocol: "responses", expected: RESPONSES_URL },
{ base: "https://api.poe.com/", protocol: "messages", expected: MESSAGES_URL },
{
base: "https://custom.example/v1/chat/completions",
protocol: "responses",
expected: "https://custom.example/v1/responses",
},
{
base: "https://custom.example/v1",
protocol: "messages",
expected: "https://custom.example/v1/messages",
},
];
for (const { base, protocol, expected } of cases) {
assert.equal(
resolvePoeUpstreamUrl({
protocol,
configuredBaseUrl: base,
responsesBaseUrl: RESPONSES_URL,
messagesUrl: MESSAGES_URL,
defaultChatUrl: CHAT_URL,
}),
expected,
`base=${String(base)} protocol=${protocol}`
);
}
});
test("#8969: buildHeaders uses Bearer auth and never sends Cookie", () => {
const executor = getExecutor("poe") as DefaultExecutor;
for (const stream of [false, true]) {
const headers = headerRecord(
executor.buildHeaders({ apiKey: "poe-test-key", providerSpecificData: {} }, stream)
);
assert.equal(headers.authorization, "Bearer poe-test-key");
assert.equal(headers.cookie, undefined);
assert.equal(headers.accept, stream ? "text/event-stream" : "application/json");
}
});
test("#8969: resolveExecutionCredentials forces responses upstream for poe", () => {
const out = resolveExecutionCredentials({
credentials: { providerSpecificData: {} },
nativeCodexPassthrough: false,
endpointPath: "/v1/responses",
targetFormat: "openai-responses",
provider: "poe",
ccSessionId: null,
}) as { providerSpecificData: Record<string, unknown> };
assert.equal(out.providerSpecificData._omnirouteForceResponsesUpstream, true);
});
test("#8969: mocked execute posts Chat Completions with Bearer, no Cookie, stripped model", async () => {
const executor = getExecutor("poe") as DefaultExecutor;
const originalFetch = globalThis.fetch;
const seen: Array<{
url: string;
method: string;
authorization: string | null;
cookie: string | null;
body: Record<string, unknown>;
}> = [];
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const headers = new Headers(init?.headers);
const rawBody = typeof init?.body === "string" ? init.body : "{}";
seen.push({
url: String(input),
method: (init?.method || "GET").toUpperCase(),
authorization: headers.get("authorization"),
cookie: headers.get("cookie"),
body: JSON.parse(rawBody) as Record<string, unknown>,
});
return Response.json({
id: "chatcmpl-test",
object: "chat.completion",
choices: [{ index: 0, message: { role: "assistant", content: "OK" }, finish_reason: "stop" }],
});
}) as typeof fetch;
try {
for (const stream of [false, true]) {
seen.length = 0;
const result = await executor.execute({
model: "gemma-4-31b",
body: {
model: "gemma-4-31b",
messages: [{ role: "user", content: "Reply with OK only." }],
max_tokens: 64,
stream,
},
stream,
credentials: { apiKey: "poe-test-key", providerSpecificData: {} },
signal: null,
});
assert.equal(seen.length, 1, `expected one upstream call (stream=${stream})`);
assert.equal(seen[0].method, "POST");
assert.equal(seen[0].url, CHAT_URL);
assert.equal(seen[0].authorization, "Bearer poe-test-key");
assert.equal(seen[0].cookie, null);
assert.equal(seen[0].body.model, "gemma-4-31b");
assert.equal(seen[0].url.includes("poe.com/api/gql"), false);
assert.ok(result.response instanceof Response);
assert.equal(result.response.status, 200);
}
} finally {
globalThis.fetch = originalFetch;
}
});
test("#8969: mocked execute routes Responses + Messages fixtures to the right URLs", async () => {
const executor = getExecutor("poe") as DefaultExecutor;
const originalFetch = globalThis.fetch;
let lastUrl = "";
globalThis.fetch = (async (input: RequestInfo | URL) => {
lastUrl = String(input);
return Response.json({ id: "ok", object: "response", output: [] });
}) as typeof fetch;
try {
await executor.execute({
model: "gpt-5.2",
body: { model: "gpt-5.2", input: "hi", stream: false },
stream: false,
credentials: {
apiKey: "poe-test-key",
providerSpecificData: { _omnirouteForceResponsesUpstream: true },
},
signal: null,
});
assert.equal(lastUrl, RESPONSES_URL);
await executor.execute({
model: "claude-opus-4.8",
body: {
model: "claude-opus-4.8",
input: "hi",
stream: true,
},
stream: true,
credentials: {
apiKey: "poe-test-key",
providerSpecificData: { _omnirouteForceResponsesUpstream: true },
},
signal: null,
});
assert.equal(lastUrl, RESPONSES_URL);
globalThis.fetch = (async (input: RequestInfo | URL) => {
lastUrl = String(input);
return Response.json({
id: "msg_ok",
type: "message",
role: "assistant",
content: [{ type: "text", text: "OK" }],
});
}) as typeof fetch;
await executor.execute({
model: "claude-opus-4.8",
body: {
model: "claude-opus-4.8",
messages: [{ role: "user", content: "hi" }],
max_tokens: 16,
stream: false,
},
stream: false,
credentials: { apiKey: "poe-test-key", providerSpecificData: {} },
signal: null,
});
assert.equal(lastUrl, MESSAGES_URL);
} finally {
globalThis.fetch = originalFetch;
}
});
test("#8969: mocked upstream 405 is preserved (not swallowed)", async () => {
const executor = getExecutor("poe") as DefaultExecutor;
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () => {
return new Response(JSON.stringify({ detail: "Method Not Allowed" }), {
status: 405,
headers: { "content-type": "application/json" },
});
}) as typeof fetch;
try {
const result = await executor.execute({
model: "gemma-4-31b",
body: {
model: "gemma-4-31b",
messages: [{ role: "user", content: "hi" }],
stream: false,
},
stream: false,
credentials: { apiKey: "poe-test-key", providerSpecificData: {} },
signal: null,
});
assert.equal(result.response.status, 405);
const text = await result.response.text();
assert.match(text, /Method Not Allowed/);
} finally {
globalThis.fetch = originalFetch;
}
});

View File

@@ -119,9 +119,11 @@ test("HuggingChat executor is registered", () => {
test("Poe Web executor is registered", () => {
assert.ok(hasSpecializedExecutor("poe-web"));
assert.ok(hasSpecializedExecutor("poe"));
const executor = getExecutor("poe-web");
assert.ok(executor instanceof PoeWebExecutor);
// #8969: canonical API-key `poe` must not route through PoeWebExecutor.
assert.equal(hasSpecializedExecutor("poe"), false);
assert.ok(!(getExecutor("poe") instanceof PoeWebExecutor));
});
test("Venice Web executor is registered", () => {