koishi-plugin-chatluna-anuneko-api-adapter 1.0.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/lib/anuneko-client.d.ts +16 -0
- package/lib/anuneko-requester.d.ts +20 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.js +594 -0
- package/lib/logger.d.ts +6 -0
- package/package.json +69 -0
- package/readme.md +5 -0
- package/src/anuneko-client.ts +112 -0
- package/src/anuneko-requester.ts +318 -0
- package/src/index.ts +265 -0
- package/src/locales/en-US.schema.yml +4 -0
- package/src/locales/zh-CN.schema.yml +4 -0
- package/src/logger.ts +18 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { PlatformModelAndEmbeddingsClient } from 'koishi-plugin-chatluna/llm-core/platform/client';
|
|
3
|
+
import { ChatLunaBaseEmbeddings, ChatLunaChatModel } from 'koishi-plugin-chatluna/llm-core/platform/model';
|
|
4
|
+
import { ModelInfo } from 'koishi-plugin-chatluna/llm-core/platform/types';
|
|
5
|
+
import { Config } from './index';
|
|
6
|
+
import { ChatLunaPlugin } from 'koishi-plugin-chatluna/services/chat';
|
|
7
|
+
import { RunnableConfig } from '@langchain/core/runnables';
|
|
8
|
+
export declare class AnunekoClient extends PlatformModelAndEmbeddingsClient {
|
|
9
|
+
private _config;
|
|
10
|
+
plugin: ChatLunaPlugin;
|
|
11
|
+
platform: string;
|
|
12
|
+
private _requester;
|
|
13
|
+
constructor(ctx: Context, _config: Config, plugin: ChatLunaPlugin);
|
|
14
|
+
refreshModels(config?: RunnableConfig): Promise<ModelInfo[]>;
|
|
15
|
+
protected _createModel(model: string): ChatLunaChatModel | ChatLunaBaseEmbeddings;
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
2
|
+
import { ModelRequester, ModelRequestParams } from 'koishi-plugin-chatluna/llm-core/platform/api';
|
|
3
|
+
import { ClientConfig, ClientConfigPool } from 'koishi-plugin-chatluna/llm-core/platform/config';
|
|
4
|
+
import { Config } from './index';
|
|
5
|
+
import { ChatLunaPlugin } from 'koishi-plugin-chatluna/services/chat';
|
|
6
|
+
import { Context } from 'koishi';
|
|
7
|
+
export declare class AnunekoRequester extends ModelRequester {
|
|
8
|
+
_pluginConfig: Config;
|
|
9
|
+
private sessionMap;
|
|
10
|
+
private modelMap;
|
|
11
|
+
constructor(ctx: Context, _configPool: ClientConfigPool<ClientConfig>, _pluginConfig: Config, _plugin: ChatLunaPlugin);
|
|
12
|
+
clearSession(userId: string): boolean;
|
|
13
|
+
clearAllSessions(): number;
|
|
14
|
+
buildHeaders(): Record<string, string>;
|
|
15
|
+
private createNewSession;
|
|
16
|
+
private switchModel;
|
|
17
|
+
private sendChoice;
|
|
18
|
+
completionStreamInternal(params: ModelRequestParams): AsyncGenerator<ChatGenerationChunk>;
|
|
19
|
+
get logger(): import("reggol");
|
|
20
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Context, Logger, Schema } from 'koishi';
|
|
2
|
+
import { ChatLunaPlugin } from 'koishi-plugin-chatluna/services/chat';
|
|
3
|
+
export declare let logger: Logger;
|
|
4
|
+
export declare let anunekoClient: any;
|
|
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";
|
|
7
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
8
|
+
export interface Config extends ChatLunaPlugin.Config {
|
|
9
|
+
platform: string;
|
|
10
|
+
xToken: string;
|
|
11
|
+
cookie?: string;
|
|
12
|
+
loggerinfo: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare const Config: Schema<Config>;
|
|
15
|
+
export declare const inject: string[];
|
|
16
|
+
export declare const name = "chatluna-anuneko-api-adapter";
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
7
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
8
|
+
};
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name2 in all)
|
|
11
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
+
|
|
23
|
+
// src/locales/zh-CN.schema.yml
|
|
24
|
+
var require_zh_CN_schema = __commonJS({
|
|
25
|
+
"src/locales/zh-CN.schema.yml"(exports2, module2) {
|
|
26
|
+
module2.exports = { platform: "平台名称", xToken: "anuneko API 的 x-token", cookie: "anuneko API 的 Cookie(可选)", loggerinfo: "日志调试模式" };
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// src/locales/en-US.schema.yml
|
|
31
|
+
var require_en_US_schema = __commonJS({
|
|
32
|
+
"src/locales/en-US.schema.yml"(exports2, module2) {
|
|
33
|
+
module2.exports = { platform: "Platform name", xToken: "x-token for anuneko API", cookie: "Cookie for anuneko API (optional)", loggerinfo: "Logger debug mode" };
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// src/index.ts
|
|
38
|
+
var src_exports = {};
|
|
39
|
+
__export(src_exports, {
|
|
40
|
+
Config: () => Config2,
|
|
41
|
+
anunekoClient: () => anunekoClient,
|
|
42
|
+
apply: () => apply,
|
|
43
|
+
inject: () => inject,
|
|
44
|
+
logger: () => logger2,
|
|
45
|
+
name: () => name,
|
|
46
|
+
reusable: () => reusable,
|
|
47
|
+
usage: () => usage
|
|
48
|
+
});
|
|
49
|
+
module.exports = __toCommonJS(src_exports);
|
|
50
|
+
var import_koishi = require("koishi");
|
|
51
|
+
var import_chat = require("koishi-plugin-chatluna/services/chat");
|
|
52
|
+
var import_error2 = require("koishi-plugin-chatluna/utils/error");
|
|
53
|
+
var import_logger3 = require("koishi-plugin-chatluna/utils/logger");
|
|
54
|
+
|
|
55
|
+
// src/anuneko-client.ts
|
|
56
|
+
var import_client = require("koishi-plugin-chatluna/llm-core/platform/client");
|
|
57
|
+
var import_model = require("koishi-plugin-chatluna/llm-core/platform/model");
|
|
58
|
+
var import_types = require("koishi-plugin-chatluna/llm-core/platform/types");
|
|
59
|
+
var import_error = require("koishi-plugin-chatluna/utils/error");
|
|
60
|
+
|
|
61
|
+
// src/anuneko-requester.ts
|
|
62
|
+
var import_outputs = require("@langchain/core/outputs");
|
|
63
|
+
var import_api = require("koishi-plugin-chatluna/llm-core/platform/api");
|
|
64
|
+
|
|
65
|
+
// src/logger.ts
|
|
66
|
+
var logger;
|
|
67
|
+
var config;
|
|
68
|
+
function initializeLogger(newLogger, newConfig) {
|
|
69
|
+
logger = newLogger;
|
|
70
|
+
config = newConfig;
|
|
71
|
+
}
|
|
72
|
+
__name(initializeLogger, "initializeLogger");
|
|
73
|
+
function logInfo(...args) {
|
|
74
|
+
if (config?.loggerinfo) {
|
|
75
|
+
logger?.info?.(...args);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
__name(logInfo, "logInfo");
|
|
79
|
+
|
|
80
|
+
// src/anuneko-requester.ts
|
|
81
|
+
var import_messages = require("@langchain/core/messages");
|
|
82
|
+
var sleep = /* @__PURE__ */ __name((ms) => new Promise((resolve) => setTimeout(resolve, ms)), "sleep");
|
|
83
|
+
var AnunekoRequester = class extends import_api.ModelRequester {
|
|
84
|
+
constructor(ctx, _configPool, _pluginConfig, _plugin) {
|
|
85
|
+
super(ctx, _configPool, _pluginConfig, _plugin);
|
|
86
|
+
this._pluginConfig = _pluginConfig;
|
|
87
|
+
}
|
|
88
|
+
static {
|
|
89
|
+
__name(this, "AnunekoRequester");
|
|
90
|
+
}
|
|
91
|
+
// 存储每个用户的会话ID
|
|
92
|
+
sessionMap = /* @__PURE__ */ new Map();
|
|
93
|
+
// 存储每个用户的当前模型
|
|
94
|
+
modelMap = /* @__PURE__ */ new Map();
|
|
95
|
+
// 清理指定用户的会话
|
|
96
|
+
clearSession(userId) {
|
|
97
|
+
const hasSession = this.sessionMap.has(userId);
|
|
98
|
+
this.sessionMap.delete(userId);
|
|
99
|
+
this.modelMap.delete(userId);
|
|
100
|
+
return hasSession;
|
|
101
|
+
}
|
|
102
|
+
// 清理所有会话
|
|
103
|
+
clearAllSessions() {
|
|
104
|
+
const count = this.sessionMap.size;
|
|
105
|
+
this.sessionMap.clear();
|
|
106
|
+
this.modelMap.clear();
|
|
107
|
+
return count;
|
|
108
|
+
}
|
|
109
|
+
// 构建请求头
|
|
110
|
+
buildHeaders() {
|
|
111
|
+
const headers = {
|
|
112
|
+
"accept": "*/*",
|
|
113
|
+
"content-type": "application/json",
|
|
114
|
+
"origin": "https://anuneko.com",
|
|
115
|
+
"referer": "https://anuneko.com/",
|
|
116
|
+
"user-agent": "Mozilla/5.0",
|
|
117
|
+
"x-app_id": "com.anuttacon.neko",
|
|
118
|
+
"x-client_type": "4",
|
|
119
|
+
"x-device_id": "7b75a432-6b24-48ad-b9d3-3dc57648e3e3",
|
|
120
|
+
"x-token": this._pluginConfig.xToken
|
|
121
|
+
};
|
|
122
|
+
if (this._pluginConfig.cookie) {
|
|
123
|
+
headers["Cookie"] = this._pluginConfig.cookie;
|
|
124
|
+
}
|
|
125
|
+
return headers;
|
|
126
|
+
}
|
|
127
|
+
// 创建新会话
|
|
128
|
+
async createNewSession(userId, modelName) {
|
|
129
|
+
const headers = this.buildHeaders();
|
|
130
|
+
const data = { model: modelName };
|
|
131
|
+
try {
|
|
132
|
+
logInfo("Creating new session with model:", modelName);
|
|
133
|
+
const response = await fetch("https://anuneko.com/api/v1/chat", {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers,
|
|
136
|
+
body: JSON.stringify(data)
|
|
137
|
+
});
|
|
138
|
+
const responseData = await response.json();
|
|
139
|
+
const chatId = responseData.chat_id || responseData.id;
|
|
140
|
+
if (chatId) {
|
|
141
|
+
this.sessionMap.set(userId, chatId);
|
|
142
|
+
this.modelMap.set(userId, modelName);
|
|
143
|
+
logInfo("New session created with ID:", chatId);
|
|
144
|
+
await this.switchModel(userId, chatId, modelName);
|
|
145
|
+
return chatId;
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
this.logger.error("Failed to create new session:", error);
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
// 切换模型
|
|
153
|
+
async switchModel(userId, chatId, modelName) {
|
|
154
|
+
const headers = this.buildHeaders();
|
|
155
|
+
const data = { chat_id: chatId, model: modelName };
|
|
156
|
+
try {
|
|
157
|
+
logInfo("Switching model to:", modelName);
|
|
158
|
+
const response = await fetch("https://anuneko.com/api/v1/user/select_model", {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers,
|
|
161
|
+
body: JSON.stringify(data)
|
|
162
|
+
});
|
|
163
|
+
if (response.ok) {
|
|
164
|
+
this.modelMap.set(userId, modelName);
|
|
165
|
+
logInfo("Model switched successfully");
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
this.logger.error("Failed to switch model:", error);
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
// 自动选择分支
|
|
174
|
+
async sendChoice(msgId) {
|
|
175
|
+
const headers = this.buildHeaders();
|
|
176
|
+
const data = { msg_id: msgId, choice_idx: 0 };
|
|
177
|
+
try {
|
|
178
|
+
await fetch("https://anuneko.com/api/v1/msg/select-choice", {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers,
|
|
181
|
+
body: JSON.stringify(data)
|
|
182
|
+
});
|
|
183
|
+
logInfo("Choice sent for msg_id:", msgId);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
this.logger.error("Failed to send choice:", error);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// 流式回复
|
|
189
|
+
async *completionStreamInternal(params) {
|
|
190
|
+
const internalParams = params;
|
|
191
|
+
const humanMessages = internalParams.input.filter(
|
|
192
|
+
(message) => message instanceof import_messages.HumanMessage
|
|
193
|
+
);
|
|
194
|
+
const lastMessage = humanMessages.at(-1);
|
|
195
|
+
logInfo("Receive params from chatluna", JSON.stringify(params, null, 2));
|
|
196
|
+
if (!lastMessage) {
|
|
197
|
+
this.logger.warn("No human message found in the input.");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const prompt = lastMessage.content;
|
|
201
|
+
const sessionKey = lastMessage.channelId || lastMessage.id || "default";
|
|
202
|
+
logInfo("使用会话标识:", sessionKey);
|
|
203
|
+
let modelName = "Orange Cat";
|
|
204
|
+
if (params.model.includes("exotic") || params.model.includes("shorthair")) {
|
|
205
|
+
modelName = "Exotic Shorthair";
|
|
206
|
+
}
|
|
207
|
+
let sessionId = this.sessionMap.get(sessionKey);
|
|
208
|
+
const currentModel = this.modelMap.get(sessionKey);
|
|
209
|
+
if (!sessionId || currentModel !== modelName) {
|
|
210
|
+
sessionId = await this.createNewSession(sessionKey, modelName);
|
|
211
|
+
if (!sessionId) {
|
|
212
|
+
const errorText = "创建会话失败,请稍后再试。";
|
|
213
|
+
yield new import_outputs.ChatGenerationChunk({
|
|
214
|
+
text: errorText,
|
|
215
|
+
message: new import_messages.AIMessageChunk({ content: errorText })
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const headers = this.buildHeaders();
|
|
221
|
+
const url = `https://anuneko.com/api/v1/msg/${sessionId}/stream`;
|
|
222
|
+
const data = { contents: [prompt] };
|
|
223
|
+
let retries = 3;
|
|
224
|
+
while (retries > 0) {
|
|
225
|
+
try {
|
|
226
|
+
logInfo("Sending request to API:", url, JSON.stringify(data, null, 2));
|
|
227
|
+
let result = "";
|
|
228
|
+
let currentMsgId = null;
|
|
229
|
+
const response = await fetch(url, {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers,
|
|
232
|
+
body: JSON.stringify(data)
|
|
233
|
+
});
|
|
234
|
+
if (!response.ok) {
|
|
235
|
+
throw new Error(`Request failed with status ${response.status}`);
|
|
236
|
+
}
|
|
237
|
+
const reader = response.body.getReader();
|
|
238
|
+
const decoder = new TextDecoder();
|
|
239
|
+
while (true) {
|
|
240
|
+
const { done, value } = await reader.read();
|
|
241
|
+
if (done) break;
|
|
242
|
+
const chunkStr = decoder.decode(value, { stream: true });
|
|
243
|
+
const lines = chunkStr.split("\n");
|
|
244
|
+
for (const line of lines) {
|
|
245
|
+
if (!line || !line.startsWith("data: ")) {
|
|
246
|
+
if (line.trim()) {
|
|
247
|
+
try {
|
|
248
|
+
const errorJson = JSON.parse(line);
|
|
249
|
+
if (errorJson.code === "chat_choice_shown") {
|
|
250
|
+
const errorText = "⚠️ 检测到对话分支未选择,请重试或新建会话。";
|
|
251
|
+
yield new import_outputs.ChatGenerationChunk({
|
|
252
|
+
text: errorText,
|
|
253
|
+
message: new import_messages.AIMessageChunk({ content: errorText })
|
|
254
|
+
});
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
} catch {
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const rawJson = line.substring(6).trim();
|
|
263
|
+
if (!rawJson) continue;
|
|
264
|
+
try {
|
|
265
|
+
const j = JSON.parse(rawJson);
|
|
266
|
+
if (j.msg_id) {
|
|
267
|
+
currentMsgId = j.msg_id;
|
|
268
|
+
}
|
|
269
|
+
if (j.c && Array.isArray(j.c)) {
|
|
270
|
+
for (const choice of j.c) {
|
|
271
|
+
const idx = choice.c ?? 0;
|
|
272
|
+
if (idx === 0 && choice.v) {
|
|
273
|
+
result += choice.v;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
} else if (j.v && typeof j.v === "string") {
|
|
277
|
+
result += j.v;
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
logInfo("Failed to parse JSON:", rawJson, error);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (currentMsgId) {
|
|
285
|
+
await this.sendChoice(currentMsgId);
|
|
286
|
+
}
|
|
287
|
+
logInfo("Received complete response:", result);
|
|
288
|
+
yield new import_outputs.ChatGenerationChunk({
|
|
289
|
+
text: result,
|
|
290
|
+
message: new import_messages.AIMessageChunk({ content: result })
|
|
291
|
+
});
|
|
292
|
+
return;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
this.logger.error(`Request failed, ${retries - 1} retries left.`, error);
|
|
295
|
+
retries--;
|
|
296
|
+
if (retries === 0) {
|
|
297
|
+
const errorText = `请求失败,请稍后再试: ${error.message}`;
|
|
298
|
+
yield new import_outputs.ChatGenerationChunk({
|
|
299
|
+
text: errorText,
|
|
300
|
+
message: new import_messages.AIMessageChunk({ content: errorText })
|
|
301
|
+
});
|
|
302
|
+
} else {
|
|
303
|
+
await sleep(1e3);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
get logger() {
|
|
309
|
+
return logger2;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// src/anuneko-client.ts
|
|
314
|
+
var import_v1_shared_adapter = require("@chatluna/v1-shared-adapter");
|
|
315
|
+
var AnunekoClient = class extends import_client.PlatformModelAndEmbeddingsClient {
|
|
316
|
+
constructor(ctx, _config, plugin) {
|
|
317
|
+
super(ctx, plugin.platformConfigPool);
|
|
318
|
+
this._config = _config;
|
|
319
|
+
this.plugin = plugin;
|
|
320
|
+
this.platform = _config.platform;
|
|
321
|
+
this._requester = new AnunekoRequester(
|
|
322
|
+
ctx,
|
|
323
|
+
plugin.platformConfigPool,
|
|
324
|
+
_config,
|
|
325
|
+
plugin
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
static {
|
|
329
|
+
__name(this, "AnunekoClient");
|
|
330
|
+
}
|
|
331
|
+
platform = "anuneko";
|
|
332
|
+
_requester;
|
|
333
|
+
async refreshModels(config2) {
|
|
334
|
+
return [
|
|
335
|
+
{
|
|
336
|
+
name: "orange-cat",
|
|
337
|
+
type: import_types.ModelType.llm,
|
|
338
|
+
capabilities: [],
|
|
339
|
+
maxTokens: 128e3
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "exotic-shorthair",
|
|
343
|
+
type: import_types.ModelType.llm,
|
|
344
|
+
capabilities: [],
|
|
345
|
+
maxTokens: 128e3
|
|
346
|
+
}
|
|
347
|
+
];
|
|
348
|
+
}
|
|
349
|
+
_createModel(model) {
|
|
350
|
+
logInfo("[anuneko] _createModel called for model:", model);
|
|
351
|
+
logInfo("[anuneko] _modelInfos keys:", Object.keys(this._modelInfos));
|
|
352
|
+
const info = this._modelInfos[model];
|
|
353
|
+
logInfo("[anuneko] Model info:", JSON.stringify(info));
|
|
354
|
+
if (info == null) {
|
|
355
|
+
this.ctx.logger.error("[anuneko] Model info is null!");
|
|
356
|
+
throw new import_error.ChatLunaError(
|
|
357
|
+
import_error.ChatLunaErrorCode.MODEL_NOT_FOUND,
|
|
358
|
+
new Error(
|
|
359
|
+
`The model ${model} is not found in the models: ${JSON.stringify(Object.keys(this._modelInfos))}`
|
|
360
|
+
)
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
logInfo("[anuneko] Model type:", info.type, "Expected:", import_types.ModelType.llm);
|
|
364
|
+
logInfo("[anuneko] Type check:", info.type === import_types.ModelType.llm);
|
|
365
|
+
if (info.type === import_types.ModelType.llm) {
|
|
366
|
+
logInfo("[anuneko] Creating ChatLunaChatModel...");
|
|
367
|
+
const modelMaxContextSize = (0, import_v1_shared_adapter.getModelMaxContextSize)(info);
|
|
368
|
+
const chatModel = new import_model.ChatLunaChatModel({
|
|
369
|
+
modelInfo: info,
|
|
370
|
+
requester: this._requester,
|
|
371
|
+
model,
|
|
372
|
+
maxTokenLimit: info.maxTokens || modelMaxContextSize || 128e3,
|
|
373
|
+
modelMaxContextSize,
|
|
374
|
+
timeout: this._config.timeout,
|
|
375
|
+
maxRetries: this._config.maxRetries,
|
|
376
|
+
llmType: "openai",
|
|
377
|
+
isThinkModel: false
|
|
378
|
+
});
|
|
379
|
+
logInfo("[anuneko] ChatLunaChatModel created successfully");
|
|
380
|
+
logInfo("[anuneko] Instance check:", chatModel instanceof import_model.ChatLunaChatModel);
|
|
381
|
+
return chatModel;
|
|
382
|
+
}
|
|
383
|
+
this.ctx.logger.error("[anuneko] Model type is not LLM!");
|
|
384
|
+
throw new import_error.ChatLunaError(
|
|
385
|
+
import_error.ChatLunaErrorCode.MODEL_NOT_FOUND,
|
|
386
|
+
new Error(
|
|
387
|
+
`The model ${model} is not a chat model, type is ${info.type}`
|
|
388
|
+
)
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// src/index.ts
|
|
394
|
+
var logger2;
|
|
395
|
+
var anunekoClient = null;
|
|
396
|
+
var reusable = true;
|
|
397
|
+
var usage = `
|
|
398
|
+
<p><strong>零成本、快速体验Chatluna</strong>。</p>
|
|
399
|
+
<ul>
|
|
400
|
+
<li><strong>API来源:</strong> anuneko.com</li>
|
|
401
|
+
<li>
|
|
402
|
+
<strong>接口地址:</strong>
|
|
403
|
+
<a href="https://anuneko.com" target="_blank" rel="noopener noreferrer">https://anuneko.com</a>
|
|
404
|
+
</li>
|
|
405
|
+
</ul>
|
|
406
|
+
<p><strong>请注意:</strong></p>
|
|
407
|
+
<p>该服务需要配置有效的 x-token 才能使用。支持橘猫(Orange Cat)和黑猫(Exotic Shorthair)两种模型。</p>
|
|
408
|
+
`;
|
|
409
|
+
function apply(ctx, config2) {
|
|
410
|
+
logger2 = (0, import_logger3.createLogger)(ctx, "chatluna-anuneko-api-adapter");
|
|
411
|
+
initializeLogger(logger2, config2);
|
|
412
|
+
ctx.command("anuneko <message:text>", "测试 anuneko API").action(async ({ session }, message) => {
|
|
413
|
+
if (!message) {
|
|
414
|
+
return "请输入消息内容,例如:/anuneko 你好";
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
const headers = {
|
|
418
|
+
"accept": "*/*",
|
|
419
|
+
"content-type": "application/json",
|
|
420
|
+
"origin": "https://anuneko.com",
|
|
421
|
+
"referer": "https://anuneko.com/",
|
|
422
|
+
"user-agent": "Mozilla/5.0",
|
|
423
|
+
"x-app_id": "com.anuttacon.neko",
|
|
424
|
+
"x-client_type": "4",
|
|
425
|
+
"x-device_id": "7b75a432-6b24-48ad-b9d3-3dc57648e3e3",
|
|
426
|
+
"x-token": config2.xToken
|
|
427
|
+
};
|
|
428
|
+
if (config2.cookie) {
|
|
429
|
+
headers["Cookie"] = config2.cookie;
|
|
430
|
+
}
|
|
431
|
+
logger2.info("创建新会话...");
|
|
432
|
+
const createResponse = await fetch("https://anuneko.com/api/v1/chat", {
|
|
433
|
+
method: "POST",
|
|
434
|
+
headers,
|
|
435
|
+
body: JSON.stringify({ model: "Orange Cat" })
|
|
436
|
+
});
|
|
437
|
+
const createData = await createResponse.json();
|
|
438
|
+
const chatId = createData.chat_id || createData.id;
|
|
439
|
+
if (!chatId) {
|
|
440
|
+
return "❌ 创建会话失败";
|
|
441
|
+
}
|
|
442
|
+
logger2.info("会话创建成功,ID:", chatId);
|
|
443
|
+
const url = `https://anuneko.com/api/v1/msg/${chatId}/stream`;
|
|
444
|
+
const data = { contents: [message] };
|
|
445
|
+
logger2.info("发送消息...");
|
|
446
|
+
const response = await fetch(url, {
|
|
447
|
+
method: "POST",
|
|
448
|
+
headers,
|
|
449
|
+
body: JSON.stringify(data)
|
|
450
|
+
});
|
|
451
|
+
if (!response.ok) {
|
|
452
|
+
return `❌ 请求失败: ${response.status} ${response.statusText}`;
|
|
453
|
+
}
|
|
454
|
+
let result = "";
|
|
455
|
+
let currentMsgId = null;
|
|
456
|
+
const reader = response.body.getReader();
|
|
457
|
+
const decoder = new TextDecoder();
|
|
458
|
+
while (true) {
|
|
459
|
+
const { done, value } = await reader.read();
|
|
460
|
+
if (done) break;
|
|
461
|
+
const chunkStr = decoder.decode(value, { stream: true });
|
|
462
|
+
logger2.info("收到数据块:", chunkStr.substring(0, 200));
|
|
463
|
+
const lines = chunkStr.split("\n");
|
|
464
|
+
for (const line of lines) {
|
|
465
|
+
if (!line.trim()) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
logger2.info("处理行:", line.substring(0, 100));
|
|
469
|
+
if (!line.startsWith("data: ")) {
|
|
470
|
+
logger2.warn("非 data: 格式的行:", line);
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
const rawJson = line.substring(6).trim();
|
|
474
|
+
if (!rawJson) continue;
|
|
475
|
+
try {
|
|
476
|
+
const j = JSON.parse(rawJson);
|
|
477
|
+
logger2.info("解析的 JSON:", JSON.stringify(j));
|
|
478
|
+
if (j.msg_id) {
|
|
479
|
+
currentMsgId = j.msg_id;
|
|
480
|
+
logger2.info("更新 msg_id:", currentMsgId);
|
|
481
|
+
}
|
|
482
|
+
if (j.c && Array.isArray(j.c)) {
|
|
483
|
+
logger2.info("处理多分支内容");
|
|
484
|
+
for (const choice of j.c) {
|
|
485
|
+
const idx = choice.c ?? 0;
|
|
486
|
+
if (idx === 0 && choice.v) {
|
|
487
|
+
result += choice.v;
|
|
488
|
+
logger2.info("添加内容:", choice.v);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
} else if (j.v && typeof j.v === "string") {
|
|
492
|
+
result += j.v;
|
|
493
|
+
logger2.info("添加常规内容:", j.v);
|
|
494
|
+
}
|
|
495
|
+
} catch (error) {
|
|
496
|
+
logger2.error("解析 JSON 失败:", rawJson, error);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
logger2.info("最终结果长度:", result.length);
|
|
501
|
+
logger2.info("最终结果内容:", result);
|
|
502
|
+
if (currentMsgId) {
|
|
503
|
+
await fetch("https://anuneko.com/api/v1/msg/select-choice", {
|
|
504
|
+
method: "POST",
|
|
505
|
+
headers,
|
|
506
|
+
body: JSON.stringify({ msg_id: currentMsgId, choice_idx: 0 })
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
logger2.info("收到完整响应");
|
|
510
|
+
return result || "❌ 未收到响应";
|
|
511
|
+
} catch (error) {
|
|
512
|
+
logger2.error("请求失败:", error);
|
|
513
|
+
return `❌ 请求失败: ${error.message}`;
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
ctx.command("anuneko-clean", "清理当前频道的 anuneko 对话记录").action(async ({ session }) => {
|
|
517
|
+
try {
|
|
518
|
+
if (!anunekoClient) {
|
|
519
|
+
return "❌ anuneko 客户端未初始化";
|
|
520
|
+
}
|
|
521
|
+
const sessionKey = session.channelId || session.userId;
|
|
522
|
+
const requester = anunekoClient._requester;
|
|
523
|
+
if (requester && typeof requester.clearSession === "function") {
|
|
524
|
+
const cleared = requester.clearSession(sessionKey);
|
|
525
|
+
if (cleared) {
|
|
526
|
+
return "✅ 已清理当前频道的对话记录,下次对话将创建新会话";
|
|
527
|
+
} else {
|
|
528
|
+
return "✅ 当前频道还没有对话记录";
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return "❌ 无法访问会话管理器";
|
|
532
|
+
} catch (error) {
|
|
533
|
+
logger2.error("清理失败:", error);
|
|
534
|
+
return `❌ 清理失败: ${error.message}`;
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
ctx.on("ready", async () => {
|
|
538
|
+
if (config2.platform == null || config2.platform.length < 1) {
|
|
539
|
+
throw new import_error2.ChatLunaError(
|
|
540
|
+
import_error2.ChatLunaErrorCode.UNKNOWN_ERROR,
|
|
541
|
+
new Error("Cannot find any platform")
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
const platform = config2.platform;
|
|
545
|
+
const plugin = new import_chat.ChatLunaPlugin(ctx, config2, platform);
|
|
546
|
+
plugin.parseConfig((config3) => {
|
|
547
|
+
return [
|
|
548
|
+
{
|
|
549
|
+
apiKey: config3.xToken || "any",
|
|
550
|
+
apiEndpoint: "https://anuneko.com",
|
|
551
|
+
platform,
|
|
552
|
+
chatLimit: config3.chatTimeLimit,
|
|
553
|
+
timeout: config3.timeout,
|
|
554
|
+
maxRetries: config3.maxRetries,
|
|
555
|
+
concurrentMaxSize: config3.chatConcurrentMaxSize
|
|
556
|
+
}
|
|
557
|
+
];
|
|
558
|
+
});
|
|
559
|
+
plugin.registerClient(() => {
|
|
560
|
+
const client = new AnunekoClient(ctx, config2, plugin);
|
|
561
|
+
if (!anunekoClient) {
|
|
562
|
+
anunekoClient = client;
|
|
563
|
+
}
|
|
564
|
+
return client;
|
|
565
|
+
});
|
|
566
|
+
await plugin.initClient();
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
__name(apply, "apply");
|
|
570
|
+
var Config2 = import_koishi.Schema.intersect([
|
|
571
|
+
import_chat.ChatLunaPlugin.Config,
|
|
572
|
+
import_koishi.Schema.object({
|
|
573
|
+
platform: import_koishi.Schema.string().default("anuneko"),
|
|
574
|
+
xToken: import_koishi.Schema.string().required().role("textarea", { rows: [2, 4] }).description("anuneko API 的 x-token"),
|
|
575
|
+
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
|
+
})
|
|
578
|
+
]).i18n({
|
|
579
|
+
"zh-CN": require_zh_CN_schema(),
|
|
580
|
+
"en-US": require_en_US_schema()
|
|
581
|
+
});
|
|
582
|
+
var inject = ["chatluna"];
|
|
583
|
+
var name = "chatluna-anuneko-api-adapter";
|
|
584
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
585
|
+
0 && (module.exports = {
|
|
586
|
+
Config,
|
|
587
|
+
anunekoClient,
|
|
588
|
+
apply,
|
|
589
|
+
inject,
|
|
590
|
+
logger,
|
|
591
|
+
name,
|
|
592
|
+
reusable,
|
|
593
|
+
usage
|
|
594
|
+
});
|
package/lib/logger.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Logger } from 'koishi';
|
|
2
|
+
import { Config } from './index';
|
|
3
|
+
export declare let logger: Logger;
|
|
4
|
+
export declare let config: Config;
|
|
5
|
+
export declare function initializeLogger(newLogger: Logger, newConfig: Config): void;
|
|
6
|
+
export declare function logInfo(...args: any[]): void;
|