mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-20 14:12:59 +03:00
* test: resolve typescript strictness complaints in unit tests * Update Claude Code obfuscation to version 2.1.114 (#1403) * fix(cloud-code): scope thinking stripping to executor boundaries (#1401) * fix(cloud-code): scope thinking stripping to executors * fix(cloud-code): guard antigravity normalized body * Update Claude Code obfuscation to version 2.1.114 - Update Claude Code version from 2.1.87 to 2.1.114 - Update X-Stainless-Package-Version from 0.80.0 to 0.81.0 - Add new beta flags: redact-thinking-2026-02-12, advisor-tool-2026-03-01, advanced-tool-use-2025-11-20 - Add missing headers: anthropic-version, anthropic-dangerous-direct-browser-access, x-app, X-Stainless-Timeout - Add all X-Stainless-* headers (Arch, Lang, OS, Runtime, Runtime-Version, Retry-Count) - Fix accept-encoding header: identity -> gzip, deflate, br, zstd - Add connection: keep-alive header - Update tool name mapping: add lsp, apply_patch, websearch These changes ensure that requests from OpenCode through Omniroute are indistinguishable from genuine Claude Code 2.1.114 requests, allowing proper authentication with Anthropic's API without triggering extra credits errors. * fix: resolve CodeQL password hash alert and TruffleHog CI failure --------- Co-authored-by: Randi <55005611+rdself@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Nikolay Popov <ekklesio.dev@gmail.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> * fix(claude-code): scope obfuscation to cli clients and fix tests * docs(workflows): enforce PR merge instead of manual close * docs(changelog): update 3.6.9 notes with missing PR 1403 and fixes * docs(workflows): update generate-release to use full changelog for PR body * fix(tsc): silence baseUrl deprecation warnings for TS 5.5+ * fix(chatcore): apply proactive compression before provider translation (#1406) Integrated into release/v3.6.9 * docs(changelog): add PR 1406 * Makes text visible in dark-mode (#1409) Integrated into release/v3.6.9 * docs(changelog): add PR 1409 * chore: save local work * chore(release): sync version references to 3.6.9 * fix(codex): prevent proactive token refresh consumption and strip background parameter * ci: shard long-running suites and relax timeouts * ci: allow manual CI dispatch for release branches * feat(skills): provider-aware marketplace UX, scored AUTO injection, and memory pipeline hardening (#1411) * fix/400 for GeminiCLI(add "ref" in GEMINI_UNSUPPORTED_SCHEMA_KEYS) * feat(cc-compatible): align request shape with Claude CLI * fix(cc-compatible): add Claude CLI system skeleton for OpenAI input * preserve reasoning when translating chat to responses (#1414) Integrated into release/v3.6.9 * fix(skills): optimize AUTO scoring and include Responses input context (#1418) Integrated into release/v3.6.9 * chore: fix TS errors and update review-prs workflow * fix(api): stop sending unsupported Gemini and Codex parameters Prevent Gemini request translation from injecting default thoughtSignature values that the upstream API strictly validates and rejects. Only preserve real signatures resolved from prior upstream responses, and strip additionalProperties from Gemini function schemas to avoid 400 "Unknown name" errors. Also remove fallback-injected session_id and conversation_id fields before sending Codex requests, and restore compatibility with the legacy OUTBOUND_SSRF_GUARD_ENABLED flag when determining whether private provider URLs are allowed. Updates the Gemini translator and regression tests for issue #1410 and related 400 error cases. * fix(core): stabilization fixes for token refresh, usage translation, and testing - Update Codex token refresh detection logic - Mark provider connections invalid on unrecoverable refresh error - Fix Claude usage translation under-reporting cached tokens - Update test expectations - Update CHANGELOG.md for v3.6.9 * fix(auth): reload fresh token state and unify expiry persistence Refresh checks now re-read the latest stored provider connection before attempting rotation so they do not use stale refresh tokens captured by an earlier sweep. Token updates also persist both expiresAt and tokenExpiresAt across the health check, usage-limit refresh path, and SSE refresh flow. This keeps known token expiry metadata in sync and avoids interval-based refreshes for connections whose tokens are still valid well into the future. * fix: resolve SSRF environment static evaluation bug (#1427) Fix import aliases and strict TS typings for tests and ACP agents. * test: resolve remaining strict type errors in test files * test: fix provider service assertion for anthropic-compatible header * fix(codex): respect openaiStoreEnabled setting during native passthrough (#1432) * fix(codex): fix token refresh unrecoverable detection for expired tokens * fix(ci): restore release v3.6.9 build and flaky tests * fix(cc-compatible): trim default OpenAI system skeleton (#1433) Integrated into release/v3.6.9 * fix: prevent masked API keys from being written to CLI tool configs (#1435) * feat: mark Qwen provider as deprecated and add deprecation warning to CLI tool (#1437) * docs(changelog): comprehensive v3.6.9 update with all 59 commits since v3.6.8 * test(ci): align qwen guide settings assertions * fix(security): resolve CodeQL alert 163 for incomplete URL sanitization in Qwen CLI settings --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: Nikolay Popov <74762779+nikolay-popov-ideogram@users.noreply.github.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com> Co-authored-by: Nikolay Popov <ekklesio.dev@gmail.com> Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com> Co-authored-by: Tim Massey <tim-massey@users.noreply.github.com> Co-authored-by: Paijo <oyi77@users.noreply.github.com> Co-authored-by: dail45 <dail45@yandex.ru> Co-authored-by: R.D. <rogerproself@gmail.com>
1084 lines
34 KiB
TypeScript
1084 lines
34 KiB
TypeScript
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-cc-compatible-"));
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
|
|
const core = await import("../../src/lib/db/core.ts");
|
|
const compliance = await import("../../src/lib/compliance/index.ts");
|
|
const providersDb = await import("../../src/lib/db/providers.ts");
|
|
const { DefaultExecutor } = await import("../../open-sse/executors/default.ts");
|
|
const {
|
|
buildClaudeCodeCompatibleRequest,
|
|
CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH,
|
|
CLAUDE_CODE_COMPATIBLE_DEFAULT_MAX_TOKENS,
|
|
CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH,
|
|
joinClaudeCodeCompatibleUrl,
|
|
} = await import("../../open-sse/services/claudeCodeCompatible.ts");
|
|
const { getModelsByProviderId } = await import("../../open-sse/config/providerModels.ts");
|
|
const { handleChatCore } = await import("../../open-sse/handlers/chatCore.ts");
|
|
const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts");
|
|
const providerNodesRoute = await import("../../src/app/api/provider-nodes/route.ts");
|
|
const providerNodesValidateRoute =
|
|
await import("../../src/app/api/provider-nodes/validate/route.ts");
|
|
const providerModelsRoute = await import("../../src/app/api/providers/[id]/models/route.ts");
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
const originalFlag = process.env.ENABLE_CC_COMPATIBLE_PROVIDER;
|
|
const originalAllowPrivateProviderUrls = process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS;
|
|
|
|
async function resetStorage() {
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
test.afterEach(async () => {
|
|
globalThis.fetch = originalFetch;
|
|
if (originalFlag === undefined) {
|
|
delete process.env.ENABLE_CC_COMPATIBLE_PROVIDER;
|
|
} else {
|
|
process.env.ENABLE_CC_COMPATIBLE_PROVIDER = originalFlag;
|
|
}
|
|
if (originalAllowPrivateProviderUrls === undefined) {
|
|
delete process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS;
|
|
} else {
|
|
process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS = originalAllowPrivateProviderUrls;
|
|
}
|
|
await resetStorage();
|
|
});
|
|
|
|
test.after(() => {
|
|
globalThis.fetch = originalFetch;
|
|
if (originalFlag === undefined) {
|
|
delete process.env.ENABLE_CC_COMPATIBLE_PROVIDER;
|
|
} else {
|
|
process.env.ENABLE_CC_COMPATIBLE_PROVIDER = originalFlag;
|
|
}
|
|
if (originalAllowPrivateProviderUrls === undefined) {
|
|
delete process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS;
|
|
} else {
|
|
process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS = originalAllowPrivateProviderUrls;
|
|
}
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
});
|
|
|
|
test("buildClaudeCodeCompatibleRequest keeps prior role history while dropping trailing assistant prefill", () => {
|
|
const payload = buildClaudeCodeCompatibleRequest({
|
|
sourceBody: {
|
|
reasoning_effort: "xhigh",
|
|
tool_choice: "required",
|
|
},
|
|
normalizedBody: {
|
|
tools: [
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "lookup_weather",
|
|
description: "Fetch weather",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
city: { type: "string" },
|
|
},
|
|
required: ["city"],
|
|
},
|
|
},
|
|
},
|
|
],
|
|
messages: [
|
|
{ role: "system", content: "sys" },
|
|
{ role: "user", content: [{ type: "text", text: "u1" }, { type: "image_url" }] },
|
|
{ role: "model", content: "a1" },
|
|
{ role: "user", content: [{ type: "text", text: "u2" }, { type: "tool_result" }] },
|
|
{ role: "model", content: "prefill" },
|
|
],
|
|
},
|
|
model: "claude-sonnet-4-6",
|
|
cwd: "/tmp/work",
|
|
now: new Date("2026-04-01T12:00:00.000Z"),
|
|
sessionId: "session-1",
|
|
});
|
|
|
|
assert.equal(payload.max_tokens, CLAUDE_CODE_COMPATIBLE_DEFAULT_MAX_TOKENS);
|
|
assert.equal(payload.output_config.effort, "high");
|
|
assert.deepEqual(
|
|
payload.messages.map((message) => ({
|
|
role: message.role,
|
|
text: message.content.map((block) => block.text).join("\n"),
|
|
})),
|
|
[
|
|
{ role: "user", text: "u1" },
|
|
{ role: "assistant", text: "a1" },
|
|
{ role: "user", text: "u2" },
|
|
]
|
|
);
|
|
assert.equal(payload.messages[0].content.at(-1).cache_control, undefined);
|
|
assert.equal(payload.messages[1].content.at(-1).cache_control, undefined);
|
|
assert.equal(payload.messages[2].content.at(-1).cache_control, undefined);
|
|
assert.equal(payload.system.length, 2);
|
|
assert.match(payload.system[0].text, /Claude Agent SDK/);
|
|
assert.equal(payload.system[0].cache_control, undefined);
|
|
assert.equal(payload.system[1].cache_control, undefined);
|
|
assert.equal(payload.system[1].text, "sys");
|
|
assert.equal(payload.system[1].cache_control, undefined);
|
|
assert.equal(payload.tools.length, 1);
|
|
assert.deepEqual(payload.tools[0], {
|
|
name: "lookup_weather",
|
|
description: "Fetch weather",
|
|
input_schema: {
|
|
type: "object",
|
|
properties: {
|
|
city: { type: "string" },
|
|
},
|
|
required: ["city"],
|
|
},
|
|
});
|
|
assert.deepEqual(payload.tool_choice, { type: "any" });
|
|
assert.equal(payload.context_management, undefined);
|
|
assert.equal(JSON.parse(payload.metadata.user_id).session_id, "session-1");
|
|
});
|
|
|
|
test("buildClaudeCodeCompatibleRequest preserves xhigh for Claude models that support it", () => {
|
|
const xhighModel = getModelsByProviderId("claude").find(
|
|
(model) => model.supportsXHighEffort === true
|
|
);
|
|
assert.ok(xhighModel, "expected at least one Claude model with xhigh support");
|
|
const payload = buildClaudeCodeCompatibleRequest({
|
|
sourceBody: {
|
|
reasoning_effort: "xhigh",
|
|
},
|
|
normalizedBody: {
|
|
messages: [{ role: "user", content: "u1" }],
|
|
},
|
|
model: xhighModel.id,
|
|
cwd: "/tmp/work",
|
|
now: new Date("2026-04-01T12:00:00.000Z"),
|
|
});
|
|
|
|
assert.equal(payload.output_config.effort, "xhigh");
|
|
assert.equal(payload.thinking.type, "adaptive");
|
|
});
|
|
|
|
test("buildClaudeCodeCompatibleRequest preserves Claude cache markers when requested", () => {
|
|
const payload = buildClaudeCodeCompatibleRequest({
|
|
sourceBody: {
|
|
max_tokens: 64,
|
|
},
|
|
normalizedBody: {
|
|
max_tokens: 64,
|
|
messages: [{ role: "user", content: "fallback" }],
|
|
},
|
|
claudeBody: {
|
|
system: [{ type: "text", text: "sys", cache_control: { type: "ephemeral", ttl: "5m" } }],
|
|
messages: [
|
|
{
|
|
role: "user",
|
|
content: [{ type: "text", text: "u1", cache_control: { type: "ephemeral" } }],
|
|
},
|
|
{
|
|
role: "assistant",
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: "a1",
|
|
cache_control: { type: "ephemeral", ttl: "10m" },
|
|
},
|
|
],
|
|
},
|
|
{ role: "user", content: [{ type: "text", text: "u2" }] },
|
|
],
|
|
tools: [
|
|
{
|
|
name: "lookup_weather",
|
|
description: "Fetch weather",
|
|
input_schema: {
|
|
type: "object",
|
|
properties: {
|
|
city: { type: "string" },
|
|
},
|
|
required: ["city"],
|
|
},
|
|
cache_control: { type: "ephemeral", ttl: "30m" },
|
|
},
|
|
],
|
|
},
|
|
model: "claude-sonnet-4-6",
|
|
sessionId: "session-preserve",
|
|
preserveCacheControl: true,
|
|
});
|
|
|
|
assert.deepEqual(payload.system[0].cache_control, { type: "ephemeral", ttl: "5m" });
|
|
assert.deepEqual(payload.messages[0].content[0].cache_control, { type: "ephemeral" });
|
|
assert.deepEqual(payload.messages[1].content[0].cache_control, {
|
|
type: "ephemeral",
|
|
ttl: "10m",
|
|
});
|
|
assert.equal(payload.messages[2].content[0].cache_control, undefined);
|
|
assert.deepEqual(payload.tools[0].cache_control, { type: "ephemeral", ttl: "30m" });
|
|
});
|
|
|
|
test("buildClaudeCodeCompatibleRequest does not supplement missing Claude cache markers in preserve mode", () => {
|
|
const payload = buildClaudeCodeCompatibleRequest({
|
|
sourceBody: {
|
|
max_tokens: 64,
|
|
},
|
|
normalizedBody: {
|
|
max_tokens: 64,
|
|
messages: [{ role: "user", content: "fallback" }],
|
|
},
|
|
claudeBody: {
|
|
system: [{ type: "text", text: "sys" }],
|
|
messages: [
|
|
{
|
|
role: "user",
|
|
content: [{ type: "text", text: "u1" }],
|
|
},
|
|
{
|
|
role: "assistant",
|
|
content: [{ type: "text", text: "a1" }],
|
|
},
|
|
{ role: "user", content: [{ type: "text", text: "u2" }] },
|
|
],
|
|
tools: [
|
|
{
|
|
name: "lookup_weather",
|
|
description: "Fetch weather",
|
|
input_schema: {
|
|
type: "object",
|
|
properties: {
|
|
city: { type: "string" },
|
|
},
|
|
required: ["city"],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
model: "claude-sonnet-4-6",
|
|
sessionId: "session-preserve-defaults",
|
|
preserveCacheControl: true,
|
|
});
|
|
|
|
assert.equal(payload.system[0].cache_control, undefined);
|
|
assert.equal(payload.messages[0].content[0].cache_control, undefined);
|
|
assert.equal(payload.messages[1].content[0].cache_control, undefined);
|
|
assert.equal(payload.messages[2].content[0].cache_control, undefined);
|
|
assert.equal(payload.system.at(-1).cache_control, undefined);
|
|
assert.equal(payload.tools[0].cache_control, undefined);
|
|
});
|
|
|
|
test("buildClaudeCodeCompatibleRequest keeps built-in system blocks untagged when preserved system uses 1h", () => {
|
|
const payload = buildClaudeCodeCompatibleRequest({
|
|
sourceBody: {
|
|
max_tokens: 64,
|
|
},
|
|
normalizedBody: {
|
|
max_tokens: 64,
|
|
messages: [{ role: "user", content: "fallback" }],
|
|
},
|
|
claudeBody: {
|
|
system: [
|
|
{ type: "text", text: "prefix", cache_control: { type: "ephemeral" } },
|
|
{ type: "text", text: "stable", cache_control: { type: "ephemeral", ttl: "1h" } },
|
|
],
|
|
messages: [{ role: "user", content: [{ type: "text", text: "u1" }] }],
|
|
tools: [],
|
|
},
|
|
model: "claude-sonnet-4-6",
|
|
sessionId: "session-preserve-system-upgrade",
|
|
preserveCacheControl: true,
|
|
});
|
|
|
|
assert.deepEqual(payload.system[0].cache_control, { type: "ephemeral" });
|
|
assert.deepEqual(payload.system[1].cache_control, { type: "ephemeral", ttl: "1h" });
|
|
});
|
|
|
|
test("buildClaudeCodeCompatibleRequest does not add cache markers in non-preserve mode", () => {
|
|
const largeUserPrompt = Array.from(
|
|
{ length: 200 },
|
|
(_, index) => `Context line ${index + 1}: repeated stable context for cache testing.`
|
|
).join("\n");
|
|
|
|
const payload = buildClaudeCodeCompatibleRequest({
|
|
sourceBody: {
|
|
max_tokens: 64,
|
|
},
|
|
normalizedBody: {
|
|
max_tokens: 64,
|
|
messages: [
|
|
{ role: "system", content: "Follow the house style exactly." },
|
|
{ role: "user", content: "[Start a new chat]" },
|
|
{ role: "assistant", content: "Hello short ack" },
|
|
{ role: "user", content: largeUserPrompt },
|
|
],
|
|
},
|
|
model: "claude-sonnet-4-6",
|
|
sessionId: "session-last-user-cache",
|
|
preserveCacheControl: false,
|
|
});
|
|
|
|
assert.equal(payload.system.length, 2);
|
|
assert.equal(payload.system[0].cache_control, undefined);
|
|
assert.equal(payload.system[1].cache_control, undefined);
|
|
assert.equal(payload.messages[0].content[0].cache_control, undefined);
|
|
assert.equal(payload.messages[1].content[0].cache_control, undefined);
|
|
assert.equal(payload.messages[2].content[0].cache_control, undefined);
|
|
});
|
|
|
|
test("buildClaudeCodeCompatibleRequest falls back to a user turn when the source only has assistant/model text", () => {
|
|
const payload = buildClaudeCodeCompatibleRequest({
|
|
sourceBody: {
|
|
messages: [{ role: "model", content: "draft" }],
|
|
},
|
|
normalizedBody: {
|
|
messages: [{ role: "model", content: "draft" }],
|
|
},
|
|
model: "claude-sonnet-4-6",
|
|
sessionId: "session-only-assistant",
|
|
});
|
|
|
|
assert.deepEqual(payload.messages, [
|
|
{
|
|
role: "user",
|
|
content: [{ type: "text", text: "draft" }],
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("buildClaudeCodeCompatibleRequest honors token priority fields", () => {
|
|
const payload = buildClaudeCodeCompatibleRequest({
|
|
sourceBody: { max_completion_tokens: 321 },
|
|
normalizedBody: {
|
|
max_tokens: 123,
|
|
max_output_tokens: 456,
|
|
messages: [{ role: "user", content: "hi" }],
|
|
},
|
|
model: "claude-sonnet-4-6",
|
|
sessionId: "session-2",
|
|
});
|
|
|
|
assert.equal(payload.max_tokens, 321);
|
|
assert.deepEqual(payload.tools, []);
|
|
assert.equal(payload.tool_choice, undefined);
|
|
});
|
|
|
|
test("buildClaudeCodeCompatibleRequest omits auto tool_choice while preserving tools", () => {
|
|
const payload = buildClaudeCodeCompatibleRequest({
|
|
sourceBody: { tool_choice: "auto" },
|
|
normalizedBody: {
|
|
messages: [{ role: "user", content: "hi" }],
|
|
tools: [
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "ping",
|
|
parameters: { type: "object" },
|
|
},
|
|
},
|
|
],
|
|
},
|
|
model: "claude-sonnet-4-6",
|
|
sessionId: "session-4",
|
|
});
|
|
|
|
assert.equal(payload.tools.length, 1);
|
|
assert.equal(payload.tools[0].cache_control, undefined);
|
|
assert.equal(payload.tool_choice, undefined);
|
|
});
|
|
|
|
test("joinClaudeCodeCompatibleUrl preserves a single /v1 segment for CC paths", () => {
|
|
assert.equal(
|
|
joinClaudeCodeCompatibleUrl(
|
|
"https://proxy.example.com",
|
|
CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH
|
|
),
|
|
"https://proxy.example.com/v1/messages?beta=true"
|
|
);
|
|
assert.equal(
|
|
joinClaudeCodeCompatibleUrl(
|
|
"https://proxy.example.com/v1",
|
|
CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH
|
|
),
|
|
"https://proxy.example.com/v1/messages?beta=true"
|
|
);
|
|
assert.equal(
|
|
joinClaudeCodeCompatibleUrl(
|
|
"https://proxy.example.com/v1/messages?beta=true",
|
|
CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH
|
|
),
|
|
"https://proxy.example.com/v1/messages?beta=true"
|
|
);
|
|
});
|
|
|
|
test("DefaultExecutor uses CC-compatible path and headers", () => {
|
|
const executor = new DefaultExecutor("anthropic-compatible-cc-test");
|
|
const credentials = {
|
|
apiKey: "sk-test",
|
|
providerSpecificData: {
|
|
baseUrl: "https://proxy.example.com/v1/",
|
|
chatPath: "",
|
|
ccSessionId: "session-3",
|
|
},
|
|
};
|
|
|
|
assert.equal(
|
|
executor.buildUrl("claude-sonnet-4-6", true, 0, credentials),
|
|
"https://proxy.example.com/v1/messages?beta=true"
|
|
);
|
|
|
|
const headers = executor.buildHeaders(credentials, true);
|
|
assert.equal(headers.Authorization, "Bearer sk-test");
|
|
assert.equal(headers["x-api-key"], undefined);
|
|
assert.equal(headers["X-Claude-Code-Session-Id"], "session-3");
|
|
assert.equal(headers.Accept, "application/json");
|
|
});
|
|
|
|
test("validateProviderApiKey uses CC skeleton request after /models fallback", async () => {
|
|
const calls = [];
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
calls.push({
|
|
url: String(url),
|
|
method: init.method || "GET",
|
|
headers: init.headers,
|
|
body: init.body ? JSON.parse(String(init.body)) : null,
|
|
});
|
|
|
|
if (String(url).endsWith(CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH)) {
|
|
return new Response(JSON.stringify({ error: "missing models" }), { status: 500 });
|
|
}
|
|
|
|
return new Response(JSON.stringify({ error: "bad model" }), { status: 400 });
|
|
};
|
|
|
|
const result = await validateProviderApiKey({
|
|
provider: "anthropic-compatible-cc-test",
|
|
apiKey: "sk-test",
|
|
providerSpecificData: {
|
|
baseUrl: "https://proxy.example.com/v1/messages?beta=true",
|
|
validationModelId: "claude-sonnet-4-6",
|
|
},
|
|
});
|
|
|
|
assert.equal(result.valid, true);
|
|
assert.equal(result.method, "cc_bridge_request");
|
|
assert.match(result.warning, /reached upstream/i);
|
|
assert.deepEqual(
|
|
calls.map((call) => `${call.method} ${call.url}`),
|
|
["GET https://proxy.example.com/models", "POST https://proxy.example.com/v1/messages?beta=true"]
|
|
);
|
|
assert.equal(calls[1].body.model, "claude-sonnet-4-6");
|
|
assert.equal(calls[1].body.messages[0].role, "user");
|
|
assert.equal(calls[1].body.stream, true);
|
|
assert.equal(calls[1].headers.Authorization, "Bearer sk-test");
|
|
assert.equal(calls[1].headers["x-api-key"], undefined);
|
|
assert.equal(calls[1].headers.Accept, "application/json");
|
|
});
|
|
|
|
test("handleChatCore forces SSE upstream for CC compatible providers while returning JSON to non-stream clients", async () => {
|
|
const calls = [];
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
calls.push({
|
|
url: String(url),
|
|
method: init.method || "GET",
|
|
headers: init.headers,
|
|
body: init.body ? JSON.parse(String(init.body)) : null,
|
|
});
|
|
|
|
return new Response(
|
|
[
|
|
"event: message_start",
|
|
'data: {"type":"message_start","message":{"id":"msg_1","type":"message","role":"assistant","model":"claude-sonnet-4-6","usage":{"input_tokens":7,"output_tokens":0}}}',
|
|
"",
|
|
"event: content_block_start",
|
|
'data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}',
|
|
"",
|
|
"event: content_block_delta",
|
|
'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello from CC"}}',
|
|
"",
|
|
"event: message_delta",
|
|
'data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":5}}',
|
|
"",
|
|
"event: message_stop",
|
|
'data: {"type":"message_stop"}',
|
|
"",
|
|
].join("\n"),
|
|
{
|
|
status: 200,
|
|
headers: {
|
|
"content-type": "text/event-stream",
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const result = await handleChatCore({
|
|
body: {
|
|
model: "claude-sonnet-4-6",
|
|
messages: [{ role: "user", content: "Ping" }],
|
|
stream: false,
|
|
},
|
|
modelInfo: {
|
|
provider: "anthropic-compatible-cc-test",
|
|
model: "claude-sonnet-4-6",
|
|
extendedContext: false,
|
|
},
|
|
credentials: {
|
|
apiKey: "sk-test",
|
|
providerSpecificData: {
|
|
baseUrl: "https://proxy.example.com",
|
|
chatPath: CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH,
|
|
},
|
|
},
|
|
clientRawRequest: {
|
|
endpoint: "/v1/chat/completions",
|
|
body: {
|
|
model: "claude-sonnet-4-6",
|
|
messages: [{ role: "user", content: "Ping" }],
|
|
stream: false,
|
|
},
|
|
headers: new Headers({ accept: "application/json" }),
|
|
},
|
|
userAgent: "unit-test",
|
|
log: {
|
|
debug() {},
|
|
info() {},
|
|
warn() {},
|
|
error() {},
|
|
},
|
|
});
|
|
|
|
assert.equal(result.success, true);
|
|
assert.equal(calls.length, 1);
|
|
assert.equal(calls[0].headers.Accept, "application/json");
|
|
assert.equal(calls[0].body.stream, true);
|
|
assert.equal(JSON.stringify(calls[0].body).includes('"cache_control"'), false);
|
|
|
|
const payload = await result.response.json();
|
|
assert.equal(payload.choices[0].message.content, "Hello from CC");
|
|
assert.equal(payload.choices[0].finish_reason, "stop");
|
|
assert.equal(payload.usage.prompt_tokens, 2007);
|
|
assert.equal(payload.usage.completion_tokens, 5);
|
|
});
|
|
|
|
test("handleChatCore preserves client cache markers for Claude Code requests to CC-compatible providers", async () => {
|
|
const calls = [];
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
calls.push({
|
|
url: String(url),
|
|
method: init.method || "GET",
|
|
headers: init.headers,
|
|
body: init.body ? JSON.parse(String(init.body)) : null,
|
|
});
|
|
|
|
return new Response(
|
|
[
|
|
"event: message_start",
|
|
'data: {"type":"message_start","message":{"id":"msg_2","type":"message","role":"assistant","model":"claude-sonnet-4-6","usage":{"input_tokens":12,"output_tokens":0}}}',
|
|
"",
|
|
"event: content_block_start",
|
|
'data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}',
|
|
"",
|
|
"event: content_block_delta",
|
|
'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Preserved"}}',
|
|
"",
|
|
"event: message_delta",
|
|
'data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":3}}',
|
|
"",
|
|
"event: message_stop",
|
|
'data: {"type":"message_stop"}',
|
|
"",
|
|
].join("\n"),
|
|
{
|
|
status: 200,
|
|
headers: {
|
|
"content-type": "text/event-stream",
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const claudeBody = {
|
|
model: "claude-sonnet-4-6",
|
|
max_tokens: 64,
|
|
system: [{ type: "text", text: "system", cache_control: { type: "ephemeral", ttl: "5m" } }],
|
|
messages: [
|
|
{
|
|
role: "user",
|
|
content: [{ type: "text", text: "u1", cache_control: { type: "ephemeral" } }],
|
|
},
|
|
{
|
|
role: "assistant",
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: "a1",
|
|
cache_control: { type: "ephemeral", ttl: "10m" },
|
|
},
|
|
],
|
|
},
|
|
{ role: "user", content: [{ type: "text", text: "u2" }] },
|
|
],
|
|
tools: [
|
|
{
|
|
name: "lookup_weather",
|
|
description: "Fetch weather",
|
|
input_schema: {
|
|
type: "object",
|
|
properties: {
|
|
city: { type: "string" },
|
|
},
|
|
},
|
|
cache_control: { type: "ephemeral", ttl: "30m" },
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = await handleChatCore({
|
|
body: claudeBody,
|
|
modelInfo: {
|
|
provider: "anthropic-compatible-cc-test",
|
|
model: "claude-sonnet-4-6",
|
|
extendedContext: false,
|
|
},
|
|
credentials: {
|
|
apiKey: "sk-test",
|
|
providerSpecificData: {
|
|
baseUrl: "https://proxy.example.com",
|
|
chatPath: CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH,
|
|
},
|
|
},
|
|
clientRawRequest: {
|
|
endpoint: "/v1/messages",
|
|
body: claudeBody,
|
|
headers: new Headers({ accept: "application/json" }),
|
|
},
|
|
userAgent: "Claude-Code/1.0.0",
|
|
log: {
|
|
debug() {},
|
|
info() {},
|
|
warn() {},
|
|
error() {},
|
|
},
|
|
});
|
|
|
|
assert.equal(result.success, true);
|
|
assert.equal(calls.length, 1);
|
|
assert.deepEqual(calls[0].body.system[0].cache_control, {
|
|
type: "ephemeral",
|
|
ttl: "5m",
|
|
});
|
|
assert.deepEqual(calls[0].body.messages[0].content[0].cache_control, {
|
|
type: "ephemeral",
|
|
});
|
|
assert.deepEqual(calls[0].body.messages[1].content[0].cache_control, {
|
|
type: "ephemeral",
|
|
ttl: "10m",
|
|
});
|
|
assert.equal(calls[0].body.messages[2].content[0].cache_control, undefined);
|
|
assert.deepEqual(calls[0].body.tools[0].cache_control, {
|
|
type: "ephemeral",
|
|
ttl: "30m",
|
|
});
|
|
});
|
|
|
|
test("provider-nodes create route rejects CC mode when feature flag is disabled", async () => {
|
|
delete process.env.ENABLE_CC_COMPATIBLE_PROVIDER;
|
|
|
|
const response = await providerNodesRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
name: "Hidden CC",
|
|
prefix: "cc",
|
|
baseUrl: "https://proxy.example.com/v1",
|
|
type: "anthropic-compatible",
|
|
compatMode: "cc",
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(response.status, 403);
|
|
});
|
|
|
|
test("provider-nodes create route creates CC node with dedicated prefix when enabled", async () => {
|
|
process.env.ENABLE_CC_COMPATIBLE_PROVIDER = "true";
|
|
|
|
const response = await providerNodesRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
name: "Hidden CC",
|
|
prefix: "cc",
|
|
baseUrl: "https://proxy.example.com/v1/messages?beta=true",
|
|
type: "anthropic-compatible",
|
|
compatMode: "cc",
|
|
chatPath: CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH,
|
|
modelsPath: CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH,
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(response.status, 201);
|
|
const data = await response.json();
|
|
assert.match(data.node.id, /^anthropic-compatible-cc-/);
|
|
assert.equal(data.node.baseUrl, "https://proxy.example.com");
|
|
assert.equal(data.node.chatPath, CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH);
|
|
assert.equal(data.node.modelsPath, null);
|
|
});
|
|
|
|
test("provider-nodes validate route rejects CC mode when feature flag is disabled", async () => {
|
|
delete process.env.ENABLE_CC_COMPATIBLE_PROVIDER;
|
|
|
|
const response = await providerNodesValidateRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes/validate", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
baseUrl: "https://proxy.example.com/v1",
|
|
apiKey: "sk-test",
|
|
type: "anthropic-compatible",
|
|
compatMode: "cc",
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(response.status, 403);
|
|
});
|
|
|
|
test("provider-nodes validate route rejects invalid JSON and schema errors", async () => {
|
|
const invalidJsonResponse = await providerNodesValidateRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes/validate", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: "{",
|
|
})
|
|
);
|
|
|
|
assert.equal(invalidJsonResponse.status, 400);
|
|
assert.deepEqual(await invalidJsonResponse.json(), {
|
|
error: {
|
|
message: "Invalid request",
|
|
details: [{ field: "body", message: "Invalid JSON body" }],
|
|
},
|
|
});
|
|
|
|
const invalidBodyResponse = await providerNodesValidateRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes/validate", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
baseUrl: "",
|
|
apiKey: "",
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(invalidBodyResponse.status, 400);
|
|
const invalidBodyPayload = await invalidBodyResponse.json();
|
|
assert.equal(invalidBodyPayload.error.message, "Invalid request");
|
|
assert.equal(invalidBodyPayload.error.details.length >= 1, true);
|
|
});
|
|
|
|
test("provider-nodes validate route blocks private provider hosts before fetch", async () => {
|
|
delete process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS;
|
|
|
|
let called = false;
|
|
globalThis.fetch = async () => {
|
|
called = true;
|
|
return Response.json({ data: [] });
|
|
};
|
|
|
|
const response = await providerNodesValidateRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes/validate", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
baseUrl: "http://127.0.0.1:11434/v1",
|
|
apiKey: "sk-private-test",
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(await response.json(), {
|
|
error: "Blocked private or local provider URL",
|
|
});
|
|
assert.equal(called, false);
|
|
const auditEntries = compliance.getAuditLog({
|
|
action: "provider.validation.ssrf_blocked",
|
|
resourceType: "provider_validation",
|
|
});
|
|
assert.equal(auditEntries.length, 1);
|
|
assert.equal(auditEntries[0].target, "provider-node");
|
|
assert.equal(auditEntries[0].status, "blocked");
|
|
assert.deepEqual(auditEntries[0].metadata, {
|
|
route: "/api/provider-nodes/validate",
|
|
reason: "Blocked private or local provider URL",
|
|
baseUrl: "http://127.0.0.1:11434/v1",
|
|
});
|
|
});
|
|
|
|
test("provider-nodes validate route validates anthropic compatible providers against the models endpoint", async () => {
|
|
const calls = [];
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
calls.push({ url, init });
|
|
return new Response(JSON.stringify({ data: [] }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
};
|
|
|
|
const response = await providerNodesValidateRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes/validate", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
baseUrl: "https://proxy.example.com/v1/messages?beta=true",
|
|
apiKey: "sk-anthropic-test",
|
|
type: "anthropic-compatible",
|
|
modelsPath: "/catalog",
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.deepEqual(await response.json(), {
|
|
valid: true,
|
|
error: null,
|
|
});
|
|
assert.equal(calls.length, 1);
|
|
assert.equal(calls[0].url, "https://proxy.example.com/v1/catalog");
|
|
assert.equal(calls[0].init.method, "GET");
|
|
assert.equal(calls[0].init.headers["x-api-key"], "sk-anthropic-test");
|
|
assert.equal(calls[0].init.headers["anthropic-version"], "2023-06-01");
|
|
assert.equal(calls[0].init.headers.Authorization, "Bearer sk-anthropic-test");
|
|
});
|
|
|
|
test("provider-nodes validate route supports enabled CC validation and OpenAI-style failures", async () => {
|
|
process.env.ENABLE_CC_COMPATIBLE_PROVIDER = "true";
|
|
|
|
const calls = [];
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
calls.push({ url, init });
|
|
if (calls.length === 1) {
|
|
return new Response(JSON.stringify({ data: [] }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
}
|
|
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
status: 401,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
};
|
|
|
|
const ccResponse = await providerNodesValidateRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes/validate", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
baseUrl: "https://proxy.example.com/v1/messages?beta=true",
|
|
apiKey: "sk-cc-test",
|
|
type: "anthropic-compatible",
|
|
compatMode: "cc",
|
|
chatPath: CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH,
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(ccResponse.status, 200);
|
|
assert.deepEqual(await ccResponse.json(), {
|
|
valid: true,
|
|
error: null,
|
|
warning: null,
|
|
method: "models_endpoint",
|
|
});
|
|
assert.equal(String(calls[0].url).includes("/v1/messages"), false);
|
|
assert.equal(calls[0].init.method, "GET");
|
|
|
|
const openAiResponse = await providerNodesValidateRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes/validate", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
baseUrl: "https://proxy.example.com/",
|
|
apiKey: "sk-openai-test",
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(openAiResponse.status, 200);
|
|
assert.deepEqual(await openAiResponse.json(), {
|
|
valid: false,
|
|
error: "Invalid API key",
|
|
});
|
|
assert.equal(calls[1].url, "https://proxy.example.com/models");
|
|
assert.equal(calls[1].init.headers.Authorization, "Bearer sk-openai-test");
|
|
});
|
|
|
|
test("provider-nodes validate route covers default CC paths, null method, anthropic failures, and OpenAI success", async () => {
|
|
process.env.ENABLE_CC_COMPATIBLE_PROVIDER = "true";
|
|
|
|
const ccCalls = [];
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
ccCalls.push({ url, init });
|
|
if (ccCalls.length === 1) {
|
|
throw new Error("models unavailable");
|
|
}
|
|
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
status: 401,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
};
|
|
|
|
const ccResponse = await providerNodesValidateRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes/validate", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
baseUrl: "https://proxy.example.com/v1/messages",
|
|
apiKey: "sk-cc-invalid",
|
|
type: "anthropic-compatible",
|
|
compatMode: "cc",
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(ccResponse.status, 200);
|
|
assert.deepEqual(await ccResponse.json(), {
|
|
valid: false,
|
|
error: "Invalid API key",
|
|
warning: null,
|
|
method: null,
|
|
});
|
|
assert.equal(
|
|
ccCalls[0].url,
|
|
`https://proxy.example.com${CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH}`
|
|
);
|
|
assert.equal(
|
|
ccCalls[1].url,
|
|
`https://proxy.example.com${CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH}`
|
|
);
|
|
assert.equal(ccCalls.length, 2);
|
|
|
|
const anthropicCalls = [];
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
anthropicCalls.push({ url, init });
|
|
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
status: 401,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
};
|
|
|
|
const anthropicResponse = await providerNodesValidateRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes/validate", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
baseUrl: "https://proxy.example.com/v1/messages",
|
|
apiKey: "sk-anthropic-invalid",
|
|
type: "anthropic-compatible",
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(anthropicResponse.status, 200);
|
|
assert.deepEqual(await anthropicResponse.json(), {
|
|
valid: false,
|
|
error: "Invalid API key",
|
|
});
|
|
assert.equal(anthropicCalls[0].url, "https://proxy.example.com/v1/models");
|
|
assert.equal(anthropicCalls[0].init.method, "GET");
|
|
|
|
const openAiCalls = [];
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
openAiCalls.push({ url, init });
|
|
return new Response(JSON.stringify({ data: [] }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
};
|
|
|
|
const openAiResponse = await providerNodesValidateRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes/validate", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
baseUrl: "https://proxy.example.com/",
|
|
apiKey: "sk-openai-valid",
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(openAiResponse.status, 200);
|
|
assert.deepEqual(await openAiResponse.json(), {
|
|
valid: true,
|
|
error: null,
|
|
});
|
|
assert.equal(openAiCalls[0].url, "https://proxy.example.com/models");
|
|
assert.equal(openAiCalls[0].init.headers.Authorization, "Bearer sk-openai-valid");
|
|
});
|
|
|
|
test("provider-nodes validate route reports unexpected upstream failures", async () => {
|
|
globalThis.fetch = async () => {
|
|
throw new Error("boom");
|
|
};
|
|
|
|
const response = await providerNodesValidateRoute.POST(
|
|
new Request("http://localhost/api/provider-nodes/validate", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
baseUrl: "https://proxy.example.com",
|
|
apiKey: "sk-openai-test",
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(response.status, 500);
|
|
assert.deepEqual(await response.json(), {
|
|
error: "Validation failed",
|
|
});
|
|
});
|
|
|
|
test("provider-nodes list route exposes CC flag state from server env", async () => {
|
|
process.env.ENABLE_CC_COMPATIBLE_PROVIDER = "true";
|
|
|
|
const response = await providerNodesRoute.GET();
|
|
assert.equal(response.status, 200);
|
|
|
|
const data = await response.json();
|
|
assert.equal(data.ccCompatibleProviderEnabled, true);
|
|
});
|
|
|
|
test("provider models route reports CC compatible providers do not support models listing", async () => {
|
|
process.env.ENABLE_CC_COMPATIBLE_PROVIDER = "true";
|
|
|
|
const connection = await providersDb.createProviderConnection({
|
|
provider: "anthropic-compatible-cc-test",
|
|
authType: "apikey",
|
|
name: "cc-live",
|
|
apiKey: "sk-test",
|
|
providerSpecificData: {
|
|
baseUrl: "https://proxy.example.com",
|
|
chatPath: CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH,
|
|
},
|
|
});
|
|
|
|
const response = await providerModelsRoute.GET(
|
|
new Request(`http://localhost/api/providers/${connection.id}/models`),
|
|
{ params: { id: connection.id } }
|
|
);
|
|
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(await response.json(), {
|
|
error: "Provider anthropic-compatible-cc-test does not support models listing",
|
|
});
|
|
});
|