Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:Add Amazon Bedrock to support the leading large language models such as Amazon Nova, Claude, Llama, Mistral, and others. #5746

Open
wants to merge 91 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
ff356f0
修改: app/api/[provider]/[...path]/route.ts
glayyiyi Oct 29, 2024
722c288
Merge branch 'ChatGPTNextWeb:main' into main
glayyiyi Oct 31, 2024
dca4a0e
修改: app/api/bedrock.ts
glayyiyi Oct 31, 2024
fc39116
修改: app/api/bedrock.ts
glayyiyi Nov 4, 2024
0f276f5
修改: app/client/platforms/bedrock.ts
glayyiyi Nov 5, 2024
afbf5eb
修改: .env.template
glayyiyi Nov 5, 2024
58837f6
修改: app/api/bedrock.ts
glayyiyi Nov 5, 2024
f532731
修改: app/client/platforms/bedrock.ts
glayyiyi Nov 5, 2024
e3c18bb
修改: app/api/bedrock.ts
glayyiyi Nov 5, 2024
d55c752
修改: app/api/bedrock.ts
glayyiyi Nov 5, 2024
1164e1b
Merge feature/update-bedrock-api into main
glayyiyi Nov 5, 2024
1998cf5
Merge feature/update-bedrock-api into main
glayyiyi Nov 5, 2024
045adc3
修改: app/api/bedrock.ts
glayyiyi Nov 5, 2024
1f66d37
修改: app/api/bedrock.ts
glayyiyi Nov 5, 2024
cae20af
修改: app/api/bedrock.ts
glayyiyi Nov 5, 2024
ca17e90
修改: app/utils/encryption.ts
glayyiyi Nov 5, 2024
c55cea5
修改: app/store/access.ts
glayyiyi Nov 6, 2024
952d883
修改: app/components/settings.tsx
glayyiyi Nov 6, 2024
3bf55d3
Merge branch 'main' into main
glayyiyi Nov 6, 2024
f0c23cc
修改: app/store/access.ts
glayyiyi Nov 6, 2024
5d5456c
修改: app/api/bedrock.ts
glayyiyi Nov 6, 2024
4204890
Merge branch 'ChatGPTNextWeb:main' into main
glayyiyi Nov 6, 2024
9e04198
Merge branch 'main' into main
glayyiyi Nov 6, 2024
82a368a
修改: .env.template
glayyiyi Nov 7, 2024
0e09697
Merge branch 'ChatGPTNextWeb:main' into main
glayyiyi Nov 8, 2024
09e4f95
Merge branch 'main' into main
glayyiyi Nov 11, 2024
f120584
Merge branch 'main' into main
glayyiyi Nov 12, 2024
afb0752
Merge branch 'ChatGPTNextWeb:main' into main
glayyiyi Nov 12, 2024
bfa4339
Add AWS access key validation.
glayyiyi Nov 12, 2024
70f066c
Add AWS region validation and improve code style.
glayyiyi Nov 13, 2024
1b5a81c
Add AWS secret key validation.
glayyiyi Nov 13, 2024
24261d2
Consider adding more Bedrock-specific configurations
glayyiyi Nov 13, 2024
6bc1612
修改: app/locales/cn.ts
glayyiyi Nov 13, 2024
225ad30
修改: app/api/bedrock.ts
glayyiyi Nov 13, 2024
b2d5e0e
Merge branch 'ChatGPTNextWeb:main' into main
glayyiyi Nov 13, 2024
dfeb9e7
Merge branch 'ChatGPTNextWeb:main' into main
glayyiyi Nov 17, 2024
9d3f1d2
remove document function,only keep the bedrock service provider
glayyiyi Nov 18, 2024
f60c237
去掉sdk的引入,客户端也能直连
glayyiyi Nov 20, 2024
bd68df1
修改: app/api/bedrock.ts
glayyiyi Nov 21, 2024
b0c1ccd
优化和重构代码,增加前端可以设置加密配置数据的密钥
glayyiyi Nov 22, 2024
a85db21
优化代码,修改方法命名错误
glayyiyi Nov 23, 2024
ff88421
修改密钥加密逻辑
glayyiyi Nov 23, 2024
a6337e9
完善总结功能的代码逻辑
glayyiyi Nov 23, 2024
238eb70
完善mistral模型的推理结果
glayyiyi Nov 23, 2024
513cf1b
完善llama和mistral模型的推理功能
glayyiyi Nov 23, 2024
a19ba69
Merge branch 'ChatGPTNextWeb:main' into main
glayyiyi Nov 24, 2024
2ccdd17
优化前后端代码,将公共方法抽取到util类,修改客户端加密方式
glayyiyi Nov 24, 2024
6f7a635
完善llama和mistral模型的推理功能
glayyiyi Nov 24, 2024
5bd7e28
去掉Debug日志打印
glayyiyi Nov 25, 2024
0abfd27
完善bedrock中文翻译
glayyiyi Nov 25, 2024
2fe848e
去掉Debug日志打印
glayyiyi Nov 25, 2024
9a47304
去掉Debug日志打印
glayyiyi Nov 25, 2024
15d0600
Merge branch 'ChatGPTNextWeb:main' into main
glayyiyi Nov 25, 2024
e663375
完善mistral tool use功能 和llama3消息格式问题
glayyiyi Nov 25, 2024
448babd
完善mistral tool use功能
glayyiyi Nov 26, 2024
b39b3f7
Remove detailed error logging in decryption function.
glayyiyi Nov 26, 2024
9c648e5
Remove detailed error error message.
glayyiyi Nov 26, 2024
8ce2cf5
Remove detailed error error message.
glayyiyi Nov 26, 2024
471b178
modify the BEDROCK_BASE_URL to use the region from the access stor
glayyiyi Nov 26, 2024
d9d2a27
modify the BEDROCK_BASE_URL to use the region from the access store
glayyiyi Nov 26, 2024
a75b9f7
Merge branch 'ChatGPTNextWeb:main' into main
glayyiyi Dec 4, 2024
0c55850
优化bedrock流式消息处理的缓冲机制,简化app和后台api调用逻辑判断和处理
glayyiyi Dec 7, 2024
4254fd3
增加bedrock最新nova模型,包括image解析的支持
glayyiyi Dec 7, 2024
57dc44a
增加bedrock最新nova模型,优化后台代码
glayyiyi Dec 7, 2024
ad49cd0
优化bedrock工具类,以更安全的处理流式消息的边界值
glayyiyi Dec 7, 2024
5ac651a
Enhance encryption security
glayyiyi Dec 7, 2024
603415f
Enhance log security
glayyiyi Dec 8, 2024
26b9fa9
优化代码
glayyiyi Dec 8, 2024
f5ae086
Enhance encryption security with additional safeguards.
glayyiyi Dec 8, 2024
fb3437c
Update app/client/platforms/bedrock.ts
glayyiyi Dec 8, 2024
7830b37
Enhance encryption security with additional safeguards.
glayyiyi Dec 8, 2024
4b2f447
Enhance encryption security with additional safeguards.
glayyiyi Dec 8, 2024
93337b2
Update app/client/platforms/bedrock.ts
glayyiyi Dec 8, 2024
a088687
Enhance encryption security with additional safeguards.
glayyiyi Dec 8, 2024
44a1cf6
Update app/client/platforms/bedrock.ts
glayyiyi Dec 9, 2024
50a241b
Update app/utils/aws.ts
glayyiyi Dec 9, 2024
372a327
Update app/utils/aws.ts
glayyiyi Dec 9, 2024
2a9f7d7
Update app/client/platforms/bedrock.ts
glayyiyi Dec 9, 2024
12d38aa
Update app/utils/aws.ts
glayyiyi Dec 9, 2024
19437c7
Enhance encryption security with additional safeguards.
glayyiyi Dec 10, 2024
e455840
Update app/utils/aws.ts
glayyiyi Dec 10, 2024
cb0422b
Enhance processChunks by attempting to recover by processing the next…
glayyiyi Dec 10, 2024
0ec1ae6
Enhance encryption security with additional safeguards.
glayyiyi Dec 10, 2024
e839940
feat:add amazon.nova model tool use support.
glayyiyi Dec 14, 2024
9643adc
Merge branch 'main' into main
glayyiyi Dec 21, 2024
92615da
Merge branch 'main' into main
glayyiyi Dec 22, 2024
26f79aa
feat:add nova VISION_MODEL_REGEXES
glayyiyi Dec 22, 2024
89b1774
Merge branch 'ChatGPTNextWeb:main' into main
glayyiyi Dec 25, 2024
29b9a20
feat:add meta.llama3-3-70b-instruct model
glayyiyi Dec 25, 2024
b0f78e9
Merge branch 'main' into main
glayyiyi Dec 30, 2024
6d72a04
Merge branch 'ChatGPTNextWeb:main' into main
glayyiyi Jan 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,12 @@ ANTHROPIC_API_VERSION=
ANTHROPIC_URL=

