koishi-plugin-chatluna-anuneko-api-adapter 1.0.0 → 1.0.3

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/lib/index.d.ts CHANGED
@@ -3,13 +3,14 @@ import { ChatLunaPlugin } from 'koishi-plugin-chatluna/services/chat';
3
3
  export declare let logger: Logger;
4
4
  export declare let anunekoClient: any;
5
5
  export declare const reusable = true;
6
- export declare const usage = "\n<p><strong>\u96F6\u6210\u672C\u3001\u5FEB\u901F\u4F53\u9A8CChatluna</strong>\u3002</p>\n<ul>\n<li><strong>API\u6765\u6E90\uFF1A</strong> anuneko.com</li>\n<li>\n<strong>\u63A5\u53E3\u5730\u5740\uFF1A</strong>\n<a href=\"https://anuneko.com\" target=\"_blank\" rel=\"noopener noreferrer\">https://anuneko.com</a>\n</li>\n</ul>\n<p><strong>\u8BF7\u6CE8\u610F\uFF1A</strong></p>\n<p>\u8BE5\u670D\u52A1\u9700\u8981\u914D\u7F6E\u6709\u6548\u7684 x-token \u624D\u80FD\u4F7F\u7528\u3002\u652F\u6301\u6A58\u732B(Orange Cat)\u548C\u9ED1\u732B(Exotic Shorthair)\u4E24\u79CD\u6A21\u578B\u3002</p>\n";
6
+ export declare const usage = "\n<p><strong>\u96F6\u6210\u672C\u3001\u5FEB\u901F\u4F53\u9A8CChatluna</strong>\u3002</p>\n<ul>\n<li><strong>API\u6765\u6E90\uFF1A</strong> anuneko.com</li>\n<li>\n<strong>\u63A5\u53E3\u5730\u5740\uFF1A</strong>\n<a href=\"https://anuneko.com\" target=\"_blank\" rel=\"noopener noreferrer\">https://anuneko.com</a>\n</li>\n</ul>\n<p><strong>\u8BF7\u6CE8\u610F\uFF1A</strong></p>\n<p>\u8BE5\u670D\u52A1\u9700\u8981\u914D\u7F6E\u6709\u6548\u7684 x-token \u624D\u80FD\u4F7F\u7528\u3002</p>\n<p>\u8BE5\u670D\u52A1\u53EF\u80FD\u9700\u8981\u79D1\u5B66\u4E0A\u7F51\u3002</p>\n<p>\u652F\u6301\u6A58\u732B(Orange Cat)\u548C\u9ED1\u732B(Exotic Shorthair)\u4E24\u79CD\u6A21\u578B\u3002</p>\n";
7
7
  export declare function apply(ctx: Context, config: Config): void;
8
8
  export interface Config extends ChatLunaPlugin.Config {
9
9
  platform: string;
10
10
  xToken: string;
11
11
  cookie?: string;
12
12
  loggerinfo: boolean;
13
+ requestTimeout: number;
13
14
  }
14
15
  export declare const Config: Schema<Config>;
15
16
  export declare const inject: string[];
