HerzaJ commited on
Commit
55d5adf
·
verified ·
1 Parent(s): abe579e

Create zai.js

Browse files
Files changed (1) hide show
  1. plugins/zai.js +266 -0
plugins/zai.js ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { Readable, Transform } = require('node:stream');
2
+ const crypto = require('node:crypto');
3
+ const axios = require('axios');
4
+
5
+ class EventStreamParser extends Transform {
6
+ constructor(options) {
7
+ super({ ...options, readableObjectMode: true });
8
+ this.buffer = '';
9
+ }
10
+
11
+ _transform(chunk, encoding, callback) {
12
+ this.buffer += chunk.toString();
13
+ let boundary = this.buffer.indexOf('\n');
14
+ while (boundary !== -1) {
15
+ const line = this.buffer.substring(0, boundary).trim();
16
+ this.buffer = this.buffer.substring(boundary + 1);
17
+ if (line.startsWith('data:')) {
18
+ const jsonString = line.substring(5).trim();
19
+ if (jsonString) {
20
+ try {
21
+ const parsedJson = JSON.parse(jsonString);
22
+ this.push(parsedJson);
23
+ } catch (error) {}
24
+ }
25
+ }
26
+ boundary = this.buffer.indexOf('\n');
27
+ }
28
+ callback();
29
+ }
30
+ }
31
+
32
+ class ZAI {
33
+ constructor() {
34
+ this.ins = axios.create({
35
+ baseURL: 'https://chat.z.ai',
36
+ headers: {
37
+ 'user-agent': 'Mozilla/5.0 (Android 15; Mobile; SM-F958; rv:130.0) Gecko/130.0 Firefox/130.0'
38
+ }
39
+ });
40
+ this.t = 'Wi5haSBDaGF0IC0gRnJlZSBBSSBwb3dlcmVkIGJ5IEdMTS00LjYgJiBHTE0tNC01';
41
+ this._model = {
42
+ 'glm-4.6': 'GLM-4-6-API-V1',
43
+ 'glm-4.5v': 'glm-4.5v',
44
+ 'glm-4.5': '0727-360B-API',
45
+ 'glm-4.5-air': '0727-106B-API',
46
+ 'glm-4-32b': 'main_chat',
47
+ 'glm-4.1v-9b-thinking': 'GLM-4.1V-Thinking-FlashX',
48
+ 'z1-rumination': 'deep-research',
49
+ 'z1-32b': 'zero',
50
+ 'glm-4-flash': 'glm-4-flash'
51
+ };
52
+ }
53
+
54
+ sign(g, v, b, w, m, o, e) {
55
+ let [r, i, h, j, t] = [
56
+ new URL('/c/' + m, g),
57
+ String(Date.now()),
58
+ crypto.randomUUID(),
59
+ Intl.DateTimeFormat(),
60
+ new Date(),
61
+ ], [p, c] = [
62
+ { timestamp: i, requestId: h, user_id: v },
63
+ { version: "0.0.1", platform: "web", token: b, user_agent: e, language: 'id-ID', languages: 'id-ID,en-US,id,en', timezone: j.resolvedOptions().timeZone, cookie_enabled: 'true', screen_width: '461', screen_height: '1024', screen_resolution: '461x1024', viewport_height: '1051', viewport_width: '543', viewport_size: '543x1051', color_depth: '24', pixel_ratio: '1.328460693359375', current_url: r.href, pathname: r.pathname, search: r.search, hash: r.hash, host: r.host, hostname: r.hostname, protocol: r.protocol, referrer: '', title: w, timezone_offset: String(t.getTimezoneOffset()), local_time: t.toISOString(), utc_time: t.toUTCString(), is_mobile: 'true', is_touch: 'true', max_touch_points: '5', browser_name: 'Chrome', os_name: 'Android' }
64
+ ], a = {...p, ...c}, [n, x, y, z] = [
65
+ new URLSearchParams(a).toString(),
66
+ [...Object.entries(p).sort()].join(','),
67
+ Buffer.from(String.fromCharCode(...(new Uint8Array((new TextEncoder()).encode(o.trim())))), 'binary').toString('base64'),
68
+ Math.floor(Number(i) / 300000)
69
+ ], d = crypto.createHmac('sha256', 'junjie').update(String(z)).digest('hex'), s = crypto.createHmac('sha256', d).update([x,y,i].join('|')).digest('hex');
70
+ return {
71
+ signature: s,
72
+ params: { ...a, signature_timestamp: i }
73
+ };
74
+ }
75
+
76
+ parseStream(streamData) {
77
+ return new Promise((resolve, reject) => {
78
+ let mainBuffer = [];
79
+ let usageData = null;
80
+ let toolCallString = '';
81
+
82
+ const sourceStream = Readable.from(streamData);
83
+ const parser = new EventStreamParser();
84
+
85
+ sourceStream
86
+ .pipe(parser)
87
+ .on('data', (event) => {
88
+ const data = event.data;
89
+ if (!data) return;
90
+
91
+ if (data.usage) {
92
+ usageData = data.usage;
93
+ }
94
+
95
+ if (typeof data.edit_index === 'number') {
96
+ const index = data.edit_index;
97
+ const contentChunk = (data.edit_content || '').split('');
98
+ mainBuffer.splice(index, contentChunk.length, ...contentChunk);
99
+ } else if (data.delta_content) {
100
+ const currentLength = mainBuffer.length;
101
+ const contentChunk = data.delta_content.split('');
102
+ mainBuffer.splice(currentLength, 0, ...contentChunk);
103
+ }
104
+ })
105
+ .on('end', () => {
106
+ const fullOutput = mainBuffer.join('');
107
+
108
+ const result = {
109
+ thinking: '',
110
+ answer: '',
111
+ search: [],
112
+ usage: usageData
113
+ };
114
+
115
+ const toolCallMatch = fullOutput.match(/<glm_block[\s\S]*?<\/glm_block>/);
116
+ if (toolCallMatch) {
117
+ toolCallString = toolCallMatch[0];
118
+ }
119
+
120
+ const thinkingMatch = fullOutput.match(/<details[^>]*>([\s\S]*?)<\/details>/s);
121
+ if (thinkingMatch) {
122
+ result.thinking = thinkingMatch[1].trim();
123
+ const thinkingEndIndex = thinkingMatch.index + thinkingMatch[0].length;
124
+ result.answer = fullOutput.substring(thinkingEndIndex).trim();
125
+ } else {
126
+ const toolCallEndIndex = toolCallMatch ? toolCallMatch.index + toolCallString.length : 0;
127
+ result.answer = fullOutput.substring(toolCallEndIndex).trim();
128
+ }
129
+
130
+ if (toolCallString) {
131
+ try {
132
+ const jsonContentMatch = toolCallString.match(/<glm_block[^>]*>([\s\S]*)<\/glm_block>/);
133
+ if (jsonContentMatch && jsonContentMatch[1]) {
134
+ const dt = JSON.parse(jsonContentMatch[1]);
135
+ result.search = dt?.data?.browser?.search_result || {};
136
+ }
137
+ } catch (error) {}
138
+ }
139
+
140
+ result.thinking = result.thinking.replace(/【turn0search(\d+)】/g, '[$1]');
141
+ result.answer = result.answer.replace(/【turn0search(\d+)】/g, '[$1]');
142
+
143
+ resolve(result);
144
+ })
145
+ .on('error', (err) => {
146
+ reject(err);
147
+ });
148
+ });
149
+ }
150
+
151
+ getModel() {
152
+ return Object.keys(this._model);
153
+ }
154
+
155
+ async chat(question, { model = 'glm-4.6', system_prompt = null, search = false, deepthink = false } = {}) {
156
+ try {
157
+ if (!question) throw new Error('Question is required.');
158
+ if (!this._model[model]) throw new Error(`Available models: ${this.getModel().join(', ')}.`);
159
+ if (typeof search !== 'boolean') throw new Error('Search must be a boolean.');
160
+ if (typeof deepthink !== 'boolean') throw new Error('Deepthink must be a boolean.');
161
+
162
+ const usr = await this.ins.get('/api/v1/auths/');
163
+ const chat_id = crypto.randomUUID();
164
+
165
+ const xx = this.sign(this.ins.defaults.baseURL, usr.data.id, usr.data.token, Buffer.from(this.t, 'base64').toString(), chat_id, question, this.ins.defaults.headers.common['user-agent']);
166
+
167
+ const res = await this.ins.post('/api/chat/completions', {
168
+ messages: [
169
+ ...(system_prompt ? [{
170
+ role: 'system',
171
+ content: system_prompt
172
+ }] : []),
173
+ {
174
+ role: 'user',
175
+ content: question
176
+ }
177
+ ],
178
+ signature_prompt: question,
179
+ features: {
180
+ web_search: search,
181
+ auto_web_search: search,
182
+ enable_thinking: deepthink
183
+ },
184
+ model: this._model[model],
185
+ chat_id: chat_id,
186
+ id: crypto.randomUUID(),
187
+ stream: true
188
+ }, {
189
+ headers: {
190
+ authorization: `Bearer ${usr.data.token}`,
191
+ cookie: usr.headers['set-cookie'].join('; '),
192
+ 'x-signature': xx.signature,
193
+ 'x-fe-version': 'prod-fe-1.0.52'
194
+ },
195
+ params: xx.params,
196
+ });
197
+
198
+ const hasil = await this.parseStream(res.data);
199
+ return {
200
+ status: true,
201
+ data: hasil
202
+ };
203
+ } catch (error) {
204
+ return {
205
+ status: false,
206
+ msg: error.message,
207
+ ...(error?.response?.data || error?.response || {})
208
+ };
209
+ }
210
+ }
211
+ }
212
+
213
+ const handler = async (req, res) => {
214
+ try {
215
+ const { text, model = 'glm-4.6', search = false, deepthink = false, system_prompt } = req.query;
216
+
217
+ if (!text) {
218
+ return res.status(400).json({
219
+ author: 'Herza',
220
+ success: false,
221
+ msg: 'Missing required parameter: text'
222
+ });
223
+ }
224
+
225
+ const zai = new ZAI();
226
+ const result = await zai.chat(text, {
227
+ model,
228
+ system_prompt,
229
+ search: search === 'true' || search === true,
230
+ deepthink: deepthink === 'true' || deepthink === true
231
+ });
232
+
233
+ if (result.status) {
234
+ res.json({
235
+ author: 'Herza',
236
+ success: true,
237
+ data: result.data
238
+ });
239
+ } else {
240
+ res.status(500).json({
241
+ author: 'Herza',
242
+ success: false,
243
+ msg: result.msg
244
+ });
245
+ }
246
+
247
+ } catch (error) {
248
+ res.status(500).json({
249
+ author: 'Herza',
250
+ success: false,
251
+ msg: 'Terjadi kesalahan saat menghubungi AI.'
252
+ });
253
+ }
254
+ };
255
+
256
+ module.exports = {
257
+ name: 'Z.AI Chat',
258
+ description: 'Generate responses using Z.AI with multiple GLM models',
259
+ type: 'GET',
260
+ routes: ['api/AI/zai'],
261
+ tags: ['ai', 'zai', 'glm', 'chat'],
262
+ parameters: ['text', 'model', 'search', 'deepthink', 'system_prompt'],
263
+ enabled: true,
264
+ main: ['AI'],
265
+ handler
266
+ };