### (optional)
WHITE_WEBDAV_ENDPOINTS=
WHITE_WEBDAV_ENDPOINTS=

### bedrock (optional)
AWS_REGION=
AWS_ACCESS_KEY=
AWS_SECRET_KEY=

### Assign this with a secure, randomly generated key
ENCRYPTION_KEY=
4 changes: 4 additions & 0 deletions app/api/[provider]/[...path]/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ApiPath } from "@/app/constant";
import { NextRequest } from "next/server";
import { handle as openaiHandler } from "../../openai";
import { handle as bedrockHandler } from "../../bedrock";
import { handle as azureHandler } from "../../azure";
import { handle as googleHandler } from "../../google";
import { handle as anthropicHandler } from "../../anthropic";
Expand All @@ -21,12 +22,15 @@ async function handle(
const apiPath = `/api/${params.provider}`;
console.log(`[${params.provider} Route] params `, params);
switch (apiPath) {
case ApiPath.Bedrock:
return bedrockHandler(req, { params });
case ApiPath.Azure:
return azureHandler(req, { params });
case ApiPath.Google:
return googleHandler(req, { params });
case ApiPath.Anthropic:
return anthropicHandler(req, { params });

case ApiPath.Baidu:
return baiduHandler(req, { params });
case ApiPath.ByteDance:
Expand Down
22 changes: 22 additions & 0 deletions app/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
msg: "you are not allowed to access with your own api key",
};
}
// Special handling for Bedrock
if (modelProvider === ModelProvider.Bedrock) {
const region = serverConfig.awsRegion;
const accessKeyId = serverConfig.awsAccessKey;
const secretAccessKey = serverConfig.awsSecretKey;

console.log("[Auth] Bedrock credentials:", {
region,
accessKeyId: accessKeyId ? "***" : undefined,
secretKey: secretAccessKey ? "***" : undefined,
});

// Check if AWS credentials are provided
if (!region || !accessKeyId || !secretAccessKey) {
return {
error: true,
msg: "Missing AWS credentials. Please configure Region, Access Key ID, and Secret Access Key in settings.",
};
}

return { error: false };
}

