fix(sse): honor CLIProxyAPI environment API key (#12099)

Honra a chave de API dedicada de ambiente do CLIProxyAPI, com teste próprio. Validado no worktree combinado. Obrigado!
This commit is contained in:
Ravi Tharuma
2026-08-30 16:10:25 +02:00
committed by GitHub
parent 6096ea51f8
commit 2da9ade59b
5 changed files with 51 additions and 5 deletions

View File

@@ -2042,6 +2042,8 @@ APP_LOG_TO_FILE=true
# CLIPROXYAPI_HOST=127.0.0.1
# CLIPROXYAPI_PORT=5544
# CLIPROXYAPI_CONFIG_DIR=~/.cli-proxy-api
# Data-plane key fallback; the cliproxyapi_api_key setting takes precedence.
# CLIPROXYAPI_API_KEY=
# Management key for an externally managed instance. Embedded instances use
# OmniRoute's encrypted service key.
# CLIPROXYAPI_MANAGEMENT_KEY=

View File

@@ -1046,6 +1046,7 @@ desktop install.
| `EMBED_WS_PROXY_PORT` | `20131` | `src/lib/services/embedWsProxy.ts` | Port for the embedded-service WebSocket proxy server. |
| `CLIPROXYAPI_HOST` | `127.0.0.1` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge host (legacy integration). |
| `CLIPROXYAPI_PORT` | `5544` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge port. |
| `CLIPROXYAPI_API_KEY` | _(empty)_ | `open-sse/handlers/chatCore/cliproxyapiCredentials.ts` | Data-plane key fallback when the `cliproxyapi_api_key` setting is absent. |
| `CLIPROXYAPI_MANAGEMENT_KEY` | _(empty)_ | `src/lib/services/cliproxyAccountHealth.ts` | Management key for account-health reads from an externally managed CLIProxyAPI instance. |
| `CLIPROXYAPI_CONFIG_DIR` | `~/.cli-proxy-api` | `src/lib/versionManager/processManager.ts` | CLIProxyAPI config directory. |
| `MUX_SERVICE_PORT` | `8322` | `src/lib/services/bootstrap.ts` | Override the port where the embedded Mux (coder/mux) agent-orchestration daemon listens (always 127.0.0.1). |

View File

@@ -28,14 +28,16 @@ type ExecutorLike = {
};
/**
* Reads the dedicated CLIProxyAPI key out of a settings blob (as returned by
* `getCachedSettings()`), trimmed and normalized to `null` when absent/blank.
* Reads the dedicated CLIProxyAPI key from settings, then falls back to the
* environment. Values are trimmed and normalized to `null` when absent/blank.
*/
export function resolveDedicatedCliproxyapiApiKey(
settings: Record<string, unknown> | null | undefined
): string | null {
const raw = settings?.cliproxyapi_api_key;
return typeof raw === "string" && raw.trim() ? raw.trim() : null;
if (typeof raw === "string" && raw.trim()) return raw.trim();
const envKey = process.env.CLIPROXYAPI_API_KEY;
return typeof envKey === "string" && envKey.trim() ? envKey.trim() : null;
}
/**

View File

@@ -56,7 +56,7 @@ function parseFallbackCodes(raw: unknown): number[] | null {
* Reads the CLIProxyAPI-related settings shared by both the direct
* `mode: "cliproxyapi"` passthrough leg and the `mode: "fallback"` retry leg:
* the custom fallback status codes and the dedicated credential (#7645).
* Falls back to defaults / no dedicated key on any read failure.
* Falls back to defaults and the environment key on any read failure.
*/
async function loadCliproxyapiSettings(): Promise<{
fallbackCodes: number[];
@@ -71,7 +71,10 @@ async function loadCliproxyapiSettings(): Promise<{
dedicatedApiKey: resolveDedicatedCliproxyapiApiKey(allSettings),
};
} catch {
return { fallbackCodes: [...DEFAULT_FALLBACK_CODES], dedicatedApiKey: null };
return {
fallbackCodes: [...DEFAULT_FALLBACK_CODES],
dedicatedApiKey: resolveDedicatedCliproxyapiApiKey(null),
};
}
}

