@kevisual/ai 0.0.19 → 0.0.20

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.
@@ -5,8 +5,8 @@ type ChatMessage = {
5
5
  role?: 'user' | 'assistant' | 'system' | 'tool';
6
6
  content: string;
7
7
  };
8
- type ChatMessageOptions = {
9
- messages: ChatMessage[];
8
+ type ChatMessageOptions<T = {}> = {
9
+ messages?: ChatMessage[];
10
10
  /**
11
11
  * 模型名称
12
12
  */
@@ -46,7 +46,7 @@ type ChatMessageOptions = {
46
46
  stream?: boolean;
47
47
  /**
48
48
  * 是否能够思考
49
- * 如果会话是千文,服务器的接口,默认为 true
49
+ * 如果会话是千文,服务器的接口,默认为 false
50
50
  */
51
51
  enable_thinking?: boolean;
52
52
  response_format?: 'text' | 'json' | 'xml' | 'html';
@@ -54,7 +54,8 @@ type ChatMessageOptions = {
54
54
  * 工具调用参数
55
55
  */
56
56
  tool_calls?: any;
57
- };
57
+ [key: string]: any;
58
+ } & T;
58
59
  type Choice = {
59
60
  finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter' | 'function_call';
60
61
  index: number;
@@ -161,6 +162,7 @@ type EmbeddingMessageComplete = {
161
162
  usage: Usage;
162
163
  };
163
164
  interface BaseChatInterface {
165
+ chat(options: ChatMessageOptions): Promise<ChatMessageComplete>;
164
166
  chat(messages: ChatMessage[], options?: ChatMessageOptions): Promise<ChatMessageComplete>;
165
167
  }
166
168
  interface Usage {
@@ -305,8 +307,10 @@ declare class BaseChat implements BaseChatInterface, Usage {
305
307
  /**
306
308
  * 聊天
307
309
  */
310
+ chat(options: ChatMessageOptions): Promise<ChatMessageComplete>;
308
311
  chat(messages: ChatMessage[], options?: ChatMessageOptions): Promise<ChatMessageComplete>;
309
- chatStream(messages: ChatMessage[], options?: ChatMessageOptions): Promise<ChatStream>;
312
+ chatStream(options: ChatMessageOptions): AsyncGenerator<ChatMessageComplete>;
313
+ chatStream(messages: ChatMessage[], options?: ChatMessageOptions): AsyncGenerator<ChatMessageComplete>;
310
314
  /**
311
315
  * 简单提问接口
312
316
  * @param message
@@ -323,6 +327,11 @@ declare class BaseChat implements BaseChatInterface, Usage {
323
327
  total_tokens: number;
324
328
  completion_tokens: number;
325
329
  };
330
+ setChatUsage(usage: {
331
+ prompt_tokens?: number;
332
+ total_tokens?: number;
333
+ completion_tokens?: number;
334
+ }): void;
326
335
  getHeaders(headers?: Record<string, string>): {
327
336
  'Content-Type': string;
328
337
  Authorization: string;
@@ -362,7 +371,6 @@ type OllamaModel = {
362
371
  declare class Ollama extends BaseChat {
363
372
  static BASE_URL: string;
364
373
  constructor(options: OllamaOptions$1);
365
- chat(messages: ChatMessage[], options?: ChatMessageOptions): Promise<ChatMessageComplete>;
366
374
  /**
367
375
  * 获取模型列表
368
376
  * @returns
@@ -400,7 +408,6 @@ declare class SiliconFlow extends BaseChat {
400
408
  static BASE_URL: string;
401
409
  constructor(options: SiliconFlowOptions);
402
410
  getUsageInfo(): Promise<SiliconFlowUsageResponse>;
403
- chat(messages: ChatMessage[], options?: ChatMessageOptions): Promise<ChatMessageComplete>;
404
411
  }
405
412
 
406
413
  type OllamaOptions = BaseChatOptions;
@@ -1350,10 +1350,10 @@ class BaseChat {
1350
1350
  baseURL;
1351
1351
  model;
1352
1352
  apiKey;
1353
- prompt_tokens;
1354
- total_tokens;
1355
- completion_tokens;
1356
- responseText;
1353
+ prompt_tokens = 0;
1354
+ total_tokens = 0;
1355
+ completion_tokens = 0;
1356
+ responseText = "";
1357
1357
  utils = AIUtils;
1358
1358
  constructor(options) {
1359
1359
  this.baseURL = options.baseURL;
@@ -1385,11 +1385,14 @@ class BaseChat {
1385
1385
  }
1386
1386
  }).then((res) => res.json());
1387
1387
  }
1388
- async chat(messages, options) {
1388
+ async chat(messagesOrOptions, options) {
1389
+ const isFirstParamOptions = !Array.isArray(messagesOrOptions);
1390
+ const messages = isFirstParamOptions ? messagesOrOptions.messages : messagesOrOptions;
1391
+ const opts = isFirstParamOptions ? messagesOrOptions : options || {};
1389
1392
  const requestBody = {
1390
1393
  model: this.model,
1391
1394
  messages,
1392
- ...options,
1395
+ ...opts,
1393
1396
  stream: false
1394
1397
  };
1395
1398
  const response = await this.post(`${this.baseURL}/chat/completions`, { data: requestBody });
@@ -1398,20 +1401,21 @@ class BaseChat {
1398
1401
  throw new Error(`Chat API request failed: ${response.status} ${response.statusText} - ${errorText}`);
1399
1402
  }
1400
1403
  const res = await response.json();
1401
- this.prompt_tokens = res.usage?.prompt_tokens ?? 0;
1402
- this.total_tokens = res.usage?.total_tokens ?? 0;
1403
- this.completion_tokens = res.usage?.completion_tokens ?? 0;
1404
+ this.setChatUsage(res.usage);
1404
1405
  this.responseText = res.choices[0]?.message?.content || "";
1405
1406
  return res;
1406
1407
  }
1407
- async chatStream(messages, options) {
1408
- if (options?.response_format) {
1408
+ async* chatStream(messagesOrOptions, options) {
1409
+ const isFirstParamOptions = !Array.isArray(messagesOrOptions);
1410
+ const messages = isFirstParamOptions ? messagesOrOptions.messages : messagesOrOptions;
1411
+ const opts = isFirstParamOptions ? messagesOrOptions : options || {};
1412
+ if (opts.response_format) {
1409
1413
  throw new Error("response_format is not supported in stream mode");
1410
1414
  }
1411
1415
  const requestBody = {
1412
1416
  model: this.model,
1413
1417
  messages,
1414
- ...options,
1418
+ ...opts,
1415
1419
  stream: true
1416
1420
  };
1417
1421
  const response = await this.post(`${this.baseURL}/chat/completions`, { data: requestBody });
@@ -1465,6 +1469,11 @@ class BaseChat {
1465
1469
  completion_tokens: this.completion_tokens
1466
1470
  };
1467
1471
  }
1472
+ setChatUsage(usage) {
1473
+ this.prompt_tokens = usage.prompt_tokens ?? this.prompt_tokens;
1474
+ this.total_tokens = usage.total_tokens ?? this.total_tokens;
1475
+ this.completion_tokens = usage.completion_tokens ?? this.completion_tokens;
1476
+ }
1468
1477
  getHeaders(headers) {
1469
1478
  return {
1470
1479
  "Content-Type": "application/json",
@@ -1505,11 +1514,6 @@ class Ollama extends BaseChat {
1505
1514
  const baseURL = options.baseURL || Ollama.BASE_URL;
1506
1515
  super({ ...options, baseURL });
1507
1516
  }
1508
- async chat(messages, options) {
1509
- const res = await super.chat(messages, options);
1510
- console.log("thunk", this.getChatUsage());
1511
- return res;
1512
- }
1513
1517
  async listModels() {
1514
1518
  const _url = new URL(this.baseURL);
1515
1519
  const tagsURL = new URL("/api/tags", _url);
@@ -1532,10 +1536,6 @@ class SiliconFlow extends BaseChat {
1532
1536
  async getUsageInfo() {
1533
1537
  return this.get("/user/info");
1534
1538
  }
1535
- async chat(messages, options) {
1536
- const res = await super.chat(messages, options);
1537
- return res;
1538
- }
1539
1539
  }
1540
1540
 
1541
1541
  // src/provider/chat-adapter/custom.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevisual/ai",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "AI Center Services",
5
5
  "main": "index.js",
6
6
  "basename": "/root/ai-center-services",
@@ -13,6 +13,13 @@
13
13
  "src",
14
14
  "types"
15
15
  ],
16
+ "scripts": {
17
+ "build": "npm run clean && bun bun.config.mjs",
18
+ "dev": "bun run --watch bun.config.mjs",
19
+ "test": "tsx test/**/*.ts",
20
+ "clean": "rm -rf dist",
21
+ "pub": "envision pack -p -u"
22
+ },
16
23
  "keywords": [
17
24
  "kevisual",
18
25
  "ai",
@@ -20,6 +27,7 @@
20
27
  ],
21
28
  "author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
22
29
  "license": "MIT",
30
+ "packageManager": "pnpm@10.28.0",
23
31
  "type": "module",
24
32
  "publishConfig": {
25
33
  "registry": "https://registry.npmjs.org/",
@@ -45,34 +53,27 @@
45
53
  }
46
54
  },
47
55
  "devDependencies": {
48
- "@kevisual/router": "0.0.36",
56
+ "@kevisual/router": "0.0.52",
49
57
  "@kevisual/types": "^0.0.10",
50
58
  "@kevisual/use-config": "^1.0.21",
51
- "@types/bun": "^1.3.4",
59
+ "@types/bun": "^1.3.5",
52
60
  "@types/crypto-js": "^4.2.2",
53
61
  "@types/formidable": "^3.4.6",
54
- "@types/node": "^24.10.1",
62
+ "@types/node": "^25.0.5",
55
63
  "cross-env": "^10.1.0",
56
64
  "crypto-js": "^4.2.0",
57
65
  "dayjs": "^1.11.19",
58
66
  "dotenv": "^17.2.3",
59
67
  "formidable": "^3.5.4",
60
- "openai": "6.10.0",
68
+ "openai": "6.16.0",
61
69
  "pm2": "^6.0.14",
62
70
  "rimraf": "^6.1.2",
63
71
  "typescript": "^5.9.3",
64
- "vite": "^7.2.6"
72
+ "vite": "^7.3.1"
65
73
  },
66
74
  "dependencies": {
67
75
  "@kevisual/logger": "^0.0.4",
68
76
  "@kevisual/permission": "^0.0.3",
69
- "@kevisual/query": "^0.0.31"
70
- },
71
- "scripts": {
72
- "build": "npm run clean && bun bun.config.mjs",
73
- "dev": "bun run --watch bun.config.mjs",
74
- "test": "tsx test/**/*.ts",
75
- "clean": "rm -rf dist",
76
- "pub": "envision pack -p -u"
77
+ "@kevisual/query": "^0.0.35"
77
78
  }
78
79
  }
@@ -1,51 +1,121 @@
1
- import { adapter } from '@kevisual/query/query'
2
- export type CoreOpts = {
3
- baseURL?: string;
4
- token?: string;
1
+ import { Result } from '@kevisual/query'
2
+ export interface JimengOptions {
3
+ /** API密钥,用于认证请求 */
4
+ apiKey: string;
5
+ /** API基础URL */
6
+ baseUrl: string;
7
+ /** 请求超时时间(毫秒) */
8
+ timeout: number;
5
9
  }
6
- export class Core {
7
- baseURL: string = 'https://jimeng-api.kevisual.cn/v1';
8
- token?: string;
9
- constructor(opts: CoreOpts = {}) {
10
- console.log("Core initialized");
11
- if (opts.baseURL) {
12
- this.baseURL = opts.baseURL;
13
- }
14
- if (opts.token) {
15
- this.token = opts.token;
16
- }
10
+
11
+ export interface JimengGenerateOptions {
12
+ /** 图片生成提示词 */
13
+ prompt: string;
14
+ /** 使用的模型版本,默认 jimeng-4.0 */
15
+ model?: string;
16
+ /** 图片比例,默认 1:1 */
17
+ ratio?: string;
18
+ /** 图片分辨率,默认 2k */
19
+ resolution?: string;
20
+ }
21
+
22
+ interface JimengResponse {
23
+ /** 请求创建时间戳 */
24
+ created: number;
25
+ /** 生成的图片列表 */
26
+ data: Array<{
27
+ /** 图片URL */
28
+ url: string;
29
+ }>;
30
+ }
31
+
32
+ export class JimengService {
33
+ private apiKey: string;
34
+ private baseUrl: string;
35
+ private timeout: number;
36
+
37
+ constructor(options: JimengOptions) {
38
+ this.apiKey = options.apiKey;
39
+ this.baseUrl = options.baseUrl || 'https://jimeng-api.kevisual.cn/v1';
40
+ this.timeout = options.timeout;
17
41
  }
18
- makeHeader() {
19
- return {
20
- Authorization: this.token ? `Bearer ${this.token}` : undefined,
21
- 'Content-Type': 'application/json'
42
+
43
+ async generateImage(options: JimengGenerateOptions): Promise<Result<JimengResponse>> {
44
+ const {
45
+ prompt,
46
+ model = 'jimeng-4.6',
47
+ ratio = '1:1',
48
+ resolution = '2k'
49
+ } = options;
50
+
51
+ try {
52
+ const controller = new AbortController();
53
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
54
+
55
+ const response = await fetch(`${this.baseUrl}/images/generations`, {
56
+ method: 'POST',
57
+ headers: {
58
+ 'Content-Type': 'application/json',
59
+ 'Authorization': `Bearer ${this.apiKey}`,
60
+ },
61
+ body: JSON.stringify({
62
+ model,
63
+ prompt,
64
+ ratio,
65
+ resolution,
66
+ }),
67
+ signal: controller.signal,
68
+ });
69
+
70
+ clearTimeout(timeoutId);
71
+
72
+ if (!response.ok) {
73
+ throw new Error(`jimeng API error: ${response.status} ${response.statusText}`);
74
+ }
75
+
76
+ const result = await response.json() as JimengResponse;
77
+ return { code: 200, data: result };
78
+ } catch (error: any) {
79
+ return { code: 500, message: error.message || 'Unknown error' };
22
80
  }
23
81
  }
24
- generateImage({ model = 'jimeng-4.0', prompt, resolution = '2k' }: ImageOptions) {
25
- const url = `${this.baseURL}/images/generations`;
26
- return adapter({
27
- url,
28
- headers: this.makeHeader(),
29
- body: {
30
- model,
31
- prompt,
32
- resolution
82
+
83
+ async downloadImage(url: string): Promise<Uint8Array> {
84
+ const controller = new AbortController();
85
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
86
+
87
+ try {
88
+ const response = await fetch(url, {
89
+ signal: controller.signal,
90
+ });
91
+
92
+ clearTimeout(timeoutId);
93
+
94
+ if (!response.ok) {
95
+ throw new Error(`Failed to download image: ${response.statusText}`);
96
+ }
97
+
98
+ const arrayBuffer = await response.arrayBuffer();
99
+ return new Uint8Array(arrayBuffer);
100
+ } catch (error: any) {
101
+ clearTimeout(timeoutId);
102
+ if (error.name === 'AbortError') {
103
+ throw new Error('Image download timeout');
33
104
  }
34
- });
105
+ throw error;
106
+ }
107
+ }
108
+ /** 获取图片过期时间 */
109
+ async getExpiredTime(url: string): Promise<{ expiredAt: number, expired: boolean }> {
110
+ // https://p3-dreamina-sign.byteimg.com/tos-cn-i-tb4s082cfz/c018e06ee6654dd78ccacb29eff4744e~tplv-tb4s082cfz-aigc_resize:0:0.png?lk3s=43402efa&x-expires=1767852000&x-signature=34yf37N955BP37eLaYEzKeLQn0Q%3D&format=.png
111
+ const urlObj = new URL(url);
112
+ let expires = urlObj.searchParams.get('x-expires');
113
+ if (!expires) {
114
+ expires = '0';
115
+ }
116
+ const expiredAt = parseInt(expires) * 1000;
117
+ const expired = Date.now() > expiredAt;
118
+ return { expiredAt, expired };
35
119
  }
36
120
  }
37
121
 
38
- export type ImageOptions = {
39
- model?: string;
40
- prompt: string;
41
- /**
42
- * 宽高比,如 "16:9", "4:3", "1:1" 等
43
- */
44
- ratio?: string;
45
- /**
46
- *
47
- * 图片分辨率,如 "1024x768", "512x512" 等
48
- * 4k 2k
49
- */
50
- resolution?: string;
51
- }
@@ -0,0 +1,10 @@
1
+ import { BaseChat, type BaseChatOptions } from "../chat.ts";
2
+
3
+ type MimoOptions = Partial<BaseChatOptions>;
4
+ export class MimoChat extends BaseChat {
5
+ static BASE_URL = 'https://api.xiaomimimo.com/v1';
6
+ constructor(options: MimoOptions) {
7
+ const baseURL = options.baseURL || MimoChat.BASE_URL;
8
+ super({ ...(options as BaseChatOptions), baseURL: baseURL });
9
+ }
10
+ }
@@ -25,11 +25,6 @@ export class Ollama extends BaseChat {
25
25
  const baseURL = options.baseURL || Ollama.BASE_URL;
26
26
  super({ ...(options as BaseChatOptions), baseURL: baseURL });
27
27
  }
28
- async chat(messages: ChatMessage[], options?: ChatMessageOptions) {
29
- const res = await super.chat(messages, options);
30
- console.log('thunk', this.getChatUsage());
31
- return res;
32
- }
33
28
  /**
34
29
  * 获取模型列表
35
30
  * @returns
@@ -32,8 +32,4 @@ export class SiliconFlow extends BaseChat {
32
32
  async getUsageInfo(): Promise<SiliconFlowUsageResponse> {
33
33
  return this.get('/user/info');
34
34
  }
35
- async chat(messages: ChatMessage[], options?: ChatMessageOptions) {
36
- const res = await super.chat(messages, options);
37
- return res;
38
- }
39
35
  }
@@ -50,10 +50,10 @@ export class BaseChat implements BaseChatInterface, Usage {
50
50
  * 默认apiKey
51
51
  */
52
52
  apiKey: string;
53
- prompt_tokens: number;
54
- total_tokens: number;
55
- completion_tokens: number;
56
- responseText: string;
53
+ prompt_tokens: number = 0;
54
+ total_tokens: number = 0;
55
+ completion_tokens: number = 0;
56
+ responseText: string = '';
57
57
  utils = AIUtils;
58
58
  constructor(options: BaseChatOptions) {
59
59
  this.baseURL = options.baseURL;
@@ -88,11 +88,17 @@ export class BaseChat implements BaseChatInterface, Usage {
88
88
  /**
89
89
  * 聊天
90
90
  */
91
- async chat(messages: ChatMessage[], options?: ChatMessageOptions): Promise<ChatMessageComplete> {
91
+ chat(options: ChatMessageOptions): Promise<ChatMessageComplete>;
92
+ chat(messages: ChatMessage[], options?: ChatMessageOptions): Promise<ChatMessageComplete>;
93
+ async chat(messagesOrOptions: ChatMessage[] | ChatMessageOptions, options?: ChatMessageOptions): Promise<ChatMessageComplete> {
94
+ const isFirstParamOptions = !Array.isArray(messagesOrOptions);
95
+ const messages: ChatMessage[] = isFirstParamOptions ? messagesOrOptions.messages! : messagesOrOptions;
96
+ const opts: ChatMessageOptions = isFirstParamOptions ? messagesOrOptions : options || {};
97
+
92
98
  const requestBody = {
93
99
  model: this.model,
94
100
  messages,
95
- ...options,
101
+ ...opts,
96
102
  stream: false,
97
103
  };
98
104
 
@@ -105,21 +111,26 @@ export class BaseChat implements BaseChatInterface, Usage {
105
111
 
106
112
  const res = await response.json() as ChatMessageComplete;
107
113
 
108
- this.prompt_tokens = res.usage?.prompt_tokens ?? 0;
109
- this.total_tokens = res.usage?.total_tokens ?? 0;
110
- this.completion_tokens = res.usage?.completion_tokens ?? 0;
114
+ this.setChatUsage(res.usage);
115
+
111
116
  this.responseText = res.choices[0]?.message?.content || '';
112
117
  return res;
113
118
  }
114
- async chatStream(messages: ChatMessage[], options?: ChatMessageOptions) {
115
- if (options?.response_format) {
119
+ chatStream(options: ChatMessageOptions): AsyncGenerator<ChatMessageComplete>;
120
+ chatStream(messages: ChatMessage[], options?: ChatMessageOptions): AsyncGenerator<ChatMessageComplete>;
121
+ async *chatStream(messagesOrOptions: ChatMessage[] | ChatMessageOptions, options?: ChatMessageOptions) {
122
+ const isFirstParamOptions = !Array.isArray(messagesOrOptions);
123
+ const messages: ChatMessage[] = isFirstParamOptions ? messagesOrOptions.messages! : messagesOrOptions;
124
+ const opts: ChatMessageOptions = isFirstParamOptions ? messagesOrOptions : options || {};
125
+
126
+ if (opts.response_format) {
116
127
  throw new Error('response_format is not supported in stream mode');
117
128
  }
118
129
 
119
130
  const requestBody = {
120
131
  model: this.model,
121
132
  messages,
122
- ...options,
133
+ ...opts,
123
134
  stream: true,
124
135
  };
125
136
 
@@ -191,6 +202,11 @@ export class BaseChat implements BaseChatInterface, Usage {
191
202
  completion_tokens: this.completion_tokens,
192
203
  };
193
204
  }
205
+ setChatUsage(usage: { prompt_tokens?: number; total_tokens?: number; completion_tokens?: number }) {
206
+ this.prompt_tokens = usage.prompt_tokens ?? this.prompt_tokens;
207
+ this.total_tokens = usage.total_tokens ?? this.total_tokens;
208
+ this.completion_tokens = usage.completion_tokens ?? this.completion_tokens;
209
+ }
194
210
 
195
211
  getHeaders(headers?: Record<string, string>) {
196
212
  return {
@@ -2,8 +2,8 @@ export type ChatMessage = {
2
2
  role?: 'user' | 'assistant' | 'system' | 'tool';
3
3
  content: string;
4
4
  }
5
- export type ChatMessageOptions = {
6
- messages: ChatMessage[];
5
+ export type ChatMessageOptions<T = {}> = {
6
+ messages?: ChatMessage[];
7
7
  /**
8
8
  * 模型名称
9
9
  */
@@ -43,7 +43,7 @@ export type ChatMessageOptions = {
43
43
  stream?: boolean;
44
44
  /**
45
45
  * 是否能够思考
46
- * 如果会话是千文,服务器的接口,默认为 true
46
+ * 如果会话是千文,服务器的接口,默认为 false
47
47
  */
48
48
  enable_thinking?: boolean;
49
49
  response_format?: 'text' | 'json' | 'xml' | 'html';
@@ -53,7 +53,9 @@ export type ChatMessageOptions = {
53
53
  */
54
54
  tool_calls?: any;
55
55
 
56
- };
56
+ [key: string]: any;
57
+
58
+ } & T;
57
59
 
58
60
  type Choice = {
59
61
  finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter' | 'function_call';
@@ -167,6 +169,7 @@ export type EmbeddingMessageComplete = {
167
169
 
168
170
 
169
171
  export interface BaseChatInterface {
172
+ chat(options: ChatMessageOptions): Promise<ChatMessageComplete>;
170
173
  chat(messages: ChatMessage[], options?: ChatMessageOptions): Promise<ChatMessageComplete>;
171
174
  }
172
175