From 9a3ca7825672f652da789c5e5e6d66ff595c7925 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 11:50:16 +0000 Subject: [PATCH 1/3] feat(ai-gateway): support AWS Bedrock SigV4 auth for custom LLMs Add an optional aws_bedrock block to CustomLlmDefinitionSchema with access_key_id, secret_access_key and region. When present, upstream requests are SigV4-signed against the bedrock service (and the URL is rewritten to /model//invoke[-with-response-stream]) instead of using Authorization: Bearer. This is wired through a new optional signRequest hook on Provider that runs in upstream-request.ts; Bearer auth is still the default for all other providers. --- apps/web/package.json | 2 + .../ai-gateway/providers/bedrock-signer.ts | 57 +++++++++++++++++++ .../lib/ai-gateway/providers/get-provider.ts | 6 ++ .../web/src/lib/ai-gateway/providers/types.ts | 15 +++++ .../ai-gateway/providers/upstream-request.ts | 20 ++++++- packages/db/src/schema-types.ts | 14 +++++ 6 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/lib/ai-gateway/providers/bedrock-signer.ts diff --git a/apps/web/package.json b/apps/web/package.json index 4ad394813e..6310be8d5e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -33,8 +33,10 @@ "@ai-sdk/openai": "^3.0.41", "@ai-sdk/openai-compatible": "^2.0.35", "@anthropic-ai/sdk": "^0.90.0", + "@aws-crypto/sha256-js": "^5.2.0", "@aws-sdk/client-s3": "^3.1009.0", "@aws-sdk/s3-request-presigner": "^3.1009.0", + "@aws-sdk/signature-v4": "^3.374.0", "@chat-adapter/github": "4.27.0", "@chat-adapter/slack": "^4.27.0", "@chat-adapter/state-memory": "^4.27.0", diff --git a/apps/web/src/lib/ai-gateway/providers/bedrock-signer.ts b/apps/web/src/lib/ai-gateway/providers/bedrock-signer.ts new file mode 100644 index 0000000000..6593577052 --- /dev/null +++ b/apps/web/src/lib/ai-gateway/providers/bedrock-signer.ts @@ -0,0 +1,57 @@ +import { SignatureV4 } from '@aws-sdk/signature-v4'; +import { Sha256 } from '@aws-crypto/sha256-js'; +import type { CustomLlmAwsBedrock } from '@kilocode/db'; +import type { SignedRequest, SignRequestArgs } from '@/lib/ai-gateway/providers/types'; + +// Returns a signRequest implementation that rewrites the URL to the Bedrock +// `/model//invoke[-with-response-stream]` endpoint and signs it with +// SigV4 for the `bedrock` service. +export function makeBedrockSignRequest( + credentials: CustomLlmAwsBedrock, + modelId: string +): (args: SignRequestArgs) => Promise { + const signer = new SignatureV4({ + credentials: { + accessKeyId: credentials.access_key_id, + secretAccessKey: credentials.secret_access_key, + }, + region: credentials.region, + service: 'bedrock', + sha256: Sha256, + }); + + const hostname = `bedrock-runtime.${credentials.region}.amazonaws.com`; + + return async ({ method, body }) => { + const isStreaming = parseStreamFlag(body); + const path = `/model/${encodeURIComponent(modelId)}/${ + isStreaming ? 'invoke-with-response-stream' : 'invoke' + }`; + + const signed = await signer.sign({ + method, + hostname, + path, + protocol: 'https:', + headers: { + 'Content-Type': 'application/json', + host: hostname, + }, + body, + }); + + return { + url: `https://${hostname}${signed.path ?? path}`, + headers: signed.headers, + }; + }; +} + +function parseStreamFlag(body: string): boolean { + try { + const parsed = JSON.parse(body); + return parsed !== null && typeof parsed === 'object' && parsed.stream === true; + } catch { + return false; + } +} diff --git a/apps/web/src/lib/ai-gateway/providers/get-provider.ts b/apps/web/src/lib/ai-gateway/providers/get-provider.ts index f1b47466f3..2f30738b71 100644 --- a/apps/web/src/lib/ai-gateway/providers/get-provider.ts +++ b/apps/web/src/lib/ai-gateway/providers/get-provider.ts @@ -23,6 +23,7 @@ import { addCacheBreakpoints, injectReasoningIntoContent, } from '@/lib/ai-gateway/providers/openrouter/request-helpers'; +import { makeBedrockSignRequest } from '@/lib/ai-gateway/providers/bedrock-signer'; function inferSupportedChatApis( aiSdkProvider: CustomLlmProvider | undefined, @@ -94,6 +95,10 @@ async function checkCustomLlm( if (!customLlm || !customLlm.organization_ids.includes(organizationId)) { return null; } + const bedrock = customLlm.aws_bedrock; + const signRequest = bedrock + ? makeBedrockSignRequest(bedrock, customLlm.internal_id) + : undefined; return { provider: { id: 'custom', @@ -120,6 +125,7 @@ async function checkCustomLlm( injectReasoningIntoContent(context.request); } }, + signRequest, }, userByok: null, bypassAccessCheck: true, diff --git a/apps/web/src/lib/ai-gateway/providers/types.ts b/apps/web/src/lib/ai-gateway/providers/types.ts index ee7e75c028..e6e5b1a901 100644 --- a/apps/web/src/lib/ai-gateway/providers/types.ts +++ b/apps/web/src/lib/ai-gateway/providers/types.ts @@ -30,10 +30,25 @@ export type TransformRequestContext = { export type GatewayChatApiKind = GatewayRequest['kind']; +export type SignRequestArgs = { + method: string; + url: string; + body: string; +}; + +export type SignedRequest = { + // When set, replaces the target URL (used e.g. for Bedrock path rewriting). + url?: string; + // Final auth/signature headers. When `signRequest` is present, the caller + // skips the default `Authorization: Bearer ${apiKey}` header. + headers: Record; +}; + export type Provider = { id: ProviderId; apiUrl: string; apiKey: string; supportedChatApis: ReadonlyArray; transformRequest(context: TransformRequestContext): void; + signRequest?(args: SignRequestArgs): Promise; }; diff --git a/apps/web/src/lib/ai-gateway/providers/upstream-request.ts b/apps/web/src/lib/ai-gateway/providers/upstream-request.ts index ee54d04c87..d79905435c 100644 --- a/apps/web/src/lib/ai-gateway/providers/upstream-request.ts +++ b/apps/web/src/lib/ai-gateway/providers/upstream-request.ts @@ -31,14 +31,28 @@ export async function upstreamRequest({ for (const [key, value] of Object.entries(ATTRIBUTION_HEADERS)) { headers.set(key, value); } - headers.set('Authorization', `Bearer ${provider.apiKey}`); headers.set('Content-Type', 'application/json'); Object.entries(extraHeaders).forEach(([key, value]) => { headers.set(key, value); }); - const targetUrl = `${provider.apiUrl}${path}${search}`; + let targetUrl = `${provider.apiUrl}${path}${search}`; + const serializedBody = JSON.stringify(body); + + if (provider.signRequest) { + const signed = await provider.signRequest({ + method, + url: targetUrl, + body: serializedBody, + }); + if (signed.url) targetUrl = signed.url; + for (const [key, value] of Object.entries(signed.headers)) { + headers.set(key, value); + } + } else { + headers.set('Authorization', `Bearer ${provider.apiKey}`); + } const TEN_MINUTES_MS = 10 * 60 * 1000; const timeoutSignal = AbortSignal.timeout(TEN_MINUTES_MS); @@ -47,7 +61,7 @@ export async function upstreamRequest({ return await fetch(targetUrl, { method, headers, - body: JSON.stringify(body), + body: serializedBody, // @ts-expect-error see https://github.com/node-fetch/node-fetch/issues/1769 duplex: 'half', signal: combinedSignal, diff --git a/packages/db/src/schema-types.ts b/packages/db/src/schema-types.ts index 0c77ddc493..5e3fae7874 100644 --- a/packages/db/src/schema-types.ts +++ b/packages/db/src/schema-types.ts @@ -951,6 +951,19 @@ export const CustomLlmPricingSchema = z.object({ export type CustomLlmPricing = z.infer; +// AWS Bedrock credentials. When present, upstream requests are SigV4-signed +// against the `bedrock` service instead of using `Authorization: Bearer`. +// `base_url` should be `https://bedrock-runtime..amazonaws.com` and +// `internal_id` is used as the Bedrock model id in the request path +// (`/model//invoke`). +export const CustomLlmAwsBedrockSchema = z.object({ + access_key_id: z.string(), + secret_access_key: z.string(), + region: z.string(), +}); + +export type CustomLlmAwsBedrock = z.infer; + export const CustomLlmDefinitionSchema = z.object({ internal_id: z.string(), display_name: z.string(), @@ -968,6 +981,7 @@ export const CustomLlmDefinitionSchema = z.object({ opencode_settings: OpenCodeSettingsSchema.optional(), openclaw_settings: OpenClawModelSettingsSchema.optional(), pricing: CustomLlmPricingSchema.optional(), + aws_bedrock: CustomLlmAwsBedrockSchema.optional(), }); export type CustomLlmDefinition = z.infer; From b04dd73f213e614ef7071fbfce8cda9e5f84b625 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 13:09:30 +0000 Subject: [PATCH 2/3] make api_key optional, trim non-critical comments, update lockfile --- .../ai-gateway/providers/bedrock-signer.ts | 3 - .../web/src/lib/ai-gateway/providers/types.ts | 6 +- packages/db/src/schema-types.ts | 9 +- pnpm-lock.yaml | 130 ++++++++++++++++++ 4 files changed, 135 insertions(+), 13 deletions(-) diff --git a/apps/web/src/lib/ai-gateway/providers/bedrock-signer.ts b/apps/web/src/lib/ai-gateway/providers/bedrock-signer.ts index 6593577052..f962bf277c 100644 --- a/apps/web/src/lib/ai-gateway/providers/bedrock-signer.ts +++ b/apps/web/src/lib/ai-gateway/providers/bedrock-signer.ts @@ -3,9 +3,6 @@ import { Sha256 } from '@aws-crypto/sha256-js'; import type { CustomLlmAwsBedrock } from '@kilocode/db'; import type { SignedRequest, SignRequestArgs } from '@/lib/ai-gateway/providers/types'; -// Returns a signRequest implementation that rewrites the URL to the Bedrock -// `/model//invoke[-with-response-stream]` endpoint and signs it with -// SigV4 for the `bedrock` service. export function makeBedrockSignRequest( credentials: CustomLlmAwsBedrock, modelId: string diff --git a/apps/web/src/lib/ai-gateway/providers/types.ts b/apps/web/src/lib/ai-gateway/providers/types.ts index e6e5b1a901..03960e8fb0 100644 --- a/apps/web/src/lib/ai-gateway/providers/types.ts +++ b/apps/web/src/lib/ai-gateway/providers/types.ts @@ -37,18 +37,16 @@ export type SignRequestArgs = { }; export type SignedRequest = { - // When set, replaces the target URL (used e.g. for Bedrock path rewriting). url?: string; - // Final auth/signature headers. When `signRequest` is present, the caller - // skips the default `Authorization: Bearer ${apiKey}` header. headers: Record; }; export type Provider = { id: ProviderId; apiUrl: string; - apiKey: string; + apiKey: string | undefined; supportedChatApis: ReadonlyArray; transformRequest(context: TransformRequestContext): void; + // When set, replaces the default `Authorization: Bearer ${apiKey}` header. signRequest?(args: SignRequestArgs): Promise; }; diff --git a/packages/db/src/schema-types.ts b/packages/db/src/schema-types.ts index 5e3fae7874..fc386840a1 100644 --- a/packages/db/src/schema-types.ts +++ b/packages/db/src/schema-types.ts @@ -951,11 +951,8 @@ export const CustomLlmPricingSchema = z.object({ export type CustomLlmPricing = z.infer; -// AWS Bedrock credentials. When present, upstream requests are SigV4-signed -// against the `bedrock` service instead of using `Authorization: Bearer`. -// `base_url` should be `https://bedrock-runtime..amazonaws.com` and -// `internal_id` is used as the Bedrock model id in the request path -// (`/model//invoke`). +// When set, upstream requests are SigV4-signed instead of using Bearer auth; +// `internal_id` is used as the Bedrock model id in the request path. export const CustomLlmAwsBedrockSchema = z.object({ access_key_id: z.string(), secret_access_key: z.string(), @@ -970,7 +967,7 @@ export const CustomLlmDefinitionSchema = z.object({ context_length: z.number(), max_completion_tokens: z.number(), base_url: z.string(), - api_key: z.string(), + api_key: z.string().optional(), organization_ids: z.array(z.string()), supports_image_input: z.boolean().optional(), add_cache_breakpoints: z.boolean().optional(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d9c0bf53d..affddbdecf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -473,12 +473,18 @@ importers: '@anthropic-ai/sdk': specifier: ^0.90.0 version: 0.90.0(zod@4.3.6) + '@aws-crypto/sha256-js': + specifier: ^5.2.0 + version: 5.2.0 '@aws-sdk/client-s3': specifier: ^3.1009.0 version: 3.1009.0 '@aws-sdk/s3-request-presigner': specifier: ^3.1009.0 version: 3.1009.0 + '@aws-sdk/signature-v4': + specifier: ^3.374.0 + version: 3.374.0 '@chat-adapter/github': specifier: 4.27.0 version: 4.27.0 @@ -2430,6 +2436,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + '@aws-crypto/crc32@3.0.0': + resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==} + '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -2450,6 +2459,9 @@ packages: '@aws-crypto/supports-web-crypto@5.2.0': resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + '@aws-crypto/util@3.0.0': + resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} + '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} @@ -2553,6 +2565,11 @@ packages: resolution: {integrity: sha512-n1qYFD+tbqZuyskVaxUE+t10AUz9g3qzDw3Tp6QZDKmqsjfDmZBd4GIk2EKJJNtcCBtE5YiUjDYA+3djFAFBBg==} engines: {node: '>=20.0.0'} + '@aws-sdk/signature-v4@3.374.0': + resolution: {integrity: sha512-2xLJvSdzcZZAg0lsDLUAuSQuihzK0dcxIK7WmfuJeF7DGKJFmp9czQmz5f3qiDz6IDQzvgK1M9vtJSVCslJbyQ==} + engines: {node: '>=14.0.0'} + deprecated: This package has moved to @smithy/signature-v4 + '@aws-sdk/token-providers@3.1009.0': resolution: {integrity: sha512-KCPLuTqN9u0Rr38Arln78fRG9KXpzsPWmof+PZzfAHMMQq2QED6YjQrkrfiH7PDefLWEposY1o4/eGwrmKA4JA==} engines: {node: '>=20.0.0'} @@ -2589,6 +2606,9 @@ packages: aws-crt: optional: true + '@aws-sdk/util-utf8-browser@3.259.0': + resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} + '@aws-sdk/xml-builder@3.972.11': resolution: {integrity: sha512-iitV/gZKQMvY9d7ovmyFnFuTHbBAtrmLnvaSb/3X8vOKyevwtpmEtyc8AdhVWZe0pI/1GsHxlEvQeOePFzy7KQ==} engines: {node: '>=20.0.0'} @@ -7227,6 +7247,9 @@ packages: resolution: {integrity: sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-codec@1.1.0': + resolution: {integrity: sha512-3tEbUb8t8an226jKB6V/Q2XU/J53lCwCzULuBPEaF4JjSh+FlCMp7TmogE/Aij5J9DwlsZ4VAD/IRDuQ/0ZtMw==} + '@smithy/eventstream-codec@4.2.12': resolution: {integrity: sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA==} engines: {node: '>=18.0.0'} @@ -7267,6 +7290,10 @@ packages: resolution: {integrity: sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==} engines: {node: '>=18.0.0'} + '@smithy/is-array-buffer@1.1.0': + resolution: {integrity: sha512-twpQ/n+3OWZJ7Z+xu43MJErmhB/WO/mMTnqR6PwWQShvSJ/emx5d1N59LQZk6ZpTAeuRWrc+eHhkzTp9NFjNRQ==} + engines: {node: '>=14.0.0'} + '@smithy/is-array-buffer@2.2.0': resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} @@ -7331,6 +7358,10 @@ packages: resolution: {integrity: sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==} engines: {node: '>=18.0.0'} + '@smithy/signature-v4@1.1.0': + resolution: {integrity: sha512-fDo3m7YqXBs7neciOePPd/X9LPm5QLlDMdIC4m1H6dgNLnXfLMFNIxEfPyohGA8VW9Wn4X8lygnPSGxDZSmp0Q==} + engines: {node: '>=14.0.0'} + '@smithy/signature-v4@5.3.12': resolution: {integrity: sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==} engines: {node: '>=18.0.0'} @@ -7339,6 +7370,10 @@ packages: resolution: {integrity: sha512-UqwYawyqSr/aog8mnLnfbPurS0gi4G7IYDcD28cUIBhsvWs1+rQcL2IwkUQ+QZ7dibaoRzhNF99fAQ9AUcO00w==} engines: {node: '>=18.0.0'} + '@smithy/types@1.2.0': + resolution: {integrity: sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==} + engines: {node: '>=14.0.0'} + '@smithy/types@4.13.1': resolution: {integrity: sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==} engines: {node: '>=18.0.0'} @@ -7359,6 +7394,10 @@ packages: resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} engines: {node: '>=18.0.0'} + '@smithy/util-buffer-from@1.1.0': + resolution: {integrity: sha512-9m6NXE0ww+ra5HKHCHig20T+FAwxBAm7DIdwc/767uGWbRcY720ybgPacQNB96JMOI7xVr/CDa3oMzKmW4a+kw==} + engines: {node: '>=14.0.0'} + '@smithy/util-buffer-from@2.2.0': resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} @@ -7383,10 +7422,18 @@ packages: resolution: {integrity: sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==} engines: {node: '>=18.0.0'} + '@smithy/util-hex-encoding@1.1.0': + resolution: {integrity: sha512-7UtIE9eH0u41zpB60Jzr0oNCQ3hMJUabMcKRUVjmyHTXiWDE4vjSqN6qlih7rCNeKGbioS7f/y2Jgym4QZcKFg==} + engines: {node: '>=14.0.0'} + '@smithy/util-hex-encoding@4.2.2': resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} engines: {node: '>=18.0.0'} + '@smithy/util-middleware@1.1.0': + resolution: {integrity: sha512-6hhckcBqVgjWAqLy2vqlPZ3rfxLDhFWEmM7oLh2POGvsi7j0tHkbN7w4DFhuBExVJAbJ/qqxqZdRY6Fu7/OezQ==} + engines: {node: '>=14.0.0'} + '@smithy/util-middleware@4.2.12': resolution: {integrity: sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==} engines: {node: '>=18.0.0'} @@ -7399,10 +7446,18 @@ packages: resolution: {integrity: sha512-v4sa+3xTweL1CLO2UP0p7tvIMH/Rq1X4KKOxd568mpe6LSLMQCnDHs4uv7m3ukpl3HvcN2JH6jiCS0SNRXKP/w==} engines: {node: '>=18.0.0'} + '@smithy/util-uri-escape@1.1.0': + resolution: {integrity: sha512-/jL/V1xdVRt5XppwiaEU8Etp5WHZj609n0xMTuehmCqdoOFbId1M+aEeDWZsQ+8JbEB/BJ6ynY2SlYmOaKtt8w==} + engines: {node: '>=14.0.0'} + '@smithy/util-uri-escape@4.2.2': resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} engines: {node: '>=18.0.0'} + '@smithy/util-utf8@1.1.0': + resolution: {integrity: sha512-p/MYV+JmqmPyjdgyN2UxAeYDj9cBqCjp0C/NsTWnnjoZUVqoeZ6IrW915L9CAKWVECgv9lVQGc4u/yz26/bI1A==} + engines: {node: '>=14.0.0'} + '@smithy/util-utf8@2.3.0': resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} @@ -8237,6 +8292,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} @@ -14666,6 +14722,9 @@ packages: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -15539,6 +15598,12 @@ snapshots: readable-stream: 3.6.2 simple-get: 4.0.1 + '@aws-crypto/crc32@3.0.0': + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.973.6 + tslib: 1.14.1 + '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -15580,6 +15645,12 @@ snapshots: dependencies: tslib: 2.8.1 + '@aws-crypto/util@3.0.0': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + '@aws-crypto/util@5.2.0': dependencies: '@aws-sdk/types': 3.973.6 @@ -15937,6 +16008,11 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 + '@aws-sdk/signature-v4@3.374.0': + dependencies: + '@smithy/signature-v4': 1.1.0 + tslib: 2.8.1 + '@aws-sdk/token-providers@3.1009.0': dependencies: '@aws-sdk/core': 3.973.20 @@ -15993,6 +16069,10 @@ snapshots: '@smithy/util-config-provider': 4.2.2 tslib: 2.8.1 + '@aws-sdk/util-utf8-browser@3.259.0': + dependencies: + tslib: 2.8.1 + '@aws-sdk/xml-builder@3.972.11': dependencies: '@smithy/types': 4.13.1 @@ -21558,6 +21638,13 @@ snapshots: '@smithy/url-parser': 4.2.12 tslib: 2.8.1 + '@smithy/eventstream-codec@1.1.0': + dependencies: + '@aws-crypto/crc32': 3.0.0 + '@smithy/types': 1.2.0 + '@smithy/util-hex-encoding': 1.1.0 + tslib: 2.8.1 + '@smithy/eventstream-codec@4.2.12': dependencies: '@aws-crypto/crc32': 5.2.0 @@ -21621,6 +21708,10 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 + '@smithy/is-array-buffer@1.1.0': + dependencies: + tslib: 2.8.1 + '@smithy/is-array-buffer@2.2.0': dependencies: tslib: 2.8.1 @@ -21721,6 +21812,17 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 + '@smithy/signature-v4@1.1.0': + dependencies: + '@smithy/eventstream-codec': 1.1.0 + '@smithy/is-array-buffer': 1.1.0 + '@smithy/types': 1.2.0 + '@smithy/util-hex-encoding': 1.1.0 + '@smithy/util-middleware': 1.1.0 + '@smithy/util-uri-escape': 1.1.0 + '@smithy/util-utf8': 1.1.0 + tslib: 2.8.1 + '@smithy/signature-v4@5.3.12': dependencies: '@smithy/is-array-buffer': 4.2.2 @@ -21742,6 +21844,10 @@ snapshots: '@smithy/util-stream': 4.5.19 tslib: 2.8.1 + '@smithy/types@1.2.0': + dependencies: + tslib: 2.8.1 + '@smithy/types@4.13.1': dependencies: tslib: 2.8.1 @@ -21766,6 +21872,11 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/util-buffer-from@1.1.0': + dependencies: + '@smithy/is-array-buffer': 1.1.0 + tslib: 2.8.1 + '@smithy/util-buffer-from@2.2.0': dependencies: '@smithy/is-array-buffer': 2.2.0 @@ -21803,10 +21914,18 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 + '@smithy/util-hex-encoding@1.1.0': + dependencies: + tslib: 2.8.1 + '@smithy/util-hex-encoding@4.2.2': dependencies: tslib: 2.8.1 + '@smithy/util-middleware@1.1.0': + dependencies: + tslib: 2.8.1 + '@smithy/util-middleware@4.2.12': dependencies: '@smithy/types': 4.13.1 @@ -21829,10 +21948,19 @@ snapshots: '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 + '@smithy/util-uri-escape@1.1.0': + dependencies: + tslib: 2.8.1 + '@smithy/util-uri-escape@4.2.2': dependencies: tslib: 2.8.1 + '@smithy/util-utf8@1.1.0': + dependencies: + '@smithy/util-buffer-from': 1.1.0 + tslib: 2.8.1 + '@smithy/util-utf8@2.3.0': dependencies: '@smithy/util-buffer-from': 2.2.0 @@ -31076,6 +31204,8 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tslib@1.14.1: {} + tslib@2.8.1: {} tsx@4.21.0: From 4cce00eb87be2125c22eff71bd2d7a57c405306b Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 13:36:03 +0000 Subject: [PATCH 3/3] skip Bearer header when apiKey absent; format Addresses review: non-Bedrock custom LLMs with omitted api_key no longer send 'Authorization: Bearer undefined'. --- apps/web/src/lib/ai-gateway/providers/get-provider.ts | 4 +--- apps/web/src/lib/ai-gateway/providers/upstream-request.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/web/src/lib/ai-gateway/providers/get-provider.ts b/apps/web/src/lib/ai-gateway/providers/get-provider.ts index 2f30738b71..b8c9788a34 100644 --- a/apps/web/src/lib/ai-gateway/providers/get-provider.ts +++ b/apps/web/src/lib/ai-gateway/providers/get-provider.ts @@ -96,9 +96,7 @@ async function checkCustomLlm( return null; } const bedrock = customLlm.aws_bedrock; - const signRequest = bedrock - ? makeBedrockSignRequest(bedrock, customLlm.internal_id) - : undefined; + const signRequest = bedrock ? makeBedrockSignRequest(bedrock, customLlm.internal_id) : undefined; return { provider: { id: 'custom', diff --git a/apps/web/src/lib/ai-gateway/providers/upstream-request.ts b/apps/web/src/lib/ai-gateway/providers/upstream-request.ts index d79905435c..28909475fb 100644 --- a/apps/web/src/lib/ai-gateway/providers/upstream-request.ts +++ b/apps/web/src/lib/ai-gateway/providers/upstream-request.ts @@ -50,7 +50,7 @@ export async function upstreamRequest({ for (const [key, value] of Object.entries(signed.headers)) { headers.set(key, value); } - } else { + } else if (provider.apiKey) { headers.set('Authorization', `Bearer ${provider.apiKey}`); }