View File

@@ -29,18 +29,27 @@ const settingsDb = await import("../../src/lib/db/settings.ts");
const upstreamProxyDb = await import("../../src/lib/db/upstreamProxy.ts");
const { resolveExecutorWithProxy } =
await import("../../open-sse/handlers/chatCore/executorProxy.ts");
const { resolveDedicatedCliproxyapiApiKey } =
await import("../../open-sse/handlers/chatCore/cliproxyapiCredentials.ts");
const { clearUpstreamProxyConfigCache } =
await import("../../open-sse/handlers/chatCore/comboContextCache.ts");
const { updateSettingsSchema } = await import("../../src/shared/validation/settingsSchemas.ts");
const NATIVE_KEY = "sk-native-provider-key-cliproxyapi-must-not-see";
const DEDICATED_KEY = "cpa-dedicated-key-configured-by-operator";
const ENV_KEY = "cpa-dedicated-key-from-environment";
const originalEnvKey = process.env.CLIPROXYAPI_API_KEY;
before(async () => {
await coreDb.ensureDbInitialized();
});
afterEach(async () => {
if (originalEnvKey === undefined) {
delete process.env.CLIPROXYAPI_API_KEY;
} else {
process.env.CLIPROXYAPI_API_KEY = originalEnvKey;
}
clearUpstreamProxyConfigCache();
const { dbCache } = await import("../../src/lib/db/readCache.ts");
dbCache?.invalidate?.("settings");
@@ -111,6 +120,34 @@ describe("#7645 — settingsSchemas has a dedicated cliproxyapi_api_key field",
});
describe("#7645 — CLIProxyAPI fallback leg authenticates with the dedicated key", () => {
it("uses CLIPROXYAPI_API_KEY when settings are unavailable", () => {
process.env.CLIPROXYAPI_API_KEY = ` ${ENV_KEY} `;
assert.equal(resolveDedicatedCliproxyapiApiKey(null), ENV_KEY);
});
it("uses CLIPROXYAPI_API_KEY when no settings key is configured", async () => {
process.env.CLIPROXYAPI_API_KEY = ENV_KEY;
await settingsDb.updateSettings({ cliproxyapi_api_key: "" });
await upstreamProxyDb.upsertUpstreamProxyConfig({
providerId: "anthropic-7645-env-key",
mode: "cliproxyapi",
enabled: true,
});
const executor = await resolveExecutorWithProxy("anthropic-7645-env-key", undefined, null);
const { headers, called } = await withCapturedCliproxyapiRequest(() =>
(executor as ExecutorLike).execute({
model: "claude-3-opus",
body: { model: "claude-3-opus", messages: [{ role: "user", content: "hi" }] },
stream: false,
credentials: { apiKey: NATIVE_KEY },
})
);
assert.equal(called, true);
assert.equal(headers.Authorization, `Bearer ${ENV_KEY}`);
});
it("uses the dedicated cliproxyapi_api_key, not the failed native provider's own credential", async () => {
await settingsDb.updateSettings({ cliproxyapi_api_key: DEDICATED_KEY });
await upstreamProxyDb.upsertUpstreamProxyConfig({
@@ -210,6 +247,7 @@ describe("#7645 — CLIProxyAPI fallback leg authenticates with the dedicated ke
});
it("falls back to the connection's own credential when no dedicated key is configured (no regression)", async () => {
delete process.env.CLIPROXYAPI_API_KEY;
await settingsDb.updateSettings({ cliproxyapi_api_key: "" });
await upstreamProxyDb.upsertUpstreamProxyConfig({
providerId: "anthropic-7645-no-dedicated-key",