fix: provide capability for both SDK auth modes

This commit is contained in:
2026-07-11 14:59:00 +08:00
parent f065f9f978
commit eee3d9eae9
5 changed files with 28 additions and 7 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@paradigm/hub", "name": "@paradigm/hub",
"version": "0.0.16", "version": "0.0.17",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@paradigm/hub", "name": "@paradigm/hub",
"version": "0.0.16", "version": "0.0.17",
"dependencies": { "dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.3.202", "@anthropic-ai/claude-agent-sdk": "^0.3.202",
"@fastify/cookie": "^11.0.2", "@fastify/cookie": "^11.0.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@paradigm/hub", "name": "@paradigm/hub",
"version": "0.0.16", "version": "0.0.17",
"private": true, "private": true,
"type": "module", "type": "module",
"engines": { "engines": {
+17 -2
View File
@@ -41,6 +41,11 @@ export interface ProviderProxyDiagnostic {
| "provider_proxy_redirect_refused" | "provider_proxy_redirect_refused"
| "provider_proxy_upstream_failed"; | "provider_proxy_upstream_failed";
readonly category: NetworkFailureCategory | "authorization" | "request_limit" | "redirect"; readonly category: NetworkFailureCategory | "authorization" | "request_limit" | "redirect";
readonly authShape?: {
readonly bearerLength: number;
readonly apiKeyLength: number;
readonly expectedLength: number;
};
} }
export interface ProviderProxyOptions { export interface ProviderProxyOptions {
@@ -80,7 +85,7 @@ export async function openProviderProxyLease(
return { return {
sdkEnv: { sdkEnv: {
ANTHROPIC_BASE_URL: `http://127.0.0.1:${address.port}`, ANTHROPIC_BASE_URL: `http://127.0.0.1:${address.port}`,
ANTHROPIC_AUTH_TOKEN: "", ANTHROPIC_AUTH_TOKEN: capability,
ANTHROPIC_API_KEY: capability, ANTHROPIC_API_KEY: capability,
}, },
sensitiveValues: [capability], sensitiveValues: [capability],
@@ -103,7 +108,17 @@ async function forwardProviderRequest(
options: ProviderProxyOptions, options: ProviderProxyOptions,
): Promise<void> { ): Promise<void> {
if (!authorized(request.headers.authorization, header(request.headers["x-api-key"]), capability)) { if (!authorized(request.headers.authorization, header(request.headers["x-api-key"]), capability)) {
options.onDiagnostic?.({ code: "provider_proxy_unauthorized", category: "authorization" }); options.onDiagnostic?.({
code: "provider_proxy_unauthorized",
category: "authorization",
authShape: {
bearerLength: request.headers.authorization?.startsWith("Bearer ")
? request.headers.authorization.length - "Bearer ".length
: 0,
apiKeyLength: header(request.headers["x-api-key"])?.length ?? 0,
expectedLength: capability.length,
},
});
response.writeHead(401, { "content-type": "text/plain; charset=utf-8" }); response.writeHead(401, { "content-type": "text/plain; charset=utf-8" });
response.end("unauthorized"); response.end("unauthorized");
return; return;
+1
View File
@@ -73,6 +73,7 @@ export async function startHub(): Promise<void> {
providerId: context.providerId, providerId: context.providerId,
errorCode: diagnostic.code, errorCode: diagnostic.code,
failureCategory: diagnostic.category, failureCategory: diagnostic.category,
authShape: diagnostic.authShape,
}, "provider proxy diagnostic"); }, "provider proxy diagnostic");
}, },
}), }),
+7 -2
View File
@@ -52,9 +52,10 @@ describe("run-scoped provider proxy", () => {
expect(serializedAgentEnv).not.toContain(String(address.port)); expect(serializedAgentEnv).not.toContain(String(address.port));
expect(lease.sdkEnv).toMatchObject({ expect(lease.sdkEnv).toMatchObject({
ANTHROPIC_BASE_URL: expect.stringMatching(/^http:\/\/127\.0\.0\.1:\d+$/), ANTHROPIC_BASE_URL: expect.stringMatching(/^http:\/\/127\.0\.0\.1:\d+$/),
ANTHROPIC_AUTH_TOKEN: "", ANTHROPIC_AUTH_TOKEN: expect.any(String),
ANTHROPIC_API_KEY: expect.any(String), ANTHROPIC_API_KEY: expect.any(String),
}); });
expect(lease.sdkEnv.ANTHROPIC_AUTH_TOKEN).toBe(lease.sdkEnv.ANTHROPIC_API_KEY);
const response = await fetch(`${lease.sdkEnv.ANTHROPIC_BASE_URL}/v1/messages`, { const response = await fetch(`${lease.sdkEnv.ANTHROPIC_BASE_URL}/v1/messages`, {
method: "POST", method: "POST",
@@ -104,7 +105,11 @@ describe("run-scoped provider proxy", () => {
expect(response.status).toBe(401); expect(response.status).toBe(401);
expect(upstreamRequests).toBe(0); expect(upstreamRequests).toBe(0);
expect(diagnostics).toEqual([{ code: "provider_proxy_unauthorized", category: "authorization" }]); expect(diagnostics).toEqual([{
code: "provider_proxy_unauthorized",
category: "authorization",
authShape: { bearerLength: 0, apiKeyLength: 0, expectedLength: 43 },
}]);
}); });
it("accepts the run capability in the Anthropic x-api-key header", async () => { it("accepts the run capability in the Anthropic x-api-key header", async () => {