@winspan/claude-forge 1.2.3 → 1.2.4
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/ai-provider/base-provider.d.ts +1 -16
- package/dist/ai-provider/base-provider.d.ts.map +1 -1
- package/dist/ai-provider/base-provider.js +16 -50
- package/dist/ai-provider/base-provider.js.map +1 -1
- package/dist/cli/commands/backup.d.ts.map +1 -1
- package/dist/cli/commands/backup.js +57 -18
- package/dist/cli/commands/backup.js.map +1 -1
- package/dist/cli/commands/config.d.ts +1 -1
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/config.js +84 -22
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/daemon.d.ts.map +1 -1
- package/dist/cli/commands/daemon.js +21 -20
- package/dist/cli/commands/daemon.js.map +1 -1
- package/dist/cli/commands/init/index.d.ts.map +1 -1
- package/dist/cli/commands/init/index.js +24 -10
- package/dist/cli/commands/init/index.js.map +1 -1
- package/dist/cli/commands/pattern.d.ts.map +1 -1
- package/dist/cli/commands/pattern.js +88 -78
- package/dist/cli/commands/pattern.js.map +1 -1
- package/dist/cli/utils/output.d.ts.map +1 -1
- package/dist/cli/utils/output.js +36 -7
- package/dist/cli/utils/output.js.map +1 -1
- package/dist/pipeline/phase-manager.d.ts +30 -1
- package/dist/pipeline/phase-manager.d.ts.map +1 -1
- package/dist/pipeline/phase-manager.js +115 -22
- package/dist/pipeline/phase-manager.js.map +1 -1
- package/dist/utils/circuit-breaker.d.ts +35 -0
- package/dist/utils/circuit-breaker.d.ts.map +1 -0
- package/dist/utils/circuit-breaker.js +86 -0
- package/dist/utils/circuit-breaker.js.map +1 -0
- package/dist/utils/claude-api.d.ts +14 -55
- package/dist/utils/claude-api.d.ts.map +1 -1
- package/dist/utils/claude-api.js +27 -216
- package/dist/utils/claude-api.js.map +1 -1
- package/package.json +1 -1
package/dist/utils/claude-api.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
* API Key 获取优先级:
|
|
5
|
-
* 1. 显式传入的 apiKey
|
|
6
|
-
* 2. 配置文件中的 distill.api_key
|
|
7
|
-
* 3. 环境变量 ANTHROPIC_AUTH_TOKEN
|
|
8
|
-
* 4. 环境变量 ANTHROPIC_API_KEY
|
|
9
|
-
*/
|
|
2
|
+
import { BaseProvider } from '../ai-provider/base-provider.js';
|
|
3
|
+
export { ApiTimeoutError, CircuitOpenError } from '../ai-provider/types.js';
|
|
10
4
|
function resolveApiKey(apiKey) {
|
|
11
5
|
if (apiKey && apiKey !== '')
|
|
12
6
|
return apiKey;
|
|
@@ -21,16 +15,12 @@ function resolveApiKey(apiKey) {
|
|
|
21
15
|
function resolveBaseUrl() {
|
|
22
16
|
return process.env.ANTHROPIC_BASE_URL || undefined;
|
|
23
17
|
}
|
|
24
|
-
/**
|
|
25
|
-
* 查询代理支持的模型列表,自动检测最新的 haiku/sonnet/opus 模型
|
|
26
|
-
* 每个分层选择版本号最新的模型
|
|
27
|
-
*/
|
|
28
18
|
export async function detectBestModels(apiKey, baseUrl) {
|
|
29
19
|
try {
|
|
30
20
|
const key = resolveApiKey(apiKey);
|
|
31
21
|
const base = baseUrl || resolveBaseUrl();
|
|
32
22
|
if (!base)
|
|
33
|
-
return null;
|
|
23
|
+
return null;
|
|
34
24
|
const url = `${base.replace(/\/$/, '')}/v1/models`;
|
|
35
25
|
const res = await fetch(url, {
|
|
36
26
|
headers: {
|
|
@@ -44,12 +34,10 @@ export async function detectBestModels(apiKey, baseUrl) {
|
|
|
44
34
|
const data = await res.json();
|
|
45
35
|
const models = (data.data || []).map((m) => m.id).filter((id) => id.startsWith('claude-'));
|
|
46
36
|
const result = {};
|
|
47
|
-
// 为每个分层选择版本号最新的模型(字典序降序)
|
|
48
37
|
for (const tier of ['haiku', 'sonnet', 'opus']) {
|
|
49
38
|
const candidates = models.filter((m) => m.includes(tier)).sort().reverse();
|
|
50
|
-
if (candidates.length > 0)
|
|
39
|
+
if (candidates.length > 0)
|
|
51
40
|
result[tier] = candidates[0];
|
|
52
|
-
}
|
|
53
41
|
}
|
|
54
42
|
return Object.keys(result).length > 0 ? result : null;
|
|
55
43
|
}
|
|
@@ -58,206 +46,39 @@ export async function detectBestModels(apiKey, baseUrl) {
|
|
|
58
46
|
}
|
|
59
47
|
}
|
|
60
48
|
/**
|
|
61
|
-
*
|
|
49
|
+
* Claude Provider — 继承 BaseProvider 统一复用重试、超时、熔断、统计逻辑
|
|
62
50
|
*/
|
|
63
|
-
export
|
|
64
|
-
const detected = await detectBestModels(apiKey, baseUrl);
|
|
65
|
-
return detected?.sonnet ?? null;
|
|
66
|
-
}
|
|
67
|
-
export class ApiTimeoutError extends Error {
|
|
68
|
-
constructor(timeoutMs) {
|
|
69
|
-
super(`API 调用超时(${timeoutMs}ms)`);
|
|
70
|
-
this.name = 'ApiTimeoutError';
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
export class CircuitOpenError extends Error {
|
|
74
|
-
constructor() {
|
|
75
|
-
super('熔断器已打开,API 调用被拒绝');
|
|
76
|
-
this.name = 'CircuitOpenError';
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
var CircuitState;
|
|
80
|
-
(function (CircuitState) {
|
|
81
|
-
CircuitState["CLOSED"] = "closed";
|
|
82
|
-
CircuitState["OPEN"] = "open";
|
|
83
|
-
CircuitState["HALF_OPEN"] = "half_open";
|
|
84
|
-
})(CircuitState || (CircuitState = {}));
|
|
85
|
-
class CircuitBreaker {
|
|
86
|
-
state = CircuitState.CLOSED;
|
|
87
|
-
failureCount = 0;
|
|
88
|
-
lastFailureTime = 0;
|
|
89
|
-
failureThreshold = 5;
|
|
90
|
-
resetTimeoutMs = 60_000; // 60s
|
|
91
|
-
recordSuccess() {
|
|
92
|
-
this.failureCount = 0;
|
|
93
|
-
if (this.state === CircuitState.HALF_OPEN) {
|
|
94
|
-
this.state = CircuitState.CLOSED;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
recordFailure() {
|
|
98
|
-
this.failureCount++;
|
|
99
|
-
this.lastFailureTime = Date.now();
|
|
100
|
-
if (this.failureCount >= this.failureThreshold) {
|
|
101
|
-
this.state = CircuitState.OPEN;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
canAttempt() {
|
|
105
|
-
if (this.state === CircuitState.CLOSED)
|
|
106
|
-
return true;
|
|
107
|
-
if (this.state === CircuitState.HALF_OPEN)
|
|
108
|
-
return true;
|
|
109
|
-
// OPEN 状态:检查是否到达重置时间
|
|
110
|
-
if (Date.now() - this.lastFailureTime >= this.resetTimeoutMs) {
|
|
111
|
-
this.state = CircuitState.HALF_OPEN;
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
getState() {
|
|
117
|
-
return this.state;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
export class ClaudeAPI {
|
|
51
|
+
export class ClaudeAPI extends BaseProvider {
|
|
121
52
|
client;
|
|
122
53
|
model;
|
|
123
|
-
circuitBreaker = new CircuitBreaker();
|
|
124
|
-
storage;
|
|
125
|
-
stats = {
|
|
126
|
-
totalCalls: 0,
|
|
127
|
-
totalErrors: 0,
|
|
128
|
-
totalLatencyMs: 0,
|
|
129
|
-
totalRetries: 0,
|
|
130
|
-
circuitBreakerTrips: 0,
|
|
131
|
-
totalTokensIn: 0,
|
|
132
|
-
totalTokensOut: 0,
|
|
133
|
-
byLabel: {},
|
|
134
|
-
};
|
|
135
54
|
constructor(apiKey, model = 'claude-sonnet-4-6', baseUrl, storage) {
|
|
55
|
+
super();
|
|
136
56
|
const resolvedKey = resolveApiKey(apiKey);
|
|
137
57
|
const baseURL = baseUrl || resolveBaseUrl();
|
|
138
|
-
this.client = new Anthropic({
|
|
139
|
-
apiKey: resolvedKey,
|
|
140
|
-
...(baseURL ? { baseURL } : {}),
|
|
141
|
-
});
|
|
58
|
+
this.client = new Anthropic({ apiKey: resolvedKey, ...(baseURL ? { baseURL } : {}) });
|
|
142
59
|
this.model = model;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* 调用 Claude API 完成文本生成
|
|
147
|
-
* 支持两种调用方式:
|
|
148
|
-
* 1. complete(prompt, label, options) - 新接口
|
|
149
|
-
* 2. complete(prompt, label, retries, timeoutMs) - 旧接口(向后兼容)
|
|
150
|
-
*/
|
|
151
|
-
async complete(prompt, label, retriesOrOptions, timeoutMs) {
|
|
152
|
-
const actualLabel = label ?? 'unknown';
|
|
153
|
-
// 判断是旧接口还是新接口
|
|
154
|
-
let retries;
|
|
155
|
-
let timeout;
|
|
156
|
-
let options;
|
|
157
|
-
if (typeof retriesOrOptions === 'number') {
|
|
158
|
-
// 旧接口:complete(prompt, label, retries, timeoutMs)
|
|
159
|
-
retries = retriesOrOptions;
|
|
160
|
-
timeout = timeoutMs ?? 60_000;
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
// 新接口:complete(prompt, label, options)
|
|
164
|
-
options = retriesOrOptions;
|
|
165
|
-
retries = retriesOrOptions?.retries ?? 2;
|
|
166
|
-
timeout = retriesOrOptions?.timeoutMs ?? 60_000;
|
|
167
|
-
}
|
|
168
|
-
if (!this.circuitBreaker.canAttempt()) {
|
|
169
|
-
this.stats.circuitBreakerTrips++;
|
|
170
|
-
throw new CircuitOpenError();
|
|
171
|
-
}
|
|
172
|
-
const startMs = Date.now();
|
|
173
|
-
const bucket = this.stats.byLabel[actualLabel] ?? { calls: 0, errors: 0, latencyMs: 0, retries: 0, tokensIn: 0, tokensOut: 0 };
|
|
174
|
-
this.stats.byLabel[actualLabel] = bucket;
|
|
175
|
-
this.stats.totalCalls++;
|
|
176
|
-
bucket.calls++;
|
|
177
|
-
let lastErr;
|
|
178
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
179
|
-
if (attempt > 0) {
|
|
180
|
-
this.stats.totalRetries++;
|
|
181
|
-
bucket.retries++;
|
|
182
|
-
await new Promise(r => setTimeout(r, 1000 * 2 ** (attempt - 1)));
|
|
183
|
-
}
|
|
184
|
-
const controller = new AbortController();
|
|
185
|
-
const timer = setTimeout(() => controller.abort(), timeout);
|
|
186
|
-
try {
|
|
187
|
-
const apiCall = this.client.messages.create({
|
|
188
|
-
model: options?.model ?? this.model,
|
|
189
|
-
max_tokens: options?.maxTokens ?? 2048,
|
|
190
|
-
messages: [{ role: 'user', content: prompt }],
|
|
191
|
-
}, { signal: controller.signal });
|
|
192
|
-
const timeoutRace = new Promise((_, reject) => {
|
|
193
|
-
setTimeout(() => reject(new ApiTimeoutError(timeout)), timeout);
|
|
194
|
-
});
|
|
195
|
-
const response = await Promise.race([apiCall, timeoutRace]);
|
|
196
|
-
clearTimeout(timer);
|
|
197
|
-
const elapsed = Date.now() - startMs;
|
|
198
|
-
this.stats.totalLatencyMs += elapsed;
|
|
199
|
-
bucket.latencyMs += elapsed;
|
|
200
|
-
this.circuitBreaker.recordSuccess();
|
|
201
|
-
// Token 追踪
|
|
202
|
-
const tokensIn = response.usage?.input_tokens ?? 0;
|
|
203
|
-
const tokensOut = response.usage?.output_tokens ?? 0;
|
|
204
|
-
this.stats.totalTokensIn += tokensIn;
|
|
205
|
-
this.stats.totalTokensOut += tokensOut;
|
|
206
|
-
bucket.tokensIn += tokensIn;
|
|
207
|
-
bucket.tokensOut += tokensOut;
|
|
208
|
-
// 写入 api_usage 表(可选,storage 不存在时静默跳过)
|
|
209
|
-
if (this.storage && (tokensIn > 0 || tokensOut > 0)) {
|
|
210
|
-
// claude-sonnet-4-6: $3/$15 per 1M tokens
|
|
211
|
-
const costUsd = (tokensIn * 3 + tokensOut * 15) / 1_000_000;
|
|
212
|
-
this.storage.insertApiUsage({
|
|
213
|
-
id: randomUUID(),
|
|
214
|
-
session_id: 'daemon',
|
|
215
|
-
label: actualLabel,
|
|
216
|
-
model: this.model,
|
|
217
|
-
tokens_in: tokensIn,
|
|
218
|
-
tokens_out: tokensOut,
|
|
219
|
-
cost_usd: costUsd,
|
|
220
|
-
latency_ms: elapsed,
|
|
221
|
-
timestamp: new Date().toISOString(),
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
const textContent = response.content.find((c) => c.type === 'text');
|
|
225
|
-
return textContent?.text || '';
|
|
226
|
-
}
|
|
227
|
-
catch (err) {
|
|
228
|
-
clearTimeout(timer);
|
|
229
|
-
lastErr = err;
|
|
230
|
-
// 超时:不重试
|
|
231
|
-
if (err instanceof ApiTimeoutError) {
|
|
232
|
-
this.stats.totalErrors++;
|
|
233
|
-
bucket.errors++;
|
|
234
|
-
this.circuitBreaker.recordFailure();
|
|
235
|
-
throw err;
|
|
236
|
-
}
|
|
237
|
-
// 4xx 客户端错误:不重试
|
|
238
|
-
const status = err.status;
|
|
239
|
-
if (status && status >= 400 && status < 500) {
|
|
240
|
-
this.stats.totalErrors++;
|
|
241
|
-
bucket.errors++;
|
|
242
|
-
this.circuitBreaker.recordFailure();
|
|
243
|
-
throw err;
|
|
244
|
-
}
|
|
245
|
-
// 其他错误(网络、5xx):继续重试
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
this.stats.totalErrors++;
|
|
249
|
-
bucket.errors++;
|
|
250
|
-
this.circuitBreaker.recordFailure();
|
|
251
|
-
throw lastErr;
|
|
60
|
+
if (storage)
|
|
61
|
+
this.setStorage(storage, 'daemon');
|
|
252
62
|
}
|
|
253
|
-
|
|
254
|
-
|
|
63
|
+
async doComplete(prompt, maxTokens) {
|
|
64
|
+
const response = await this.client.messages.create({
|
|
65
|
+
model: this.model,
|
|
66
|
+
max_tokens: maxTokens,
|
|
67
|
+
messages: [{ role: 'user', content: prompt }],
|
|
68
|
+
});
|
|
69
|
+
const textContent = response.content.find((c) => c.type === 'text');
|
|
255
70
|
return {
|
|
256
|
-
|
|
257
|
-
|
|
71
|
+
content: textContent?.text || '',
|
|
72
|
+
usage: {
|
|
73
|
+
prompt_tokens: response.usage?.input_tokens ?? 0,
|
|
74
|
+
completion_tokens: response.usage?.output_tokens ?? 0,
|
|
75
|
+
},
|
|
258
76
|
};
|
|
259
77
|
}
|
|
260
|
-
|
|
78
|
+
getModelName() { return this.model; }
|
|
79
|
+
calculateCost(tokensIn, tokensOut) {
|
|
80
|
+
return (tokensIn * 3 + tokensOut * 15) / 1_000_000;
|
|
81
|
+
}
|
|
261
82
|
formatStats() {
|
|
262
83
|
const s = this.stats;
|
|
263
84
|
if (s.totalCalls === 0)
|
|
@@ -265,7 +86,7 @@ export class ClaudeAPI {
|
|
|
265
86
|
const avgMs = Math.round(s.totalLatencyMs / (s.totalCalls - s.totalErrors || 1));
|
|
266
87
|
const costUsd = (s.totalTokensIn * 3 + s.totalTokensOut * 15) / 1_000_000;
|
|
267
88
|
const lines = [
|
|
268
|
-
`API calls: ${s.totalCalls} | errors: ${s.totalErrors} | retries: ${s.totalRetries} | avg latency: ${avgMs}ms | tokens: ↑${s.totalTokensIn} ↓${s.totalTokensOut} | cost: $${costUsd.toFixed(4)}`,
|
|
89
|
+
`API calls: ${s.totalCalls} | errors: ${s.totalErrors} | retries: ${s.totalRetries} | circuit trips: ${s.circuitBreakerTrips} | avg latency: ${avgMs}ms | tokens: ↑${s.totalTokensIn} ↓${s.totalTokensOut} | cost: $${costUsd.toFixed(4)}`,
|
|
269
90
|
];
|
|
270
91
|
for (const [label, b] of Object.entries(s.byLabel)) {
|
|
271
92
|
const avg = b.calls > b.errors ? Math.round(b.latencyMs / (b.calls - b.errors)) : 0;
|
|
@@ -274,11 +95,6 @@ export class ClaudeAPI {
|
|
|
274
95
|
return lines.join('\n');
|
|
275
96
|
}
|
|
276
97
|
}
|
|
277
|
-
/**
|
|
278
|
-
* 工厂函数:用 distill 配置创建共享 API 实例。
|
|
279
|
-
* 解决 daemon/index.ts 原来用相同参数创建多次的问题。
|
|
280
|
-
* 返回 null 而不是抛出,让调用方根据业务决定是否降级。
|
|
281
|
-
*/
|
|
282
98
|
export function createSharedApi(apiKey, model, baseUrl) {
|
|
283
99
|
try {
|
|
284
100
|
return new ClaudeAPI(apiKey || undefined, model, baseUrl || undefined);
|
|
@@ -287,11 +103,6 @@ export function createSharedApi(apiKey, model, baseUrl) {
|
|
|
287
103
|
return null;
|
|
288
104
|
}
|
|
289
105
|
}
|
|
290
|
-
/**
|
|
291
|
-
* 工厂函数:创建必需的 API 实例,失败时抛出 Error。
|
|
292
|
-
* 用于需要 API 才能继续的命令(evolve/distill/repair 等)。
|
|
293
|
-
* 调用方应捕获并包装为 CLIError。
|
|
294
|
-
*/
|
|
295
106
|
export function createRequiredApi(apiKey, model, baseUrl) {
|
|
296
107
|
if (!apiKey) {
|
|
297
108
|
throw new Error('未配置 API Key,请设置 ANTHROPIC_AUTH_TOKEN / ANTHROPIC_API_KEY 环境变量,或在 ~/.claude-forge/config.yaml 中配置 distill.api_key');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-api.js","sourceRoot":"","sources":["../../src/utils/claude-api.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"claude-api.js","sourceRoot":"","sources":["../../src/utils/claude-api.ts"],"names":[],"mappings":"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"}
|