repowiki-core 0.1.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/dist/analyzer/api-analyzer.d.ts +22 -0
- package/dist/analyzer/api-analyzer.d.ts.map +1 -0
- package/dist/analyzer/api-analyzer.js +272 -0
- package/dist/analyzer/api-analyzer.js.map +1 -0
- package/dist/analyzer/config-analyzer.d.ts +18 -0
- package/dist/analyzer/config-analyzer.d.ts.map +1 -0
- package/dist/analyzer/config-analyzer.js +200 -0
- package/dist/analyzer/config-analyzer.js.map +1 -0
- package/dist/analyzer/database-analyzer.d.ts +24 -0
- package/dist/analyzer/database-analyzer.d.ts.map +1 -0
- package/dist/analyzer/database-analyzer.js +391 -0
- package/dist/analyzer/database-analyzer.js.map +1 -0
- package/dist/analyzer/index.d.ts +10 -0
- package/dist/analyzer/index.d.ts.map +1 -0
- package/dist/analyzer/index.js +10 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/analyzer/module-analyzer.d.ts +20 -0
- package/dist/analyzer/module-analyzer.d.ts.map +1 -0
- package/dist/analyzer/module-analyzer.js +252 -0
- package/dist/analyzer/module-analyzer.js.map +1 -0
- package/dist/analyzer/workflow-analyzer.d.ts +19 -0
- package/dist/analyzer/workflow-analyzer.d.ts.map +1 -0
- package/dist/analyzer/workflow-analyzer.js +165 -0
- package/dist/analyzer/workflow-analyzer.js.map +1 -0
- package/dist/detector/dependency-detector.d.ts +50 -0
- package/dist/detector/dependency-detector.d.ts.map +1 -0
- package/dist/detector/dependency-detector.js +326 -0
- package/dist/detector/dependency-detector.js.map +1 -0
- package/dist/detector/entrypoint-detector.d.ts +30 -0
- package/dist/detector/entrypoint-detector.d.ts.map +1 -0
- package/dist/detector/entrypoint-detector.js +240 -0
- package/dist/detector/entrypoint-detector.js.map +1 -0
- package/dist/detector/index.d.ts +10 -0
- package/dist/detector/index.d.ts.map +1 -0
- package/dist/detector/index.js +10 -0
- package/dist/detector/index.js.map +1 -0
- package/dist/detector/tech-stack-detector.d.ts +41 -0
- package/dist/detector/tech-stack-detector.d.ts.map +1 -0
- package/dist/detector/tech-stack-detector.js +300 -0
- package/dist/detector/tech-stack-detector.js.map +1 -0
- package/dist/generator/index.d.ts +9 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +9 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/markdown-generator.d.ts +71 -0
- package/dist/generator/markdown-generator.d.ts.map +1 -0
- package/dist/generator/markdown-generator.js +235 -0
- package/dist/generator/markdown-generator.js.map +1 -0
- package/dist/generator/mermaid-generator.d.ts +30 -0
- package/dist/generator/mermaid-generator.d.ts.map +1 -0
- package/dist/generator/mermaid-generator.js +297 -0
- package/dist/generator/mermaid-generator.js.map +1 -0
- package/dist/generator/sidebar-generator.d.ts +10 -0
- package/dist/generator/sidebar-generator.d.ts.map +1 -0
- package/dist/generator/sidebar-generator.js +120 -0
- package/dist/generator/sidebar-generator.js.map +1 -0
- package/dist/generator/wiki-generator.d.ts +45 -0
- package/dist/generator/wiki-generator.d.ts.map +1 -0
- package/dist/generator/wiki-generator.js +217 -0
- package/dist/generator/wiki-generator.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/auth-manager.d.ts +50 -0
- package/dist/llm/auth-manager.d.ts.map +1 -0
- package/dist/llm/auth-manager.js +172 -0
- package/dist/llm/auth-manager.js.map +1 -0
- package/dist/llm/index.d.ts +10 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +9 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/llm-client.d.ts +132 -0
- package/dist/llm/llm-client.d.ts.map +1 -0
- package/dist/llm/llm-client.js +308 -0
- package/dist/llm/llm-client.js.map +1 -0
- package/dist/llm/prompt-manager.d.ts +67 -0
- package/dist/llm/prompt-manager.d.ts.map +1 -0
- package/dist/llm/prompt-manager.js +283 -0
- package/dist/llm/prompt-manager.js.map +1 -0
- package/dist/models/analysis-result.d.ts +425 -0
- package/dist/models/analysis-result.d.ts.map +1 -0
- package/dist/models/analysis-result.js +34 -0
- package/dist/models/analysis-result.js.map +1 -0
- package/dist/models/analysis-types.d.ts +223 -0
- package/dist/models/analysis-types.d.ts.map +1 -0
- package/dist/models/analysis-types.js +95 -0
- package/dist/models/analysis-types.js.map +1 -0
- package/dist/models/file-reference.d.ts +62 -0
- package/dist/models/file-reference.d.ts.map +1 -0
- package/dist/models/file-reference.js +34 -0
- package/dist/models/file-reference.js.map +1 -0
- package/dist/models/index.d.ts +10 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +10 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/project-profile.d.ts +48 -0
- package/dist/models/project-profile.d.ts.map +1 -0
- package/dist/models/project-profile.js +26 -0
- package/dist/models/project-profile.js.map +1 -0
- package/dist/models/wiki-page.d.ts +57 -0
- package/dist/models/wiki-page.d.ts.map +1 -0
- package/dist/models/wiki-page.js +19 -0
- package/dist/models/wiki-page.js.map +1 -0
- package/dist/pipeline.d.ts +30 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +159 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/scanner/file-scanner.d.ts +27 -0
- package/dist/scanner/file-scanner.d.ts.map +1 -0
- package/dist/scanner/file-scanner.js +149 -0
- package/dist/scanner/file-scanner.js.map +1 -0
- package/dist/scanner/ignore-rules.d.ts +31 -0
- package/dist/scanner/ignore-rules.d.ts.map +1 -0
- package/dist/scanner/ignore-rules.js +98 -0
- package/dist/scanner/ignore-rules.js.map +1 -0
- package/dist/scanner/index.d.ts +8 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +8 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/tree-builder.d.ts +20 -0
- package/dist/scanner/tree-builder.d.ts.map +1 -0
- package/dist/scanner/tree-builder.js +118 -0
- package/dist/scanner/tree-builder.js.map +1 -0
- package/package.json +34 -0
- package/src/analyzer/api-analyzer.ts +324 -0
- package/src/analyzer/config-analyzer.ts +209 -0
- package/src/analyzer/database-analyzer.ts +468 -0
- package/src/analyzer/index.ts +26 -0
- package/src/analyzer/module-analyzer.ts +308 -0
- package/src/analyzer/workflow-analyzer.ts +190 -0
- package/src/detector/dependency-detector.ts +390 -0
- package/src/detector/entrypoint-detector.ts +270 -0
- package/src/detector/index.ts +21 -0
- package/src/detector/tech-stack-detector.ts +377 -0
- package/src/generator/index.ts +36 -0
- package/src/generator/markdown-generator.ts +277 -0
- package/src/generator/mermaid-generator.ts +340 -0
- package/src/generator/sidebar-generator.ts +134 -0
- package/src/generator/wiki-generator.ts +281 -0
- package/src/index.ts +12 -0
- package/src/llm/auth-manager.ts +207 -0
- package/src/llm/index.ts +21 -0
- package/src/llm/llm-client.ts +417 -0
- package/src/llm/prompt-manager.ts +325 -0
- package/src/models/analysis-result.ts +44 -0
- package/src/models/analysis-types.ts +121 -0
- package/src/models/file-reference.ts +41 -0
- package/src/models/index.ts +44 -0
- package/src/models/project-profile.ts +29 -0
- package/src/models/wiki-page.ts +23 -0
- package/src/pipeline.ts +225 -0
- package/src/scanner/file-scanner.ts +192 -0
- package/src/scanner/ignore-rules.ts +112 -0
- package/src/scanner/index.ts +19 -0
- package/src/scanner/tree-builder.ts +156 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module auth-manager
|
|
3
|
+
* @description 管理 LLM API 凭证,支持多来源优先级链解析配置。
|
|
4
|
+
*
|
|
5
|
+
* 优先级链(按优先级从高到低):
|
|
6
|
+
* 1. 环境变量: REPOWIKI_API_KEY, REPOWIKI_BASE_URL, REPOWIKI_MODEL
|
|
7
|
+
* 2. 环境变量 (OpenAI 兼容): OPENAI_API_KEY, OPENAI_BASE_URL
|
|
8
|
+
* 3. 全局配置文件: ~/.repowiki/config.json
|
|
9
|
+
* 4. 工作区 .env 文件(使用 dotenv 解析)
|
|
10
|
+
*/
|
|
11
|
+
import { promises as fs } from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import os from 'node:os';
|
|
14
|
+
import dotenv from 'dotenv';
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// 常量
|
|
17
|
+
// ============================================================================
|
|
18
|
+
/** 默认 API 端点 */
|
|
19
|
+
const DEFAULT_API_ENDPOINT = 'https://api.openai.com/v1';
|
|
20
|
+
/** 默认模型名称 */
|
|
21
|
+
const DEFAULT_MODEL_NAME = 'gpt-4o';
|
|
22
|
+
/** 全局配置目录名 */
|
|
23
|
+
const GLOBAL_CONFIG_DIR = '.repowiki';
|
|
24
|
+
/** 全局配置文件名 */
|
|
25
|
+
const GLOBAL_CONFIG_FILE = 'config.json';
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// 内部工具函数
|
|
28
|
+
// ============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* 获取全局配置文件的绝对路径
|
|
31
|
+
*/
|
|
32
|
+
function getGlobalConfigPath() {
|
|
33
|
+
return path.join(os.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_FILE);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 尝试读取并解析 JSON 配置文件,若文件不存在或解析失败则返回空对象
|
|
37
|
+
*/
|
|
38
|
+
async function readJsonConfig(filePath) {
|
|
39
|
+
try {
|
|
40
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
41
|
+
const parsed = JSON.parse(content);
|
|
42
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// 文件不存在或内容无效,均视为空配置
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 尝试解析工作区 .env 文件,返回解析后的键值对
|
|
54
|
+
*/
|
|
55
|
+
async function readWorkspaceEnv(workspacePath) {
|
|
56
|
+
const envFilePath = path.join(workspacePath, '.env');
|
|
57
|
+
try {
|
|
58
|
+
const content = await fs.readFile(envFilePath, 'utf-8');
|
|
59
|
+
const parsed = dotenv.parse(content);
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 返回第一个非空字符串值,若全部为空则返回 undefined
|
|
68
|
+
*/
|
|
69
|
+
function firstNonEmpty(...values) {
|
|
70
|
+
for (const v of values) {
|
|
71
|
+
if (v && v.trim().length > 0) {
|
|
72
|
+
return v.trim();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// 公开 API
|
|
79
|
+
// ============================================================================
|
|
80
|
+
/**
|
|
81
|
+
* 按优先级链加载 LLM 配置。
|
|
82
|
+
*
|
|
83
|
+
* 解析顺序:
|
|
84
|
+
* 1. 进程环境变量(REPOWIKI_* 优先,其次 OPENAI_*)
|
|
85
|
+
* 2. 全局配置文件 ~/.repowiki/config.json
|
|
86
|
+
* 3. 工作区 .env 文件
|
|
87
|
+
*
|
|
88
|
+
* 对于每个字段,取优先级最高的非空值;若全部为空则使用默认值或空字符串。
|
|
89
|
+
*
|
|
90
|
+
* @param workspacePath - 可选的工作区路径,用于读取 .env 文件
|
|
91
|
+
* @returns 解析后的 LLM 配置
|
|
92
|
+
*/
|
|
93
|
+
export async function loadLLMConfig(workspacePath) {
|
|
94
|
+
// --- 来源 1 & 2: 环境变量(已在 process.env 中) ---
|
|
95
|
+
const envApiKey = firstNonEmpty(process.env['REPOWIKI_API_KEY'], process.env['OPENAI_API_KEY']);
|
|
96
|
+
const envEndpoint = firstNonEmpty(process.env['REPOWIKI_BASE_URL'], process.env['OPENAI_BASE_URL']);
|
|
97
|
+
const envModel = firstNonEmpty(process.env['REPOWIKI_MODEL']);
|
|
98
|
+
// --- 来源 3: 全局配置文件 ---
|
|
99
|
+
const globalConfig = await readJsonConfig(getGlobalConfigPath());
|
|
100
|
+
const globalApiKey = typeof globalConfig['apiKey'] === 'string' ? globalConfig['apiKey'] : undefined;
|
|
101
|
+
const globalEndpoint = typeof globalConfig['apiEndpoint'] === 'string' ? globalConfig['apiEndpoint'] : undefined;
|
|
102
|
+
const globalModel = typeof globalConfig['modelName'] === 'string' ? globalConfig['modelName'] : undefined;
|
|
103
|
+
// --- 来源 4: 工作区 .env ---
|
|
104
|
+
let wsApiKey;
|
|
105
|
+
let wsEndpoint;
|
|
106
|
+
let wsModel;
|
|
107
|
+
if (workspacePath) {
|
|
108
|
+
const wsEnv = await readWorkspaceEnv(workspacePath);
|
|
109
|
+
wsApiKey = firstNonEmpty(wsEnv['REPOWIKI_API_KEY'], wsEnv['OPENAI_API_KEY']);
|
|
110
|
+
wsEndpoint = firstNonEmpty(wsEnv['REPOWIKI_BASE_URL'], wsEnv['OPENAI_BASE_URL']);
|
|
111
|
+
wsModel = firstNonEmpty(wsEnv['REPOWIKI_MODEL']);
|
|
112
|
+
}
|
|
113
|
+
// --- 合并:对每个字段,按优先级取第一个非空值 ---
|
|
114
|
+
const apiKey = firstNonEmpty(envApiKey, globalApiKey, wsApiKey) ?? '';
|
|
115
|
+
const apiEndpoint = firstNonEmpty(envEndpoint, globalEndpoint, wsEndpoint) ?? DEFAULT_API_ENDPOINT;
|
|
116
|
+
const modelName = firstNonEmpty(envModel, globalModel, wsModel) ?? DEFAULT_MODEL_NAME;
|
|
117
|
+
return { apiEndpoint, modelName, apiKey };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 将配置保存到全局配置文件 ~/.repowiki/config.json。
|
|
121
|
+
*
|
|
122
|
+
* 如果文件已存在,则与现有配置合并(新值覆盖旧值)。
|
|
123
|
+
* 如果目录不存在,则自动创建。
|
|
124
|
+
*
|
|
125
|
+
* @param config - 要保存的部分配置
|
|
126
|
+
*/
|
|
127
|
+
export async function saveGlobalConfig(config) {
|
|
128
|
+
const configPath = getGlobalConfigPath();
|
|
129
|
+
const configDir = path.dirname(configPath);
|
|
130
|
+
// 确保配置目录存在
|
|
131
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
132
|
+
// 读取现有配置并合并
|
|
133
|
+
const existing = await readJsonConfig(configPath);
|
|
134
|
+
// 仅合并非 undefined 的字段
|
|
135
|
+
const merged = { ...existing };
|
|
136
|
+
if (config.apiEndpoint !== undefined)
|
|
137
|
+
merged['apiEndpoint'] = config.apiEndpoint;
|
|
138
|
+
if (config.modelName !== undefined)
|
|
139
|
+
merged['modelName'] = config.modelName;
|
|
140
|
+
if (config.apiKey !== undefined)
|
|
141
|
+
merged['apiKey'] = config.apiKey;
|
|
142
|
+
await fs.writeFile(configPath, JSON.stringify(merged, null, 2), 'utf-8');
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 验证 LLM 配置的完整性。
|
|
146
|
+
*
|
|
147
|
+
* @param config - 待验证的配置
|
|
148
|
+
* @returns 错误消息数组。若为空数组,则配置有效。
|
|
149
|
+
*/
|
|
150
|
+
export function validateConfig(config) {
|
|
151
|
+
const errors = [];
|
|
152
|
+
if (!config.apiKey || config.apiKey.trim().length === 0) {
|
|
153
|
+
errors.push('缺少 API 密钥 (apiKey)。请通过环境变量 REPOWIKI_API_KEY 或全局配置文件设置。');
|
|
154
|
+
}
|
|
155
|
+
if (!config.apiEndpoint || config.apiEndpoint.trim().length === 0) {
|
|
156
|
+
errors.push('缺少 API 端点 (apiEndpoint)。');
|
|
157
|
+
}
|
|
158
|
+
if (!config.modelName || config.modelName.trim().length === 0) {
|
|
159
|
+
errors.push('缺少模型名称 (modelName)。');
|
|
160
|
+
}
|
|
161
|
+
// 验证 apiEndpoint 是合法 URL
|
|
162
|
+
if (config.apiEndpoint && config.apiEndpoint.trim().length > 0) {
|
|
163
|
+
try {
|
|
164
|
+
new URL(config.apiEndpoint);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
errors.push(`API 端点格式无效: "${config.apiEndpoint}"。请提供合法的 URL。`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return errors;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=auth-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-manager.js","sourceRoot":"","sources":["../../src/llm/auth-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module llm
|
|
3
|
+
* @description LLM 层入口 - 统一导出认证管理、客户端和提示词管理功能。
|
|
4
|
+
*/
|
|
5
|
+
export type { LLMConfig } from './auth-manager.js';
|
|
6
|
+
export { loadLLMConfig, saveGlobalConfig, validateConfig } from './auth-manager.js';
|
|
7
|
+
export type { ChatMessage, LLMClientOptions, LLMResponse, ChatOptions } from './llm-client.js';
|
|
8
|
+
export { LLMClient, LLMError, LLMAuthError, LLMRateLimitError, LLMJsonParseError } from './llm-client.js';
|
|
9
|
+
export { buildModuleAnalysisPrompt, buildMermaidPrompt, buildWikiPagePrompt, buildWikiPlanPrompt, buildSourceSummaryPrompt, } from './prompt-manager.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/llm/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGpF,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC/F,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAG1G,OAAO,EACH,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,GAC3B,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module llm
|
|
3
|
+
* @description LLM 层入口 - 统一导出认证管理、客户端和提示词管理功能。
|
|
4
|
+
*/
|
|
5
|
+
export { loadLLMConfig, saveGlobalConfig, validateConfig } from './auth-manager.js';
|
|
6
|
+
export { LLMClient, LLMError, LLMAuthError, LLMRateLimitError, LLMJsonParseError } from './llm-client.js';
|
|
7
|
+
// 提示词管理
|
|
8
|
+
export { buildModuleAnalysisPrompt, buildMermaidPrompt, buildWikiPagePrompt, buildWikiPlanPrompt, buildSourceSummaryPrompt, } from './prompt-manager.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/llm/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIpF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAE1G,QAAQ;AACR,OAAO,EACH,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,GAC3B,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module llm-client
|
|
3
|
+
* @description OpenAI 兼容的 LLM API 客户端。
|
|
4
|
+
*
|
|
5
|
+
* 特性:
|
|
6
|
+
* - 使用原生 fetch (Node 18+)
|
|
7
|
+
* - 指数退避重试 (429 / 5xx)
|
|
8
|
+
* - AbortController 超时控制
|
|
9
|
+
* - chatJSON: 自动解析 JSON 响应并可选 Zod 校验
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import type { LLMConfig } from './auth-manager.js';
|
|
13
|
+
/** 聊天消息 */
|
|
14
|
+
export interface ChatMessage {
|
|
15
|
+
role: 'system' | 'user' | 'assistant';
|
|
16
|
+
content: string;
|
|
17
|
+
}
|
|
18
|
+
/** LLM 客户端选项 */
|
|
19
|
+
export interface LLMClientOptions {
|
|
20
|
+
/** LLM 服务配置 */
|
|
21
|
+
config: LLMConfig;
|
|
22
|
+
/** 最大重试次数 (默认 3) */
|
|
23
|
+
maxRetries?: number;
|
|
24
|
+
/** 基础重试延迟,毫秒 (默认 1000) */
|
|
25
|
+
retryDelayMs?: number;
|
|
26
|
+
/** 请求超时,毫秒 (默认 120000,即 2 分钟) */
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
/** LLM 响应 */
|
|
30
|
+
export interface LLMResponse {
|
|
31
|
+
/** 模型回复的文本内容 */
|
|
32
|
+
content: string;
|
|
33
|
+
/** Token 使用统计 */
|
|
34
|
+
usage?: {
|
|
35
|
+
promptTokens: number;
|
|
36
|
+
completionTokens: number;
|
|
37
|
+
totalTokens: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** chat 方法可选参数 */
|
|
41
|
+
export interface ChatOptions {
|
|
42
|
+
/** 温度,控制随机性 (默认 0.3) */
|
|
43
|
+
temperature?: number;
|
|
44
|
+
/** 最大生成 token 数 */
|
|
45
|
+
maxTokens?: number;
|
|
46
|
+
}
|
|
47
|
+
/** LLM 请求错误基类 */
|
|
48
|
+
export declare class LLMError extends Error {
|
|
49
|
+
readonly statusCode?: number | undefined;
|
|
50
|
+
readonly responseBody?: string | undefined;
|
|
51
|
+
constructor(message: string, statusCode?: number | undefined, responseBody?: string | undefined);
|
|
52
|
+
}
|
|
53
|
+
/** 认证错误 (401 / 403) */
|
|
54
|
+
export declare class LLMAuthError extends LLMError {
|
|
55
|
+
constructor(message: string, statusCode: number, responseBody?: string);
|
|
56
|
+
}
|
|
57
|
+
/** 速率限制错误 (429) */
|
|
58
|
+
export declare class LLMRateLimitError extends LLMError {
|
|
59
|
+
constructor(message: string, responseBody?: string);
|
|
60
|
+
}
|
|
61
|
+
/** JSON 解析错误 */
|
|
62
|
+
export declare class LLMJsonParseError extends LLMError {
|
|
63
|
+
readonly rawContent: string;
|
|
64
|
+
constructor(message: string, rawContent: string);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* OpenAI 兼容的 LLM 客户端。
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const client = new LLMClient({ config });
|
|
72
|
+
* const response = await client.chat([
|
|
73
|
+
* { role: 'system', content: '你是一个助手。' },
|
|
74
|
+
* { role: 'user', content: '你好' },
|
|
75
|
+
* ]);
|
|
76
|
+
* console.log(response.content);
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare class LLMClient {
|
|
80
|
+
private readonly config;
|
|
81
|
+
private readonly maxRetries;
|
|
82
|
+
private readonly retryDelayMs;
|
|
83
|
+
private readonly timeoutMs;
|
|
84
|
+
constructor(options: LLMClientOptions);
|
|
85
|
+
/**
|
|
86
|
+
* 发送聊天补全请求。
|
|
87
|
+
*
|
|
88
|
+
* @param messages - 聊天消息列表
|
|
89
|
+
* @param options - 可选参数(温度、最大 token 数)
|
|
90
|
+
* @returns LLM 响应
|
|
91
|
+
* @throws {LLMAuthError} 认证失败
|
|
92
|
+
* @throws {LLMRateLimitError} 速率限制(重试耗尽后)
|
|
93
|
+
* @throws {LLMError} 其他请求错误
|
|
94
|
+
*/
|
|
95
|
+
chat(messages: ChatMessage[], options?: ChatOptions): Promise<LLMResponse>;
|
|
96
|
+
/**
|
|
97
|
+
* 发送聊天请求并将响应解析为 JSON。
|
|
98
|
+
*
|
|
99
|
+
* 工作流程:
|
|
100
|
+
* 1. 调用 chat() 获取文本响应
|
|
101
|
+
* 2. 尝试解析 JSON(支持从 Markdown 代码块中提取)
|
|
102
|
+
* 3. 若提供了 Zod schema,进行运行时校验
|
|
103
|
+
* 4. 若 JSON 解析失败,追加修正提示后重试一次
|
|
104
|
+
*
|
|
105
|
+
* @param messages - 聊天消息列表
|
|
106
|
+
* @param schema - 可选的 Zod 校验 schema
|
|
107
|
+
* @returns 解析后的 JSON 对象
|
|
108
|
+
* @throws {LLMJsonParseError} JSON 解析或校验失败
|
|
109
|
+
*/
|
|
110
|
+
chatJSON<T = unknown>(messages: ChatMessage[], schema?: z.ZodType<T>): Promise<T>;
|
|
111
|
+
/**
|
|
112
|
+
* 构建 OpenAI 兼容的请求体
|
|
113
|
+
*/
|
|
114
|
+
private buildRequestBody;
|
|
115
|
+
/**
|
|
116
|
+
* 带超时控制的 fetch 请求
|
|
117
|
+
*/
|
|
118
|
+
private fetchWithTimeout;
|
|
119
|
+
/**
|
|
120
|
+
* 根据 HTTP 状态码抛出对应错误
|
|
121
|
+
*/
|
|
122
|
+
private handleErrorStatus;
|
|
123
|
+
/**
|
|
124
|
+
* 从 OpenAI 兼容响应中解析内容和 usage
|
|
125
|
+
*/
|
|
126
|
+
private parseResponse;
|
|
127
|
+
/**
|
|
128
|
+
* 验证 JSON 响应:解析 + 可选 Zod 校验
|
|
129
|
+
*/
|
|
130
|
+
private validateJsonResponse;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=llm-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-client.d.ts","sourceRoot":"","sources":["../../src/llm/llm-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAMnD,WAAW;AACX,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,gBAAgB;AAChB,MAAM,WAAW,gBAAgB;IAC7B,eAAe;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,oBAAoB;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,aAAa;AACb,MAAM,WAAW,WAAW;IACxB,gBAAgB;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB;IACjB,KAAK,CAAC,EAAE;QACJ,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC;KACvB,CAAC;CACL;AAED,kBAAkB;AAClB,MAAM,WAAW,WAAW;IACxB,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAMD,iBAAiB;AACjB,qBAAa,QAAS,SAAQ,KAAK;aAGX,UAAU,CAAC,EAAE,MAAM;aACnB,YAAY,CAAC,EAAE,MAAM;gBAFrC,OAAO,EAAE,MAAM,EACC,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,YAAY,CAAC,EAAE,MAAM,YAAA;CAK5C;AAED,uBAAuB;AACvB,qBAAa,YAAa,SAAQ,QAAQ;gBAC1B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM;CAIzE;AAED,mBAAmB;AACnB,qBAAa,iBAAkB,SAAQ,QAAQ;gBAC/B,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM;CAIrD;AAED,gBAAgB;AAChB,qBAAa,iBAAkB,SAAQ,QAAQ;aAGvB,UAAU,EAAE,MAAM;gBADlC,OAAO,EAAE,MAAM,EACC,UAAU,EAAE,MAAM;CAKzC;AAmDD;;;;;;;;;;;;GAYG;AACH,qBAAa,SAAS;IAClB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,EAAE,gBAAgB;IAOrC;;;;;;;;;OASG;IACG,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAwChF;;;;;;;;;;;;;OAaG;IACG,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAqCvF;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAcxB;;OAEG;YACW,gBAAgB;IAuC9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAgCzB;;OAEG;YACW,aAAa;IAyB3B;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAgB/B"}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module llm-client
|
|
3
|
+
* @description OpenAI 兼容的 LLM API 客户端。
|
|
4
|
+
*
|
|
5
|
+
* 特性:
|
|
6
|
+
* - 使用原生 fetch (Node 18+)
|
|
7
|
+
* - 指数退避重试 (429 / 5xx)
|
|
8
|
+
* - AbortController 超时控制
|
|
9
|
+
* - chatJSON: 自动解析 JSON 响应并可选 Zod 校验
|
|
10
|
+
*/
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// 错误类
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/** LLM 请求错误基类 */
|
|
15
|
+
export class LLMError extends Error {
|
|
16
|
+
statusCode;
|
|
17
|
+
responseBody;
|
|
18
|
+
constructor(message, statusCode, responseBody) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.statusCode = statusCode;
|
|
21
|
+
this.responseBody = responseBody;
|
|
22
|
+
this.name = 'LLMError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** 认证错误 (401 / 403) */
|
|
26
|
+
export class LLMAuthError extends LLMError {
|
|
27
|
+
constructor(message, statusCode, responseBody) {
|
|
28
|
+
super(message, statusCode, responseBody);
|
|
29
|
+
this.name = 'LLMAuthError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** 速率限制错误 (429) */
|
|
33
|
+
export class LLMRateLimitError extends LLMError {
|
|
34
|
+
constructor(message, responseBody) {
|
|
35
|
+
super(message, 429, responseBody);
|
|
36
|
+
this.name = 'LLMRateLimitError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** JSON 解析错误 */
|
|
40
|
+
export class LLMJsonParseError extends LLMError {
|
|
41
|
+
rawContent;
|
|
42
|
+
constructor(message, rawContent) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.rawContent = rawContent;
|
|
45
|
+
this.name = 'LLMJsonParseError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// 内部工具函数
|
|
50
|
+
// ============================================================================
|
|
51
|
+
/** 可重试的 HTTP 状态码 */
|
|
52
|
+
const RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503]);
|
|
53
|
+
/**
|
|
54
|
+
* 延迟指定毫秒
|
|
55
|
+
*/
|
|
56
|
+
function sleep(ms) {
|
|
57
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 尝试从 Markdown 代码块中提取 JSON 字符串。
|
|
61
|
+
* 匹配 ```json ... ``` 或 ``` ... ``` 格式。
|
|
62
|
+
*/
|
|
63
|
+
function extractJsonFromMarkdown(text) {
|
|
64
|
+
// 匹配 ```json\n...\n``` 或 ```\n...\n```
|
|
65
|
+
const fencePattern = /```(?:json)?\s*\n([\s\S]*?)\n\s*```/;
|
|
66
|
+
const match = text.match(fencePattern);
|
|
67
|
+
if (match?.[1]) {
|
|
68
|
+
return match[1].trim();
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 尝试解析 JSON 字符串,失败时尝试从 Markdown 代码块中提取再解析。
|
|
74
|
+
*/
|
|
75
|
+
function parseJsonSafe(text) {
|
|
76
|
+
// 首先直接尝试解析
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(text);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// 尝试从代码块提取
|
|
82
|
+
const extracted = extractJsonFromMarkdown(text);
|
|
83
|
+
if (extracted) {
|
|
84
|
+
return JSON.parse(extracted);
|
|
85
|
+
}
|
|
86
|
+
throw new Error(`无法解析 JSON 内容`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// LLMClient 类
|
|
91
|
+
// ============================================================================
|
|
92
|
+
/**
|
|
93
|
+
* OpenAI 兼容的 LLM 客户端。
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* const client = new LLMClient({ config });
|
|
98
|
+
* const response = await client.chat([
|
|
99
|
+
* { role: 'system', content: '你是一个助手。' },
|
|
100
|
+
* { role: 'user', content: '你好' },
|
|
101
|
+
* ]);
|
|
102
|
+
* console.log(response.content);
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export class LLMClient {
|
|
106
|
+
config;
|
|
107
|
+
maxRetries;
|
|
108
|
+
retryDelayMs;
|
|
109
|
+
timeoutMs;
|
|
110
|
+
constructor(options) {
|
|
111
|
+
this.config = options.config;
|
|
112
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
113
|
+
this.retryDelayMs = options.retryDelayMs ?? 1000;
|
|
114
|
+
this.timeoutMs = options.timeoutMs ?? 120_000;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 发送聊天补全请求。
|
|
118
|
+
*
|
|
119
|
+
* @param messages - 聊天消息列表
|
|
120
|
+
* @param options - 可选参数(温度、最大 token 数)
|
|
121
|
+
* @returns LLM 响应
|
|
122
|
+
* @throws {LLMAuthError} 认证失败
|
|
123
|
+
* @throws {LLMRateLimitError} 速率限制(重试耗尽后)
|
|
124
|
+
* @throws {LLMError} 其他请求错误
|
|
125
|
+
*/
|
|
126
|
+
async chat(messages, options) {
|
|
127
|
+
const url = `${this.config.apiEndpoint.replace(/\/+$/, '')}/chat/completions`;
|
|
128
|
+
const body = this.buildRequestBody(messages, options);
|
|
129
|
+
let lastError = null;
|
|
130
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
131
|
+
// 非首次请求时等待(指数退避)
|
|
132
|
+
if (attempt > 0) {
|
|
133
|
+
const delay = this.retryDelayMs * Math.pow(2, attempt - 1);
|
|
134
|
+
await sleep(delay);
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const response = await this.fetchWithTimeout(url, body);
|
|
138
|
+
return this.parseResponse(response);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
lastError = error;
|
|
142
|
+
// 认证错误不重试
|
|
143
|
+
if (error instanceof LLMAuthError) {
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
// 非可重试错误不重试
|
|
147
|
+
if (error instanceof LLMError && error.statusCode && !RETRYABLE_STATUS_CODES.has(error.statusCode)) {
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
// 最后一次重试仍然失败
|
|
151
|
+
if (attempt === this.maxRetries) {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// 重试耗尽
|
|
157
|
+
throw lastError ?? new LLMError('LLM 请求失败: 未知错误');
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* 发送聊天请求并将响应解析为 JSON。
|
|
161
|
+
*
|
|
162
|
+
* 工作流程:
|
|
163
|
+
* 1. 调用 chat() 获取文本响应
|
|
164
|
+
* 2. 尝试解析 JSON(支持从 Markdown 代码块中提取)
|
|
165
|
+
* 3. 若提供了 Zod schema,进行运行时校验
|
|
166
|
+
* 4. 若 JSON 解析失败,追加修正提示后重试一次
|
|
167
|
+
*
|
|
168
|
+
* @param messages - 聊天消息列表
|
|
169
|
+
* @param schema - 可选的 Zod 校验 schema
|
|
170
|
+
* @returns 解析后的 JSON 对象
|
|
171
|
+
* @throws {LLMJsonParseError} JSON 解析或校验失败
|
|
172
|
+
*/
|
|
173
|
+
async chatJSON(messages, schema) {
|
|
174
|
+
// 第一次尝试
|
|
175
|
+
const response = await this.chat(messages);
|
|
176
|
+
try {
|
|
177
|
+
return this.validateJsonResponse(response.content, schema);
|
|
178
|
+
}
|
|
179
|
+
catch (firstError) {
|
|
180
|
+
// JSON 解析或校验失败,追加修正提示后重试
|
|
181
|
+
const fixMessages = [
|
|
182
|
+
...messages,
|
|
183
|
+
{ role: 'assistant', content: response.content },
|
|
184
|
+
{
|
|
185
|
+
role: 'user',
|
|
186
|
+
content: '你上次的回复不是合法的 JSON 格式。请严格按照要求只输出合法的 JSON,' +
|
|
187
|
+
'不要在 JSON 前后添加任何额外文字或 Markdown 格式标记。' +
|
|
188
|
+
(firstError instanceof Error ? `\n错误信息: ${firstError.message}` : ''),
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
const retryResponse = await this.chat(fixMessages);
|
|
192
|
+
try {
|
|
193
|
+
return this.validateJsonResponse(retryResponse.content, schema);
|
|
194
|
+
}
|
|
195
|
+
catch (secondError) {
|
|
196
|
+
throw new LLMJsonParseError(`JSON 解析失败(已重试): ${secondError instanceof Error ? secondError.message : '未知错误'}`, retryResponse.content);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// ========================================================================
|
|
201
|
+
// 私有方法
|
|
202
|
+
// ========================================================================
|
|
203
|
+
/**
|
|
204
|
+
* 构建 OpenAI 兼容的请求体
|
|
205
|
+
*/
|
|
206
|
+
buildRequestBody(messages, options) {
|
|
207
|
+
const payload = {
|
|
208
|
+
model: this.config.modelName,
|
|
209
|
+
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
210
|
+
temperature: options?.temperature ?? 0.3,
|
|
211
|
+
};
|
|
212
|
+
if (options?.maxTokens !== undefined) {
|
|
213
|
+
payload['max_tokens'] = options.maxTokens;
|
|
214
|
+
}
|
|
215
|
+
return JSON.stringify(payload);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* 带超时控制的 fetch 请求
|
|
219
|
+
*/
|
|
220
|
+
async fetchWithTimeout(url, body) {
|
|
221
|
+
const controller = new AbortController();
|
|
222
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
223
|
+
try {
|
|
224
|
+
const response = await fetch(url, {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
headers: {
|
|
227
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
228
|
+
'Content-Type': 'application/json',
|
|
229
|
+
},
|
|
230
|
+
body,
|
|
231
|
+
signal: controller.signal,
|
|
232
|
+
});
|
|
233
|
+
// 处理错误状态码
|
|
234
|
+
if (!response.ok) {
|
|
235
|
+
const responseBody = await response.text().catch(() => '');
|
|
236
|
+
this.handleErrorStatus(response.status, responseBody);
|
|
237
|
+
}
|
|
238
|
+
return response;
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (error instanceof LLMError) {
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
245
|
+
throw new LLMError(`请求超时 (${this.timeoutMs}ms)`);
|
|
246
|
+
}
|
|
247
|
+
throw new LLMError(`网络请求失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
clearTimeout(timer);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* 根据 HTTP 状态码抛出对应错误
|
|
255
|
+
*/
|
|
256
|
+
handleErrorStatus(status, responseBody) {
|
|
257
|
+
// 尝试从响应体中提取错误消息
|
|
258
|
+
let detail = '';
|
|
259
|
+
try {
|
|
260
|
+
const parsed = JSON.parse(responseBody);
|
|
261
|
+
detail = parsed?.error?.message ?? '';
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
detail = responseBody.slice(0, 200);
|
|
265
|
+
}
|
|
266
|
+
if (status === 401 || status === 403) {
|
|
267
|
+
throw new LLMAuthError(`认证失败 (HTTP ${status}): ${detail || 'API 密钥无效或已过期'}`, status, responseBody);
|
|
268
|
+
}
|
|
269
|
+
if (status === 429) {
|
|
270
|
+
throw new LLMRateLimitError(`请求速率超限 (HTTP 429): ${detail || '请稍后重试'}`, responseBody);
|
|
271
|
+
}
|
|
272
|
+
throw new LLMError(`API 请求失败 (HTTP ${status}): ${detail || '未知错误'}`, status, responseBody);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 从 OpenAI 兼容响应中解析内容和 usage
|
|
276
|
+
*/
|
|
277
|
+
async parseResponse(response) {
|
|
278
|
+
const json = (await response.json());
|
|
279
|
+
const content = json.choices?.[0]?.message?.content ?? '';
|
|
280
|
+
const result = { content };
|
|
281
|
+
if (json.usage) {
|
|
282
|
+
result.usage = {
|
|
283
|
+
promptTokens: json.usage.prompt_tokens ?? 0,
|
|
284
|
+
completionTokens: json.usage.completion_tokens ?? 0,
|
|
285
|
+
totalTokens: json.usage.total_tokens ?? 0,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* 验证 JSON 响应:解析 + 可选 Zod 校验
|
|
292
|
+
*/
|
|
293
|
+
validateJsonResponse(content, schema) {
|
|
294
|
+
const parsed = parseJsonSafe(content);
|
|
295
|
+
if (schema) {
|
|
296
|
+
const result = schema.safeParse(parsed);
|
|
297
|
+
if (!result.success) {
|
|
298
|
+
const issues = result.error.issues
|
|
299
|
+
.map((i) => ` - ${i.path.join('.')}: ${i.message}`)
|
|
300
|
+
.join('\n');
|
|
301
|
+
throw new Error(`JSON 数据校验失败:\n${issues}`);
|
|
302
|
+
}
|
|
303
|
+
return result.data;
|
|
304
|
+
}
|
|
305
|
+
return parsed;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=llm-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-client.js","sourceRoot":"","sources":["../../src/llm/llm-client.ts"],"names":[],"mappings":"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"}
|