Skip to content

Commit

Permalink
hotfix for tencent sign
Browse files Browse the repository at this point in the history
  • Loading branch information
lloydzhou committed Aug 1, 2024
1 parent a024980 commit f85ec95
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 135 deletions.
135 changes: 19 additions & 116 deletions app/api/tencent/[...path]/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"use server";
import { getServerSideConfig } from "@/app/config/server";
import {
TENCENT_BASE_URL,
Expand All @@ -10,11 +11,7 @@ import { prettyObject } from "@/app/utils/format";
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/app/api/auth";
import { isModelAvailableInServer } from "@/app/utils/model";
import CryptoJS from "crypto-js";
import mapKeys from "lodash-es/mapKeys";
import mapValues from "lodash-es/mapValues";
import isArray from "lodash-es/isArray";
import isObject from "lodash-es/isObject";
import * as crypto from "node:crypto";

const serverConfig = getServerSideConfig();

Expand Down Expand Up @@ -47,27 +44,6 @@ async function handle(
export const GET = handle;
export const POST = handle;

export const runtime = "edge";
export const preferredRegion = [
"arn1",
"bom1",
"cdg1",
"cle1",
"cpt1",
"dub1",
"fra1",
"gru1",
"hnd1",
"iad1",
"icn1",
"kix1",
"lhr1",
"pdx1",
"sfo1",
"sin1",
"syd1",
];

async function request(req: NextRequest) {
const controller = new AbortController();

Expand Down Expand Up @@ -99,63 +75,22 @@ async function request(req: NextRequest) {

const fetchUrl = `${baseUrl}${path}`;

let body = null;
if (req.body) {
const bodyText = await req.text();
console.log(
"Dogtiti ~ request ~ capitalizeKeys(JSON.parse(bodyText):",
capitalizeKeys(JSON.parse(bodyText)),
);
body = JSON.stringify(capitalizeKeys(JSON.parse(bodyText)));
}

const body = await req.text();
const fetchOptions: RequestInit = {
headers: {
...getHeader(body),
},
method: req.method,
body: '{"Model":"hunyuan-pro","Messages":[{"Role":"user","Content":"你好"}]}', // FIXME
body,
redirect: "manual",
// @ts-ignore
duplex: "half",
signal: controller.signal,
};

// #1815 try to refuse some request to some models
if (serverConfig.customModels && req.body) {
try {
const clonedBody = await req.text();
fetchOptions.body = clonedBody;

const jsonBody = JSON.parse(clonedBody) as { model?: string };

// not undefined and is false
if (
isModelAvailableInServer(
serverConfig.customModels,
jsonBody?.model as string,
ServiceProvider.Tencent as string,
)
) {
return NextResponse.json(
{
error: true,
message: `you are not allowed to use ${jsonBody?.model} model`,
},
{
status: 403,
},
);
}
} catch (e) {
console.error(`[Tencent] filter`, e);
}
}
console.log("[Tencent request]", fetchOptions.headers, req.method);
try {
const res = await fetch(fetchUrl, fetchOptions);

console.log("[Tencent response]", res.status, " ", res.headers, res.url);
// to prevent browser prompt for credentials
const newHeaders = new Headers(res.headers);
newHeaders.delete("www-authenticate");
Expand All @@ -172,45 +107,16 @@ async function request(req: NextRequest) {
}
}

function capitalizeKeys(obj: any): any {
if (isArray(obj)) {
return obj.map(capitalizeKeys);
} else if (isObject(obj)) {
return mapValues(
mapKeys(
obj,
(value: any, key: string) => key.charAt(0).toUpperCase() + key.slice(1),
),
capitalizeKeys,
);
} else {
return obj;
}
}

// 使用 SHA-256 和 secret 进行 HMAC 加密
function sha256(message: any, secret = "", encoding = "hex") {
const hmac = CryptoJS.HmacSHA256(message, secret);
if (encoding === "hex") {
return hmac.toString(CryptoJS.enc.Hex);
} else if (encoding === "base64") {
return hmac.toString(CryptoJS.enc.Base64);
} else {
return hmac.toString();
}
function sha256(message: any, secret = "", encoding?: string) {
return crypto.createHmac("sha256", secret).update(message).digest(encoding);
}

// 使用 SHA-256 进行哈希
function getHash(message: any, encoding = "hex") {
const hash = CryptoJS.SHA256(message);
if (encoding === "hex") {
return hash.toString(CryptoJS.enc.Hex);
} else if (encoding === "base64") {
return hash.toString(CryptoJS.enc.Base64);
} else {
return hash.toString();
}
return crypto.createHash("sha256").update(message).digest(encoding);
}

function getDate(timestamp: number) {
const date = new Date(timestamp * 1000);
const year = date.getUTCFullYear();
Expand Down Expand Up @@ -238,10 +144,11 @@ function getHeader(payload: any) {

const hashedRequestPayload = getHash(payload);
const httpRequestMethod = "POST";
const contentType = "application/json";
const canonicalUri = "/";
const canonicalQueryString = "";
const canonicalHeaders =
"content-type:application/json; charset=utf-8\n" +
`content-type:${contentType}\n` +
"host:" +
endpoint +
"\n" +
Expand All @@ -250,18 +157,14 @@ function getHeader(payload: any) {
"\n";
const signedHeaders = "content-type;host;x-tc-action";

const canonicalRequest =
httpRequestMethod +
"\n" +
canonicalUri +
"\n" +
canonicalQueryString +
"\n" +
canonicalHeaders +
"\n" +
signedHeaders +
"\n" +
hashedRequestPayload;
const canonicalRequest = [
httpRequestMethod,
canonicalUri,
canonicalQueryString,
canonicalHeaders,
signedHeaders,
hashedRequestPayload,
].join("\n");

// ************* 步骤 2:拼接待签名字符串 *************
const algorithm = "TC3-HMAC-SHA256";
Expand Down Expand Up @@ -299,7 +202,7 @@ function getHeader(payload: any) {

return {
Authorization: authorization,
"Content-Type": "application/json; charset=utf-8",
"Content-Type": contentType,
Host: endpoint,
"X-TC-Action": action,
"X-TC-Timestamp": timestamp.toString(),
Expand Down
50 changes: 32 additions & 18 deletions app/client/platforms/tencent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import {
import { prettyObject } from "@/app/utils/format";
import { getClientConfig } from "@/app/config/client";
import { getMessageTextContent, isVisionModel } from "@/app/utils";
import mapKeys from "lodash-es/mapKeys";
import mapValues from "lodash-es/mapValues";
import isArray from "lodash-es/isArray";
import isObject from "lodash-es/isObject";

export interface OpenAIListModelResponse {
object: string;
Expand All @@ -33,17 +37,29 @@ export interface OpenAIListModelResponse {
}

interface RequestPayload {
messages: {
role: "system" | "user" | "assistant";
content: string | MultimodalContent[];
Messages: {
Role: "system" | "user" | "assistant";
Content: string | MultimodalContent[];
}[];
stream?: boolean;
model: string;
temperature: number;
presence_penalty: number;
frequency_penalty: number;
top_p: number;
max_tokens?: number;
Stream?: boolean;
Model: string;
Temperature: number;
TopP: number;
}

function capitalizeKeys(obj: any): any {
if (isArray(obj)) {
return obj.map(capitalizeKeys);
} else if (isObject(obj)) {
return mapValues(
mapKeys(obj, (value: any, key: string) =>
key.replace(/(^|_)(\w)/g, (m, $1, $2) => $2.toUpperCase()),
),
capitalizeKeys,
);
} else {
return obj;
}
}

export class HunyuanApi implements LLMApi {
Expand Down Expand Up @@ -76,7 +92,7 @@ export class HunyuanApi implements LLMApi {
}

extractMessage(res: any) {
return res.choices?.at(0)?.message?.content ?? "";
return res.Choices?.at(0)?.Message?.Content ?? "";
}

async chat(options: ChatOptions) {
Expand All @@ -94,15 +110,13 @@ export class HunyuanApi implements LLMApi {
},
};

const requestPayload: RequestPayload = {
const requestPayload: RequestPayload = capitalizeKeys({
messages,
stream: options.config.stream,
model: modelConfig.model,
temperature: modelConfig.temperature,
presence_penalty: modelConfig.presence_penalty,
frequency_penalty: modelConfig.frequency_penalty,
top_p: modelConfig.top_p,
};
});

console.log("[Request] Tencent payload: ", requestPayload);

Expand Down Expand Up @@ -213,10 +227,10 @@ export class HunyuanApi implements LLMApi {
const text = msg.data;
try {
const json = JSON.parse(text);
const choices = json.choices as Array<{
delta: { content: string };
const choices = json.Choices as Array<{
Delta: { Content: string };
}>;
const delta = choices[0]?.delta?.content;
const delta = choices[0]?.Delta?.Content;
if (delta) {
remainText += delta;
}
Expand Down
53 changes: 53 additions & 0 deletions app/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
Anthropic,
Azure,
Baidu,
Tencent,
ByteDance,
Alibaba,
Google,
Expand Down Expand Up @@ -964,6 +965,57 @@ export function Settings() {
</>
);

const tencentConfigComponent = accessStore.provider ===
ServiceProvider.Tencent && (
<>
<ListItem
title={Locale.Settings.Access.Tencent.Endpoint.Title}
subTitle={Locale.Settings.Access.Tencent.Endpoint.SubTitle}
>
<input
type="text"
value={accessStore.tencentUrl}
placeholder={Tencent.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.tencentUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Tencent.ApiKey.Title}
subTitle={Locale.Settings.Access.Tencent.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.tencentApiKey}
type="text"
placeholder={Locale.Settings.Access.Tencent.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.tencentApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Tencent.SecretKey.Title}
subTitle={Locale.Settings.Access.Tencent.SecretKey.SubTitle}
>
<PasswordInput
value={accessStore.tencentSecretKey}
type="text"
placeholder={Locale.Settings.Access.Tencent.SecretKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.tencentSecretKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);

const byteDanceConfigComponent = accessStore.provider ===
ServiceProvider.ByteDance && (
<>
Expand Down Expand Up @@ -1364,6 +1416,7 @@ export function Settings() {
{baiduConfigComponent}
{byteDanceConfigComponent}
{alibabaConfigComponent}
{tencentConfigComponent}
{stabilityConfigComponent}
</>
)}
Expand Down
16 changes: 16 additions & 0 deletions app/locales/cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,22 @@ const cn = {
SubTitle: "不支持自定义前往.env配置",
},
},
Tencent: {
ApiKey: {
Title: "API Key",
SubTitle: "使用自定义腾讯云API Key",
Placeholder: "Tencent API Key",
},
SecretKey: {
Title: "Secret Key",
SubTitle: "使用自定义腾讯云Secret Key",
Placeholder: "Tencent Secret Key",
},
Endpoint: {
Title: "接口地址",
SubTitle: "不支持自定义前往.env配置",
},
},
ByteDance: {
ApiKey: {
Title: "接口密钥",
Expand Down
Loading

0 comments on commit f85ec95

Please sign in to comment.