hins111 commited on
Commit
46fa70a
·
verified ·
1 Parent(s): 258d093

Rename main.ts to adapter.ts

Browse files
Files changed (2) hide show
  1. adapter.ts +255 -0
  2. main.ts +0 -171
adapter.ts ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // deno run --allow-net --allow-env adapter.ts
2
+
3
+ import { serve } from "https://deno.land/std@0.203.0/http/server.ts";
4
+
5
+ // --- Configuration from Environment Variables (Safer for deployment) ---
6
+
7
+ function getKeysFromEnv(envVarName: string): Set<string> {
8
+ const keysString = Deno.env.get(envVarName);
9
+ if (!keysString) {
10
+ console.warn(`Environment variable ${envVarName} is not set.`);
11
+ return new Set();
12
+ }
13
+ // Split by comma and trim whitespace, filter out empty strings
14
+ return new Set(keysString.split(',').map(k => k.trim()).filter(Boolean));
15
+ }
16
+
17
+ // Client keys will be read from Hugging Face Secrets
18
+ const CLIENT_API_KEYS = getKeysFromEnv("CLIENT_KEYS");
19
+
20
+ // CodeGeeX tokens will also be read from Hugging Face Secrets
21
+ const codegeeXTokensRaw = Array.from(getKeysFromEnv("CODEGEEX_KEYS"));
22
+
23
+ const CODEGEEX_TOKENS: {
24
+ token: string;
25
+ isValid: boolean;
26
+ lastUsed: number;
27
+ errorCount: number;
28
+ }[] = codegeeXTokensRaw.map(token => ({
29
+ token: token,
30
+ isValid: true,
31
+ lastUsed: 0,
32
+ errorCount: 0
33
+ }));
34
+
35
+
36
+ const MAX_ERROR_COUNT = 3;
37
+ const ERROR_COOLDOWN = 300 * 1000; // ms
38
+
39
+ // --- Utilities ---
40
+ function now(): number {
41
+ return Date.now();
42
+ }
43
+
44
+ function rotateToken(): typeof CODEGEEX_TOKENS[0] | null {
45
+ if (CODEGEEX_TOKENS.length === 0) {
46
+ console.error("CODEGEEX_TOKENS array is empty. Check your CODEGEEX_KEYS secret.");
47
+ return null;
48
+ }
49
+
50
+ const available = CODEGEEX_TOKENS.filter(t => {
51
+ if (!t.isValid) return false;
52
+ if (t.errorCount >= MAX_ERROR_COUNT && now() - t.lastUsed < ERROR_COOLDOWN) return false;
53
+ return true;
54
+ });
55
+ if (available.length === 0) return null;
56
+
57
+ // reset cooled-down tokens
58
+ for (const t of available) {
59
+ if (t.errorCount >= MAX_ERROR_COUNT && now() - t.lastUsed >= ERROR_COOLDOWN) {
60
+ t.errorCount = 0;
61
+ }
62
+ }
63
+
64
+ // pick the one least recently used, then lowest errorCount
65
+ available.sort((a, b) => a.lastUsed - b.lastUsed || a.errorCount - b.errorCount);
66
+ const tok = available[0];
67
+ tok.lastUsed = now();
68
+ return tok;
69
+ }
70
+
71
+ // This function translates the OpenAI format to CodeGeeX format
72
+ function convertToCodeGeeXPayload(params: { model: string; messages: any[] }) {
73
+ // CodeGeeX seems to use the last message's content as the main prompt.
74
+ // The history part is more complex, here we simplify it.
75
+ const lastMessage = params.messages.slice(-1)[0];
76
+ const history = params.messages.slice(0, -1)
77
+ .filter(msg => msg.role === 'user' || msg.role === 'assistant')
78
+ .map(msg => ({
79
+ role: msg.role,
80
+ content: msg.content
81
+ }));
82
+
83
+ return {
84
+ user_role: 0, // This seems to be a fixed value
85
+ ide: "HuggingFace", // Let's identify the source
86
+ prompt: lastMessage?.content || "",
87
+ history: history, // Passing a simplified history
88
+ model: params.model,
89
+ };
90
+ }
91
+
92
+
93
+ async function proxyChat(req: Request, params: { stream: boolean; model: string; messages: any[] }) {
94
+ const tokenObj = rotateToken();
95
+ if (!tokenObj) {
96
+ return new Response(JSON.stringify({ error: { message: "No valid CodeGeeX tokens available", type: "server_error" } }), { status: 503, headers: { "Content-Type": "application/json" }});
97
+ }
98
+
99
+ const payload = convertToCodeGeeXPayload(params);
100
+
101
+ try {
102
+ const response = await fetch("https://codegeex.cn/prod/code/chatCodeSseV3/chat", {
103
+ method: "POST",
104
+ headers: {
105
+ "Content-Type": "application/json",
106
+ "Accept": "text/event-stream",
107
+ "code-token": tokenObj.token,
108
+ },
109
+ body: JSON.stringify(payload),
110
+ });
111
+
112
+ if (!response.ok) {
113
+ console.error(`Upstream error from CodeGeeX: ${response.status}`);
114
+ if (response.status === 401 || response.status === 403) {
115
+ tokenObj.isValid = false;
116
+ console.warn(`Token ${tokenObj.token.substring(0, 15)}... marked as invalid due to 401/403 error.`);
117
+ } else {
118
+ tokenObj.errorCount++;
119
+ console.warn(`Token ${tokenObj.token.substring(0, 15)}... error count increased to ${tokenObj.errorCount}.`);
120
+ }
121
+ const errorBody = await response.text();
122
+ return new Response(JSON.stringify({ error: { message: `Upstream error ${response.status}: ${errorBody}`, type: "upstream_error" } }), { status: 502, headers: { "Content-Type": "application/json" }});
123
+ }
124
+
125
+ // For stream, we must transform the raw CodeGeeX SSE to OpenAI format
126
+ if (params.stream) {
127
+ const { readable, writable } = new TransformStream();
128
+ const writer = writable.getWriter();
129
+ const encoder = new TextEncoder();
130
+
131
+ // This function processes the stream from CodeGeeX and sends OpenAI compatible chunks
132
+ (async () => {
133
+ const reader = response.body?.getReader();
134
+ if (!reader) {
135
+ await writer.close();
136
+ return;
137
+ }
138
+ const decoder = new TextDecoder();
139
+ const completionId = `chatcmpl-${crypto.randomUUID()}`;
140
+ const creationTime = Math.floor(now() / 1000);
141
+
142
+ try {
143
+ while(true) {
144
+ const { done, value } = await reader.read();
145
+ if (done) break;
146
+
147
+ const chunkText = decoder.decode(value);
148
+ // A simple transformation: assume the raw chunk is the content delta
149
+ const openAIChunk = {
150
+ id: completionId,
151
+ object: "chat.completion.chunk",
152
+ created: creationTime,
153
+ model: params.model,
154
+ choices: [{ delta: { content: chunkText }, index: 0, finish_reason: null }]
155
+ };
156
+ await writer.write(encoder.encode(`data: ${JSON.stringify(openAIChunk)}\n\n`));
157
+ }
158
+ // Send the final DONE chunk
159
+ await writer.write(encoder.encode(`data: [DONE]\n\n`));
160
+ } catch (e) {
161
+ console.error("Error while transforming stream:", e);
162
+ } finally {
163
+ await writer.close();
164
+ }
165
+ })();
166
+
167
+ return new Response(readable, {
168
+ status: 200,
169
+ headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive" },
170
+ });
171
+ } else {
172
+ // accumulate and return JSON
173
+ const text = await response.text();
174
+ return new Response(JSON.stringify({
175
+ id: `chatcmpl-${crypto.randomUUID()}`,
176
+ object: "chat.completion",
177
+ created: Math.floor(now() / 1000),
178
+ model: params.model,
179
+ choices: [{ message: { role: "assistant", content: text }, index: 0, finish_reason: "stop" }],
180
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } // Placeholder usage
181
+ }), {
182
+ status: 200,
183
+ headers: { "Content-Type": "application/json" },
184
+ });
185
+ }
186
+ } catch (err) {
187
+ tokenObj.errorCount++;
188
+ console.error("Fetch to CodeGeeX failed:", err);
189
+ return new Response(JSON.stringify({ error: { message: err.message, type: "server_error" } }), { status: 500, headers: { "Content-Type": "application/json" }});
190
+ }
191
+ }
192
+
193
+ // --- Main Handler ---
194
+ async function handler(req: Request): Promise<Response> {
195
+ const url = new URL(req.url);
196
+ console.log(`Received request: ${req.method} ${url.pathname}`);
197
+
198
+ // CORS preflight request handler for web clients
199
+ if (req.method === 'OPTIONS') {
200
+ return new Response(null, {
201
+ status: 204,
202
+ headers: {
203
+ 'Access-Control-Allow-Origin': '*',
204
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
205
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
206
+ },
207
+ });
208
+ }
209
+
210
+ // Authentication middleware
211
+ const auth = req.headers.get("Authorization")?.replace(/^Bearer\s+/, "");
212
+ if (CLIENT_API_KEYS.size === 0) {
213
+ console.error("Server misconfigured: CLIENT_KEYS secret is not set or empty.");
214
+ return new Response(JSON.stringify({ error: { message: "Server misconfigured: no client keys", type: "server_error" }}), { status: 503, headers: { "Content-Type": "application/json" }});
215
+ }
216
+ if (!auth || !CLIENT_API_KEYS.has(auth)) {
217
+ return new Response(JSON.stringify({ error: { message: "Invalid or missing API key", type: "auth_error" }}), {
218
+ status: 401,
219
+ headers: { "WWW-Authenticate": "Bearer", "Content-Type": "application/json" },
220
+ });
221
+ }
222
+
223
+ // GET /v1/models
224
+ if (url.pathname === "/v1/models" && req.method === "GET") {
225
+ const modelData = [
226
+ { id: "codegeex-4", object: "model", created: Math.floor(now() / 1000), owned_by: "codegeex" },
227
+ { id: "codegeex-pro", object: "model", created: Math.floor(now() / 1000), owned_by: "codegeex" }
228
+ ];
229
+ return new Response(JSON.stringify({ object: "list", data: modelData }), {
230
+ headers: { "Content-Type": "application/json" },
231
+ });
232
+ }
233
+
234
+ // POST /v1/chat/completions
235
+ if (url.pathname === "/v1/chat/completions" && req.method === "POST") {
236
+ try {
237
+ const body = await req.json();
238
+ const { model, messages, stream = true } = body;
239
+ if (!model || !Array.isArray(messages) || messages.length === 0) {
240
+ return new Response(JSON.stringify({ error: { message: "Bad Request: 'model' and 'messages' are required.", type: "invalid_request_error" } }), { status: 400, headers: { "Content-Type": "application/json" }});
241
+ }
242
+ return proxyChat(req, { model, messages, stream });
243
+ } catch (e) {
244
+ return new Response(JSON.stringify({ error: { message: "Invalid JSON body.", type: "invalid_request_error" } }), { status: 400, headers: { "Content-Type": "application/json" }});
245
+ }
246
+ }
247
+
248
+ // Not found
249
+ return new Response(JSON.stringify({ error: "Not Found" }), { status: 404, headers: { "Content-Type": "application/json" }});
250
+ }
251
+
252
+ // --- Start Server ---
253
+ const PORT = 7860; // Use the standard port for Hugging Face Spaces
254
+ console.log(`Starting Deno CodeGeeX Adapter on http://0.0.0.0:${PORT}`);
255
+ serve(handler, { port: PORT });
main.ts DELETED
@@ -1,171 +0,0 @@
1
- // deno run --allow-net --allow-env adapter.ts
2
-
3
- import { serve } from "https://deno.land/std@0.203.0/http/server.ts";
4
-
5
- // --- Hardcoded API Keys ---
6
- const CLIENT_API_KEYS = new Set([
7
- "sk-client-1234567890abcdef",
8
- // add more client keys here
9
- ]);
10
-
11
- const CODEGEEX_TOKENS: {
12
- token: string;
13
- isValid: boolean;
14
- lastUsed: number;
15
- errorCount: number;
16
- }[] = [
17
- { token: "cgx-token-AAAA", isValid: true, lastUsed: 0, errorCount: 0 },
18
- { token: "cgx-token-BBBB", isValid: true, lastUsed: 0, errorCount: 0 },
19
- // add more CodeGeeX tokens here
20
- ];
21
-
22
- const MAX_ERROR_COUNT = 3;
23
- const ERROR_COOLDOWN = 300 * 1000; // ms
24
-
25
- // --- Utilities ---
26
- function now(): number {
27
- return Date.now();
28
- }
29
-
30
- function rotateToken(): typeof CODEGEEX_TOKENS[0] | null {
31
- const available = CODEGEEX_TOKENS.filter(t => {
32
- if (!t.isValid) return false;
33
- if (t.errorCount >= MAX_ERROR_COUNT && now() - t.lastUsed < ERROR_COOLDOWN) return false;
34
- return true;
35
- });
36
- if (available.length === 0) return null;
37
-
38
- // reset cooled-down tokens
39
- for (const t of available) {
40
- if (t.errorCount >= MAX_ERROR_COUNT && now() - t.lastUsed >= ERROR_COOLDOWN) {
41
- t.errorCount = 0;
42
- }
43
- }
44
-
45
- // pick the one least recently used, then lowest errorCount
46
- available.sort((a, b) => a.lastUsed - b.lastUsed || a.errorCount - b.errorCount);
47
- const tok = available[0];
48
- tok.lastUsed = now();
49
- return tok;
50
- }
51
-
52
- async function proxyChat(req: Request, params: { stream: boolean; model: string; messages: any[] }) {
53
- const tokenObj = rotateToken();
54
- if (!tokenObj) {
55
- return new Response(JSON.stringify({ error: "No valid CodeGeeX tokens available" }), { status: 503 });
56
- }
57
-
58
- const payload = {
59
- user_role: 0,
60
- ide: "Deno",
61
- prompt: params.messages.slice(-1)[0]?.content || "",
62
- history: [], // Simplified: not preserving history in this example
63
- model: params.model,
64
- };
65
-
66
- try {
67
- const response = await fetch("https://codegeex.cn/prod/code/chatCodeSseV3/chat", {
68
- method: "POST",
69
- headers: {
70
- "Content-Type": "application/json",
71
- "Accept": "text/event-stream",
72
- "code-token": tokenObj.token,
73
- },
74
- body: JSON.stringify(payload),
75
- });
76
- if (!response.ok) {
77
- if (response.status === 401 || response.status === 403) {
78
- tokenObj.isValid = false;
79
- } else {
80
- tokenObj.errorCount++;
81
- }
82
- return new Response(JSON.stringify({ error: `Upstream error ${response.status}` }), { status: 502 });
83
- }
84
-
85
- if (params.stream) {
86
- // proxy SSE
87
- return new Response(response.body, {
88
- status: 200,
89
- headers: {
90
- "Content-Type": "text/event-stream",
91
- "Cache-Control": "no-cache",
92
- Connection: "keep-alive",
93
- },
94
- });
95
- } else {
96
- // accumulate and return JSON
97
- const text = await response.text();
98
- return new Response(JSON.stringify({
99
- id: `chatcmpl-${crypto.randomUUID()}`,
100
- object: "chat.completion",
101
- created: Math.floor(now() / 1000),
102
- model: params.model,
103
- choices: [{ message: { role: "assistant", content: text }, index: 0, finish_reason: "stop" }],
104
- }), {
105
- status: 200,
106
- headers: { "Content-Type": "application/json" },
107
- });
108
- }
109
- } catch (err) {
110
- tokenObj.errorCount++;
111
- return new Response(JSON.stringify({ error: err.message }), { status: 500 });
112
- }
113
- }
114
-
115
- // --- Handlers ---
116
- async function handler(req: Request): Promise<Response> {
117
- const url = new URL(req.url);
118
-
119
- // /debug?enable=true|false
120
- if (url.pathname === "/debug" && req.method === "GET") {
121
- const en = url.searchParams.get("enable");
122
- if (en !== null) {
123
- Deno.env.set("DEBUG_MODE", en);
124
- }
125
- return new Response(JSON.stringify({ debug_mode: Deno.env.get("DEBUG_MODE") === "true" }), {
126
- headers: { "Content-Type": "application/json" },
127
- });
128
- }
129
-
130
- // Authentication middleware
131
- const auth = req.headers.get("Authorization")?.replace(/^Bearer\s+/, "");
132
- if (!CLIENT_API_KEYS.size) {
133
- return new Response(JSON.stringify({ error: "Server misconfigured: no client keys" }), { status: 503 });
134
- }
135
- if (!auth || !CLIENT_API_KEYS.has(auth)) {
136
- return new Response(JSON.stringify({ error: "Invalid or missing API key" }), {
137
- status: 401,
138
- headers: { "WWW-Authenticate": "Bearer" },
139
- });
140
- }
141
-
142
- // GET /v1/models and GET /models
143
- if ((url.pathname === "/v1/models" || url.pathname === "/models") && req.method === "GET") {
144
- const data = CODEGEEX_TOKENS.map((_, i) => ({
145
- id: [`claude-3-7-sonnet`, `claude-sonnet-4`][i % 2],
146
- owned_by: "anthropic",
147
- created: Math.floor(now() / 1000),
148
- object: "model",
149
- }));
150
- return new Response(JSON.stringify({ object: "list", data }), {
151
- headers: { "Content-Type": "application/json" },
152
- });
153
- }
154
-
155
- // POST /v1/chat/completions
156
- if (url.pathname === "/v1/chat/completions" && req.method === "POST") {
157
- const body = await req.json();
158
- const { model, messages, stream = true } = body;
159
- if (!model || !Array.isArray(messages)) {
160
- return new Response(JSON.stringify({ error: "Bad Request" }), { status: 400 });
161
- }
162
- return proxyChat(req, { model, messages, stream });
163
- }
164
-
165
- // Not found
166
- return new Response(JSON.stringify({ error: "Not Found" }), { status: 404 });
167
- }
168
-
169
- // --- Start Server ---
170
- console.log("Starting Deno CodeGeeX Adapter on http://0.0.0.0:8000");
171
- serve(handler, { port: 8000 });