@maiyunnet/kebab 9.10.0 → 9.11.0

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/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * --- 本文件用来定义每个目录实体地址的常量 ---
6
6
  */
7
7
  /** --- 当前系统版本号 --- */
8
- export declare const VER = "9.10.0";
8
+ export declare const VER = "9.11.0";
9
9
  /** --- 框架根目录,以 / 结尾 --- */
10
10
  export declare const ROOT_PATH: string;
11
11
  /** --- 框架的 LIB,以 / 结尾 --- */
package/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * --- 本文件用来定义每个目录实体地址的常量 ---
7
7
  */
8
8
  /** --- 当前系统版本号 --- */
9
- export const VER = '9.10.0';
9
+ export const VER = '9.11.0';
10
10
  // --- 服务端用的路径 ---
11
11
  const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
12
12
  /** --- /xxx/xxx --- */
package/lib/ai.d.ts CHANGED
@@ -32,7 +32,11 @@ export declare enum ESERVICE {
32
32
  /** --- 火山引擎中国大陆区 --- */
33
33
  'VOLCN' = 8,
34
34
  /** --- 火山引擎国际区 --- */
35
- 'VOLAS' = 9
35
+ 'VOLAS' = 9,
36
+ /** --- OpenRouter --- */
37
+ 'OPENROUTER' = 10,
38
+ /** --- OfoxAI --- */
39
+ 'OFOX' = 11
36
40
  }
37
41
  /** --- 选项 --- */
38
42
  export interface IOptions {
@@ -59,6 +63,10 @@ export declare class Ai {
59
63
  chat(body: openai.default.Chat.Completions.ChatCompletionCreateParamsNonStreaming): Promise<openai.APIPromise<openai.default.Chat.ChatCompletion> | false>;
60
64
  /** --- 创建流式对话 --- */
61
65
  chat(body: openai.default.Chat.Completions.ChatCompletionCreateParamsStreaming): Promise<openai.APIPromise<streaming.Stream<openai.default.Chat.ChatCompletionChunk>> | false>;
66
+ /** --- 创建非流式 Response --- */
67
+ response(body: openai.default.Responses.ResponseCreateParamsNonStreaming): Promise<openai.APIPromise<openai.default.Responses.Response> | false>;
68
+ /** --- 创建流式 Response --- */
69
+ response(body: openai.default.Responses.ResponseCreateParamsStreaming): Promise<openai.APIPromise<streaming.Stream<openai.default.Responses.ResponseStreamEvent>> | false>;
62
70
  /** --- 创建向量 --- */
63
71
  embedding(body: openai.default.EmbeddingCreateParams): Promise<openai.APIPromise<openai.default.CreateEmbeddingResponse> | false>;
64
72
  /** --- 生成图像,不支持 GEMINI、GROK 服务商 --- */
package/lib/ai.js CHANGED
@@ -34,6 +34,11 @@ export var ESERVICE;
34
34
  ESERVICE[ESERVICE["VOLCN"] = 8] = "VOLCN";
35
35
  /** --- 火山引擎国际区 --- */
36
36
  ESERVICE[ESERVICE["VOLAS"] = 9] = "VOLAS";
37
+ // --- 以下为第三方服务商 ---
38
+ /** --- OpenRouter --- */
39
+ ESERVICE[ESERVICE["OPENROUTER"] = 10] = "OPENROUTER";
40
+ /** --- OfoxAI --- */
41
+ ESERVICE[ESERVICE["OFOX"] = 11] = "OFOX";
37
42
  })(ESERVICE || (ESERVICE = {}));
38
43
  /** --- openai 的连接对象 --- */
39
44
  const links = [];
@@ -94,6 +99,14 @@ export class Ai {
94
99
  endpoint = opt.endpoint ?? `https://ark.ap-southeast.bytepluses.com/api/v3`;
95
100
  break;
96
101
  }
102
+ case ESERVICE.OPENROUTER: {
103
+ endpoint = opt.endpoint ?? `https://openrouter.ai/api/v1`;
104
+ break;
105
+ }
106
+ case ESERVICE.OFOX: {
107
+ endpoint = opt.endpoint ?? `https://api.ofox.ai/v1`;
108
+ break;
109
+ }
97
110
  default: {
98
111
  // --- ESERVICE.AZURE3 ---
99
112
  endpoint = opt.endpoint ?? configAi.endpoint ?? '';
@@ -136,6 +149,19 @@ export class Ai {
136
149
  return false;
137
150
  }
138
151
  }
