@dahawa/hawa-cli-analysis 1.0.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/.tools.json +7 -0
- package/.vscode/launch.json +27 -0
- package/LICENSE +21 -0
- package/README.md +196 -0
- package/_uclaude.js +165 -0
- package/anthropic-transformer.js +986 -0
- package/api-anthropic.js +279 -0
- package/api-openai.js +539 -0
- package/claude/claude-openai-proxy.js +305 -0
- package/claude/claude-proxy.js +341 -0
- package/clogger-openai.js +190 -0
- package/clogger.js +318 -0
- package/codex/mcp-client.js +556 -0
- package/codex/mcpclient.js +118 -0
- package/codex/mcpserver.js +374 -0
- package/codex/mcpserverproxy.js +144 -0
- package/codex/test.js +30 -0
- package/config.js +105 -0
- package/index.js +0 -0
- package/logger-manager.js +85 -0
- package/logger.js +112 -0
- package/mcp/claude-mcpproxy-launcher.js +5 -0
- package/mcp_oauth_tokens.js +40 -0
- package/package.json +36 -0
- package/port-manager.js +80 -0
- package/simple-transform-example.js +213 -0
- package/tests/test-lazy-load.js +36 -0
- package/tests/test.js +30 -0
- package/tests/test_mcp_proxy.js +51 -0
- package/tests/test_supabase_mcp.js +42 -0
- package/uclaude.js +221 -0
- package/ucodex-proxy.js +173 -0
- package/ucodex.js +129 -0
- package/untils.js +261 -0
package/api-anthropic.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import LogManager from './logger-manager.js';
|
|
2
|
+
|
|
3
|
+
const logger = LogManager.getSystemLogger();
|
|
4
|
+
|
|
5
|
+
export function mergeAnthropic(all){
|
|
6
|
+
logger.debug("mergeAnthropic input: "+ all);
|
|
7
|
+
return mergeAnthropicChunks(all.split("\n"));
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 将 Anthropic 流式 chunk(原始字符串数组)合并为完整响应消息体。
|
|
11
|
+
* 兼容 chunk 形态:
|
|
12
|
+
* - "data: {...}" 或 "{...}"
|
|
13
|
+
* - 事件类型:message_start, content_block_start, content_block_delta,
|
|
14
|
+
* content_block_stop, message_delta, message_stop, ping
|
|
15
|
+
* - 文本增量:delta.type === "text_delta", delta.text
|
|
16
|
+
* - 工具增量:delta.partial_json / delta.input_json / delta.input_json_delta
|
|
17
|
+
*
|
|
18
|
+
* @param {string[]} rawChunks - 原始 chunk 文本数组,每个元素是一行 SSE data。
|
|
19
|
+
* @returns {{
|
|
20
|
+
* message: {
|
|
21
|
+
* id?: string,
|
|
22
|
+
* type: "message",
|
|
23
|
+
* role: "assistant",
|
|
24
|
+
* model?: string,
|
|
25
|
+
* stop_reason?: string | null,
|
|
26
|
+
* stop_sequence?: string | null,
|
|
27
|
+
* usage: { input_tokens: number, output_tokens: number },
|
|
28
|
+
* content: Array<
|
|
29
|
+
* | { type: "text", text: string }
|
|
30
|
+
* | { type: "tool_use", id: string, name: string, input: any }
|
|
31
|
+
* >
|
|
32
|
+
* },
|
|
33
|
+
* debug: { parsedCount: number, ignoredCount: number, errors: Array<{idx:number,error:string,raw:string}> }
|
|
34
|
+
* }}
|
|
35
|
+
*/
|
|
36
|
+
export function mergeAnthropicChunks(rawChunks) {
|
|
37
|
+
const message = {
|
|
38
|
+
type: "message",
|
|
39
|
+
role: "assistant",
|
|
40
|
+
content: [],
|
|
41
|
+
usage: { input_tokens: 0, output_tokens: 0 },
|
|
42
|
+
stop_reason: null,
|
|
43
|
+
stop_sequence: null,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// content block 临时态:按 index 管理;为工具输入保留 JSON 片段缓冲
|
|
47
|
+
const blockMap = new Map(); // index -> { ref, toolJsonBuffer }
|
|
48
|
+
const debug = { parsedCount: 0, ignoredCount: 0, errors: [] };
|
|
49
|
+
|
|
50
|
+
const parseMaybeJson = (line) => {
|
|
51
|
+
// 去掉 "data:" 前缀及空白/末尾 [DONE]
|
|
52
|
+
const trimmed = line.trim();
|
|
53
|
+
if (!trimmed || trimmed === "data: [DONE]") return null;
|
|
54
|
+
let jsonText = trimmed.startsWith("data:")
|
|
55
|
+
? trimmed.slice(5).trim()
|
|
56
|
+
: trimmed;
|
|
57
|
+
|
|
58
|
+
if (!jsonText || jsonText === "[DONE]") return null;
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(jsonText);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
throw new Error(`JSON parse failed: ${e.message}`);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const ensureBlock = (index, starter) => {
|
|
67
|
+
if (blockMap.has(index)) return blockMap.get(index);
|
|
68
|
+
|
|
69
|
+
let ref;
|
|
70
|
+
let toolJsonBuffer = "";
|
|
71
|
+
|
|
72
|
+
if (starter?.type === "tool_use" || starter?.content_block?.type === "tool_use") {
|
|
73
|
+
// 工具块
|
|
74
|
+
const b = starter.type === "tool_use" ? starter : starter.content_block;
|
|
75
|
+
ref = { type: "tool_use", id: b.id, name: b.name, input: {} };
|
|
76
|
+
message.content.push(ref);
|
|
77
|
+
const slot = { ref, toolJsonBuffer };
|
|
78
|
+
blockMap.set(index, slot);
|
|
79
|
+
return slot;
|
|
80
|
+
} else {
|
|
81
|
+
// 文本块
|
|
82
|
+
ref = { type: "text", text: "" };
|
|
83
|
+
message.content.push(ref);
|
|
84
|
+
const slot = { ref, toolJsonBuffer };
|
|
85
|
+
blockMap.set(index, slot);
|
|
86
|
+
return slot;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const feedToolJson = (slot, piece) => {
|
|
91
|
+
// Anthropic 常见:delta.partial_json / delta.input_json 逐段拼接
|
|
92
|
+
if (typeof piece === "string") {
|
|
93
|
+
slot.toolJsonBuffer += piece;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// 兜底:对象/数组直接合并为字符串再解析
|
|
97
|
+
try {
|
|
98
|
+
slot.toolJsonBuffer += JSON.stringify(piece);
|
|
99
|
+
} catch {
|
|
100
|
+
slot.toolJsonBuffer += String(piece);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const finalizeToolInputIfAny = (slot) => {
|
|
105
|
+
if (!slot || slot.ref?.type !== "tool_use") return;
|
|
106
|
+
if (!slot.toolJsonBuffer) return;
|
|
107
|
+
const raw = slot.toolJsonBuffer.trim();
|
|
108
|
+
if (!raw) return;
|
|
109
|
+
try {
|
|
110
|
+
// 尝试直接解析完整 JSON;若为裸字串片段,做一次修复
|
|
111
|
+
slot.ref.input = JSON.parse(raw);
|
|
112
|
+
} catch {
|
|
113
|
+
// 容错:尝试补齐 JSON(例如少量逗号/丢尾),否则作为字符串原样输出
|
|
114
|
+
// 这里不做激进修复,只保底为字符串,避免抛错中断
|
|
115
|
+
slot.ref.input = raw;
|
|
116
|
+
}
|
|
117
|
+
// 清空缓冲
|
|
118
|
+
slot.toolJsonBuffer = "";
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
for (let i = 0; i < rawChunks.length; i++) {
|
|
122
|
+
const raw = rawChunks[i];
|
|
123
|
+
let evt;
|
|
124
|
+
try {
|
|
125
|
+
evt = parseMaybeJson(raw);
|
|
126
|
+
if (!evt) {
|
|
127
|
+
debug.ignoredCount++;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
debug.parsedCount++;
|
|
131
|
+
} catch (e) {
|
|
132
|
+
debug.errors.push({ idx: i, error: e.message, raw });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const t = evt.type;
|
|
137
|
+
|
|
138
|
+
switch (t) {
|
|
139
|
+
case "ping":
|
|
140
|
+
// 心跳,无内容
|
|
141
|
+
break;
|
|
142
|
+
|
|
143
|
+
case "message_start": {
|
|
144
|
+
// evt.message 里常携带 id/model/usage
|
|
145
|
+
const m = evt.message || {};
|
|
146
|
+
if (m.id) message.id = m.id;
|
|
147
|
+
if (m.model) message.model = m.model;
|
|
148
|
+
if (m.usage?.input_tokens) message.usage.input_tokens += Number(m.usage.input_tokens) || 0;
|
|
149
|
+
if (m.usage?.output_tokens) message.usage.output_tokens += Number(m.usage.output_tokens) || 0;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case "content_block_start": {
|
|
154
|
+
const idx = evt.index ?? 0;
|
|
155
|
+
const block = evt.content_block;
|
|
156
|
+
// 文本块或工具块都在这里创建
|
|
157
|
+
ensureBlock(idx, { content_block: block });
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
case "content_block_delta": {
|
|
162
|
+
const idx = evt.index ?? 0;
|
|
163
|
+
const slot = ensureBlock(idx);
|
|
164
|
+
const d = evt.delta || evt; // 兼容有的实现把字段拍平
|
|
165
|
+
|
|
166
|
+
// 文本增量
|
|
167
|
+
if (d.type === "text_delta" && typeof d.text === "string") {
|
|
168
|
+
if (slot.ref.type !== "text") {
|
|
169
|
+
// 如果之前误判为工具,则转为文本(极少见)
|
|
170
|
+
slot.ref = { type: "text", text: "" };
|
|
171
|
+
message.content.push(slot.ref);
|
|
172
|
+
}
|
|
173
|
+
slot.ref.text += d.text;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 工具输入增量:partial_json / input_json / input_json_delta(都当作字符串片段拼接)
|
|
178
|
+
if (slot.ref.type === "tool_use") {
|
|
179
|
+
if (typeof d.partial_json === "string") {
|
|
180
|
+
feedToolJson(slot, d.partial_json);
|
|
181
|
+
} else if (typeof d.input_json === "string") {
|
|
182
|
+
feedToolJson(slot, d.input_json);
|
|
183
|
+
} else if (typeof d.input_json_delta === "string") {
|
|
184
|
+
feedToolJson(slot, d.input_json_delta);
|
|
185
|
+
} else if (d.json !== undefined) {
|
|
186
|
+
// 有些实现可能用 json 字段携带片段
|
|
187
|
+
feedToolJson(slot, d.json);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
case "content_block_stop": {
|
|
194
|
+
const idx = evt.index ?? 0;
|
|
195
|
+
const slot = blockMap.get(idx);
|
|
196
|
+
// 若是工具块,尝试把缓冲片段解析为最终 input
|
|
197
|
+
finalizeToolInputIfAny(slot);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
case "message_delta": {
|
|
202
|
+
// 累计 usage 增量、停止原因等
|
|
203
|
+
if (evt.delta?.stop_reason !== undefined) {
|
|
204
|
+
message.stop_reason = evt.delta.stop_reason;
|
|
205
|
+
}
|
|
206
|
+
if (evt.delta?.stop_sequence !== undefined) {
|
|
207
|
+
message.stop_sequence = evt.delta.stop_sequence;
|
|
208
|
+
}
|
|
209
|
+
if (evt.usage) {
|
|
210
|
+
if (evt.usage.output_tokens) {
|
|
211
|
+
message.usage.output_tokens += Number(evt.usage.output_tokens) || 0;
|
|
212
|
+
}
|
|
213
|
+
if (evt.usage.input_tokens) {
|
|
214
|
+
message.usage.input_tokens += Number(evt.usage.input_tokens) || 0;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
case "message_stop": {
|
|
221
|
+
// 终止事件,通常无需额外处理
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
default: {
|
|
226
|
+
// 未识别事件,忽略
|
|
227
|
+
debug.ignoredCount++;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 收尾:确保所有工具块的 JSON 缓冲被解析
|
|
233
|
+
for (const slot of blockMap.values()) {
|
|
234
|
+
finalizeToolInputIfAny(slot);
|
|
235
|
+
}
|
|
236
|
+
message.content = message.content.filter(
|
|
237
|
+
b => !(b.type === 'text' && b.text.trim() === '')
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
if(message.content.length == 0){
|
|
241
|
+
for(let c in rawChunks){
|
|
242
|
+
logger.debug(c);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
return message;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* =========================
|
|
251
|
+
* 使用示例
|
|
252
|
+
* =========================
|
|
253
|
+
const chunks = [
|
|
254
|
+
'data: {"type":"message_start","message":{"id":"msg_1","model":"claude-3-5-sonnet","usage":{"input_tokens":42}}}',
|
|
255
|
+
'data: {"type":"content_block_start","index":0,"content_block":{"type":"text"}}',
|
|
256
|
+
'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"你好,"}}',
|
|
257
|
+
'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"这里是 Claude。"}}',
|
|
258
|
+
'data: {"type":"content_block_stop","index":0}',
|
|
259
|
+
|
|
260
|
+
'data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_1","name":"search"}}',
|
|
261
|
+
'data: {"type":"content_block_delta","index":1,"delta":{"partial_json":"{\\"q\\":\\"Node.js\\","}}',
|
|
262
|
+
'data: {"type":"content_block_delta","index":1,"delta":{"partial_json":"\\"limit\\":5}"}}',
|
|
263
|
+
'data: {"type":"content_block_stop","index":1}',
|
|
264
|
+
|
|
265
|
+
'data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":18}}',
|
|
266
|
+
'data: {"type":"message_stop"}'
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
const { message, debug } = mergeAnthropicChunks(chunks);
|
|
270
|
+
logger.debug(JSON.stringify(message, null, 2), debug);
|
|
271
|
+
*/
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
export default mergeAnthropicChunks;
|
|
275
|
+
|
|
276
|
+
// ---------------- 示例 ----------------
|
|
277
|
+
// const chunks = [...]; // 这里放从 SSE 收集的 chunk
|
|
278
|
+
// const merged = mergeAnthropicChunks(chunks);
|
|
279
|
+
// console.log(JSON.stringify(merged, null, 2));
|