@weisiren000/oiiai 0.1.0 → 0.1.2
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/README.md +96 -7
- package/dist/index.d.mts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +9 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -49,6 +49,9 @@ for await (const chunk of ai.chatStream({
|
|
|
49
49
|
})) {
|
|
50
50
|
if (chunk.type === 'content') {
|
|
51
51
|
process.stdout.write(chunk.text);
|
|
52
|
+
} else if (chunk.type === 'reasoning') {
|
|
53
|
+
// 思考过程
|
|
54
|
+
process.stdout.write(`[思考] ${chunk.text}`);
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
```
|
|
@@ -62,9 +65,10 @@ const result = await ai.chat({
|
|
|
62
65
|
model: 'deepseek/deepseek-r1',
|
|
63
66
|
messages: [{ role: 'user', content: '9.11 和 9.9 哪个大?' }],
|
|
64
67
|
reasoning: {
|
|
65
|
-
effort: 'high', // OpenAI/Grok
|
|
66
|
-
// maxTokens: 2000, // Anthropic
|
|
68
|
+
effort: 'high', // OpenAI o1/o3, Grok
|
|
69
|
+
// maxTokens: 2000, // Anthropic, Gemini, Qwen
|
|
67
70
|
// exclude: true, // 不返回思考内容
|
|
71
|
+
// enabled: true, // 显式启用
|
|
68
72
|
}
|
|
69
73
|
});
|
|
70
74
|
|
|
@@ -72,9 +76,11 @@ console.log('思考过程:', result.reasoning);
|
|
|
72
76
|
console.log('最终答案:', result.content);
|
|
73
77
|
```
|
|
74
78
|
|
|
75
|
-
##
|
|
79
|
+
## API
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
### AIProvider 接口
|
|
82
|
+
|
|
83
|
+
所有 Provider 实现统一接口:
|
|
78
84
|
|
|
79
85
|
```typescript
|
|
80
86
|
interface AIProvider {
|
|
@@ -87,16 +93,85 @@ interface AIProvider {
|
|
|
87
93
|
}
|
|
88
94
|
```
|
|
89
95
|
|
|
96
|
+
### 类型定义
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
interface ChatMessage {
|
|
100
|
+
role: 'system' | 'user' | 'assistant';
|
|
101
|
+
content: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface ChatOptions {
|
|
105
|
+
model: string;
|
|
106
|
+
messages: ChatMessage[];
|
|
107
|
+
temperature?: number;
|
|
108
|
+
maxTokens?: number;
|
|
109
|
+
reasoning?: ReasoningConfig;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface ChatResult {
|
|
113
|
+
content: string;
|
|
114
|
+
reasoning: string | null;
|
|
115
|
+
model: string;
|
|
116
|
+
usage: TokenUsage;
|
|
117
|
+
finishReason: string | null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface StreamChunk {
|
|
121
|
+
type: 'reasoning' | 'content';
|
|
122
|
+
text: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface ReasoningConfig {
|
|
126
|
+
effort?: 'high' | 'medium' | 'low';
|
|
127
|
+
maxTokens?: number;
|
|
128
|
+
exclude?: boolean;
|
|
129
|
+
enabled?: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface TokenUsage {
|
|
133
|
+
promptTokens: number;
|
|
134
|
+
completionTokens: number;
|
|
135
|
+
totalTokens: number;
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## ModelScope Provider
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { ModelScopeProvider } from '@weisiren000/oiiai';
|
|
143
|
+
|
|
144
|
+
const ai = new ModelScopeProvider('your-modelscope-token');
|
|
145
|
+
|
|
146
|
+
// 流式输出(自动启用思考模式)
|
|
147
|
+
for await (const chunk of ai.chatStream({
|
|
148
|
+
model: 'deepseek-ai/DeepSeek-V3.2',
|
|
149
|
+
messages: [{ role: 'user', content: '9.9和9.11谁大' }]
|
|
150
|
+
})) {
|
|
151
|
+
if (chunk.type === 'reasoning') {
|
|
152
|
+
process.stdout.write(chunk.text);
|
|
153
|
+
} else {
|
|
154
|
+
process.stdout.write(chunk.text);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
90
159
|
## 自定义 Provider
|
|
91
160
|
|
|
92
|
-
继承 `
|
|
161
|
+
继承 `BaseProvider` 实现自定义 Provider:
|
|
93
162
|
|
|
94
163
|
```typescript
|
|
95
|
-
import {
|
|
164
|
+
import { BaseProvider } from '@weisiren000/oiiai';
|
|
165
|
+
import type { ChatOptions, ChatResult, StreamChunk } from '@weisiren000/oiiai';
|
|
96
166
|
|
|
97
|
-
class MyProvider extends
|
|
167
|
+
class MyProvider extends BaseProvider {
|
|
98
168
|
readonly name = 'my-provider';
|
|
99
169
|
|
|
170
|
+
constructor(apiKey: string) {
|
|
171
|
+
super();
|
|
172
|
+
// 初始化 SDK 客户端
|
|
173
|
+
}
|
|
174
|
+
|
|
100
175
|
async chat(options: ChatOptions): Promise<ChatResult> {
|
|
101
176
|
// 实现非流式请求
|
|
102
177
|
}
|
|
@@ -107,6 +182,20 @@ class MyProvider extends BaseAIProvider {
|
|
|
107
182
|
}
|
|
108
183
|
```
|
|
109
184
|
|
|
185
|
+
`BaseProvider` 已实现 `ask` 和 `askWithSystem` 的默认逻辑,子类只需实现 `chat` 和 `chatStream`。
|
|
186
|
+
|
|
187
|
+
## 获取模型列表
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
const models = await ai.listModels();
|
|
191
|
+
|
|
192
|
+
for (const model of models) {
|
|
193
|
+
console.log(`${model.id}: ${model.name}`);
|
|
194
|
+
console.log(` 上下文长度: ${model.contextLength}`);
|
|
195
|
+
console.log(` 价格: $${model.pricing.prompt}/token (输入)`);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
110
199
|
## License
|
|
111
200
|
|
|
112
201
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -79,7 +79,7 @@ interface ModelInfo {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
|
-
* AI Provider
|
|
82
|
+
* AI Provider 接口和基类
|
|
83
83
|
*/
|
|
84
84
|
|
|
85
85
|
/**
|
|
@@ -114,16 +114,18 @@ interface AIProvider {
|
|
|
114
114
|
* AI Provider 基础抽象类
|
|
115
115
|
* 提供一些通用实现,子类只需实现核心方法
|
|
116
116
|
*/
|
|
117
|
-
declare abstract class
|
|
117
|
+
declare abstract class BaseProvider implements AIProvider {
|
|
118
118
|
abstract readonly name: string;
|
|
119
119
|
abstract chat(options: ChatOptions): Promise<ChatResult>;
|
|
120
120
|
abstract chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown>;
|
|
121
121
|
/**
|
|
122
122
|
* 简单对话:单轮问答(默认实现)
|
|
123
|
+
* 对于思考模型,如果 content 为空则返回 reasoning
|
|
123
124
|
*/
|
|
124
125
|
ask(model: string, question: string, options?: Omit<ChatOptions, 'model' | 'messages'>): Promise<string>;
|
|
125
126
|
/**
|
|
126
127
|
* 带系统提示的对话(默认实现)
|
|
128
|
+
* 对于思考模型,如果 content 为空则返回 reasoning
|
|
127
129
|
*/
|
|
128
130
|
askWithSystem(model: string, systemPrompt: string, userMessage: string, options?: Omit<ChatOptions, 'model' | 'messages'>): Promise<string>;
|
|
129
131
|
}
|
|
@@ -152,7 +154,7 @@ interface OpenRouterModelInfo extends ModelInfo {
|
|
|
152
154
|
/**
|
|
153
155
|
* OpenRouter Provider
|
|
154
156
|
*/
|
|
155
|
-
declare class OpenRouterProvider extends
|
|
157
|
+
declare class OpenRouterProvider extends BaseProvider {
|
|
156
158
|
readonly name = "openrouter";
|
|
157
159
|
private client;
|
|
158
160
|
constructor(apiKey: string);
|
|
@@ -170,4 +172,4 @@ declare class OpenRouterProvider extends BaseAIProvider {
|
|
|
170
172
|
listModels(): Promise<OpenRouterModelInfo[]>;
|
|
171
173
|
}
|
|
172
174
|
|
|
173
|
-
export { type AIProvider,
|
|
175
|
+
export { type AIProvider, BaseProvider, type ChatMessage, type ChatOptions, type ChatResult, type ModelInfo, type ModelPricing, type OpenRouterModelInfo, OpenRouterProvider, type ReasoningConfig, type StreamChunk, type TokenUsage };
|
package/dist/index.d.ts
CHANGED
|
@@ -79,7 +79,7 @@ interface ModelInfo {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
|
-
* AI Provider
|
|
82
|
+
* AI Provider 接口和基类
|
|
83
83
|
*/
|
|
84
84
|
|
|
85
85
|
/**
|
|
@@ -114,16 +114,18 @@ interface AIProvider {
|
|
|
114
114
|
* AI Provider 基础抽象类
|
|
115
115
|
* 提供一些通用实现,子类只需实现核心方法
|
|
116
116
|
*/
|
|
117
|
-
declare abstract class
|
|
117
|
+
declare abstract class BaseProvider implements AIProvider {
|
|
118
118
|
abstract readonly name: string;
|
|
119
119
|
abstract chat(options: ChatOptions): Promise<ChatResult>;
|
|
120
120
|
abstract chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown>;
|
|
121
121
|
/**
|
|
122
122
|
* 简单对话:单轮问答(默认实现)
|
|
123
|
+
* 对于思考模型,如果 content 为空则返回 reasoning
|
|
123
124
|
*/
|
|
124
125
|
ask(model: string, question: string, options?: Omit<ChatOptions, 'model' | 'messages'>): Promise<string>;
|
|
125
126
|
/**
|
|
126
127
|
* 带系统提示的对话(默认实现)
|
|
128
|
+
* 对于思考模型,如果 content 为空则返回 reasoning
|
|
127
129
|
*/
|
|
128
130
|
askWithSystem(model: string, systemPrompt: string, userMessage: string, options?: Omit<ChatOptions, 'model' | 'messages'>): Promise<string>;
|
|
129
131
|
}
|
|
@@ -152,7 +154,7 @@ interface OpenRouterModelInfo extends ModelInfo {
|
|
|
152
154
|
/**
|
|
153
155
|
* OpenRouter Provider
|
|
154
156
|
*/
|
|
155
|
-
declare class OpenRouterProvider extends
|
|
157
|
+
declare class OpenRouterProvider extends BaseProvider {
|
|
156
158
|
readonly name = "openrouter";
|
|
157
159
|
private client;
|
|
158
160
|
constructor(apiKey: string);
|
|
@@ -170,4 +172,4 @@ declare class OpenRouterProvider extends BaseAIProvider {
|
|
|
170
172
|
listModels(): Promise<OpenRouterModelInfo[]>;
|
|
171
173
|
}
|
|
172
174
|
|
|
173
|
-
export { type AIProvider,
|
|
175
|
+
export { type AIProvider, BaseProvider, type ChatMessage, type ChatOptions, type ChatResult, type ModelInfo, type ModelPricing, type OpenRouterModelInfo, OpenRouterProvider, type ReasoningConfig, type StreamChunk, type TokenUsage };
|
package/dist/index.js
CHANGED
|
@@ -20,15 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
|
|
23
|
+
BaseProvider: () => BaseProvider,
|
|
24
24
|
OpenRouterProvider: () => OpenRouterProvider
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(index_exports);
|
|
27
27
|
|
|
28
|
-
// src/
|
|
29
|
-
var
|
|
28
|
+
// src/providers/__base__.ts
|
|
29
|
+
var BaseProvider = class {
|
|
30
30
|
/**
|
|
31
31
|
* 简单对话:单轮问答(默认实现)
|
|
32
|
+
* 对于思考模型,如果 content 为空则返回 reasoning
|
|
32
33
|
*/
|
|
33
34
|
async ask(model, question, options) {
|
|
34
35
|
const result = await this.chat({
|
|
@@ -36,10 +37,11 @@ var BaseAIProvider = class {
|
|
|
36
37
|
messages: [{ role: "user", content: question }],
|
|
37
38
|
...options
|
|
38
39
|
});
|
|
39
|
-
return result.content;
|
|
40
|
+
return result.content || result.reasoning || "";
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* 带系统提示的对话(默认实现)
|
|
44
|
+
* 对于思考模型,如果 content 为空则返回 reasoning
|
|
43
45
|
*/
|
|
44
46
|
async askWithSystem(model, systemPrompt, userMessage, options) {
|
|
45
47
|
const result = await this.chat({
|
|
@@ -50,7 +52,7 @@ var BaseAIProvider = class {
|
|
|
50
52
|
],
|
|
51
53
|
...options
|
|
52
54
|
});
|
|
53
|
-
return result.content;
|
|
55
|
+
return result.content || result.reasoning || "";
|
|
54
56
|
}
|
|
55
57
|
};
|
|
56
58
|
|
|
@@ -84,7 +86,7 @@ function buildReasoningParam(config) {
|
|
|
84
86
|
}
|
|
85
87
|
return Object.keys(param).length > 0 ? param : void 0;
|
|
86
88
|
}
|
|
87
|
-
var OpenRouterProvider = class extends
|
|
89
|
+
var OpenRouterProvider = class extends BaseProvider {
|
|
88
90
|
name = "openrouter";
|
|
89
91
|
client;
|
|
90
92
|
constructor(apiKey) {
|
|
@@ -186,7 +188,7 @@ var OpenRouterProvider = class extends BaseAIProvider {
|
|
|
186
188
|
};
|
|
187
189
|
// Annotate the CommonJS export names for ESM import in node:
|
|
188
190
|
0 && (module.exports = {
|
|
189
|
-
|
|
191
|
+
BaseProvider,
|
|
190
192
|
OpenRouterProvider
|
|
191
193
|
});
|
|
192
194
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/base.ts","../src/providers/openrouter.ts"],"sourcesContent":["// 基础接口和类型\r\nexport type { AIProvider } from './base';\r\nexport { BaseAIProvider } from './base';\r\nexport type {\r\n ChatMessage,\r\n ChatOptions,\r\n ChatResult,\r\n ReasoningConfig,\r\n StreamChunk,\r\n TokenUsage,\r\n ModelInfo,\r\n ModelPricing,\r\n} from './types';\r\n\r\n// Provider 实现\r\nexport { OpenRouterProvider } from './providers';\r\nexport type { OpenRouterModelInfo } from './providers';\r\n","/**\r\n * AI Provider 统一接口\r\n */\r\n\r\nimport type { ChatOptions, ChatResult, StreamChunk, ModelInfo } from './types';\r\n\r\n/**\r\n * AI Provider 基础接口\r\n * 所有 provider 实现都需要实现这个接口\r\n */\r\nexport interface AIProvider {\r\n /** Provider 名称 */\r\n readonly name: string;\r\n\r\n /**\r\n * 发送聊天请求(非流式)\r\n */\r\n chat(options: ChatOptions): Promise<ChatResult>;\r\n\r\n /**\r\n * 发送流式聊天请求\r\n */\r\n chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown>;\r\n\r\n /**\r\n * 简单对话:单轮问答\r\n */\r\n ask(model: string, question: string, options?: Omit<ChatOptions, 'model' | 'messages'>): Promise<string>;\r\n\r\n /**\r\n * 带系统提示的对话\r\n */\r\n askWithSystem(\r\n model: string,\r\n systemPrompt: string,\r\n userMessage: string,\r\n options?: Omit<ChatOptions, 'model' | 'messages'>\r\n ): Promise<string>;\r\n\r\n /**\r\n * 获取可用模型列表(可选实现)\r\n */\r\n listModels?(): Promise<ModelInfo[]>;\r\n}\r\n\r\n/**\r\n * AI Provider 基础抽象类\r\n * 提供一些通用实现,子类只需实现核心方法\r\n */\r\nexport abstract class BaseAIProvider implements AIProvider {\r\n abstract readonly name: string;\r\n\r\n abstract chat(options: ChatOptions): Promise<ChatResult>;\r\n\r\n abstract chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown>;\r\n\r\n /**\r\n * 简单对话:单轮问答(默认实现)\r\n */\r\n async ask(model: string, question: string, options?: Omit<ChatOptions, 'model' | 'messages'>): Promise<string> {\r\n const result = await this.chat({\r\n model,\r\n messages: [{ role: 'user', content: question }],\r\n ...options,\r\n });\r\n return result.content;\r\n }\r\n\r\n /**\r\n * 带系统提示的对话(默认实现)\r\n */\r\n async askWithSystem(\r\n model: string,\r\n systemPrompt: string,\r\n userMessage: string,\r\n options?: Omit<ChatOptions, 'model' | 'messages'>\r\n ): Promise<string> {\r\n const result = await this.chat({\r\n model,\r\n messages: [\r\n { role: 'system', content: systemPrompt },\r\n { role: 'user', content: userMessage },\r\n ],\r\n ...options,\r\n });\r\n return result.content;\r\n }\r\n}\r\n","/**\r\n * OpenRouter Provider 实现\r\n */\r\n\r\nimport { OpenRouter } from '@openrouter/sdk';\r\nimport { BaseAIProvider } from '../base';\r\nimport type { ChatOptions, ChatResult, StreamChunk, ModelInfo, ReasoningConfig } from '../types';\r\n\r\n/**\r\n * OpenRouter 扩展的模型信息\r\n */\r\nexport interface OpenRouterModelInfo extends ModelInfo {\r\n /** 规范化的 slug */\r\n canonicalSlug: string;\r\n /** 创建时间戳 */\r\n created: number;\r\n /** 架构信息 */\r\n architecture: {\r\n modality: string;\r\n inputModalities: string[];\r\n outputModalities: string[];\r\n tokenizer: string;\r\n instructType: string;\r\n };\r\n}\r\n\r\n/**\r\n * 从 SDK 返回的 content 中提取文本\r\n */\r\nfunction extractTextContent(content: unknown): string {\r\n if (typeof content === 'string') {\r\n return content;\r\n }\r\n if (Array.isArray(content)) {\r\n return content\r\n .filter(\r\n (item): item is { type: 'text'; text: string } =>\r\n typeof item === 'object' && item !== null && item.type === 'text' && typeof item.text === 'string'\r\n )\r\n .map((item) => item.text)\r\n .join('');\r\n }\r\n return '';\r\n}\r\n\r\n/**\r\n * 构建 reasoning 参数(转换为 API 需要的 snake_case 格式)\r\n */\r\nfunction buildReasoningParam(config?: ReasoningConfig): Record<string, unknown> | undefined {\r\n if (!config) return undefined;\r\n\r\n const param: Record<string, unknown> = {};\r\n\r\n if (config.effort !== undefined) {\r\n param.effort = config.effort;\r\n }\r\n if (config.maxTokens !== undefined) {\r\n param.max_tokens = config.maxTokens;\r\n }\r\n if (config.exclude !== undefined) {\r\n param.exclude = config.exclude;\r\n }\r\n if (config.enabled !== undefined) {\r\n param.enabled = config.enabled;\r\n }\r\n\r\n return Object.keys(param).length > 0 ? param : undefined;\r\n}\r\n\r\n\r\n/**\r\n * OpenRouter Provider\r\n */\r\nexport class OpenRouterProvider extends BaseAIProvider {\r\n readonly name = 'openrouter';\r\n private client: OpenRouter;\r\n\r\n constructor(apiKey: string) {\r\n super();\r\n this.client = new OpenRouter({ apiKey });\r\n }\r\n\r\n /**\r\n * 发送聊天请求(非流式)\r\n */\r\n async chat(options: ChatOptions): Promise<ChatResult> {\r\n const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;\r\n\r\n const reasoningParam = buildReasoningParam(reasoning);\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const requestParams: any = {\r\n model,\r\n messages,\r\n temperature,\r\n maxTokens,\r\n stream: false,\r\n };\r\n\r\n if (reasoningParam) {\r\n requestParams.reasoning = reasoningParam;\r\n }\r\n\r\n const result = await this.client.chat.send(requestParams);\r\n\r\n const choice = result.choices[0];\r\n if (!choice) {\r\n throw new Error('No response from model');\r\n }\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const msg = choice.message as any;\r\n const reasoningContent = msg.reasoning_content ?? msg.reasoning ?? null;\r\n\r\n return {\r\n content: extractTextContent(msg.content),\r\n reasoning: reasoningContent ? extractTextContent(reasoningContent) : null,\r\n model: result.model,\r\n usage: {\r\n promptTokens: result.usage?.promptTokens ?? 0,\r\n completionTokens: result.usage?.completionTokens ?? 0,\r\n totalTokens: result.usage?.totalTokens ?? 0,\r\n },\r\n finishReason: choice.finishReason,\r\n };\r\n }\r\n\r\n /**\r\n * 发送流式聊天请求\r\n */\r\n async *chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown> {\r\n const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;\r\n\r\n const reasoningParam = buildReasoningParam(reasoning);\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const requestParams: any = {\r\n model,\r\n messages,\r\n temperature,\r\n maxTokens,\r\n stream: true,\r\n };\r\n\r\n if (reasoningParam) {\r\n requestParams.reasoning = reasoningParam;\r\n }\r\n\r\n const stream = (await this.client.chat.send(requestParams)) as unknown as AsyncIterable<{\r\n choices?: Array<{ delta?: unknown }>;\r\n }>;\r\n\r\n for await (const chunk of stream) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const delta = chunk.choices?.[0]?.delta as any;\r\n if (!delta) continue;\r\n\r\n const reasoningContent = delta.reasoning_content ?? delta.reasoning;\r\n if (reasoningContent) {\r\n yield { type: 'reasoning', text: extractTextContent(reasoningContent) };\r\n }\r\n\r\n if (delta.content) {\r\n yield { type: 'content', text: extractTextContent(delta.content) };\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 获取可用模型列表\r\n */\r\n async listModels(): Promise<OpenRouterModelInfo[]> {\r\n const result = await this.client.models.list();\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n return (result.data ?? []).map((m: any) => ({\r\n id: m.id,\r\n canonicalSlug: m.canonical_slug ?? m.id,\r\n name: m.name,\r\n description: m.description ?? '',\r\n created: m.created ?? 0,\r\n pricing: {\r\n prompt: m.pricing?.prompt ?? '0',\r\n completion: m.pricing?.completion ?? '0',\r\n request: m.pricing?.request ?? '0',\r\n image: m.pricing?.image ?? '0',\r\n },\r\n contextLength: m.context_length ?? 0,\r\n architecture: {\r\n modality: m.architecture?.modality ?? '',\r\n inputModalities: m.architecture?.input_modalities ?? [],\r\n outputModalities: m.architecture?.output_modalities ?? [],\r\n tokenizer: m.architecture?.tokenizer ?? '',\r\n instructType: m.architecture?.instruct_type ?? '',\r\n },\r\n supportedParameters: m.supported_parameters ?? [],\r\n }));\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACiDO,IAAe,iBAAf,MAAoD;AAAA;AAAA;AAAA;AAAA,EAUzD,MAAM,IAAI,OAAe,UAAkB,SAAoE;AAC7G,UAAM,SAAS,MAAM,KAAK,KAAK;AAAA,MAC7B;AAAA,MACA,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,SAAS,CAAC;AAAA,MAC9C,GAAG;AAAA,IACL,CAAC;AACD,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cACJ,OACA,cACA,aACA,SACiB;AACjB,UAAM,SAAS,MAAM,KAAK,KAAK;AAAA,MAC7B;AAAA,MACA,UAAU;AAAA,QACR,EAAE,MAAM,UAAU,SAAS,aAAa;AAAA,QACxC,EAAE,MAAM,QAAQ,SAAS,YAAY;AAAA,MACvC;AAAA,MACA,GAAG;AAAA,IACL,CAAC;AACD,WAAO,OAAO;AAAA,EAChB;AACF;;;ACnFA,iBAA2B;AAyB3B,SAAS,mBAAmB,SAA0B;AACpD,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,WAAO,QACJ;AAAA,MACC,CAAC,SACC,OAAO,SAAS,YAAY,SAAS,QAAQ,KAAK,SAAS,UAAU,OAAO,KAAK,SAAS;AAAA,IAC9F,EACC,IAAI,CAAC,SAAS,KAAK,IAAI,EACvB,KAAK,EAAE;AAAA,EACZ;AACA,SAAO;AACT;AAKA,SAAS,oBAAoB,QAA+D;AAC1F,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,QAAiC,CAAC;AAExC,MAAI,OAAO,WAAW,QAAW;AAC/B,UAAM,SAAS,OAAO;AAAA,EACxB;AACA,MAAI,OAAO,cAAc,QAAW;AAClC,UAAM,aAAa,OAAO;AAAA,EAC5B;AACA,MAAI,OAAO,YAAY,QAAW;AAChC,UAAM,UAAU,OAAO;AAAA,EACzB;AACA,MAAI,OAAO,YAAY,QAAW;AAChC,UAAM,UAAU,OAAO;AAAA,EACzB;AAEA,SAAO,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,QAAQ;AACjD;AAMO,IAAM,qBAAN,cAAiC,eAAe;AAAA,EAC5C,OAAO;AAAA,EACR;AAAA,EAER,YAAY,QAAgB;AAC1B,UAAM;AACN,SAAK,SAAS,IAAI,sBAAW,EAAE,OAAO,CAAC;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,SAA2C;AACpD,UAAM,EAAE,OAAO,UAAU,cAAc,KAAK,WAAW,UAAU,IAAI;AAErE,UAAM,iBAAiB,oBAAoB,SAAS;AAGpD,UAAM,gBAAqB;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,IACV;AAEA,QAAI,gBAAgB;AAClB,oBAAc,YAAY;AAAA,IAC5B;AAEA,UAAM,SAAS,MAAM,KAAK,OAAO,KAAK,KAAK,aAAa;AAExD,UAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,wBAAwB;AAAA,IAC1C;AAGA,UAAM,MAAM,OAAO;AACnB,UAAM,mBAAmB,IAAI,qBAAqB,IAAI,aAAa;AAEnE,WAAO;AAAA,MACL,SAAS,mBAAmB,IAAI,OAAO;AAAA,MACvC,WAAW,mBAAmB,mBAAmB,gBAAgB,IAAI;AAAA,MACrE,OAAO,OAAO;AAAA,MACd,OAAO;AAAA,QACL,cAAc,OAAO,OAAO,gBAAgB;AAAA,QAC5C,kBAAkB,OAAO,OAAO,oBAAoB;AAAA,QACpD,aAAa,OAAO,OAAO,eAAe;AAAA,MAC5C;AAAA,MACA,cAAc,OAAO;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,WAAW,SAAkE;AAClF,UAAM,EAAE,OAAO,UAAU,cAAc,KAAK,WAAW,UAAU,IAAI;AAErE,UAAM,iBAAiB,oBAAoB,SAAS;AAGpD,UAAM,gBAAqB;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,IACV;AAEA,QAAI,gBAAgB;AAClB,oBAAc,YAAY;AAAA,IAC5B;AAEA,UAAM,SAAU,MAAM,KAAK,OAAO,KAAK,KAAK,aAAa;AAIzD,qBAAiB,SAAS,QAAQ;AAEhC,YAAM,QAAQ,MAAM,UAAU,CAAC,GAAG;AAClC,UAAI,CAAC,MAAO;AAEZ,YAAM,mBAAmB,MAAM,qBAAqB,MAAM;AAC1D,UAAI,kBAAkB;AACpB,cAAM,EAAE,MAAM,aAAa,MAAM,mBAAmB,gBAAgB,EAAE;AAAA,MACxE;AAEA,UAAI,MAAM,SAAS;AACjB,cAAM,EAAE,MAAM,WAAW,MAAM,mBAAmB,MAAM,OAAO,EAAE;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA6C;AACjD,UAAM,SAAS,MAAM,KAAK,OAAO,OAAO,KAAK;AAG7C,YAAQ,OAAO,QAAQ,CAAC,GAAG,IAAI,CAAC,OAAY;AAAA,MAC1C,IAAI,EAAE;AAAA,MACN,eAAe,EAAE,kBAAkB,EAAE;AAAA,MACrC,MAAM,EAAE;AAAA,MACR,aAAa,EAAE,eAAe;AAAA,MAC9B,SAAS,EAAE,WAAW;AAAA,MACtB,SAAS;AAAA,QACP,QAAQ,EAAE,SAAS,UAAU;AAAA,QAC7B,YAAY,EAAE,SAAS,cAAc;AAAA,QACrC,SAAS,EAAE,SAAS,WAAW;AAAA,QAC/B,OAAO,EAAE,SAAS,SAAS;AAAA,MAC7B;AAAA,MACA,eAAe,EAAE,kBAAkB;AAAA,MACnC,cAAc;AAAA,QACZ,UAAU,EAAE,cAAc,YAAY;AAAA,QACtC,iBAAiB,EAAE,cAAc,oBAAoB,CAAC;AAAA,QACtD,kBAAkB,EAAE,cAAc,qBAAqB,CAAC;AAAA,QACxD,WAAW,EAAE,cAAc,aAAa;AAAA,QACxC,cAAc,EAAE,cAAc,iBAAiB;AAAA,MACjD;AAAA,MACA,qBAAqB,EAAE,wBAAwB,CAAC;AAAA,IAClD,EAAE;AAAA,EACJ;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/providers/__base__.ts","../src/providers/openrouter.ts"],"sourcesContent":["// 从 providers 模块导出所有内容\r\nexport {\r\n // 基类和接口\r\n BaseProvider,\r\n // Provider 实现\r\n OpenRouterProvider,\r\n} from './providers/__index__';\r\n\r\nexport type {\r\n // 接口\r\n AIProvider,\r\n // 类型\r\n ChatMessage,\r\n ChatOptions,\r\n ChatResult,\r\n ReasoningConfig,\r\n StreamChunk,\r\n TokenUsage,\r\n ModelInfo,\r\n ModelPricing,\r\n // Provider 特定类型\r\n OpenRouterModelInfo,\r\n} from './providers/__index__';\r\n","/**\r\n * AI Provider 接口和基类\r\n */\r\n\r\nimport type { ChatOptions, ChatResult, StreamChunk, ModelInfo } from './__types__';\r\n\r\n/**\r\n * AI Provider 基础接口\r\n * 所有 provider 实现都需要实现这个接口\r\n */\r\nexport interface AIProvider {\r\n /** Provider 名称 */\r\n readonly name: string;\r\n\r\n /**\r\n * 发送聊天请求(非流式)\r\n */\r\n chat(options: ChatOptions): Promise<ChatResult>;\r\n\r\n /**\r\n * 发送流式聊天请求\r\n */\r\n chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown>;\r\n\r\n /**\r\n * 简单对话:单轮问答\r\n */\r\n ask(model: string, question: string, options?: Omit<ChatOptions, 'model' | 'messages'>): Promise<string>;\r\n\r\n /**\r\n * 带系统提示的对话\r\n */\r\n askWithSystem(\r\n model: string,\r\n systemPrompt: string,\r\n userMessage: string,\r\n options?: Omit<ChatOptions, 'model' | 'messages'>\r\n ): Promise<string>;\r\n\r\n /**\r\n * 获取可用模型列表(可选实现)\r\n */\r\n listModels?(): Promise<ModelInfo[]>;\r\n}\r\n\r\n/**\r\n * AI Provider 基础抽象类\r\n * 提供一些通用实现,子类只需实现核心方法\r\n */\r\nexport abstract class BaseProvider implements AIProvider {\r\n abstract readonly name: string;\r\n\r\n abstract chat(options: ChatOptions): Promise<ChatResult>;\r\n\r\n abstract chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown>;\r\n\r\n /**\r\n * 简单对话:单轮问答(默认实现)\r\n * 对于思考模型,如果 content 为空则返回 reasoning\r\n */\r\n async ask(model: string, question: string, options?: Omit<ChatOptions, 'model' | 'messages'>): Promise<string> {\r\n const result = await this.chat({\r\n model,\r\n messages: [{ role: 'user', content: question }],\r\n ...options,\r\n });\r\n return result.content || result.reasoning || '';\r\n }\r\n\r\n /**\r\n * 带系统提示的对话(默认实现)\r\n * 对于思考模型,如果 content 为空则返回 reasoning\r\n */\r\n async askWithSystem(\r\n model: string,\r\n systemPrompt: string,\r\n userMessage: string,\r\n options?: Omit<ChatOptions, 'model' | 'messages'>\r\n ): Promise<string> {\r\n const result = await this.chat({\r\n model,\r\n messages: [\r\n { role: 'system', content: systemPrompt },\r\n { role: 'user', content: userMessage },\r\n ],\r\n ...options,\r\n });\r\n return result.content || result.reasoning || '';\r\n }\r\n}\r\n","/**\r\n * OpenRouter Provider 实现\r\n */\r\n\r\nimport { OpenRouter } from '@openrouter/sdk';\r\nimport { BaseProvider } from './__base__';\r\nimport type { ChatOptions, ChatResult, StreamChunk, ModelInfo, ReasoningConfig } from './__types__';\r\n\r\n/**\r\n * OpenRouter 扩展的模型信息\r\n */\r\nexport interface OpenRouterModelInfo extends ModelInfo {\r\n /** 规范化的 slug */\r\n canonicalSlug: string;\r\n /** 创建时间戳 */\r\n created: number;\r\n /** 架构信息 */\r\n architecture: {\r\n modality: string;\r\n inputModalities: string[];\r\n outputModalities: string[];\r\n tokenizer: string;\r\n instructType: string;\r\n };\r\n}\r\n\r\n/**\r\n * 从 SDK 返回的 content 中提取文本\r\n */\r\nfunction extractTextContent(content: unknown): string {\r\n if (typeof content === 'string') {\r\n return content;\r\n }\r\n if (Array.isArray(content)) {\r\n return content\r\n .filter(\r\n (item): item is { type: 'text'; text: string } =>\r\n typeof item === 'object' && item !== null && item.type === 'text' && typeof item.text === 'string'\r\n )\r\n .map((item) => item.text)\r\n .join('');\r\n }\r\n return '';\r\n}\r\n\r\n/**\r\n * 构建 reasoning 参数(转换为 API 需要的 snake_case 格式)\r\n */\r\nfunction buildReasoningParam(config?: ReasoningConfig): Record<string, unknown> | undefined {\r\n if (!config) return undefined;\r\n\r\n const param: Record<string, unknown> = {};\r\n\r\n if (config.effort !== undefined) {\r\n param.effort = config.effort;\r\n }\r\n if (config.maxTokens !== undefined) {\r\n param.max_tokens = config.maxTokens;\r\n }\r\n if (config.exclude !== undefined) {\r\n param.exclude = config.exclude;\r\n }\r\n if (config.enabled !== undefined) {\r\n param.enabled = config.enabled;\r\n }\r\n\r\n return Object.keys(param).length > 0 ? param : undefined;\r\n}\r\n\r\n\r\n/**\r\n * OpenRouter Provider\r\n */\r\nexport class OpenRouterProvider extends BaseProvider {\r\n readonly name = 'openrouter';\r\n private client: OpenRouter;\r\n\r\n constructor(apiKey: string) {\r\n super();\r\n this.client = new OpenRouter({ apiKey });\r\n }\r\n\r\n /**\r\n * 发送聊天请求(非流式)\r\n */\r\n async chat(options: ChatOptions): Promise<ChatResult> {\r\n const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;\r\n\r\n const reasoningParam = buildReasoningParam(reasoning);\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const requestParams: any = {\r\n model,\r\n messages,\r\n temperature,\r\n maxTokens,\r\n stream: false,\r\n };\r\n\r\n if (reasoningParam) {\r\n requestParams.reasoning = reasoningParam;\r\n }\r\n\r\n const result = await this.client.chat.send(requestParams);\r\n\r\n const choice = result.choices[0];\r\n if (!choice) {\r\n throw new Error('No response from model');\r\n }\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const msg = choice.message as any;\r\n const reasoningContent = msg.reasoning_content ?? msg.reasoning ?? null;\r\n\r\n return {\r\n content: extractTextContent(msg.content),\r\n reasoning: reasoningContent ? extractTextContent(reasoningContent) : null,\r\n model: result.model,\r\n usage: {\r\n promptTokens: result.usage?.promptTokens ?? 0,\r\n completionTokens: result.usage?.completionTokens ?? 0,\r\n totalTokens: result.usage?.totalTokens ?? 0,\r\n },\r\n finishReason: choice.finishReason,\r\n };\r\n }\r\n\r\n /**\r\n * 发送流式聊天请求\r\n */\r\n async *chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown> {\r\n const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;\r\n\r\n const reasoningParam = buildReasoningParam(reasoning);\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const requestParams: any = {\r\n model,\r\n messages,\r\n temperature,\r\n maxTokens,\r\n stream: true,\r\n };\r\n\r\n if (reasoningParam) {\r\n requestParams.reasoning = reasoningParam;\r\n }\r\n\r\n const stream = (await this.client.chat.send(requestParams)) as unknown as AsyncIterable<{\r\n choices?: Array<{ delta?: unknown }>;\r\n }>;\r\n\r\n for await (const chunk of stream) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const delta = chunk.choices?.[0]?.delta as any;\r\n if (!delta) continue;\r\n\r\n const reasoningContent = delta.reasoning_content ?? delta.reasoning;\r\n if (reasoningContent) {\r\n yield { type: 'reasoning', text: extractTextContent(reasoningContent) };\r\n }\r\n\r\n if (delta.content) {\r\n yield { type: 'content', text: extractTextContent(delta.content) };\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 获取可用模型列表\r\n */\r\n async listModels(): Promise<OpenRouterModelInfo[]> {\r\n const result = await this.client.models.list();\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n return (result.data ?? []).map((m: any) => ({\r\n id: m.id,\r\n canonicalSlug: m.canonical_slug ?? m.id,\r\n name: m.name,\r\n description: m.description ?? '',\r\n created: m.created ?? 0,\r\n pricing: {\r\n prompt: m.pricing?.prompt ?? '0',\r\n completion: m.pricing?.completion ?? '0',\r\n request: m.pricing?.request ?? '0',\r\n image: m.pricing?.image ?? '0',\r\n },\r\n contextLength: m.context_length ?? 0,\r\n architecture: {\r\n modality: m.architecture?.modality ?? '',\r\n inputModalities: m.architecture?.input_modalities ?? [],\r\n outputModalities: m.architecture?.output_modalities ?? [],\r\n tokenizer: m.architecture?.tokenizer ?? '',\r\n instructType: m.architecture?.instruct_type ?? '',\r\n },\r\n supportedParameters: m.supported_parameters ?? [],\r\n }));\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
var
|
|
1
|
+
// src/providers/__base__.ts
|
|
2
|
+
var BaseProvider = class {
|
|
3
3
|
/**
|
|
4
4
|
* 简单对话:单轮问答(默认实现)
|
|
5
|
+
* 对于思考模型,如果 content 为空则返回 reasoning
|
|
5
6
|
*/
|
|
6
7
|
async ask(model, question, options) {
|
|
7
8
|
const result = await this.chat({
|
|
@@ -9,10 +10,11 @@ var BaseAIProvider = class {
|
|
|
9
10
|
messages: [{ role: "user", content: question }],
|
|
10
11
|
...options
|
|
11
12
|
});
|
|
12
|
-
return result.content;
|
|
13
|
+
return result.content || result.reasoning || "";
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* 带系统提示的对话(默认实现)
|
|
17
|
+
* 对于思考模型,如果 content 为空则返回 reasoning
|
|
16
18
|
*/
|
|
17
19
|
async askWithSystem(model, systemPrompt, userMessage, options) {
|
|
18
20
|
const result = await this.chat({
|
|
@@ -23,7 +25,7 @@ var BaseAIProvider = class {
|
|
|
23
25
|
],
|
|
24
26
|
...options
|
|
25
27
|
});
|
|
26
|
-
return result.content;
|
|
28
|
+
return result.content || result.reasoning || "";
|
|
27
29
|
}
|
|
28
30
|
};
|
|
29
31
|
|
|
@@ -57,7 +59,7 @@ function buildReasoningParam(config) {
|
|
|
57
59
|
}
|
|
58
60
|
return Object.keys(param).length > 0 ? param : void 0;
|
|
59
61
|
}
|
|
60
|
-
var OpenRouterProvider = class extends
|
|
62
|
+
var OpenRouterProvider = class extends BaseProvider {
|
|
61
63
|
name = "openrouter";
|
|
62
64
|
client;
|
|
63
65
|
constructor(apiKey) {
|
|
@@ -158,7 +160,7 @@ var OpenRouterProvider = class extends BaseAIProvider {
|
|
|
158
160
|
}
|
|
159
161
|
};
|
|
160
162
|
export {
|
|
161
|
-
|
|
163
|
+
BaseProvider,
|
|
162
164
|
OpenRouterProvider
|
|
163
165
|
};
|
|
164
166
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/base.ts","../src/providers/openrouter.ts"],"sourcesContent":["/**\r\n * AI Provider 统一接口\r\n */\r\n\r\nimport type { ChatOptions, ChatResult, StreamChunk, ModelInfo } from './types';\r\n\r\n/**\r\n * AI Provider 基础接口\r\n * 所有 provider 实现都需要实现这个接口\r\n */\r\nexport interface AIProvider {\r\n /** Provider 名称 */\r\n readonly name: string;\r\n\r\n /**\r\n * 发送聊天请求(非流式)\r\n */\r\n chat(options: ChatOptions): Promise<ChatResult>;\r\n\r\n /**\r\n * 发送流式聊天请求\r\n */\r\n chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown>;\r\n\r\n /**\r\n * 简单对话:单轮问答\r\n */\r\n ask(model: string, question: string, options?: Omit<ChatOptions, 'model' | 'messages'>): Promise<string>;\r\n\r\n /**\r\n * 带系统提示的对话\r\n */\r\n askWithSystem(\r\n model: string,\r\n systemPrompt: string,\r\n userMessage: string,\r\n options?: Omit<ChatOptions, 'model' | 'messages'>\r\n ): Promise<string>;\r\n\r\n /**\r\n * 获取可用模型列表(可选实现)\r\n */\r\n listModels?(): Promise<ModelInfo[]>;\r\n}\r\n\r\n/**\r\n * AI Provider 基础抽象类\r\n * 提供一些通用实现,子类只需实现核心方法\r\n */\r\nexport abstract class BaseAIProvider implements AIProvider {\r\n abstract readonly name: string;\r\n\r\n abstract chat(options: ChatOptions): Promise<ChatResult>;\r\n\r\n abstract chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown>;\r\n\r\n /**\r\n * 简单对话:单轮问答(默认实现)\r\n */\r\n async ask(model: string, question: string, options?: Omit<ChatOptions, 'model' | 'messages'>): Promise<string> {\r\n const result = await this.chat({\r\n model,\r\n messages: [{ role: 'user', content: question }],\r\n ...options,\r\n });\r\n return result.content;\r\n }\r\n\r\n /**\r\n * 带系统提示的对话(默认实现)\r\n */\r\n async askWithSystem(\r\n model: string,\r\n systemPrompt: string,\r\n userMessage: string,\r\n options?: Omit<ChatOptions, 'model' | 'messages'>\r\n ): Promise<string> {\r\n const result = await this.chat({\r\n model,\r\n messages: [\r\n { role: 'system', content: systemPrompt },\r\n { role: 'user', content: userMessage },\r\n ],\r\n ...options,\r\n });\r\n return result.content;\r\n }\r\n}\r\n","/**\r\n * OpenRouter Provider 实现\r\n */\r\n\r\nimport { OpenRouter } from '@openrouter/sdk';\r\nimport { BaseAIProvider } from '../base';\r\nimport type { ChatOptions, ChatResult, StreamChunk, ModelInfo, ReasoningConfig } from '../types';\r\n\r\n/**\r\n * OpenRouter 扩展的模型信息\r\n */\r\nexport interface OpenRouterModelInfo extends ModelInfo {\r\n /** 规范化的 slug */\r\n canonicalSlug: string;\r\n /** 创建时间戳 */\r\n created: number;\r\n /** 架构信息 */\r\n architecture: {\r\n modality: string;\r\n inputModalities: string[];\r\n outputModalities: string[];\r\n tokenizer: string;\r\n instructType: string;\r\n };\r\n}\r\n\r\n/**\r\n * 从 SDK 返回的 content 中提取文本\r\n */\r\nfunction extractTextContent(content: unknown): string {\r\n if (typeof content === 'string') {\r\n return content;\r\n }\r\n if (Array.isArray(content)) {\r\n return content\r\n .filter(\r\n (item): item is { type: 'text'; text: string } =>\r\n typeof item === 'object' && item !== null && item.type === 'text' && typeof item.text === 'string'\r\n )\r\n .map((item) => item.text)\r\n .join('');\r\n }\r\n return '';\r\n}\r\n\r\n/**\r\n * 构建 reasoning 参数(转换为 API 需要的 snake_case 格式)\r\n */\r\nfunction buildReasoningParam(config?: ReasoningConfig): Record<string, unknown> | undefined {\r\n if (!config) return undefined;\r\n\r\n const param: Record<string, unknown> = {};\r\n\r\n if (config.effort !== undefined) {\r\n param.effort = config.effort;\r\n }\r\n if (config.maxTokens !== undefined) {\r\n param.max_tokens = config.maxTokens;\r\n }\r\n if (config.exclude !== undefined) {\r\n param.exclude = config.exclude;\r\n }\r\n if (config.enabled !== undefined) {\r\n param.enabled = config.enabled;\r\n }\r\n\r\n return Object.keys(param).length > 0 ? param : undefined;\r\n}\r\n\r\n\r\n/**\r\n * OpenRouter Provider\r\n */\r\nexport class OpenRouterProvider extends BaseAIProvider {\r\n readonly name = 'openrouter';\r\n private client: OpenRouter;\r\n\r\n constructor(apiKey: string) {\r\n super();\r\n this.client = new OpenRouter({ apiKey });\r\n }\r\n\r\n /**\r\n * 发送聊天请求(非流式)\r\n */\r\n async chat(options: ChatOptions): Promise<ChatResult> {\r\n const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;\r\n\r\n const reasoningParam = buildReasoningParam(reasoning);\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const requestParams: any = {\r\n model,\r\n messages,\r\n temperature,\r\n maxTokens,\r\n stream: false,\r\n };\r\n\r\n if (reasoningParam) {\r\n requestParams.reasoning = reasoningParam;\r\n }\r\n\r\n const result = await this.client.chat.send(requestParams);\r\n\r\n const choice = result.choices[0];\r\n if (!choice) {\r\n throw new Error('No response from model');\r\n }\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const msg = choice.message as any;\r\n const reasoningContent = msg.reasoning_content ?? msg.reasoning ?? null;\r\n\r\n return {\r\n content: extractTextContent(msg.content),\r\n reasoning: reasoningContent ? extractTextContent(reasoningContent) : null,\r\n model: result.model,\r\n usage: {\r\n promptTokens: result.usage?.promptTokens ?? 0,\r\n completionTokens: result.usage?.completionTokens ?? 0,\r\n totalTokens: result.usage?.totalTokens ?? 0,\r\n },\r\n finishReason: choice.finishReason,\r\n };\r\n }\r\n\r\n /**\r\n * 发送流式聊天请求\r\n */\r\n async *chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown> {\r\n const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;\r\n\r\n const reasoningParam = buildReasoningParam(reasoning);\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const requestParams: any = {\r\n model,\r\n messages,\r\n temperature,\r\n maxTokens,\r\n stream: true,\r\n };\r\n\r\n if (reasoningParam) {\r\n requestParams.reasoning = reasoningParam;\r\n }\r\n\r\n const stream = (await this.client.chat.send(requestParams)) as unknown as AsyncIterable<{\r\n choices?: Array<{ delta?: unknown }>;\r\n }>;\r\n\r\n for await (const chunk of stream) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const delta = chunk.choices?.[0]?.delta as any;\r\n if (!delta) continue;\r\n\r\n const reasoningContent = delta.reasoning_content ?? delta.reasoning;\r\n if (reasoningContent) {\r\n yield { type: 'reasoning', text: extractTextContent(reasoningContent) };\r\n }\r\n\r\n if (delta.content) {\r\n yield { type: 'content', text: extractTextContent(delta.content) };\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 获取可用模型列表\r\n */\r\n async listModels(): Promise<OpenRouterModelInfo[]> {\r\n const result = await this.client.models.list();\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n return (result.data ?? []).map((m: any) => ({\r\n id: m.id,\r\n canonicalSlug: m.canonical_slug ?? m.id,\r\n name: m.name,\r\n description: m.description ?? '',\r\n created: m.created ?? 0,\r\n pricing: {\r\n prompt: m.pricing?.prompt ?? '0',\r\n completion: m.pricing?.completion ?? '0',\r\n request: m.pricing?.request ?? '0',\r\n image: m.pricing?.image ?? '0',\r\n },\r\n contextLength: m.context_length ?? 0,\r\n architecture: {\r\n modality: m.architecture?.modality ?? '',\r\n inputModalities: m.architecture?.input_modalities ?? [],\r\n outputModalities: m.architecture?.output_modalities ?? [],\r\n tokenizer: m.architecture?.tokenizer ?? '',\r\n instructType: m.architecture?.instruct_type ?? '',\r\n },\r\n supportedParameters: m.supported_parameters ?? [],\r\n }));\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/providers/__base__.ts","../src/providers/openrouter.ts"],"sourcesContent":["/**\r\n * AI Provider 接口和基类\r\n */\r\n\r\nimport type { ChatOptions, ChatResult, StreamChunk, ModelInfo } from './__types__';\r\n\r\n/**\r\n * AI Provider 基础接口\r\n * 所有 provider 实现都需要实现这个接口\r\n */\r\nexport interface AIProvider {\r\n /** Provider 名称 */\r\n readonly name: string;\r\n\r\n /**\r\n * 发送聊天请求(非流式)\r\n */\r\n chat(options: ChatOptions): Promise<ChatResult>;\r\n\r\n /**\r\n * 发送流式聊天请求\r\n */\r\n chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown>;\r\n\r\n /**\r\n * 简单对话:单轮问答\r\n */\r\n ask(model: string, question: string, options?: Omit<ChatOptions, 'model' | 'messages'>): Promise<string>;\r\n\r\n /**\r\n * 带系统提示的对话\r\n */\r\n askWithSystem(\r\n model: string,\r\n systemPrompt: string,\r\n userMessage: string,\r\n options?: Omit<ChatOptions, 'model' | 'messages'>\r\n ): Promise<string>;\r\n\r\n /**\r\n * 获取可用模型列表(可选实现)\r\n */\r\n listModels?(): Promise<ModelInfo[]>;\r\n}\r\n\r\n/**\r\n * AI Provider 基础抽象类\r\n * 提供一些通用实现,子类只需实现核心方法\r\n */\r\nexport abstract class BaseProvider implements AIProvider {\r\n abstract readonly name: string;\r\n\r\n abstract chat(options: ChatOptions): Promise<ChatResult>;\r\n\r\n abstract chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown>;\r\n\r\n /**\r\n * 简单对话:单轮问答(默认实现)\r\n * 对于思考模型,如果 content 为空则返回 reasoning\r\n */\r\n async ask(model: string, question: string, options?: Omit<ChatOptions, 'model' | 'messages'>): Promise<string> {\r\n const result = await this.chat({\r\n model,\r\n messages: [{ role: 'user', content: question }],\r\n ...options,\r\n });\r\n return result.content || result.reasoning || '';\r\n }\r\n\r\n /**\r\n * 带系统提示的对话(默认实现)\r\n * 对于思考模型,如果 content 为空则返回 reasoning\r\n */\r\n async askWithSystem(\r\n model: string,\r\n systemPrompt: string,\r\n userMessage: string,\r\n options?: Omit<ChatOptions, 'model' | 'messages'>\r\n ): Promise<string> {\r\n const result = await this.chat({\r\n model,\r\n messages: [\r\n { role: 'system', content: systemPrompt },\r\n { role: 'user', content: userMessage },\r\n ],\r\n ...options,\r\n });\r\n return result.content || result.reasoning || '';\r\n }\r\n}\r\n","/**\r\n * OpenRouter Provider 实现\r\n */\r\n\r\nimport { OpenRouter } from '@openrouter/sdk';\r\nimport { BaseProvider } from './__base__';\r\nimport type { ChatOptions, ChatResult, StreamChunk, ModelInfo, ReasoningConfig } from './__types__';\r\n\r\n/**\r\n * OpenRouter 扩展的模型信息\r\n */\r\nexport interface OpenRouterModelInfo extends ModelInfo {\r\n /** 规范化的 slug */\r\n canonicalSlug: string;\r\n /** 创建时间戳 */\r\n created: number;\r\n /** 架构信息 */\r\n architecture: {\r\n modality: string;\r\n inputModalities: string[];\r\n outputModalities: string[];\r\n tokenizer: string;\r\n instructType: string;\r\n };\r\n}\r\n\r\n/**\r\n * 从 SDK 返回的 content 中提取文本\r\n */\r\nfunction extractTextContent(content: unknown): string {\r\n if (typeof content === 'string') {\r\n return content;\r\n }\r\n if (Array.isArray(content)) {\r\n return content\r\n .filter(\r\n (item): item is { type: 'text'; text: string } =>\r\n typeof item === 'object' && item !== null && item.type === 'text' && typeof item.text === 'string'\r\n )\r\n .map((item) => item.text)\r\n .join('');\r\n }\r\n return '';\r\n}\r\n\r\n/**\r\n * 构建 reasoning 参数(转换为 API 需要的 snake_case 格式)\r\n */\r\nfunction buildReasoningParam(config?: ReasoningConfig): Record<string, unknown> | undefined {\r\n if (!config) return undefined;\r\n\r\n const param: Record<string, unknown> = {};\r\n\r\n if (config.effort !== undefined) {\r\n param.effort = config.effort;\r\n }\r\n if (config.maxTokens !== undefined) {\r\n param.max_tokens = config.maxTokens;\r\n }\r\n if (config.exclude !== undefined) {\r\n param.exclude = config.exclude;\r\n }\r\n if (config.enabled !== undefined) {\r\n param.enabled = config.enabled;\r\n }\r\n\r\n return Object.keys(param).length > 0 ? param : undefined;\r\n}\r\n\r\n\r\n/**\r\n * OpenRouter Provider\r\n */\r\nexport class OpenRouterProvider extends BaseProvider {\r\n readonly name = 'openrouter';\r\n private client: OpenRouter;\r\n\r\n constructor(apiKey: string) {\r\n super();\r\n this.client = new OpenRouter({ apiKey });\r\n }\r\n\r\n /**\r\n * 发送聊天请求(非流式)\r\n */\r\n async chat(options: ChatOptions): Promise<ChatResult> {\r\n const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;\r\n\r\n const reasoningParam = buildReasoningParam(reasoning);\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const requestParams: any = {\r\n model,\r\n messages,\r\n temperature,\r\n maxTokens,\r\n stream: false,\r\n };\r\n\r\n if (reasoningParam) {\r\n requestParams.reasoning = reasoningParam;\r\n }\r\n\r\n const result = await this.client.chat.send(requestParams);\r\n\r\n const choice = result.choices[0];\r\n if (!choice) {\r\n throw new Error('No response from model');\r\n }\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const msg = choice.message as any;\r\n const reasoningContent = msg.reasoning_content ?? msg.reasoning ?? null;\r\n\r\n return {\r\n content: extractTextContent(msg.content),\r\n reasoning: reasoningContent ? extractTextContent(reasoningContent) : null,\r\n model: result.model,\r\n usage: {\r\n promptTokens: result.usage?.promptTokens ?? 0,\r\n completionTokens: result.usage?.completionTokens ?? 0,\r\n totalTokens: result.usage?.totalTokens ?? 0,\r\n },\r\n finishReason: choice.finishReason,\r\n };\r\n }\r\n\r\n /**\r\n * 发送流式聊天请求\r\n */\r\n async *chatStream(options: ChatOptions): AsyncGenerator<StreamChunk, void, unknown> {\r\n const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;\r\n\r\n const reasoningParam = buildReasoningParam(reasoning);\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const requestParams: any = {\r\n model,\r\n messages,\r\n temperature,\r\n maxTokens,\r\n stream: true,\r\n };\r\n\r\n if (reasoningParam) {\r\n requestParams.reasoning = reasoningParam;\r\n }\r\n\r\n const stream = (await this.client.chat.send(requestParams)) as unknown as AsyncIterable<{\r\n choices?: Array<{ delta?: unknown }>;\r\n }>;\r\n\r\n for await (const chunk of stream) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const delta = chunk.choices?.[0]?.delta as any;\r\n if (!delta) continue;\r\n\r\n const reasoningContent = delta.reasoning_content ?? delta.reasoning;\r\n if (reasoningContent) {\r\n yield { type: 'reasoning', text: extractTextContent(reasoningContent) };\r\n }\r\n\r\n if (delta.content) {\r\n yield { type: 'content', text: extractTextContent(delta.content) };\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 获取可用模型列表\r\n */\r\n async listModels(): Promise<OpenRouterModelInfo[]> {\r\n const result = await this.client.models.list();\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n return (result.data ?? []).map((m: any) => ({\r\n id: m.id,\r\n canonicalSlug: m.canonical_slug ?? m.id,\r\n name: m.name,\r\n description: m.description ?? '',\r\n created: m.created ?? 0,\r\n pricing: {\r\n prompt: m.pricing?.prompt ?? '0',\r\n completion: m.pricing?.completion ?? '0',\r\n request: m.pricing?.request ?? '0',\r\n image: m.pricing?.image ?? '0',\r\n },\r\n contextLength: m.context_length ?? 0,\r\n architecture: {\r\n modality: m.architecture?.modality ?? '',\r\n inputModalities: m.architecture?.input_modalities ?? [],\r\n outputModalities: m.architecture?.output_modalities ?? [],\r\n tokenizer: m.architecture?.tokenizer ?? '',\r\n instructType: m.architecture?.instruct_type ?? '',\r\n },\r\n supportedParameters: m.supported_parameters ?? [],\r\n }));\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weisiren000/oiiai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "统一的 AI Provider 接口封装,支持 OpenRouter、OpenAI、Anthropic 等",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
10
11
|
"import": "./dist/index.mjs",
|
|
11
|
-
"require": "./dist/index.js"
|
|
12
|
-
"types": "./dist/index.d.ts"
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
@@ -19,7 +19,12 @@
|
|
|
19
19
|
"build": "tsup",
|
|
20
20
|
"dev": "tsup --watch",
|
|
21
21
|
"prepublishOnly": "npm run build",
|
|
22
|
-
"test": "tsx src/__tests__/openrouter.test.ts"
|
|
22
|
+
"test:openrouter": "tsx src/__tests__/openrouter.test.ts",
|
|
23
|
+
"test:modelscope": "tsx src/__tests__/modelscope.test.ts",
|
|
24
|
+
"test:huggingface": "tsx src/__tests__/huggingface.test.ts",
|
|
25
|
+
"test:groq": "tsx src/__tests__/groq.test.ts",
|
|
26
|
+
"test:gemini": "tsx src/__tests__/gemini.test.ts",
|
|
27
|
+
"test:iflow": "tsx src/__tests__/iflow.test.ts"
|
|
23
28
|
},
|
|
24
29
|
"keywords": [
|
|
25
30
|
"ai",
|
|
@@ -35,10 +40,11 @@
|
|
|
35
40
|
"@openrouter/sdk": "^0.2.11"
|
|
36
41
|
},
|
|
37
42
|
"devDependencies": {
|
|
43
|
+
"@types/node": "^25.0.2",
|
|
44
|
+
"dotenv": "^16.0.0",
|
|
38
45
|
"tsup": "^8.0.0",
|
|
39
|
-
"typescript": "^5.0.0",
|
|
40
46
|
"tsx": "^4.0.0",
|
|
41
|
-
"
|
|
47
|
+
"typescript": "^5.0.0"
|
|
42
48
|
},
|
|
43
49
|
"peerDependencies": {
|
|
44
50
|
"typescript": ">=5.0.0"
|