// if user does not provide an api key, inject system api key
if (!apiKey) {
Expand Down
330 changes: 330 additions & 0 deletions app/api/bedrock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
import { NextRequest, NextResponse } from "next/server";
import { sign, decrypt } from "../utils/aws";
glayyiyi marked this conversation as resolved.
Show resolved Hide resolved

const ALLOWED_PATH = new Set(["chat", "models"]);

function parseEventData(chunk: Uint8Array): any {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add type definition for parsed response

The function returns any type which reduces type safety. Consider defining a specific type for the parsed response.

-function parseEventData(chunk: Uint8Array): any {
+type ParsedResponse = {
+  body?: string | Record<string, unknown>;
+  output?: string;
+  type?: string;
+  delta?: {
+    type?: string;
+    text?: string;
+  };
+};
+
+function parseEventData(chunk: Uint8Array): ParsedResponse | null {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function parseEventData(chunk: Uint8Array): any {
type ParsedResponse = {
body?: string | Record<string, unknown>;
output?: string;
type?: string;
delta?: {
type?: string;
text?: string;
};
};
function parseEventData(chunk: Uint8Array): ParsedResponse | null {

const decoder = new TextDecoder();
const text = decoder.decode(chunk);
try {
const parsed = JSON.parse(text);
// AWS Bedrock wraps the response in a 'body' field
if (typeof parsed.body === "string") {
try {
return JSON.parse(parsed.body);
} catch (e) {
return { output: parsed.body };
}
}
return parsed.body || parsed;
} catch (e) {
console.error("Error parsing event data:", e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid logging sensitive response data

Logging the entire error object could expose sensitive information in the response.

Apply this diff:

-    console.error("Error parsing event data:", e);
+    console.error("Error parsing event data:", e instanceof Error ? e.message : "Unknown error");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.error("Error parsing event data:", e);
console.error("Error parsing event data:", e instanceof Error ? e.message : "Unknown error");

try {
// Handle base64 encoded responses
const base64Match = text.match(/:"([A-Za-z0-9+/=]+)"/);
if (base64Match) {
const decoded = Buffer.from(base64Match[1], "base64").toString("utf-8");
try {
return JSON.parse(decoded);
} catch (e) {
return { output: decoded };
}
}

// Handle event-type responses
const eventMatch = text.match(/:event-type[^\{]+({.*})/);
if (eventMatch) {
try {
return JSON.parse(eventMatch[1]);
} catch (e) {
return { output: eventMatch[1] };
}
}

// Handle plain text responses
if (text.trim()) {
// Clean up any malformed JSON characters
const cleanText = text.replace(/[\x00-\x1F\x7F-\x9F]/g, "");
glayyiyi marked this conversation as resolved.
Show resolved Hide resolved
return { output: cleanText };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use a safer method to clean control characters

The current regex pattern for cleaning control characters is flagged as potentially unsafe.

-        const cleanText = text.replace(/[\x00-\x1F\x7F-\x9F]/g, "");
+        const cleanText = text.replace(/[^\x20-\x7E]/g, "");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const cleanText = text.replace(/[\x00-\x1F\x7F-\x9F]/g, "");
return { output: cleanText };
const cleanText = text.replace(/[^\x20-\x7E]/g, "");
return { output: cleanText };
🧰 Tools
🪛 Biome (1.9.4)

[error] 48-48: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)


[error] 48-48: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)

}
} catch (innerError) {
console.error("Error in fallback parsing:", innerError);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling in fallback parsing

The catch block for innerError should provide more context about the parsing failure.

     } catch (innerError) {
-      console.error("Error in fallback parsing:", innerError);
+      console.error(
+        "Failed to parse response in fallback handlers:",
+        innerError instanceof Error ? innerError.message : "Unknown error"
+      );
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (innerError) {
console.error("Error in fallback parsing:", innerError);
}
} catch (innerError) {
console.error(
"Failed to parse response in fallback handlers:",
innerError instanceof Error ? innerError.message : "Unknown error"
);
}

}
return null;
}

async function* transformBedrockStream(
stream: ReadableStream,
modelId: string,
) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add timeout handling for stream processing

The stream processing lacks timeout handling which could lead to hung connections.

 async function* transformBedrockStream(
   stream: ReadableStream,
   modelId: string,
+  signal?: AbortSignal,
 ) {

And update the implementation to handle timeouts:

if (signal) {
  signal.addEventListener('abort', () => {
    reader.cancel('Stream processing timeout');
  });
}

const reader = stream.getReader();
let buffer = "";

try {
while (true) {
const { done, value } = await reader.read();
if (done) {
if (buffer) {
yield `data: ${JSON.stringify({
delta: { text: buffer },
})}\n\n`;
}
break;
}

const parsed = parseEventData(value);
if (!parsed) continue;

console.log("Parsed response:", JSON.stringify(parsed, null, 2));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove sensitive data logging

Logging the parsed response could expose sensitive information.

Apply this diff:

-      console.log("Parsed response:", JSON.stringify(parsed, null, 2));
+      console.log("Received parsed response from Bedrock");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("Parsed response:", JSON.stringify(parsed, null, 2));
console.log("Received parsed response from Bedrock");


// Handle Titan models
if (modelId.startsWith("amazon.titan")) {
const text = parsed.outputText || "";
if (text) {
yield `data: ${JSON.stringify({
delta: { text },
})}\n\n`;
}
}
glayyiyi marked this conversation as resolved.
Show resolved Hide resolved
// Handle LLaMA3 models
else if (modelId.startsWith("us.meta.llama3")) {
let text = "";
if (parsed.generation) {
text = parsed.generation;
} else if (parsed.output) {
text = parsed.output;
} else if (typeof parsed === "string") {
text = parsed;
}

if (text) {
// Clean up any control characters or invalid JSON characters
text = text.replace(/[\x00-\x1F\x7F-\x9F]/g, "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use a safer method to clean control characters

Using control characters in regex is flagged as potentially unsafe.

Apply this diff:

-          text = text.replace(/[\x00-\x1F\x7F-\x9F]/g, "");
+          text = text.replace(/[^\x20-\x7E]/g, "");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
text = text.replace(/[\x00-\x1F\x7F-\x9F]/g, "");
text = text.replace(/[^\x20-\x7E]/g, "");
🧰 Tools
🪛 Biome (1.9.4)

[error] 103-103: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)


[error] 103-103: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)

yield `data: ${JSON.stringify({
delta: { text },
})}\n\n`;
}
}
// Handle Mistral models
else if (modelId.startsWith("mistral.mistral")) {
const text =
parsed.output || parsed.outputs?.[0]?.text || parsed.completion || "";
if (text) {
yield `data: ${JSON.stringify({
delta: { text },
})}\n\n`;
}
}
// Handle Claude models
else if (modelId.startsWith("anthropic.claude")) {
if (parsed.type === "content_block_delta") {
if (parsed.delta?.type === "text_delta") {
yield `data: ${JSON.stringify({
delta: { text: parsed.delta.text },
})}\n\n`;
} else if (parsed.delta?.type === "input_json_delta") {
yield `data: ${JSON.stringify(parsed)}\n\n`;
}
} else if (
parsed.type === "message_delta" &&
parsed.delta?.stop_reason
) {
yield `data: ${JSON.stringify({
delta: { stop_reason: parsed.delta.stop_reason },
})}\n\n`;
} else if (
parsed.type === "content_block_start" &&
parsed.content_block?.type === "tool_use"
) {
yield `data: ${JSON.stringify(parsed)}\n\n`;
} else if (parsed.type === "content_block_stop") {
yield `data: ${JSON.stringify(parsed)}\n\n`;
}
}
}
} finally {
reader.releaseLock();
}
}

function validateRequest(body: any, modelId: string): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace 'any' type with a specific type definition

Using any type reduces type safety. Define a specific type for the request body.

-function validateRequest(body: any, modelId: string): void {
+type RequestBody = {
+  body?: Record<string, unknown>;
+  anthropic_version?: string;
+  max_tokens?: number;
+  messages?: Array<Record<string, unknown>>;
+  prompt?: string;
+  inputText?: string;
+};
+
+function validateRequest(body: RequestBody, modelId: string): void {

if (!modelId) throw new Error("Model ID is required");

const bodyContent = body.body || body;

if (modelId.startsWith("anthropic.claude")) {
if (
!body.anthropic_version ||
body.anthropic_version !== "bedrock-2023-05-31"
) {
throw new Error("anthropic_version must be 'bedrock-2023-05-31'");
}
if (typeof body.max_tokens !== "number" || body.max_tokens < 0) {
throw new Error("max_tokens must be a positive number");
}
if (modelId.startsWith("anthropic.claude-3")) {
if (!Array.isArray(body.messages))
throw new Error("messages array is required for Claude 3");
} else if (typeof body.prompt !== "string") {
throw new Error("prompt is required for Claude 2 and earlier");
}
} else if (modelId.startsWith("us.meta.llama3")) {
if (!bodyContent.prompt) {
throw new Error("prompt is required for LLaMA3 models");
}
} else if (modelId.startsWith("mistral.mistral")) {
if (!bodyContent.prompt) throw new Error("Mistral requires a prompt");
} else if (modelId.startsWith("amazon.titan")) {
if (!bodyContent.inputText) throw new Error("Titan requires inputText");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation for unsupported model IDs

The function should explicitly reject unsupported model IDs.

   } else if (modelId.startsWith("amazon.titan")) {
     if (!bodyContent.inputText) throw new Error("Titan requires inputText");
+  } else {
+    throw new Error(`Unsupported model ID: ${modelId}`);
   }

Committable suggestion skipped: line range outside the PR's diff.


async function requestBedrock(req: NextRequest) {
const controller = new AbortController();
const awsRegion = req.headers.get("X-Region") ?? "";
const awsAccessKey = req.headers.get("X-Access-Key") ?? "";
const awsSecretKey = req.headers.get("X-Secret-Key") ?? "";
const awsSessionToken = req.headers.get("X-Session-Token");
const modelId = req.headers.get("X-Model-Id") ?? "";

if (!awsRegion || !awsAccessKey || !awsSecretKey || !modelId) {
throw new Error("Missing required AWS credentials or model ID");
}

const decryptedAccessKey = decrypt(awsAccessKey);
const decryptedSecretKey = decrypt(awsSecretKey);
const decryptedSessionToken = awsSessionToken
? decrypt(awsSessionToken)
: undefined;

if (!decryptedAccessKey || !decryptedSecretKey) {
throw new Error("Failed to decrypt AWS credentials");
}

// Construct the base endpoint
const baseEndpoint = `https://bedrock-runtime.${awsRegion}.amazonaws.com`;
glayyiyi marked this conversation as resolved.
Show resolved Hide resolved

// Set up timeout
const timeoutId = setTimeout(() => controller.abort(), 10 * 60 * 1000);

try {
// Determine the endpoint and request body based on model type
let endpoint;
let requestBody;
let additionalHeaders = {};

const bodyText = await req.clone().text();
if (!bodyText) {
throw new Error("Request body is empty");
}

const bodyJson = JSON.parse(bodyText);
validateRequest(bodyJson, modelId);

// For all other models, use standard endpoint
endpoint = `${baseEndpoint}/model/${modelId}/invoke-with-response-stream`;
requestBody = JSON.stringify(bodyJson.body || bodyJson);

console.log("Request to AWS Bedrock:", {
endpoint,
modelId,
body: requestBody,
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove sensitive request logging

Logging the entire request body could expose sensitive information.

-    console.log("Request to AWS Bedrock:", {
-      endpoint,
-      modelId,
-      body: requestBody,
-    });
+    console.log("[Bedrock] Sending request to model:", modelId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("Request to AWS Bedrock:", {
endpoint,
modelId,
body: requestBody,
});
console.log("[Bedrock] Sending request to model:", modelId);

const headers = await sign({
method: "POST",
url: endpoint,
region: awsRegion,
accessKeyId: decryptedAccessKey,
secretAccessKey: decryptedSecretKey,
sessionToken: decryptedSessionToken,
body: requestBody,
service: "bedrock",
});

const res = await fetch(endpoint, {
method: "POST",
headers: {
...headers,
...additionalHeaders,
},
body: requestBody,
redirect: "manual",
// @ts-ignore
duplex: "half",
signal: controller.signal,
});

if (!res.ok) {
const error = await res.text();
console.error("AWS Bedrock error response:", error);
try {
const errorJson = JSON.parse(error);
throw new Error(errorJson.message || error);
} catch {
throw new Error(error || "Failed to get response from Bedrock");
}
}
glayyiyi marked this conversation as resolved.
Show resolved Hide resolved

if (!res.body) {
throw new Error("Empty response from Bedrock");
}

const transformedStream = transformBedrockStream(res.body, modelId);
const stream = new ReadableStream({
async start(controller) {
try {
for await (const chunk of transformedStream) {
controller.enqueue(new TextEncoder().encode(chunk));
}
controller.close();
} catch (err) {
console.error("Stream error:", err);
controller.error(err);
}
},
});

return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
"X-Accel-Buffering": "no",
},
});
} catch (e) {
console.error("Request error:", e);
throw e;
} finally {
clearTimeout(timeoutId);
}
}

export async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
if (req.method === "OPTIONS") {
return NextResponse.json({ body: "OK" }, { status: 200 });
}

const subpath = params.path.join("/");
if (!ALLOWED_PATH.has(subpath)) {
return NextResponse.json(
{ error: true, msg: "you are not allowed to request " + subpath },
{ status: 403 },
);
Comment on lines +156 to +161
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve path validation

The current path validation is basic and could be bypassed.

+  const VALID_PATHS = new Set(["chat", "models"]);
+  
+  if (!params?.path?.length) {
+    return NextResponse.json(
+      { error: true, message: "Invalid request path" },
+      { status: 400 },
+    );
+  }
+
   const subpath = params.path.join("/");
-  if (!ALLOWED_PATH.has(subpath)) {
+  if (!VALID_PATHS.has(subpath)) {
     return NextResponse.json(
-      { error: true, msg: "you are not allowed to request " + subpath },
+      { error: true, message: "Invalid request path" },
       { status: 403 },
     );
   }

Committable suggestion skipped: line range outside the PR's diff.

}

try {
return await requestBedrock(req);
} catch (e) {
console.error("Handler error:", e);
return NextResponse.json(
{ error: true, msg: e instanceof Error ? e.message : "Unknown error" },
{ status: 500 },
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Sanitize error messages in response

Exposing raw error messages in the response could reveal sensitive information.

Apply this diff:

     console.error("Handler error:", e);
     return NextResponse.json(
-      { error: true, msg: e instanceof Error ? e.message : "Unknown error" },
+      { error: true, msg: "An internal server error occurred" },
       { status: 500 },
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.error("Handler error:", e);
return NextResponse.json(
{ error: true, msg: e instanceof Error ? e.message : "Unknown error" },
{ status: 500 },
);
console.error("Handler error:", e);
return NextResponse.json(
{ error: true, msg: "An internal server error occurred" },
{ status: 500 },
);

}
}
Loading