152
+ /** --- 创建 Response --- */
153
+ async response(body) {
154
+ try {
155
+ return await this.link.responses.create(body);
156
+ }
157
+ catch (e) {
158
+ if (!e.message.includes('Input data may contain inappropriate content')) {
159
+ lCore.debug('[AI][RESPONSE]', e);
160
+ lCore.log(this._ctr ?? {}, `[AI][RESPONSE] ${e.message}`, '-error');
161
+ }
162
+ return false;
163
+ }
164
+ }
139
165
  /** --- 创建向量 --- */
140
166
  async embedding(body) {
141
167
  try {
@@ -151,6 +177,9 @@ export class Ai {
151
177
  }
152
178
  /** --- 生成图像,不支持 GEMINI、GROK 服务商 --- */
153
179
  async image(opt) {
180
+ if (!this.link.apiKey) {
181
+ return false;
182
+ }
154
183
  const seed = opt.seed ?? lCore.rand(0, 2147483647);
155
184
  switch (this._service) {
156
185
  case ESERVICE.ALICN:
@@ -300,6 +329,12 @@ export class Ai {
300
329
  return false;
301
330
  }
302
331
  }
332
+ case ESERVICE.OPENROUTER: {
333
+ return false;
334
+ }
335
+ case ESERVICE.OFOX: {
336
+ return false;
337
+ }
303
338
  }
304
339
  }
305
340
  /** --- 异步生成视频,仅支持 ALICN、ALIAS、ALINE --- */
@@ -307,6 +342,9 @@ export class Ai {
307
342
  if (this._service !== ESERVICE.ALICN && this._service !== ESERVICE.ALIAS && this._service !== ESERVICE.ALINE) {
308
343
  return false;
309
344
  }
345
+ if (!this.link.apiKey) {
346
+ return false;
347
+ }
310
348
  const mode = opt.mode ?? 'text';
311
349
  const imgs = opt.imgs ?? [];
312
350
  const resolution = opt.resolution ?? '720p';
@@ -442,6 +480,9 @@ export class Ai {
442
480
  if (this._service !== ESERVICE.ALICN && this._service !== ESERVICE.ALIAS && this._service !== ESERVICE.ALINE) {
443
481
  return false;
444
482
  }
483
+ if (!this.link.apiKey) {
484
+ return false;
485
+ }
445
486
  try {
446
487
  const res = await this._fetch(`https://dashscope${this._service === ESERVICE.ALIAS ? '-intl' : (this._service === ESERVICE.ALINE ? '-us' : '')}.aliyuncs.com/api/v1/tasks/${opt.task}`, {
447
488
  'method': 'GET',
package/lib/core.d.ts CHANGED
@@ -299,7 +299,7 @@ export declare function getLog(opt: {
299
299
  * @param opt 参数
300
300
  */
301
301
  export declare function ls(opt: {
302
- /** --- 如 2024/08/01/22,无所谓开头结尾 --- */
302
+ /** --- 如 2024/08/01/22,无所谓开头结尾是否有 /,不会逃逸出 cwd 路径 --- */
303
303
  'path': string;
304
304
  'encoding'?: BufferEncoding;
305
305
  /** --- 获取局域网服务器的目录列表,为空代表获取本机的 --- */
package/lib/text.d.ts CHANGED
@@ -28,8 +28,9 @@ export declare function parseUrl(url: string): kebab.IUrlParse;
28
28
  * --- 将相对路径根据基准路径进行转换 ---
29
29
  * @param from 基准路径
30
30
  * @param to 相对路径
31
+ * @param limit 是否限定结果不能逃逸出基准路径
31
32
  */
32
- export declare function urlResolve(from: string, to: string): string;
33
+ export declare function urlResolve(from: string, to: string, limit?: boolean): string;
33
34
  /**
34
35
  * --- 将路径中的 ../ ./ 都按规范妥善处理 ---
35
36
  * @param url 要处理的地址
package/lib/text.js CHANGED
@@ -117,14 +117,30 @@ export function parseUrl(url) {
117
117
  rtn['path'] = rtn['pathname'] + (rtn['query'] ? '?' + rtn['query'] : '');
118
118
  return rtn;
119
119
  }
120
+ /** --- 限定结果不能逃逸出基准路径 --- */
121
+ function urlResolveLimit(from, limit, rtn) {
122
+ if (!limit) {
123
+ return rtn;
124
+ }
125
+ const base = urlAtom(from).replace(/\/+$/, '') + '/';
126
+ if ((rtn === base.slice(0, -1)) || rtn.startsWith(base)) {
127
+ return rtn;
128
+ }
129
+ return base;
130
+ }
120
131
  /**
121
132
  * --- 将相对路径根据基准路径进行转换 ---
122
133
  * @param from 基准路径
123
134
  * @param to 相对路径
135
+ * @param limit 是否限定结果不能逃逸出基准路径
124
136
  */
125
- export function urlResolve(from, to) {
126
- from = from.replace('\\', '/');
127
- to = to.replace('\\', '/');
137
+ export function urlResolve(from, to, limit = false) {
138
+ from = from.replace(/\\/g, '/');
139
+ to = to.replace(/\\/g, '/');
140
+ if (limit) {
141
+ // --- 限定模式下,开头的 / 代表从 from 内部开始,而不是系统根目录 ---
142
+ to = to.replace(/^\/+/, '');
143
+ }
128
144
  // --- to 为空,直接返回 form ---
129
145
  if (to === '') {
130
146
  return urlAtom(from);
@@ -144,7 +160,7 @@ export function urlResolve(from, to) {
144
160
  // --- 已经是绝对路径,直接返回 ---
145
161
  if (t.protocol) {
146
162
  // --- 获取小写的 protocol ---
147
- return urlAtom(t.protocol + to.slice(t.protocol.length));
163
+ return urlResolveLimit(from, limit, urlAtom(t.protocol + to.slice(t.protocol.length)));
148
164
  }
149
165
  // --- # 或 ? 替换后返回 ---
150
166
  if (to.startsWith('#') || to.startsWith('?')) {
@@ -172,11 +188,11 @@ export function urlResolve(from, to) {
172
188
  // --- 返回最终结果 ---
173
189
  if (f.protocol && (f.protocol !== 'file:') && !f.host) {
174
190
  // --- 类似 c:/ ---
175
- return urlAtom(f.protocol + abs);
191
+ return urlResolveLimit(from, limit, urlAtom(f.protocol + abs));
176
192
  }
177
193
  else {
178
194
  // --- 类似 http:// ---
179
- return urlAtom((f.protocol ? f.protocol + '//' : '') + abs);
195
+ return urlResolveLimit(from, limit, urlAtom((f.protocol ? f.protocol + '//' : '') + abs));
180
196
  }
181
197
  }
182
198
  /**
@@ -1,5 +1,6 @@
1
1
  import * as zlib from 'zlib';
2
2
  import * as lBuffer from '#kebab/lib/buffer.js';
3
+ import * as lCore from '#kebab/lib/core.js';
3
4
  import * as lText from '#kebab/lib/text.js';
4
5
  export class Response {
5
6
  /** --- httpClient 请求对象 --- */
@@ -26,7 +27,8 @@ export class Response {
26
27
  }
27
28
  return this._req ? await lBuffer.getFull(stream) : null;
28
29
  }
29
- catch {
30
+ catch (e) {
31
+ lCore.log({}, '[Undici][Response][getContent] ' + e.message, '-error');
30
32
  return null;
31
33
  }
32
34
  }
@@ -39,7 +41,8 @@ export class Response {
39
41
  }
40
42
  return buf.toString('utf-8');
41
43
  }
42
- catch {
44
+ catch (e) {
45
+ lCore.log({}, '[Undici][Response][getText] ' + e.message, '-error');
43
46
  return null;
44
47
  }
45
48
  }
@@ -52,7 +55,8 @@ export class Response {
52
55
  }
53
56
  return lText.parseJson(text);
54
57
  }
55
- catch {
58
+ catch (e) {
59
+ lCore.log({}, '[Undici][Response][getJson] ' + e.message, '-error');
56
60
  return null;
57
61
  }
58
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maiyunnet/kebab",
3
- "version": "9.10.0",
3
+ "version": "9.11.0",
4
4
  "description": "Simple, easy-to-use, and fully-featured Node.js framework that is ready-to-use out of the box.",
5
5
  "type": "module",
6
6
  "keywords": [
package/sys/cmd.js CHANGED
@@ -191,6 +191,10 @@ async function run() {
191
191
  config.ai['VOLCN'].skey ??= '';
192
192
  config.ai['VOLAS'] ??= {};
193
193
  config.ai['VOLAS'].skey ??= '';
194
+ config.ai['OPENROUTER'] ??= {};
195
+ config.ai['OPENROUTER'].skey ??= '';
196
+ config.ai['OFOX'] ??= {};
197
+ config.ai['OFOX'].skey ??= '';
194
198
  // --- config - vector ---
195
199
  config.vector ??= {};
196
200
  config.vector.host ??= '127.0.0.1';
package/sys/master.js CHANGED
@@ -386,6 +386,7 @@ function createRpcListener() {
386
386
  '.nyc_output',
387
387
  'tmp',
388
388
  'temp',
389
+ 'AGENTS.md',
389
390
  ]);
390
391
  for (const path in ls) {
391
392
  /** --- 带 / 开头的 zip 中文件完整路径,例如 "/www/pika/ctr/api.js" --- */
@@ -737,7 +738,11 @@ function createRpcListener() {
737
738
  }
738
739
  case 'ls': {
739
740
  // --- 获取目录内文件/文件夹列表 ---
740
- const path = lText.urlResolve(kebab.ROOT_CWD, msg.path);
741
+ if (typeof msg.path !== 'string') {
742
+ res.end('Invalid path');
743
+ return;
744
+ }
745
+ const path = lText.urlResolve(kebab.ROOT_CWD, msg.path, true);
741
746
  res.end(lText.stringifyJson({
742
747
  'result': 1,
743
748
  'data': (await lFs.readDir(path, msg.encoding)).map(item => ({
package/sys/mod.js CHANGED
@@ -681,6 +681,10 @@ export default class Mod {
681
681
  */
682
682
  async refresh(lock = false) {
683
683
  const cstr = this.constructor;
684
+ if (!this._data[cstr._$primary]) {
685
+ lCore.log(this._ctr ?? {}, '[MOD][refresh] primary key not found', '-error');
686
+ return false;
687
+ }
684
688
  this._sql.select('*', cstr._$table + (this._index ? ('_' + this._index[0]) : '')).where([{
685
689
  [cstr._$primary]: this._data[cstr._$primary]
686
690
  }]);
@@ -711,6 +715,10 @@ export default class Mod {
711
715
  return true;
712
716
  }
713
717
  const cstr = this.constructor;
718
+ if (!this._data[cstr._$primary]) {
719
+ lCore.log(this._ctr ?? {}, '[MOD][save] primary key not found', '-error');
720
+ return false;
721
+ }
714
722
  const updates = {};
715
723
  for (const k in this._updates) {
716
724
  updates[k] = this._data[k];
@@ -736,6 +744,10 @@ export default class Mod {
736
744
  */
737
745
  async remove() {
738
746
  const cstr = this.constructor;
747
+ if (!this._data[cstr._$primary]) {
748
+ lCore.log(this._ctr ?? {}, '[MOD][remove] primary key not found', '-error');
749
+ return false;
750
+ }
739
751
  this._sql.delete(cstr._$table + (this._index ? ('_' + this._index[0]) : '')).where([{
740
752
  [cstr._$primary]: this._data[cstr._$primary]
741
753
  }]);
@@ -140,6 +140,7 @@ export default class extends sCtr.Ctr {
140
140
  `<br><br><b>Ai:</b>`,
141
141
  `<br><a href="${this._config.const.urlBase}test/ai-stream">View "test/ai-stream"</a>`,
142
142
  `<br><a href="${this._config.const.urlBase}test/ai?action=chat">View "test/ai?action=chat"</a>`,
143
+ `<br><a href="${this._config.const.urlBase}test/ai?action=response">View "test/ai?action=response"</a>`,
143
144
  `<br><a href="${this._config.const.urlBase}test/ai?action=text-to-image">View "test/ai?action=text-to-image"</a>`,
144
145
  `<br><a href="${this._config.const.urlBase}test/ai?action=image-to-image">View "test/ai?action=image-to-image"</a>`,
145
146
  `<br><a href="${this._config.const.urlBase}test/ai?action=text-to-video">View "test/ai?action=text-to-video"</a>`,
@@ -3692,6 +3693,8 @@ ${lText.htmlescape(lText.urlResolve('C:\\Windows\\Misc', '/'))}
3692
3693
  ${lText.htmlescape(lText.urlResolve('C:\\Windows\\Misc', '/xxx/yyy'))}
3693
3694
  <pre>lText.urlResolve('/abc/def/', '');</pre>
3694
3695
  ${lText.htmlescape(lText.urlResolve('/abc/def/', ''))}
3696
+ <pre>lText.urlResolve('/abc/def/', '../1/2/3', true);</pre>
3697
+ ${lText.htmlescape(lText.urlResolve('/abc/def/', '../1/2/3', true))}
3695
3698
  <pre>lText.isEMail('test@gmail.com');</pre>
3696
3699
  ${JSON.stringify(lText.isEMail('test@gmail.com'))}
3697
3700
  <pre>lText.isEMail('test@x');</pre>
@@ -4093,11 +4096,13 @@ rtn.push(reader.readBCDString());</pre>${JSON.stringify(rtn)}`);
4093
4096
  return echo.join('') + '<br>' + this._getEnd();
4094
4097
  }
4095
4098
  async ai() {
4099
+ this.timeout = 60_000 * 10;
4096
4100
  const echo = [`<pre>const ai = lAi.get(this, {
4097
4101
  'service': lAi.ESERVICE.${lText.htmlescape(this._get['service']?.toUpperCase() ?? 'ALICN')},
4098
4102
  });</pre>`];
4099
4103
  const ai = lAi.get(this, {
4100
4104
  'service': lAi.ESERVICE[this._get['service']?.toUpperCase() ?? 'ALICN'] ?? lAi.ESERVICE.ALICN,
4105
+ 'endpoint': this._get['service'] === 'ofox' ? 'https://api.ofox.io/v1' : undefined,
4101
4106
  });
4102
4107
  switch (this._get['action']) {
4103
4108
  case 'text-to-image': {
@@ -4241,6 +4246,128 @@ rtn.push(reader.readBCDString());</pre>${JSON.stringify(rtn)}`);
4241
4246
  }
4242
4247
  break;
4243
4248
  }
4249
+ case 'response': {
4250
+ // --- Responses API:多轮对话 + Function Calling + 数据库存储方案 ---
4251
+ let model = ai.service === lAi.ESERVICE.ALICN ? 'qwen3.5-flash' : 'openai/gpt-5-nano';
4252
+ const responseInstructions = 'You are Kebab, a helpful assistant. Do not mention any model names or AI identity. Respond in Chinese.';
4253
+ // --- 工具定义 ---
4254
+ const responseTools = [
4255
+ {
4256
+ 'type': 'function',
4257
+ 'name': 'get_weather',
4258
+ 'description': '获取指定城市的当前天气,返回 `false`: 调用失败, `null`: 无数据, 成功: 温度和天气',
4259
+ 'parameters': {
4260
+ 'type': 'object',
4261
+ 'required': ['city'],
4262
+ 'properties': {
4263
+ 'city': { 'type': 'string', 'description': '城市名称,如"北京"' },
4264
+ },
4265
+ },
4266
+ },
4267
+ ];
4268
+ // --- 模拟本地工具执行 ---
4269
+ const execTool = (name, argsJson) => {
4270
+ const args = lText.parseJson(argsJson);
4271
+ if (!args) {
4272
+ return 'false';
4273
+ }
4274
+ if (name === 'get_weather') {
4275
+ const table = {
4276
+ '北京': { 'temp': '22°C', 'condition': '晴' },
4277
+ '上海': { 'temp': '28°C', 'condition': '多云' },
4278
+ };
4279
+ return table[args['city']] ? lText.stringifyJson(table[args['city']]) : 'null';
4280
+ }
4281
+ return 'false';
4282
+ };
4283
+ // --- 完整对话历史(同时作为数据库存储内容) ---
4284
+ const conversation = [];
4285
+ // --- 运行一轮(自动处理 function_call 循环) ---
4286
+ const runTurn = async (userMsg) => {
4287
+ conversation.push({ 'role': 'user', 'content': userMsg });
4288
+ let data = await ai.response({
4289
+ 'model': model,
4290
+ 'store': false,
4291
+ 'reasoning': {
4292
+ 'effort': 'minimal',
4293
+ },
4294
+ 'instructions': responseInstructions,
4295
+ 'input': conversation,
4296
+ 'tools': responseTools,
4297
+ });
4298
+ while (data) {
4299
+ const calls = data.output.filter(item => item.type === 'function_call');
4300
+ if (!calls.length) {
4301
+ // --- 无工具调用,追加 assistant 回复,供下一轮使用 ---
4302
+ conversation.push({ 'role': 'assistant', 'content': data.output_text });
4303
+ return data;
4304
+ }
4305
+ // --- 将 function_call 追加到 conversation ---
4306
+ for (const call of calls) {
4307
+ conversation.push({
4308
+ 'type': 'function_call',
4309
+ 'name': call.name,
4310
+ 'arguments': call.arguments,
4311
+ 'call_id': call.call_id,
4312
+ });
4313
+ }
4314
+ // --- 全部追加完后再统一执行工具,将结果追加到 conversation,再次调用 ---
4315
+ for (const call of calls) {
4316
+ conversation.push({
4317
+ 'type': 'function_call_output',
4318
+ 'call_id': call.call_id,
4319
+ 'output': execTool(call.name, call.arguments),
4320
+ });
4321
+ }
4322
+ data = await ai.response({
4323
+ 'model': model,
4324
+ 'store': false,
4325
+ 'reasoning': {
4326
+ 'effort': 'minimal',
4327
+ },
4328
+ 'instructions': responseInstructions,
4329
+ 'input': conversation,
4330
+ 'tools': responseTools,
4331
+ });
4332
+ }
4333
+ return false;
4334
+ };
4335
+ // --- 第一轮:自我介绍 + 查北京天气(触发 Function Calling) ---
4336
+ const d1str = '你是谁?今天北京天气怎么样?';
4337
+ const d1 = await runTurn(d1str);
4338
+ if (!d1) {
4339
+ echo.push('Turn 1 Failed');
4340
+ break;
4341
+ }
4342
+ echo.push(`<b>第一轮(触发 get_weather 工具):</b><br>${d1str + '<br>' + lText.htmlescape(d1.output_text)}<br><br>`);
4343
+ // --- 第二轮:续接上文,查上海天气 ---
4344
+ const d2str = '那上海呢?';
4345
+ const d2 = await runTurn(d2str);
4346
+ if (!d2) {
4347
+ echo.push('Turn 2 Failed');
4348
+ break;
4349
+ }
4350
+ echo.push(`<b>第二轮(conversation 续接,自动触发工具):</b><br>${d2str + '<br>' + lText.htmlescape(d2.output_text)}<br><br>`);
4351
+ // --- 第三轮:基于前两轮结果直接推理,无需工具 ---
4352
+ const d3str = '两个城市比较,今天哪个更适合出行?';
4353
+ const d3 = await runTurn(d3str);
4354
+ if (!d3) {
4355
+ echo.push('Turn 3 Failed');
4356
+ break;
4357
+ }
4358
+ echo.push(`<b>第三轮(直接推理):</b><br>${d3str + '<br>' + lText.htmlescape(d3.output_text)}<br>`);
4359
+ // --- 数据库存储格式 ---
4360
+ // --- conversation 顺序:user → function_call → function_call_output → assistant → user → ... ---
4361
+ echo.push(`<hr><b>数据库存储 input:</b><pre>${lText.htmlescape(lText.stringifyJson(conversation, 4))}</pre>`);
4362
+ // --- 从头重建(直接将 dbRecord.input 发送即可) ---
4363
+ echo.push(`<hr><b>从头重建:</b><pre>${lText.htmlescape(`const replay = await ai.response({
4364
+ 'model': model,
4365
+ 'instructions': instructions,
4366
+ 'input': conversation,
4367
+ 'tools': tools,
4368
+ });`)}</pre>`);
4369
+ break;
4370
+ }
4244
4371
  default: {
4245
4372
  // --- CHAT ---
4246
4373
  let model = ai.service === lAi.ESERVICE.ALICN ? 'qwen-plus' : 'doubao-seed-2-0-mini-260215';