@8-/gemini-web-api 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -24
- package/index.js +62 -876
- package/package.json +4 -2
- package/src/constant.js +89 -0
- package/src/cookieRead.js +65 -0
- package/src/modelDiscover.js +278 -0
- package/src/payloadBuild.js +115 -0
- package/src/router.js +302 -0
- package/src/serverStart.js +120 -0
- package/src/sessionState.js +70 -0
- package/src/sseResponse.js +166 -0
- package/src/streamExtract.js +75 -0
- package/src/toolHandle.js +332 -0
- package/test/modelDiscover.test.js +105 -0
- package/test/toolHandle.test.js +243 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@8-/gemini-web-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "OpenAI-compatible Gemini Web API reverse proxy running seamlessly on both Bun and Node.js with zero external dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "bun index.js",
|
|
12
|
-
"start:node": "node index.js"
|
|
12
|
+
"start:node": "node index.js",
|
|
13
|
+
"test": "node --test",
|
|
14
|
+
"test:bun": "bun test"
|
|
13
15
|
},
|
|
14
16
|
"repository": {
|
|
15
17
|
"type": "git",
|
package/src/constant.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export const HOST = process.env.HOST ?? "0.0.0.0",
|
|
5
|
+
PORT = parseInt(process.env.PORT ?? "7860", 10),
|
|
6
|
+
API_KEY = process.env.API_KEY ?? "",
|
|
7
|
+
ENABLE_THINKING = process.env.ENABLE_THINKING === "true",
|
|
8
|
+
INIT_URL = "https://gemini.google.com/app",
|
|
9
|
+
GEN_URL =
|
|
10
|
+
"https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate",
|
|
11
|
+
BATCH_URL = "https://gemini.google.com/_/BardChatUi/data/batchexecute",
|
|
12
|
+
USER_AGENT =
|
|
13
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
|
|
14
|
+
SALT = new TextEncoder().encode("saltysalt"),
|
|
15
|
+
IV = new Uint8Array(16).fill(32),
|
|
16
|
+
CORS_HEADERS = {
|
|
17
|
+
"Access-Control-Allow-Origin": "*",
|
|
18
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
19
|
+
"Access-Control-Allow-Headers": "*",
|
|
20
|
+
},
|
|
21
|
+
STATUS_OK = 200,
|
|
22
|
+
STATUS_NO_CONTENT = 204,
|
|
23
|
+
STATUS_BAD_REQUEST = 400,
|
|
24
|
+
STATUS_UNAUTHORIZED = 401,
|
|
25
|
+
STATUS_NOT_FOUND = 404,
|
|
26
|
+
STATUS_SERVER_ERR = 500,
|
|
27
|
+
ERR_MISSING_URL = 4001,
|
|
28
|
+
ERR_UNAUTHORIZED = 4011,
|
|
29
|
+
ERR_NOT_FOUND = 4041,
|
|
30
|
+
THINKING_LEVEL_DISABLED = 1,
|
|
31
|
+
THINKING_LEVEL_ENABLED = 2,
|
|
32
|
+
ROLE_ASSISTANT = "assistant",
|
|
33
|
+
ROLE_USER = "user",
|
|
34
|
+
ROLE_SYSTEM = "system",
|
|
35
|
+
ROLE_TOOL = "tool",
|
|
36
|
+
STOP_REASON_STOP = "stop",
|
|
37
|
+
STOP_REASON_TOOL_CALLS = "tool_calls",
|
|
38
|
+
TOOL_FENCE_OPEN = "```tool_call",
|
|
39
|
+
TOOL_FENCE_CLOSE = "```",
|
|
40
|
+
PAYLOAD_INNER_REQ_IDX = 0,
|
|
41
|
+
PAYLOAD_MODEL_HEADER_IDX = 1,
|
|
42
|
+
PAYLOAD_UUID_IDX = 2,
|
|
43
|
+
BROWSER_PATH_LI = [
|
|
44
|
+
{
|
|
45
|
+
db_path: join(homedir(), "Library/Application Support/Google/Chrome/Default/Cookies"),
|
|
46
|
+
account: "Chrome",
|
|
47
|
+
service: "Chrome Safe Storage",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
db_path: join(homedir(), "Library/Application Support/Google/Chrome/Profile 1/Cookies"),
|
|
51
|
+
account: "Chrome",
|
|
52
|
+
service: "Chrome Safe Storage",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
db_path: join(
|
|
56
|
+
homedir(),
|
|
57
|
+
"Library/Application Support/BraveSoftware/Brave-Browser/Default/Cookies",
|
|
58
|
+
),
|
|
59
|
+
account: "Brave",
|
|
60
|
+
service: "Brave Safe Storage",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
db_path: join(homedir(), "Library/Application Support/Microsoft Edge/Default/Cookies"),
|
|
64
|
+
account: "Microsoft Edge",
|
|
65
|
+
service: "Microsoft Edge Safe Storage",
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
DEFAULT_MODEL_LI = [
|
|
69
|
+
{
|
|
70
|
+
id: "gemini-flash",
|
|
71
|
+
model_id: "56fdd199312815e2",
|
|
72
|
+
disp: "Flash",
|
|
73
|
+
cat: "Flash",
|
|
74
|
+
desc: "Default Flash",
|
|
75
|
+
num: 1,
|
|
76
|
+
capacity: 1,
|
|
77
|
+
alias_li: ["gemini-flash", "flash", "default"],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "gemini-pro",
|
|
81
|
+
model_id: "797f3d0293f288ad",
|
|
82
|
+
disp: "Pro",
|
|
83
|
+
cat: "Pro",
|
|
84
|
+
desc: "Default Pro",
|
|
85
|
+
num: 3,
|
|
86
|
+
capacity: 1,
|
|
87
|
+
alias_li: ["gemini-pro", "pro"],
|
|
88
|
+
},
|
|
89
|
+
];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
import { BROWSER_PATH_LI, IV, SALT } from "./constant.js";
|
|
5
|
+
|
|
6
|
+
export const keychainPwdRead = (account, service) => {
|
|
7
|
+
const proc = spawnSync(
|
|
8
|
+
"/usr/bin/security",
|
|
9
|
+
["-q", "find-generic-password", "-w", "-a", account, "-s", service],
|
|
10
|
+
{ encoding: "utf8" },
|
|
11
|
+
);
|
|
12
|
+
return (proc.stdout ?? "").trim();
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
valDecrypt = async (enc_data, aes_key, has_integrity) => {
|
|
16
|
+
const data = enc_data.slice(3),
|
|
17
|
+
dec = await crypto.subtle.decrypt({ name: "AES-CBC", iv: IV }, aes_key, data);
|
|
18
|
+
let byte_li = new Uint8Array(dec);
|
|
19
|
+
if (has_integrity) byte_li = byte_li.slice(32);
|
|
20
|
+
return new TextDecoder().decode(byte_li);
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
cookieRead = async () => {
|
|
24
|
+
if (process.env.SECURE_1PSID && process.env.SECURE_1PSIDTS) {
|
|
25
|
+
return (
|
|
26
|
+
"__Secure-1PSID=" +
|
|
27
|
+
process.env.SECURE_1PSID +
|
|
28
|
+
"; __Secure-1PSIDTS=" +
|
|
29
|
+
process.env.SECURE_1PSIDTS
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const target = BROWSER_PATH_LI.find((browser_cfg) => existsSync(browser_cfg.db_path));
|
|
33
|
+
if (!target) return "";
|
|
34
|
+
const pwd = keychainPwdRead(target.account, target.service),
|
|
35
|
+
key_mat = await crypto.subtle.importKey("raw", new TextEncoder().encode(pwd), "PBKDF2", false, [
|
|
36
|
+
"deriveKey",
|
|
37
|
+
]),
|
|
38
|
+
aes_key = await crypto.subtle.deriveKey(
|
|
39
|
+
{ name: "PBKDF2", salt: SALT, iterations: 1003, hash: "SHA-1" },
|
|
40
|
+
key_mat,
|
|
41
|
+
{ name: "AES-CBC", length: 128 },
|
|
42
|
+
false,
|
|
43
|
+
["decrypt"],
|
|
44
|
+
),
|
|
45
|
+
db = new DatabaseSync(target.db_path, { readOnly: true }),
|
|
46
|
+
version_row = db.prepare("SELECT value FROM meta WHERE key = 'version'").get(),
|
|
47
|
+
has_integrity = parseInt(version_row?.value ?? "0", 10) >= 24,
|
|
48
|
+
row_li = db
|
|
49
|
+
.prepare(
|
|
50
|
+
"SELECT name, encrypted_value FROM cookies WHERE host_key LIKE '%google.com%' AND name IN ('__Secure-1PSID', '__Secure-1PSIDTS')",
|
|
51
|
+
)
|
|
52
|
+
.all(),
|
|
53
|
+
cookie_map = {};
|
|
54
|
+
|
|
55
|
+
for (const { name, encrypted_value } of row_li) {
|
|
56
|
+
if (encrypted_value && encrypted_value.length > 3) {
|
|
57
|
+
const val = await valDecrypt(encrypted_value, aes_key, has_integrity);
|
|
58
|
+
cookie_map[name] = val;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
db.close();
|
|
62
|
+
return Object.entries(cookie_map)
|
|
63
|
+
.map(([cookie_name, cookie_val]) => cookie_name + "=" + cookie_val)
|
|
64
|
+
.join("; ");
|
|
65
|
+
};
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { BATCH_URL, DEFAULT_MODEL_LI, USER_AGENT } from "./constant.js";
|
|
2
|
+
import { session_state } from "./sessionState.js";
|
|
3
|
+
|
|
4
|
+
export const versionCompare = (a_ver, b_ver) => {
|
|
5
|
+
const a_match = (a_ver ?? "").match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/),
|
|
6
|
+
b_match = (b_ver ?? "").match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/),
|
|
7
|
+
a_maj = a_match ? parseInt(a_match[1], 10) : 0,
|
|
8
|
+
a_min = a_match && a_match[2] ? parseInt(a_match[2], 10) : 0,
|
|
9
|
+
a_pat = a_match && a_match[3] ? parseInt(a_match[3], 10) : 0,
|
|
10
|
+
b_maj = b_match ? parseInt(b_match[1], 10) : 0,
|
|
11
|
+
b_min = b_match && b_match[2] ? parseInt(b_match[2], 10) : 0,
|
|
12
|
+
b_pat = b_match && b_match[3] ? parseInt(b_match[3], 10) : 0;
|
|
13
|
+
if (a_maj !== b_maj) return a_maj - b_maj;
|
|
14
|
+
if (a_min !== b_min) return a_min - b_min;
|
|
15
|
+
return a_pat - b_pat;
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
stringVisualWidth = (str) => {
|
|
19
|
+
let width = 0;
|
|
20
|
+
for (const ch of str ?? "") {
|
|
21
|
+
const code = ch.codePointAt(0);
|
|
22
|
+
if (
|
|
23
|
+
(code >= 0x1100 && code <= 0x115f) ||
|
|
24
|
+
(code >= 0x2e80 && code <= 0xa4cf) ||
|
|
25
|
+
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
26
|
+
(code >= 0xf900 && code <= 0xfaff) ||
|
|
27
|
+
(code >= 0xfe10 && code <= 0xfe19) ||
|
|
28
|
+
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
29
|
+
(code >= 0xff00 && code <= 0xff60) ||
|
|
30
|
+
(code >= 0xffe0 && code <= 0xffe6)
|
|
31
|
+
) {
|
|
32
|
+
width += 2;
|
|
33
|
+
} else {
|
|
34
|
+
width += 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return width;
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
stringPadEnd = (str, target_width) => {
|
|
41
|
+
const current_width = stringVisualWidth(str);
|
|
42
|
+
return str + " ".repeat(Math.max(0, target_width - current_width));
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
modelListFormat = (model_li = null, default_model = null) => {
|
|
46
|
+
const target_model_li =
|
|
47
|
+
model_li && model_li.length > 0
|
|
48
|
+
? model_li
|
|
49
|
+
: session_state.model_li.length > 0
|
|
50
|
+
? session_state.model_li
|
|
51
|
+
: DEFAULT_MODEL_LI,
|
|
52
|
+
target_default = default_model ?? session_state.default_model ?? target_model_li[0],
|
|
53
|
+
sorted_model_li = [...target_model_li].sort((a, b) => {
|
|
54
|
+
if (a.id === target_default?.id) return -1;
|
|
55
|
+
if (b.id === target_default?.id) return 1;
|
|
56
|
+
return 0;
|
|
57
|
+
}),
|
|
58
|
+
is_tty = Boolean(process.stdout?.isTTY),
|
|
59
|
+
color = {
|
|
60
|
+
reset: is_tty ? "\x1b[0m" : "",
|
|
61
|
+
bold: is_tty ? "\x1b[1m" : "",
|
|
62
|
+
dim: is_tty ? "\x1b[2m" : "",
|
|
63
|
+
cyan: is_tty ? "\x1b[36m" : "",
|
|
64
|
+
green: is_tty ? "\x1b[32m" : "",
|
|
65
|
+
yellow: is_tty ? "\x1b[33m" : "",
|
|
66
|
+
gray: is_tty ? "\x1b[90m" : "",
|
|
67
|
+
},
|
|
68
|
+
headers = ["模型 ID (Model ID)", "显示名称 (Name)", "别名 (Aliases)"],
|
|
69
|
+
raw_rows = sorted_model_li.map((item) => {
|
|
70
|
+
const is_def = item.id === target_default?.id,
|
|
71
|
+
clean_aliases = (item.alias_li ?? [])
|
|
72
|
+
.filter((a) => a && a !== item.id && !/^[0-9a-f]{16}$/i.test(a))
|
|
73
|
+
.filter((a, idx, arr) => arr.indexOf(a) === idx)
|
|
74
|
+
.slice(0, 5)
|
|
75
|
+
.join(", ");
|
|
76
|
+
return [
|
|
77
|
+
(is_def ? "★ " : " ") + item.id + (is_def ? " (默认)" : ""),
|
|
78
|
+
item.disp ?? item.id,
|
|
79
|
+
clean_aliases || "-",
|
|
80
|
+
];
|
|
81
|
+
}),
|
|
82
|
+
col_widths = headers.map((h, i) =>
|
|
83
|
+
Math.max(stringVisualWidth(h), ...raw_rows.map((r) => stringVisualWidth(r[i]))),
|
|
84
|
+
),
|
|
85
|
+
border_color = color.gray,
|
|
86
|
+
top_border =
|
|
87
|
+
border_color +
|
|
88
|
+
"┌─" +
|
|
89
|
+
col_widths.map((w) => "─".repeat(w)).join("─┬─") +
|
|
90
|
+
"─┐" +
|
|
91
|
+
color.reset,
|
|
92
|
+
mid_border =
|
|
93
|
+
border_color +
|
|
94
|
+
"├─" +
|
|
95
|
+
col_widths.map((w) => "─".repeat(w)).join("─┼─") +
|
|
96
|
+
"─┤" +
|
|
97
|
+
color.reset,
|
|
98
|
+
bot_border =
|
|
99
|
+
border_color +
|
|
100
|
+
"└─" +
|
|
101
|
+
col_widths.map((w) => "─".repeat(w)).join("─┴─") +
|
|
102
|
+
"─┘" +
|
|
103
|
+
color.reset,
|
|
104
|
+
header_line =
|
|
105
|
+
border_color +
|
|
106
|
+
"│ " +
|
|
107
|
+
headers
|
|
108
|
+
.map((h, i) => color.bold + color.cyan + stringPadEnd(h, col_widths[i]) + color.reset)
|
|
109
|
+
.join(border_color + " │ ") +
|
|
110
|
+
border_color +
|
|
111
|
+
" │" +
|
|
112
|
+
color.reset,
|
|
113
|
+
row_lines = raw_rows.map((r, idx) => {
|
|
114
|
+
const item = sorted_model_li[idx],
|
|
115
|
+
is_def = item.id === target_default?.id,
|
|
116
|
+
c0_pad = stringPadEnd(r[0], col_widths[0]),
|
|
117
|
+
c1_pad = stringPadEnd(r[1], col_widths[1]),
|
|
118
|
+
c2_pad = stringPadEnd(r[2], col_widths[2]),
|
|
119
|
+
c0 = is_def
|
|
120
|
+
? color.green + color.bold + c0_pad + color.reset
|
|
121
|
+
: color.yellow + c0_pad + color.reset,
|
|
122
|
+
c1 = color.bold + c1_pad + color.reset,
|
|
123
|
+
c2 = color.dim + c2_pad + color.reset;
|
|
124
|
+
return (
|
|
125
|
+
border_color +
|
|
126
|
+
"│ " +
|
|
127
|
+
c0 +
|
|
128
|
+
border_color +
|
|
129
|
+
" │ " +
|
|
130
|
+
c1 +
|
|
131
|
+
border_color +
|
|
132
|
+
" │ " +
|
|
133
|
+
c2 +
|
|
134
|
+
border_color +
|
|
135
|
+
" │" +
|
|
136
|
+
color.reset
|
|
137
|
+
);
|
|
138
|
+
}),
|
|
139
|
+
output_parts = [
|
|
140
|
+
color.bold + color.cyan + "支持的模型列表 (Supported Models):" + color.reset,
|
|
141
|
+
top_border,
|
|
142
|
+
header_line,
|
|
143
|
+
mid_border,
|
|
144
|
+
...row_lines,
|
|
145
|
+
bot_border,
|
|
146
|
+
color.dim + " ★ = 默认模型 | 支持在请求中传入模型 ID 或任意别名" + color.reset,
|
|
147
|
+
];
|
|
148
|
+
return output_parts.join("\n");
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
modelListPrint = (model_li = null, default_model = null) => {
|
|
152
|
+
console.log(modelListFormat(model_li, default_model));
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
modelFetch = async (at, bl, fsid, cookie_header) => {
|
|
156
|
+
++session_state.req_id;
|
|
157
|
+
const session_id = crypto.randomUUID().toUpperCase(),
|
|
158
|
+
batch_headers = {
|
|
159
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
|
160
|
+
Origin: "https://gemini.google.com",
|
|
161
|
+
Referer: "https://gemini.google.com/",
|
|
162
|
+
"X-Same-Domain": "1",
|
|
163
|
+
"User-Agent": USER_AGENT,
|
|
164
|
+
Cookie: cookie_header,
|
|
165
|
+
"x-goog-ext-525001261-jspb": JSON.stringify([
|
|
166
|
+
1,
|
|
167
|
+
null,
|
|
168
|
+
null,
|
|
169
|
+
null,
|
|
170
|
+
null,
|
|
171
|
+
null,
|
|
172
|
+
null,
|
|
173
|
+
null,
|
|
174
|
+
[4, 5, 6, 8],
|
|
175
|
+
null,
|
|
176
|
+
null,
|
|
177
|
+
null,
|
|
178
|
+
null,
|
|
179
|
+
null,
|
|
180
|
+
null,
|
|
181
|
+
null,
|
|
182
|
+
session_id,
|
|
183
|
+
]),
|
|
184
|
+
"x-goog-ext-73010989-jspb": "[0]",
|
|
185
|
+
},
|
|
186
|
+
payload = [[["otAQ7b", "[]", null, "generic"]]],
|
|
187
|
+
form_data = new URLSearchParams(),
|
|
188
|
+
batch_url = new URL(BATCH_URL),
|
|
189
|
+
discovered_li = [];
|
|
190
|
+
|
|
191
|
+
form_data.set("at", at);
|
|
192
|
+
form_data.set("f.req", JSON.stringify(payload));
|
|
193
|
+
|
|
194
|
+
batch_url.searchParams.set("rpcids", "otAQ7b");
|
|
195
|
+
batch_url.searchParams.set("hl", "en");
|
|
196
|
+
batch_url.searchParams.set("_reqid", String(session_state.req_id));
|
|
197
|
+
batch_url.searchParams.set("rt", "c");
|
|
198
|
+
batch_url.searchParams.set("source-path", "/app");
|
|
199
|
+
if (bl) batch_url.searchParams.set("bl", bl);
|
|
200
|
+
if (fsid) batch_url.searchParams.set("f.sid", fsid);
|
|
201
|
+
|
|
202
|
+
const res = await fetch(batch_url.toString(), {
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: batch_headers,
|
|
205
|
+
body: form_data.toString(),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const res_text = await res.text();
|
|
209
|
+
let clean_text = res_text;
|
|
210
|
+
if (clean_text.startsWith(")]}\x27")) clean_text = clean_text.slice(4).trimStart();
|
|
211
|
+
const match_res = clean_text.match(/^(\d+)\n/);
|
|
212
|
+
if (match_res) {
|
|
213
|
+
const chunk_len = parseInt(match_res[1], 10),
|
|
214
|
+
start_idx = match_res[1].length,
|
|
215
|
+
chunk_str = clean_text.slice(start_idx, start_idx + chunk_len).trim();
|
|
216
|
+
if (chunk_str.startsWith("[")) {
|
|
217
|
+
const part_li = JSON.parse(chunk_str);
|
|
218
|
+
part_li.forEach((part) => {
|
|
219
|
+
if (part[1] === "otAQ7b" && part[2]) {
|
|
220
|
+
const part_body = JSON.parse(part[2]),
|
|
221
|
+
model_li = part_body[15],
|
|
222
|
+
tier_flag_li = part_body[16] ?? [],
|
|
223
|
+
capability_flag_li = part_body[17] ?? [];
|
|
224
|
+
let cap = 1;
|
|
225
|
+
if (capability_flag_li.includes(115)) cap = 4;
|
|
226
|
+
else if (tier_flag_li.includes(16) || capability_flag_li.includes(106)) cap = 3;
|
|
227
|
+
else if (tier_flag_li.includes(8) || capability_flag_li.includes(19)) cap = 2;
|
|
228
|
+
|
|
229
|
+
(model_li ?? []).forEach((item) => {
|
|
230
|
+
const id = item[0],
|
|
231
|
+
cat = String(item[1] ?? item[10] ?? ""),
|
|
232
|
+
disp = String(item[11] ?? item[19] ?? item[1] ?? ""),
|
|
233
|
+
desc = String(item[12] ?? item[2] ?? ""),
|
|
234
|
+
num =
|
|
235
|
+
typeof item[17] === "number" ? item[17] : typeof item[9] === "number" ? item[9] : 1,
|
|
236
|
+
disp_slug = disp.toLowerCase().replaceAll(/\s+/g, "-"),
|
|
237
|
+
cat_slug = cat.toLowerCase().replaceAll(/\s+/g, "-"),
|
|
238
|
+
primary_id = "gemini-" + disp_slug,
|
|
239
|
+
alias_li = [id.toLowerCase(), primary_id, "gemini-" + cat_slug, cat_slug, disp_slug];
|
|
240
|
+
discovered_li.push({
|
|
241
|
+
id: primary_id,
|
|
242
|
+
model_id: id,
|
|
243
|
+
disp,
|
|
244
|
+
cat,
|
|
245
|
+
desc,
|
|
246
|
+
num,
|
|
247
|
+
capacity: cap,
|
|
248
|
+
alias_li,
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const non_lite_li = discovered_li.filter(
|
|
257
|
+
(model_item) =>
|
|
258
|
+
!model_item.disp.toLowerCase().includes("lite") &&
|
|
259
|
+
!model_item.cat.toLowerCase().includes("lite") &&
|
|
260
|
+
!model_item.id.toLowerCase().includes("lite"),
|
|
261
|
+
);
|
|
262
|
+
non_lite_li.sort((a, b) => versionCompare(b.disp, a.disp));
|
|
263
|
+
session_state.default_model = non_lite_li[0] ?? discovered_li[0] ?? DEFAULT_MODEL_LI[0];
|
|
264
|
+
|
|
265
|
+
return discovered_li;
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
modelMap = (req_model) => {
|
|
269
|
+
const norm = (req_model ?? "").toLowerCase(),
|
|
270
|
+
available_li = session_state.model_li.length > 0 ? session_state.model_li : DEFAULT_MODEL_LI,
|
|
271
|
+
found = available_li.find(
|
|
272
|
+
(model_item) =>
|
|
273
|
+
model_item.id === norm ||
|
|
274
|
+
model_item.model_id === norm ||
|
|
275
|
+
model_item.alias_li.some((alias_item) => norm.includes(alias_item) || alias_item === norm),
|
|
276
|
+
);
|
|
277
|
+
return found ?? session_state.default_model ?? available_li[0];
|
|
278
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ENABLE_THINKING,
|
|
3
|
+
THINKING_LEVEL_DISABLED,
|
|
4
|
+
THINKING_LEVEL_ENABLED,
|
|
5
|
+
} from "./constant.js";
|
|
6
|
+
import {
|
|
7
|
+
toolPromptBuild,
|
|
8
|
+
toolResultFormat,
|
|
9
|
+
toolTailAnchorBuild,
|
|
10
|
+
} from "./toolHandle.js";
|
|
11
|
+
|
|
12
|
+
export const conversationFormat = (msg_li, tool_li = null, tool_choice = null) => {
|
|
13
|
+
const parts_li = [],
|
|
14
|
+
tools_prompt = toolPromptBuild(tool_li, tool_choice);
|
|
15
|
+
|
|
16
|
+
if (tools_prompt) {
|
|
17
|
+
parts_li.push("[System instruction]: " + tools_prompt);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
(msg_li ?? []).forEach((msg) => {
|
|
21
|
+
const { role, content, name, toolName, tool_name, toolCallId, tool_call_id } = msg,
|
|
22
|
+
raw_calls = msg.tool_calls ?? msg.toolCalls,
|
|
23
|
+
tool_calls = Array.isArray(raw_calls) ? raw_calls : null,
|
|
24
|
+
text_content = Array.isArray(content)
|
|
25
|
+
? content
|
|
26
|
+
.filter((item) => item.type === "text" || item.type === "input_text")
|
|
27
|
+
.map((item) => item.text)
|
|
28
|
+
.join("")
|
|
29
|
+
: content ?? "";
|
|
30
|
+
|
|
31
|
+
if (role === "system") {
|
|
32
|
+
parts_li.push("System: " + text_content);
|
|
33
|
+
} else if (role === "user") {
|
|
34
|
+
parts_li.push("Human: " + text_content);
|
|
35
|
+
} else if (role === "assistant") {
|
|
36
|
+
if (tool_calls && tool_calls.length > 0) {
|
|
37
|
+
const tc_str_li = tool_calls.map((tc) => {
|
|
38
|
+
const fn = tc.function ?? tc,
|
|
39
|
+
fn_name = fn.name ?? tc.name ?? "",
|
|
40
|
+
raw_args = fn.arguments ?? tc.arguments ?? tc.input ?? {};
|
|
41
|
+
let parsed_args = {};
|
|
42
|
+
try {
|
|
43
|
+
parsed_args = typeof raw_args === "string" ? JSON.parse(raw_args) : raw_args;
|
|
44
|
+
} catch {
|
|
45
|
+
parsed_args = {};
|
|
46
|
+
}
|
|
47
|
+
return (
|
|
48
|
+
"```tool_call\n" +
|
|
49
|
+
JSON.stringify({ name: fn_name, arguments: parsed_args }) +
|
|
50
|
+
"\n```"
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
const prefix = text_content ? "Assistant: " + text_content + "\n" : "Assistant:\n";
|
|
54
|
+
parts_li.push(prefix + tc_str_li.join("\n"));
|
|
55
|
+
} else {
|
|
56
|
+
parts_li.push("Assistant: " + text_content);
|
|
57
|
+
}
|
|
58
|
+
} else if (role === "tool") {
|
|
59
|
+
const tool_label = name ?? toolName ?? tool_name ?? toolCallId ?? tool_call_id ?? "";
|
|
60
|
+
parts_li.push(toolResultFormat(tool_label, text_content));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (tools_prompt) {
|
|
65
|
+
parts_li.push(toolTailAnchorBuild());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return parts_li.join("\n\n").trim();
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
payloadBuild = (prompt, model_info) => {
|
|
72
|
+
const session_id = crypto.randomUUID().toUpperCase(),
|
|
73
|
+
uuid_val = crypto.randomUUID().toUpperCase(),
|
|
74
|
+
thinking_level = ENABLE_THINKING ? THINKING_LEVEL_ENABLED : THINKING_LEVEL_DISABLED,
|
|
75
|
+
model_header = [
|
|
76
|
+
1,
|
|
77
|
+
null,
|
|
78
|
+
null,
|
|
79
|
+
null,
|
|
80
|
+
model_info.model_id,
|
|
81
|
+
null,
|
|
82
|
+
null,
|
|
83
|
+
0,
|
|
84
|
+
[4, 5, 6, 8],
|
|
85
|
+
null,
|
|
86
|
+
null,
|
|
87
|
+
model_info.capacity ?? 1,
|
|
88
|
+
null,
|
|
89
|
+
null,
|
|
90
|
+
model_info.num,
|
|
91
|
+
thinking_level,
|
|
92
|
+
session_id,
|
|
93
|
+
],
|
|
94
|
+
inner_req = Array.from({ length: 81 }, () => null);
|
|
95
|
+
|
|
96
|
+
inner_req[0] = [prompt, 0, null, null, null, null, 0];
|
|
97
|
+
inner_req[1] = ["en"];
|
|
98
|
+
inner_req[2] = ["", "", "", null, null, null, null, null, null, ""];
|
|
99
|
+
inner_req[6] = [1];
|
|
100
|
+
inner_req[7] = 1;
|
|
101
|
+
inner_req[10] = 1;
|
|
102
|
+
inner_req[11] = 0;
|
|
103
|
+
inner_req[17] = [[0]];
|
|
104
|
+
inner_req[18] = 0;
|
|
105
|
+
inner_req[27] = 1;
|
|
106
|
+
inner_req[30] = [4];
|
|
107
|
+
inner_req[41] = [1];
|
|
108
|
+
inner_req[53] = 0;
|
|
109
|
+
inner_req[59] = uuid_val;
|
|
110
|
+
inner_req[61] = [];
|
|
111
|
+
inner_req[68] = 1;
|
|
112
|
+
inner_req[79] = model_info.num;
|
|
113
|
+
inner_req[80] = thinking_level;
|
|
114
|
+
return [inner_req, model_header, uuid_val];
|
|
115
|
+
};
|