package/lib/index.js CHANGED
@@ -130,10 +130,12 @@ var AnunekoRequester = class extends import_api.ModelRequester {
130
130
  const data = { model: modelName };
131
131
  try {
132
132
  logInfo("Creating new session with model:", modelName);
133
+ const signal = AbortSignal.timeout(this._pluginConfig.requestTimeout * 1e3);
133
134
  const response = await fetch("https://anuneko.com/api/v1/chat", {
134
135
  method: "POST",
135
136
  headers,
136
- body: JSON.stringify(data)
137
+ body: JSON.stringify(data),
138
+ signal
137
139
  });
138
140
  const responseData = await response.json();
139
141
  const chatId = responseData.chat_id || responseData.id;
@@ -155,10 +157,12 @@ var AnunekoRequester = class extends import_api.ModelRequester {
155
157
  const data = { chat_id: chatId, model: modelName };
156
158
  try {
157
159
  logInfo("Switching model to:", modelName);
160
+ const signal = AbortSignal.timeout(this._pluginConfig.requestTimeout * 1e3);
158
161
  const response = await fetch("https://anuneko.com/api/v1/user/select_model", {
159
162
  method: "POST",
160
163
  headers,
161
- body: JSON.stringify(data)
164
+ body: JSON.stringify(data),
165
+ signal
162
166
  });
163
167
  if (response.ok) {
164
168
  this.modelMap.set(userId, modelName);
@@ -175,10 +179,12 @@ var AnunekoRequester = class extends import_api.ModelRequester {
175
179
  const headers = this.buildHeaders();
176
180
  const data = { msg_id: msgId, choice_idx: 0 };
177
181
  try {
182
+ const signal = AbortSignal.timeout(this._pluginConfig.requestTimeout * 1e3);
178
183
  await fetch("https://anuneko.com/api/v1/msg/select-choice", {
179
184
  method: "POST",
180
185
  headers,
181
- body: JSON.stringify(data)
186
+ body: JSON.stringify(data),
187
+ signal
182
188
  });
183
189
  logInfo("Choice sent for msg_id:", msgId);
184
190
  } catch (error) {
@@ -226,10 +232,12 @@ var AnunekoRequester = class extends import_api.ModelRequester {
226
232
  logInfo("Sending request to API:", url, JSON.stringify(data, null, 2));
227
233
  let result = "";
228
234
  let currentMsgId = null;
235
+ const signal = AbortSignal.timeout(this._pluginConfig.requestTimeout * 1e3);
229
236
  const response = await fetch(url, {
230
237
  method: "POST",
231
238
  headers,
232
- body: JSON.stringify(data)
239
+ body: JSON.stringify(data),
240
+ signal
233
241
  });
234
242
  if (!response.ok) {
235
243
  throw new Error(`Request failed with status ${response.status}`);
@@ -404,7 +412,9 @@ var usage = `
404
412
  </li>
405
413
  </ul>
406
414
  <p><strong>请注意:</strong></p>
407
- <p>该服务需要配置有效的 x-token 才能使用。支持橘猫(Orange Cat)和黑猫(Exotic Shorthair)两种模型。</p>
415
+ <p>该服务需要配置有效的 x-token 才能使用。</p>
416
+ <p>该服务可能需要科学上网。</p>
417
+ <p>支持橘猫(Orange Cat)和黑猫(Exotic Shorthair)两种模型。</p>
408
418
  `;
409
419
  function apply(ctx, config2) {
410
420
  logger2 = (0, import_logger3.createLogger)(ctx, "chatluna-anuneko-api-adapter");
@@ -425,28 +435,31 @@ function apply(ctx, config2) {
425
435
  "x-device_id": "7b75a432-6b24-48ad-b9d3-3dc57648e3e3",
426
436
  "x-token": config2.xToken
427
437
  };
438
+ const signal = AbortSignal.timeout(config2.requestTimeout * 1e3);
428
439
  if (config2.cookie) {
429
440
  headers["Cookie"] = config2.cookie;
430
441
  }
431
- logger2.info("创建新会话...");
442
+ logInfo("创建新会话...");
432
443
  const createResponse = await fetch("https://anuneko.com/api/v1/chat", {
433
444
  method: "POST",
434
445
  headers,
435
- body: JSON.stringify({ model: "Orange Cat" })
446
+ body: JSON.stringify({ model: "Orange Cat" }),
447
+ signal
436
448
  });
437
449
  const createData = await createResponse.json();
438
450
  const chatId = createData.chat_id || createData.id;
439
451
  if (!chatId) {
440
452
  return "❌ 创建会话失败";
441
453
  }
442
- logger2.info("会话创建成功,ID:", chatId);
454
+ logInfo("会话创建成功,ID:", chatId);
443
455
  const url = `https://anuneko.com/api/v1/msg/${chatId}/stream`;
444
456
  const data = { contents: [message] };
445
- logger2.info("发送消息...");
457
+ logInfo("发送消息...");
446
458
  const response = await fetch(url, {
447
459
  method: "POST",
448
460
  headers,
449
- body: JSON.stringify(data)
461
+ body: JSON.stringify(data),
462
+ signal
450
463
  });
451
464
  if (!response.ok) {
452
465
  return `❌ 请求失败: ${response.status} ${response.statusText}`;
@@ -459,54 +472,55 @@ function apply(ctx, config2) {
459
472
  const { done, value } = await reader.read();
460
473
  if (done) break;
461
474
  const chunkStr = decoder.decode(value, { stream: true });
462
- logger2.info("收到数据块:", chunkStr.substring(0, 200));
475
+ logInfo("收到数据块:", chunkStr.substring(0, 200));
463
476
  const lines = chunkStr.split("\n");
464
477
  for (const line of lines) {
465
478
  if (!line.trim()) {
466
479
  continue;
467
480
  }
468
- logger2.info("处理行:", line.substring(0, 100));
481
+ logInfo("处理行:", line.substring(0, 100));
469
482
  if (!line.startsWith("data: ")) {
470
- logger2.warn("非 data: 格式的行:", line);
483
+ logInfo("非 data: 格式的行:", line);
471
484
  continue;
472
485
  }
473
486
  const rawJson = line.substring(6).trim();
474
487
  if (!rawJson) continue;
475
488
  try {
476
489
  const j = JSON.parse(rawJson);
477
- logger2.info("解析的 JSON:", JSON.stringify(j));
490
+ logInfo("解析的 JSON:", JSON.stringify(j));
478
491
  if (j.msg_id) {
479
492
  currentMsgId = j.msg_id;
480
- logger2.info("更新 msg_id:", currentMsgId);
493
+ logInfo("更新 msg_id:", currentMsgId);
481
494
  }
482
495
  if (j.c && Array.isArray(j.c)) {
483
- logger2.info("处理多分支内容");
496
+ logInfo("处理多分支内容");
484
497
  for (const choice of j.c) {
485
498
  const idx = choice.c ?? 0;
486
499
  if (idx === 0 && choice.v) {
487
500
  result += choice.v;
488
- logger2.info("添加内容:", choice.v);
501
+ logInfo("添加内容:", choice.v);
489
502
  }
490
503
  }
491
504
  } else if (j.v && typeof j.v === "string") {
492
505
  result += j.v;
493
- logger2.info("添加常规内容:", j.v);
506
+ logInfo("添加常规内容:", j.v);
494
507
  }
495
508
  } catch (error) {
496
509
  logger2.error("解析 JSON 失败:", rawJson, error);
497
510
  }
498
511
  }
499
512
  }
500
- logger2.info("最终结果长度:", result.length);
501
- logger2.info("最终结果内容:", result);
513
+ logInfo("最终结果长度:", result.length);
514
+ logInfo("最终结果内容:", result);
502
515
  if (currentMsgId) {
503
516
  await fetch("https://anuneko.com/api/v1/msg/select-choice", {
504
517
  method: "POST",
505
518
  headers,
506
- body: JSON.stringify({ msg_id: currentMsgId, choice_idx: 0 })
519
+ body: JSON.stringify({ msg_id: currentMsgId, choice_idx: 0 }),
520
+ signal
507
521
  });
508
522
  }
509
- logger2.info("收到完整响应");
523
+ logInfo("收到完整响应");
510
524
  return result || "❌ 未收到响应";
511
525
  } catch (error) {
512
526
  logger2.error("请求失败:", error);
@@ -568,13 +582,14 @@ function apply(ctx, config2) {
568
582
  }
569
583
  __name(apply, "apply");
570
584
  var Config2 = import_koishi.Schema.intersect([
571
- import_chat.ChatLunaPlugin.Config,
572
585
  import_koishi.Schema.object({
573
586
  platform: import_koishi.Schema.string().default("anuneko"),
574
587
  xToken: import_koishi.Schema.string().required().role("textarea", { rows: [2, 4] }).description("anuneko API 的 x-token"),
575
588
  cookie: import_koishi.Schema.string().role("textarea", { rows: [2, 4] }).description("anuneko API 的 Cookie(可选)"),
576
- loggerinfo: import_koishi.Schema.boolean().default(false).description("日志调试模式").experimental()
577
- })
589
+ loggerinfo: import_koishi.Schema.boolean().default(false).description("日志调试模式").experimental(),
590
+ requestTimeout: import_koishi.Schema.number().default(120).description("请求 API 的超时时间,单位为秒。")
591
+ }).description("基础设置"),
592
+ import_chat.ChatLunaPlugin.Config
578
593
  ]).i18n({
579
594
  "zh-CN": require_zh_CN_schema(),
580
595
  "en-US": require_en_US_schema()
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chatluna-anuneko-api-adapter",
3
3
  "description": "anuneko API adapter for ChatLuna, using pearktrue API.",
4
- "version": "1.0.0",
4
+ "version": "1.0.3",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -86,10 +86,12 @@ export class AnunekoRequester extends ModelRequester {
86
86
 
87
87
  try {
88
88
  logInfo('Creating new session with model:', modelName)
89
+ const signal = AbortSignal.timeout(this._pluginConfig.requestTimeout * 1000)
89
90
  const response = await fetch('https://anuneko.com/api/v1/chat', {
90
91
  method: 'POST',
91
92
  headers,
92
- body: JSON.stringify(data)
93
+ body: JSON.stringify(data),
94
+ signal
93
95
  })
94
96
 
95
97
  const responseData = await response.json()
@@ -117,10 +119,12 @@ export class AnunekoRequester extends ModelRequester {
117
119
 
118
120
  try {
119
121
  logInfo('Switching model to:', modelName)
122
+ const signal = AbortSignal.timeout(this._pluginConfig.requestTimeout * 1000)
120
123
  const response = await fetch('https://anuneko.com/api/v1/user/select_model', {
121
124
  method: 'POST',
122
125
  headers,
123
- body: JSON.stringify(data)
126
+ body: JSON.stringify(data),
127
+ signal
124
128
  })
125
129
 
126
130
  if (response.ok) {
@@ -141,10 +145,12 @@ export class AnunekoRequester extends ModelRequester {
141
145
  const data = { msg_id: msgId, choice_idx: 0 }
142
146
 
143
147
  try {
148
+ const signal = AbortSignal.timeout(this._pluginConfig.requestTimeout * 1000)
144
149
  await fetch('https://anuneko.com/api/v1/msg/select-choice', {
145
150
  method: 'POST',
146
151
  headers,
147
- body: JSON.stringify(data)
152
+ body: JSON.stringify(data),
153
+ signal
148
154
  })
149
155
  logInfo('Choice sent for msg_id:', msgId)
150
156
  } catch (error) {
@@ -212,10 +218,12 @@ export class AnunekoRequester extends ModelRequester {
212
218
  let currentMsgId: string | null = null
213
219
 
214
220
  // 使用流式请求
221
+ const signal = AbortSignal.timeout(this._pluginConfig.requestTimeout * 1000)
215
222
  const response = await fetch(url, {
216
223
  method: 'POST',
217
224
  headers,
218
- body: JSON.stringify(data)
225
+ body: JSON.stringify(data),
226
+ signal
219
227
  })
220
228
 
221
229
  if (!response.ok) {
package/src/index.ts CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  } from 'koishi-plugin-chatluna/utils/error'
7
7
  import { createLogger } from 'koishi-plugin-chatluna/utils/logger'
8
8
  import { AnunekoClient } from './anuneko-client'
9
- import { initializeLogger } from './logger'
9
+ import { initializeLogger, logInfo } from './logger'
10
10
 
11
11
  export let logger: Logger
12
12
  export let anunekoClient: any = null
@@ -21,7 +21,9 @@ export const usage = `
21
21
  </li>
22
22
  </ul>
23
23
  <p><strong>请注意:</strong></p>
24
- <p>该服务需要配置有效的 x-token 才能使用。支持橘猫(Orange Cat)和黑猫(Exotic Shorthair)两种模型。</p>
24
+ <p>该服务需要配置有效的 x-token 才能使用。</p>
25
+ <p>该服务可能需要科学上网。</p>
26
+ <p>支持橘猫(Orange Cat)和黑猫(Exotic Shorthair)两种模型。</p>
25
27
  `
26
28
 
27
29
  export function apply(ctx: Context, config: Config) {
@@ -47,17 +49,19 @@ export function apply(ctx: Context, config: Config) {
47
49
  'x-device_id': '7b75a432-6b24-48ad-b9d3-3dc57648e3e3',
48
50
  'x-token': config.xToken
49
51
  }
52
+ const signal = AbortSignal.timeout(config.requestTimeout * 1000)
50
53
 
51
54
  if (config.cookie) {
52
55
  headers['Cookie'] = config.cookie
53
56
  }
54
57
 
55
58
  // 创建新会话
56
- logger.info('创建新会话...')
59
+ logInfo('创建新会话...')
57
60
  const createResponse = await fetch('https://anuneko.com/api/v1/chat', {
58
61
  method: 'POST',
59
62
  headers,
60
- body: JSON.stringify({ model: 'Orange Cat' })
63
+ body: JSON.stringify({ model: 'Orange Cat' }),
64
+ signal
61
65
  })
62
66
 
63
67
  const createData = await createResponse.json()
@@ -66,17 +70,18 @@ export function apply(ctx: Context, config: Config) {
66
70
  return '❌ 创建会话失败'
67
71
  }
68
72
 
69
- logger.info('会话创建成功,ID:', chatId)
73
+ logInfo('会话创建成功,ID:', chatId)
70
74
 
71
75
  // 发送消息并获取流式响应
72
76
  const url = `https://anuneko.com/api/v1/msg/${chatId}/stream`
73
77
  const data = { contents: [message] }
74
78
 
75
- logger.info('发送消息...')
79
+ logInfo('发送消息...')
76
80
  const response = await fetch(url, {
77
81
  method: 'POST',
78
82
  headers,
79
- body: JSON.stringify(data)
83
+ body: JSON.stringify(data),
84
+ signal
80
85
  })
81
86
 
82
87
  if (!response.ok) {
@@ -95,7 +100,7 @@ export function apply(ctx: Context, config: Config) {
95
100
  if (done) break
96
101
 
97
102
  const chunkStr = decoder.decode(value, { stream: true })
98
- logger.info('收到数据块:', chunkStr.substring(0, 200))
103
+ logInfo('收到数据块:', chunkStr.substring(0, 200))
99
104
 
100
105
  const lines = chunkStr.split('\n')
101
106
 
@@ -104,10 +109,10 @@ export function apply(ctx: Context, config: Config) {
104
109
  continue
105
110
  }
106
111
 
107
- logger.info('处理行:', line.substring(0, 100))
112
+ logInfo('处理行:', line.substring(0, 100))
108
113
 
109
114
  if (!line.startsWith('data: ')) {
110
- logger.warn('非 data: 格式的行:', line)
115
+ logInfo('非 data: 格式的行:', line)
111
116
  continue
112
117
  }
113
118
 
@@ -116,28 +121,28 @@ export function apply(ctx: Context, config: Config) {
116
121
 
117
122
  try {
118
123
  const j = JSON.parse(rawJson)
119
- logger.info('解析的 JSON:', JSON.stringify(j))
124
+ logInfo('解析的 JSON:', JSON.stringify(j))
120
125
 
121
126
  if (j.msg_id) {
122
127
  currentMsgId = j.msg_id
123
- logger.info('更新 msg_id:', currentMsgId)
128
+ logInfo('更新 msg_id:', currentMsgId)
124
129
  }
125
130
 
126
131
  // 处理多分支内容
127
132
  if (j.c && Array.isArray(j.c)) {
128
- logger.info('处理多分支内容')
133
+ logInfo('处理多分支内容')
129
134
  for (const choice of j.c) {
130
135
  const idx = choice.c ?? 0
131
136
  if (idx === 0 && choice.v) {
132
137
  result += choice.v
133
- logger.info('添加内容:', choice.v)
138
+ logInfo('添加内容:', choice.v)
134
139
  }
135
140
  }
136
141
  }
137
142
  // 处理常规内容
138
143
  else if (j.v && typeof j.v === 'string') {
139
144
  result += j.v
140
- logger.info('添加常规内容:', j.v)
145
+ logInfo('添加常规内容:', j.v)
141
146
  }
142
147
  } catch (error) {
143
148
  logger.error('解析 JSON 失败:', rawJson, error)
@@ -145,19 +150,20 @@ export function apply(ctx: Context, config: Config) {
145
150
  }
146
151
  }
147
152
 
148
- logger.info('最终结果长度:', result.length)
149
- logger.info('最终结果内容:', result)
153
+ logInfo('最终结果长度:', result.length)
154
+ logInfo('最终结果内容:', result)
150
155
 
151
156
  // 自动选择分支
152
157
  if (currentMsgId) {
153
158
  await fetch('https://anuneko.com/api/v1/msg/select-choice', {
154
159
  method: 'POST',
155
160
  headers,
156
- body: JSON.stringify({ msg_id: currentMsgId, choice_idx: 0 })
161
+ body: JSON.stringify({ msg_id: currentMsgId, choice_idx: 0 }),
162
+ signal
157
163
  })
158
164
  }
159
165
 
160
- logger.info('收到完整响应')
166
+ logInfo('收到完整响应')
161
167
  return result || '❌ 未收到响应'
162
168
  } catch (error) {
163
169
  logger.error('请求失败:', error)
@@ -237,10 +243,10 @@ export interface Config extends ChatLunaPlugin.Config {
237
243
  xToken: string
238
244
  cookie?: string
239
245
  loggerinfo: boolean
246
+ requestTimeout: number
240
247
  }
241
248
 
242
249
  export const Config: Schema<Config> = Schema.intersect([
243
- ChatLunaPlugin.Config,
244
250
  Schema.object({
245
251
  platform: Schema.string().default('anuneko'),
246
252
  xToken: Schema.string()
@@ -253,8 +259,12 @@ export const Config: Schema<Config> = Schema.intersect([
253
259
  loggerinfo: Schema.boolean()
254
260
  .default(false)
255
261
  .description('日志调试模式')
256
- .experimental()
257
- })
262
+ .experimental(),
263
+ requestTimeout: Schema.number()
264
+ .default(120)
265
+ .description('请求 API 的超时时间,单位为秒。')
266
+ }).description('基础设置'),
267
+ ChatLunaPlugin.Config,
258
268
  ]).i18n({
259
269
  'zh-CN': require('./locales/zh-CN.schema.yml'),
260
270
  'en-US': require('./locales/en-US.schema.yml')