weifuwu 0.57.0 → 0.58.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/README.md +76 -0
- package/dist/ai/agent.d.ts +50 -0
- package/dist/ai/client.d.ts +104 -0
- package/dist/ai/index.d.ts +54 -0
- package/dist/ai/sse.d.ts +26 -0
- package/dist/ai/types.d.ts +148 -0
- package/dist/client/ai.d.ts +59 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +128 -0
- package/dist/components/ApprovalCard/ApprovalCard.d.ts +30 -0
- package/dist/components/ToolCallCard/ToolCallCard.d.ts +23 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +86 -0
- package/dist/components/style.css +138 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +372 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2578,6 +2578,377 @@ function isPoolClosed(e) {
|
|
|
2578
2578
|
return /pool is closed|connection closed/.test(msg);
|
|
2579
2579
|
}
|
|
2580
2580
|
|
|
2581
|
+
// src/ai/client.ts
|
|
2582
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2583
|
+
|
|
2584
|
+
// src/ai/sse.ts
|
|
2585
|
+
function sseResponse(run, options) {
|
|
2586
|
+
const encoder = new TextEncoder();
|
|
2587
|
+
const stream = new ReadableStream({
|
|
2588
|
+
async start(controller) {
|
|
2589
|
+
const emit = (name, data) => {
|
|
2590
|
+
controller.enqueue(encoder.encode(`event: ${name}
|
|
2591
|
+
data: ${JSON.stringify(data)}
|
|
2592
|
+
|
|
2593
|
+
`));
|
|
2594
|
+
};
|
|
2595
|
+
try {
|
|
2596
|
+
await run(emit);
|
|
2597
|
+
controller.close();
|
|
2598
|
+
} catch (err) {
|
|
2599
|
+
try {
|
|
2600
|
+
emit("wf:error", {
|
|
2601
|
+
code: "provider_error",
|
|
2602
|
+
message: err instanceof Error ? err.message : String(err)
|
|
2603
|
+
});
|
|
2604
|
+
controller.close();
|
|
2605
|
+
} catch {
|
|
2606
|
+
controller.error(err);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
},
|
|
2610
|
+
cancel() {
|
|
2611
|
+
options?.onAbort?.();
|
|
2612
|
+
}
|
|
2613
|
+
});
|
|
2614
|
+
return new Response(stream, {
|
|
2615
|
+
headers: {
|
|
2616
|
+
"Content-Type": "text/event-stream",
|
|
2617
|
+
"Cache-Control": "no-cache",
|
|
2618
|
+
Connection: "keep-alive"
|
|
2619
|
+
}
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
// src/ai/client.ts
|
|
2624
|
+
var AiError = class extends Error {
|
|
2625
|
+
code;
|
|
2626
|
+
constructor(code, message) {
|
|
2627
|
+
super(message);
|
|
2628
|
+
this.name = "AiError";
|
|
2629
|
+
this.code = code;
|
|
2630
|
+
}
|
|
2631
|
+
};
|
|
2632
|
+
async function* parseProviderSse(stream) {
|
|
2633
|
+
const reader = stream.getReader();
|
|
2634
|
+
const decoder = new TextDecoder();
|
|
2635
|
+
let buffer = "";
|
|
2636
|
+
try {
|
|
2637
|
+
while (true) {
|
|
2638
|
+
const { done, value } = await reader.read();
|
|
2639
|
+
if (done) break;
|
|
2640
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2641
|
+
const lines = buffer.split("\n");
|
|
2642
|
+
buffer = lines.pop() ?? "";
|
|
2643
|
+
for (const line of lines) {
|
|
2644
|
+
const trimmed = line.trim();
|
|
2645
|
+
if (!trimmed || trimmed.startsWith(":")) continue;
|
|
2646
|
+
const data = trimmed.startsWith("data: ") ? trimmed.slice(6) : trimmed;
|
|
2647
|
+
if (data === "[DONE]") return;
|
|
2648
|
+
try {
|
|
2649
|
+
yield JSON.parse(data);
|
|
2650
|
+
} catch {
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
} finally {
|
|
2655
|
+
reader.releaseLock();
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
async function providerError(res) {
|
|
2659
|
+
let message = "";
|
|
2660
|
+
try {
|
|
2661
|
+
const body = await res.json();
|
|
2662
|
+
message = body?.error?.message ?? JSON.stringify(body);
|
|
2663
|
+
} catch {
|
|
2664
|
+
message = await res.text().catch(() => "");
|
|
2665
|
+
}
|
|
2666
|
+
const status = res.status;
|
|
2667
|
+
let code;
|
|
2668
|
+
if (status === 401 || status === 403) code = "auth_failed";
|
|
2669
|
+
else if (status === 429) code = "rate_limited";
|
|
2670
|
+
else if (status >= 500) code = "provider_error";
|
|
2671
|
+
else if (/context|token|length/i.test(message)) code = "context_length";
|
|
2672
|
+
else if (status === 400) code = "invalid_request";
|
|
2673
|
+
else code = "provider_error";
|
|
2674
|
+
return { code, message: message || `provider error (${status})` };
|
|
2675
|
+
}
|
|
2676
|
+
function createAiClient(opts) {
|
|
2677
|
+
const endpoint = `${opts.baseUrl.replace(/\/$/, "")}/chat/completions`;
|
|
2678
|
+
const headers = { "Content-Type": "application/json", Authorization: `Bearer ${opts.apiKey}` };
|
|
2679
|
+
const approvals = /* @__PURE__ */ new Map();
|
|
2680
|
+
function approve(response) {
|
|
2681
|
+
const resolve3 = approvals.get(response.id);
|
|
2682
|
+
if (!resolve3) return false;
|
|
2683
|
+
approvals.delete(response.id);
|
|
2684
|
+
resolve3(response);
|
|
2685
|
+
return true;
|
|
2686
|
+
}
|
|
2687
|
+
async function waitApproval(req, emit, timeoutMs = DEFAULT_APPROVAL_TIMEOUT) {
|
|
2688
|
+
const expiresAt = Date.now() + timeoutMs;
|
|
2689
|
+
emit("wf:approval_request", { ...req, expiresAt });
|
|
2690
|
+
return new Promise((resolve3) => {
|
|
2691
|
+
const timer = setTimeout(() => {
|
|
2692
|
+
if (approvals.has(req.id)) {
|
|
2693
|
+
approvals.delete(req.id);
|
|
2694
|
+
resolve3({ id: req.id, decision: "rejected" });
|
|
2695
|
+
}
|
|
2696
|
+
}, timeoutMs);
|
|
2697
|
+
approvals.set(req.id, (resp) => {
|
|
2698
|
+
clearTimeout(timer);
|
|
2699
|
+
resolve3(resp);
|
|
2700
|
+
});
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
function aggregateToolCalls(chunks) {
|
|
2704
|
+
const calls = [];
|
|
2705
|
+
for (const chunk of chunks) {
|
|
2706
|
+
const delta = chunk.choices[0]?.delta;
|
|
2707
|
+
if (!delta?.tool_calls) continue;
|
|
2708
|
+
for (const tc of delta.tool_calls) {
|
|
2709
|
+
if (tc.id) {
|
|
2710
|
+
calls.push(tc);
|
|
2711
|
+
} else if (calls.length > 0) {
|
|
2712
|
+
const last = calls[calls.length - 1];
|
|
2713
|
+
if (tc.function?.arguments) last.function.arguments += tc.function.arguments;
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
return calls;
|
|
2718
|
+
}
|
|
2719
|
+
async function chat(params, options) {
|
|
2720
|
+
const res = await fetch(endpoint, {
|
|
2721
|
+
method: "POST",
|
|
2722
|
+
headers,
|
|
2723
|
+
body: JSON.stringify({ ...params, model: params.model ?? opts.defaultModel, stream: false }),
|
|
2724
|
+
signal: options?.signal
|
|
2725
|
+
});
|
|
2726
|
+
if (!res.ok) {
|
|
2727
|
+
const { code, message } = await providerError(res);
|
|
2728
|
+
throw new AiError(code, message);
|
|
2729
|
+
}
|
|
2730
|
+
return res.json();
|
|
2731
|
+
}
|
|
2732
|
+
function stream(params, options) {
|
|
2733
|
+
const controller = new AbortController();
|
|
2734
|
+
const external = options?.signal;
|
|
2735
|
+
if (external) {
|
|
2736
|
+
if (external.aborted) controller.abort();
|
|
2737
|
+
else external.addEventListener("abort", () => controller.abort(), { once: true });
|
|
2738
|
+
}
|
|
2739
|
+
return sseResponse(
|
|
2740
|
+
async (emit) => {
|
|
2741
|
+
emit("wf:message_start", { id: options?.traceId ?? randomUUID2() });
|
|
2742
|
+
await streamStep(params, {
|
|
2743
|
+
emit,
|
|
2744
|
+
signal: controller.signal,
|
|
2745
|
+
onFinish: (r) => emit("wf:done", { content: r.content, usage: r.usage })
|
|
2746
|
+
});
|
|
2747
|
+
},
|
|
2748
|
+
{ onAbort: () => controller.abort() }
|
|
2749
|
+
);
|
|
2750
|
+
}
|
|
2751
|
+
async function streamStep(params, stepOpts) {
|
|
2752
|
+
const { emit, signal } = stepOpts;
|
|
2753
|
+
let res;
|
|
2754
|
+
try {
|
|
2755
|
+
res = await fetch(endpoint, {
|
|
2756
|
+
method: "POST",
|
|
2757
|
+
headers,
|
|
2758
|
+
body: JSON.stringify({ ...params, model: params.model ?? opts.defaultModel, stream: true }),
|
|
2759
|
+
signal
|
|
2760
|
+
});
|
|
2761
|
+
} catch (err) {
|
|
2762
|
+
if (signal?.aborted) return;
|
|
2763
|
+
emit("wf:error", { code: "provider_error", message: err instanceof Error ? err.message : String(err) });
|
|
2764
|
+
return;
|
|
2765
|
+
}
|
|
2766
|
+
if (!res.ok) {
|
|
2767
|
+
const { code, message } = await providerError(res);
|
|
2768
|
+
emit("wf:error", { code, message });
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
let content = "";
|
|
2772
|
+
let reasoning = "";
|
|
2773
|
+
const chunks = [];
|
|
2774
|
+
let usage;
|
|
2775
|
+
try {
|
|
2776
|
+
for await (const chunk of parseProviderSse(res.body)) {
|
|
2777
|
+
if (signal?.aborted) return;
|
|
2778
|
+
const delta = chunk.choices[0]?.delta;
|
|
2779
|
+
if (delta?.content) {
|
|
2780
|
+
content += delta.content;
|
|
2781
|
+
emit("wf:token", { text: delta.content });
|
|
2782
|
+
}
|
|
2783
|
+
if (delta?.reasoning_content) reasoning += delta.reasoning_content;
|
|
2784
|
+
if (delta?.tool_calls) chunks.push(chunk);
|
|
2785
|
+
if (chunk.usage) usage = chunk.usage;
|
|
2786
|
+
}
|
|
2787
|
+
} catch (err) {
|
|
2788
|
+
if (signal?.aborted) return;
|
|
2789
|
+
emit("wf:error", { code: "provider_error", message: err instanceof Error ? err.message : String(err) });
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
const toolCalls = aggregateToolCalls(chunks);
|
|
2793
|
+
for (const tc of toolCalls) {
|
|
2794
|
+
emit("wf:tool_call", {
|
|
2795
|
+
id: tc.id,
|
|
2796
|
+
name: tc.function?.name ?? "",
|
|
2797
|
+
args: safeParseArgs(tc.function?.arguments ?? "")
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
2800
|
+
if (usage && stepOpts.emitUsage !== false) emit("wf:usage", usage);
|
|
2801
|
+
stepOpts.onFinish?.({ content, reasoning_content: reasoning || void 0, toolCalls, usage });
|
|
2802
|
+
}
|
|
2803
|
+
function sse(run, options) {
|
|
2804
|
+
const controller = new AbortController();
|
|
2805
|
+
const external = options?.signal;
|
|
2806
|
+
if (external) {
|
|
2807
|
+
if (external.aborted) controller.abort();
|
|
2808
|
+
else external.addEventListener("abort", () => controller.abort(), { once: true });
|
|
2809
|
+
}
|
|
2810
|
+
return sseResponse(run, { onAbort: () => controller.abort() });
|
|
2811
|
+
}
|
|
2812
|
+
return { chat, stream, sse, streamStep, waitApproval, approve };
|
|
2813
|
+
}
|
|
2814
|
+
var DEFAULT_APPROVAL_TIMEOUT = 5 * 6e4;
|
|
2815
|
+
function safeParseArgs(raw) {
|
|
2816
|
+
if (!raw) return {};
|
|
2817
|
+
try {
|
|
2818
|
+
return JSON.parse(raw);
|
|
2819
|
+
} catch {
|
|
2820
|
+
return {};
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
// src/ai/agent.ts
|
|
2825
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
2826
|
+
function createAgent(client, config) {
|
|
2827
|
+
const maxSteps = config.maxSteps ?? 10;
|
|
2828
|
+
const toolDefs = config.tools.map((t) => ({
|
|
2829
|
+
type: "function",
|
|
2830
|
+
function: { name: t.name, description: t.description ?? "", parameters: t.parameters ?? {} }
|
|
2831
|
+
}));
|
|
2832
|
+
function run(messages, options) {
|
|
2833
|
+
const controller = new AbortController();
|
|
2834
|
+
const external = options?.signal;
|
|
2835
|
+
if (external) {
|
|
2836
|
+
if (external.aborted) controller.abort();
|
|
2837
|
+
else external.addEventListener("abort", () => controller.abort(), { once: true });
|
|
2838
|
+
}
|
|
2839
|
+
return sseResponse(
|
|
2840
|
+
async (emit) => {
|
|
2841
|
+
emit("wf:message_start", { id: options?.traceId ?? randomUUID3() });
|
|
2842
|
+
await loop(messages, emit, controller.signal);
|
|
2843
|
+
},
|
|
2844
|
+
{ onAbort: () => controller.abort() }
|
|
2845
|
+
);
|
|
2846
|
+
}
|
|
2847
|
+
async function loop(messages, emit, signal) {
|
|
2848
|
+
const all = [{ role: "system", content: config.systemPrompt }, ...messages];
|
|
2849
|
+
const usage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
|
|
2850
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
2851
|
+
if (signal?.aborted) return;
|
|
2852
|
+
emit("wf:step", { type: "llm" });
|
|
2853
|
+
let finish = { content: "", toolCalls: [] };
|
|
2854
|
+
await client.streamStep(
|
|
2855
|
+
{ model: config.model, messages: all, tools: toolDefs },
|
|
2856
|
+
{
|
|
2857
|
+
emit,
|
|
2858
|
+
signal,
|
|
2859
|
+
emitUsage: false,
|
|
2860
|
+
// agent 只发累积 usage(done 前统一发)
|
|
2861
|
+
onFinish: (r) => {
|
|
2862
|
+
finish = r;
|
|
2863
|
+
if (r.usage) {
|
|
2864
|
+
usage.prompt_tokens += r.usage.prompt_tokens ?? 0;
|
|
2865
|
+
usage.completion_tokens += r.usage.completion_tokens ?? 0;
|
|
2866
|
+
usage.total_tokens += r.usage.total_tokens ?? 0;
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
);
|
|
2871
|
+
if (signal?.aborted) return;
|
|
2872
|
+
if (!finish.toolCalls?.length) {
|
|
2873
|
+
emit("wf:usage", usage);
|
|
2874
|
+
emit("wf:done", { content: finish.content, usage });
|
|
2875
|
+
return;
|
|
2876
|
+
}
|
|
2877
|
+
all.push({
|
|
2878
|
+
role: "assistant",
|
|
2879
|
+
content: finish.content ?? "",
|
|
2880
|
+
reasoning_content: finish.reasoning_content,
|
|
2881
|
+
tool_calls: finish.toolCalls
|
|
2882
|
+
});
|
|
2883
|
+
for (const tc of finish.toolCalls) {
|
|
2884
|
+
if (signal?.aborted) return;
|
|
2885
|
+
const name = tc.function?.name ?? "";
|
|
2886
|
+
const args = safeParseArgs(tc.function?.arguments ?? "");
|
|
2887
|
+
emit("wf:step", { type: "tool", toolCallId: tc.id, name });
|
|
2888
|
+
let execArgs = args;
|
|
2889
|
+
if (config.humanInTheLoop) {
|
|
2890
|
+
const decision = await client.waitApproval(
|
|
2891
|
+
{ id: randomUUID3(), toolCallId: tc.id, name, args },
|
|
2892
|
+
emit,
|
|
2893
|
+
config.approvalTimeoutMs
|
|
2894
|
+
);
|
|
2895
|
+
if (decision.decision === "rejected") {
|
|
2896
|
+
emit("wf:tool_result", { id: tc.id, ok: false, error: { code: "rejected", message: decision.note ?? "\u7528\u6237\u62D2\u7EDD" } });
|
|
2897
|
+
all.push({ role: "tool", content: `Human rejected: ${decision.note ?? ""}`, tool_call_id: tc.id, name });
|
|
2898
|
+
continue;
|
|
2899
|
+
}
|
|
2900
|
+
if (decision.decision === "modified" && decision.modifiedArgs) execArgs = decision.modifiedArgs;
|
|
2901
|
+
}
|
|
2902
|
+
const tool = config.tools.find((t) => t.name === name);
|
|
2903
|
+
if (!tool) {
|
|
2904
|
+
emit("wf:tool_result", { id: tc.id, ok: false, error: { code: "tool_error", message: `\u5DE5\u5177\u4E0D\u5B58\u5728: ${name}` } });
|
|
2905
|
+
all.push({ role: "tool", content: `Error: tool not found: ${name}`, tool_call_id: tc.id, name });
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
try {
|
|
2909
|
+
const output = await tool.run(execArgs, { emit, signal });
|
|
2910
|
+
emit("wf:tool_result", { id: tc.id, ok: true, output });
|
|
2911
|
+
all.push({ role: "tool", content: JSON.stringify(output ?? ""), tool_call_id: tc.id, name });
|
|
2912
|
+
} catch (err) {
|
|
2913
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2914
|
+
emit("wf:tool_result", { id: tc.id, ok: false, error: { code: "tool_error", message: msg } });
|
|
2915
|
+
all.push({ role: "tool", content: `Error: ${msg}`, tool_call_id: tc.id, name });
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
emit("wf:usage", usage);
|
|
2920
|
+
emit("wf:done", { content: "", usage });
|
|
2921
|
+
}
|
|
2922
|
+
return { run };
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
// src/ai/index.ts
|
|
2926
|
+
function ai(options) {
|
|
2927
|
+
const apiKey = options?.apiKey ?? process.env.DEEPSEEK_API_KEY ?? "";
|
|
2928
|
+
const baseUrl = options?.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? "https://api.deepseek.com/v1";
|
|
2929
|
+
const defaultModel = options?.defaultModel ?? process.env.DEEPSEEK_MODEL ?? "deepseek-v4-flash";
|
|
2930
|
+
if (!apiKey) {
|
|
2931
|
+
throw new Error("ai: DEEPSEEK_API_KEY \u672A\u8BBE\u7F6E\u3002\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\u6216\u4F20\u5165 apiKey");
|
|
2932
|
+
}
|
|
2933
|
+
const client = createAiClient({ apiKey, baseUrl, defaultModel });
|
|
2934
|
+
const mw = (req, ctx, next) => {
|
|
2935
|
+
ctx.ai = module;
|
|
2936
|
+
return next(req, ctx);
|
|
2937
|
+
};
|
|
2938
|
+
mw.__meta = { injects: ["ai"], depends: [] };
|
|
2939
|
+
const module = mw;
|
|
2940
|
+
module.chat = client.chat;
|
|
2941
|
+
module.stream = client.stream;
|
|
2942
|
+
module.sse = client.sse;
|
|
2943
|
+
module.streamStep = client.streamStep;
|
|
2944
|
+
module.waitApproval = client.waitApproval;
|
|
2945
|
+
module.approve = client.approve;
|
|
2946
|
+
module.agent = (config) => createAgent(client, config);
|
|
2947
|
+
module.close = async () => {
|
|
2948
|
+
};
|
|
2949
|
+
return module;
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2581
2952
|
// src/ui/index.ts
|
|
2582
2953
|
import { build } from "esbuild";
|
|
2583
2954
|
import { readFile, stat } from "node:fs/promises";
|
|
@@ -4148,6 +4519,7 @@ export {
|
|
|
4148
4519
|
HttpError,
|
|
4149
4520
|
MIGRATIONS_TABLE,
|
|
4150
4521
|
Router,
|
|
4522
|
+
ai,
|
|
4151
4523
|
badRequest,
|
|
4152
4524
|
conflict,
|
|
4153
4525
|
cors,
|