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.
@@ -1584,6 +1584,133 @@ function auth(options) {
1584
1584
  };
1585
1585
  }
1586
1586
 
1587
+ // src/client/ai.ts
1588
+ var RECORD_LIMIT = 1e3;
1589
+ function aiStream(url, body, options) {
1590
+ const controller = new AbortController();
1591
+ const external = options?.signal;
1592
+ if (external) {
1593
+ if (external.aborted) controller.abort();
1594
+ else external.addEventListener("abort", () => controller.abort(), { once: true });
1595
+ }
1596
+ const traceId = options?.traceId ?? randomId();
1597
+ const record = options?.record !== false;
1598
+ const events = [];
1599
+ const done = (async () => {
1600
+ let res;
1601
+ try {
1602
+ res = await fetch(url, {
1603
+ method: "POST",
1604
+ headers: {
1605
+ "Content-Type": "application/json",
1606
+ "X-Trace-Id": traceId,
1607
+ Accept: "text/event-stream",
1608
+ ...options?.headers
1609
+ },
1610
+ body: JSON.stringify(body),
1611
+ signal: controller.signal
1612
+ });
1613
+ } catch (err) {
1614
+ if (controller.signal.aborted) return;
1615
+ options?.onError?.({ code: "provider_error", message: err instanceof Error ? err.message : String(err) });
1616
+ return;
1617
+ }
1618
+ if (!res.ok) {
1619
+ options?.onError?.({
1620
+ code: httpErrorCode(res.status),
1621
+ message: `HTTP ${res.status} ${res.statusText}`
1622
+ });
1623
+ return;
1624
+ }
1625
+ try {
1626
+ for await (const { name, data } of parseWfEvents(res.body)) {
1627
+ if (controller.signal.aborted) return;
1628
+ pushEvent(events, { name, data }, record);
1629
+ dispatch(name, data, options ?? {});
1630
+ }
1631
+ } catch (err) {
1632
+ if (controller.signal.aborted) return;
1633
+ options?.onError?.({ code: "provider_error", message: err instanceof Error ? err.message : String(err) });
1634
+ }
1635
+ })();
1636
+ return {
1637
+ abort: () => controller.abort(),
1638
+ done,
1639
+ traceId,
1640
+ events
1641
+ };
1642
+ }
1643
+ function dispatch(name, data, o) {
1644
+ switch (name) {
1645
+ case "wf:token":
1646
+ return o.onToken?.(data.text);
1647
+ case "wf:tool_call":
1648
+ return o.onToolCall?.(data);
1649
+ case "wf:tool_result":
1650
+ return o.onToolResult?.(data);
1651
+ case "wf:tool_progress":
1652
+ return o.onToolProgress?.(data);
1653
+ case "wf:step":
1654
+ return o.onStep?.(data);
1655
+ case "wf:approval_request":
1656
+ return o.onApproval?.(data);
1657
+ case "wf:usage":
1658
+ return o.onUsage?.(data);
1659
+ case "wf:done":
1660
+ return o.onDone?.(data);
1661
+ case "wf:error":
1662
+ return o.onError?.(data);
1663
+ default:
1664
+ return o.onEvent?.(name, data);
1665
+ }
1666
+ }
1667
+ async function* parseWfEvents(stream) {
1668
+ const reader = stream.getReader();
1669
+ const decoder = new TextDecoder();
1670
+ let buffer = "";
1671
+ try {
1672
+ while (true) {
1673
+ const { done, value } = await reader.read();
1674
+ if (done) break;
1675
+ buffer += decoder.decode(value, { stream: true });
1676
+ const blocks = buffer.split("\n\n");
1677
+ buffer = blocks.pop() ?? "";
1678
+ for (const block of blocks) {
1679
+ let name = "message";
1680
+ let data = "";
1681
+ for (const line of block.split("\n")) {
1682
+ if (line.startsWith("event: ")) name = line.slice(7);
1683
+ else if (line.startsWith("data: ")) data += line.slice(6);
1684
+ }
1685
+ if (!data) continue;
1686
+ try {
1687
+ yield { name, data: JSON.parse(data) };
1688
+ } catch {
1689
+ }
1690
+ }
1691
+ }
1692
+ } finally {
1693
+ reader.releaseLock();
1694
+ }
1695
+ }
1696
+ function pushEvent(events, e, record) {
1697
+ if (!record) return;
1698
+ if (events.length >= RECORD_LIMIT) events.shift();
1699
+ events.push(e);
1700
+ }
1701
+ function httpErrorCode(status) {
1702
+ if (status === 401 || status === 403) return "auth_failed";
1703
+ if (status === 429) return "rate_limited";
1704
+ if (status >= 500) return "provider_error";
1705
+ return "invalid_request";
1706
+ }
1707
+ function randomId() {
1708
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
1709
+ return crypto.randomUUID();
1710
+ }
1711
+ return `trace-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
1712
+ }
1713
+
1587
1714
  // src/client/error-boundary.ts
1588
1715
  function ErrorBoundary(props, ctx) {
1589
1716
  let error = null;
@@ -1842,6 +1969,7 @@ export {
1842
1969
  Fragment,
1843
1970
  Portal,
1844
1971
  RouteView,
1972
+ aiStream,
1845
1973
  api,
1846
1974
  asyncComponent,
1847
1975
  auth,
@@ -0,0 +1,30 @@
1
+ import type { Component } from '../../client/vnode.ts';
2
+ import type { WfApprovalRequest } from '../../ai/types.ts';
3
+ /**
4
+ * ApprovalCard — 人工审批卡片(wf: 协议 §4.5 交互原语)
5
+ *
6
+ * 状态机:pending(待批,允许/拒绝 + 备注)→ approved / rejected / timeout(终态)。
7
+ * 纯受控组件:决策回传由 onApprove / onReject 上抛(app 负责 POST /approve)。
8
+ *
9
+ * ```tsx
10
+ * <ApprovalCard
11
+ * request={approvalReq}
12
+ * onApprove={() => respond('approved')}
13
+ * onReject={(note) => respond('rejected', note)}
14
+ * />
15
+ * ```
16
+ */
17
+ export type ApprovalStatus = 'pending' | 'approved' | 'rejected' | 'timeout';
18
+ export interface ApprovalCardProps {
19
+ /** wf:approval_request 数据 */
20
+ request: WfApprovalRequest;
21
+ /** 卡片状态(默认 pending) */
22
+ status?: ApprovalStatus;
23
+ /** 用户点"允许" */
24
+ onApprove?: () => void;
25
+ /** 用户点"拒绝",note 为可选备注(进 agent 上下文) */
26
+ onReject?: (note?: string) => void;
27
+ /** 自定义详情渲染(默认显示 name + args) */
28
+ renderDetail?: (request: WfApprovalRequest) => any;
29
+ }
30
+ export declare const ApprovalCard: Component<ApprovalCardProps>;
@@ -0,0 +1,23 @@
1
+ import type { Component } from '../../client/vnode.ts';
2
+ import type { WfToolCall, WfToolProgress, WfToolResult } from '../../ai/types.ts';
3
+ /**
4
+ * ToolCallCard — 工具调用卡片(wf: 协议 §4 交互原语)
5
+ *
6
+ * 纯展示组件:接收协议事件数据 + 可选回调,无内部状态。
7
+ * 状态机:running(等待/执行中,带进度条)→ ok / error(有 result 时)。
8
+ *
9
+ * ```tsx
10
+ * <ToolCallCard call={toolCall} progress={progress} result={result} />
11
+ * ```
12
+ */
13
+ export interface ToolCallCardProps {
14
+ /** wf:tool_call 数据 */
15
+ call: WfToolCall;
16
+ /** wf:tool_progress 数据(可选,展示进度条) */
17
+ progress?: WfToolProgress;
18
+ /** wf:tool_result 数据(可选,ok/error 终态) */
19
+ result?: WfToolResult;
20
+ /** 自定义参数渲染(默认 JSON.stringify) */
21
+ renderArgs?: (args: Record<string, unknown>) => any;
22
+ }
23
+ export declare const ToolCallCard: Component<ToolCallCardProps>;
@@ -92,3 +92,7 @@ export type { EditorProps, ToolbarItem } from './Editor/Editor.ts';
92
92
  export { ThemeSwitch } from './ThemeSwitch/ThemeSwitch.ts';
93
93
  export type { ThemeSwitchProps, ThemeMode } from './ThemeSwitch/ThemeSwitch.ts';
94
94
  export { applyTheme, getTheme } from './ThemeSwitch/ThemeSwitch.ts';
95
+ export { ToolCallCard } from './ToolCallCard/ToolCallCard.ts';
96
+ export type { ToolCallCardProps } from './ToolCallCard/ToolCallCard.ts';
97
+ export { ApprovalCard } from './ApprovalCard/ApprovalCard.ts';
98
+ export type { ApprovalCardProps, ApprovalStatus } from './ApprovalCard/ApprovalCard.ts';
@@ -3084,9 +3084,94 @@ var ThemeSwitch = (initProps, ctx) => {
3084
3084
  }, segments);
3085
3085
  };
3086
3086
  };
3087
+
3088
+ // src/components/ToolCallCard/ToolCallCard.ts
3089
+ var stateIcon = { running: "\u2699", ok: "\u2713", error: "\u2715" };
3090
+ var ToolCallCard = (_init, _ctx) => (props) => {
3091
+ const { call, progress, result, renderArgs } = props;
3092
+ const state = result ? result.ok ? "ok" : "error" : "running";
3093
+ const argsNode = renderArgs ? renderArgs(call.args) : h("code", { class: "wf-toolcall-args" }, JSON.stringify(call.args));
3094
+ const progressNode = progress ? [
3095
+ progress.message ? h("div", { class: "wf-toolcall-msg" }, `${progress.message} (${progress.step}/${progress.total})`) : null,
3096
+ h("div", { class: "wf-toolcall-bar", role: "progressbar", "aria-valuenow": progress.step, "aria-valuemax": progress.total }, [
3097
+ h("div", {
3098
+ class: "wf-toolcall-bar-fill",
3099
+ style: { width: `${Math.min(100, progress.step / Math.max(1, progress.total) * 100)}%` }
3100
+ })
3101
+ ])
3102
+ ] : null;
3103
+ const errorNode = result && !result.ok && result.error ? h("div", { class: "wf-toolcall-error" }, `${result.error.code}: ${result.error.message}`) : null;
3104
+ return h("div", { class: `wf-toolcall wf-toolcall--${state}` }, [
3105
+ h("div", { class: "wf-toolcall-header" }, [
3106
+ h("span", { class: `wf-toolcall-icon wf-toolcall-icon--${state}` }, stateIcon[state]),
3107
+ h("span", { class: "wf-toolcall-name" }, call.name)
3108
+ ]),
3109
+ h("div", { class: "wf-toolcall-body" }, [argsNode, progressNode, errorNode].filter(Boolean))
3110
+ ]);
3111
+ };
3112
+
3113
+ // src/components/ApprovalCard/ApprovalCard.ts
3114
+ var statusText = {
3115
+ pending: "\u7B49\u5F85\u5BA1\u6279",
3116
+ approved: "\u5DF2\u6279\u51C6",
3117
+ rejected: "\u5DF2\u62D2\u7EDD",
3118
+ timeout: "\u5BA1\u6279\u8D85\u65F6"
3119
+ };
3120
+ var ApprovalCard = (_init, ctx) => {
3121
+ let note = "";
3122
+ let showNote = false;
3123
+ return (props) => {
3124
+ const { request, status = "pending", onApprove, onReject, renderDetail } = props;
3125
+ const detail = renderDetail ? renderDetail(request) : [
3126
+ h("div", { class: "wf-approval-name" }, request.name),
3127
+ h("code", { class: "wf-approval-args" }, JSON.stringify(request.args))
3128
+ ];
3129
+ const actions = status === "pending" ? h("div", { class: "wf-approval-actions" }, [
3130
+ showNote ? h("div", { class: "wf-approval-note" }, [
3131
+ h("input", {
3132
+ class: "wf-approval-note-input",
3133
+ placeholder: "\u5907\u6CE8\uFF08\u62D2\u7EDD\u539F\u56E0\uFF0C\u5C06\u8FDB\u5165 agent \u4E0A\u4E0B\u6587\uFF09\u2026",
3134
+ value: note,
3135
+ onInput: (e) => {
3136
+ note = e.target.value;
3137
+ }
3138
+ })
3139
+ ]) : null,
3140
+ h("div", { class: "wf-approval-btns" }, [
3141
+ h("button", {
3142
+ class: "wf-btn wf-btn--primary wf-btn--sm",
3143
+ type: "button",
3144
+ onClick: () => onApprove?.()
3145
+ }, "\u5141\u8BB8"),
3146
+ h("button", {
3147
+ class: "wf-btn wf-btn--danger wf-btn--sm",
3148
+ type: "button",
3149
+ onClick: () => {
3150
+ if (!showNote) {
3151
+ showNote = true;
3152
+ ctx.ui.render();
3153
+ return;
3154
+ }
3155
+ onReject?.(note);
3156
+ }
3157
+ }, showNote ? "\u786E\u8BA4\u62D2\u7EDD" : "\u62D2\u7EDD")
3158
+ ])
3159
+ ].filter(Boolean)) : h("div", { class: `wf-approval-status wf-approval-status--${status}` }, statusText[status]);
3160
+ return h("div", { class: `wf-approval wf-approval--${status}` }, [
3161
+ h("div", { class: "wf-approval-header" }, [
3162
+ h("span", { class: "wf-approval-icon" }, status === "pending" ? "\u23F8" : status === "approved" ? "\u2713" : "\u2715"),
3163
+ h("span", { class: "wf-approval-title" }, "\u5DE5\u5177\u5BA1\u6279")
3164
+ ]),
3165
+ h("div", { class: "wf-approval-detail" }, detail),
3166
+ request.reason ? h("div", { class: "wf-approval-reason" }, request.reason) : null,
3167
+ actions
3168
+ ]);
3169
+ };
3170
+ };
3087
3171
  export {
3088
3172
  Accordion,
3089
3173
  Alert,
3174
+ ApprovalCard,
3090
3175
  Avatar,
3091
3176
  Badge,
3092
3177
  Breadcrumb,
@@ -3127,6 +3212,7 @@ export {
3127
3212
  Textarea,
3128
3213
  ThemeSwitch,
3129
3214
  Toast,
3215
+ ToolCallCard,
3130
3216
  Tooltip,
3131
3217
  applyTheme,
3132
3218
  confirm,
@@ -3617,3 +3617,141 @@ code {
3617
3617
  cursor: not-allowed;
3618
3618
  }
3619
3619
 
3620
+ /* weifuwu/components — ToolCallCard */
3621
+
3622
+ .wf-toolcall {
3623
+ border: 1px solid var(--wf-color-border);
3624
+ border-radius: var(--wf-radius);
3625
+ padding: var(--wf-space) var(--wf-space-md);
3626
+ font-family: var(--wf-font-sans);
3627
+ font-size: var(--wf-font-size-sm);
3628
+ line-height: var(--wf-line-height);
3629
+ background: var(--wf-color-bg-tertiary);
3630
+ }
3631
+
3632
+ .wf-toolcall-header {
3633
+ display: flex;
3634
+ align-items: center;
3635
+ gap: var(--wf-gap-xs);
3636
+ margin-bottom: var(--wf-gap-xs);
3637
+ }
3638
+
3639
+ .wf-toolcall-icon { font-size: var(--wf-font-size-sm); }
3640
+
3641
+ .wf-toolcall-icon--running { color: var(--wf-color-info); }
3642
+ .wf-toolcall-icon--ok { color: var(--wf-color-success); }
3643
+ .wf-toolcall-icon--error { color: var(--wf-color-error); }
3644
+
3645
+ .wf-toolcall-name {
3646
+ font-weight: var(--wf-font-weight-semibold);
3647
+ color: var(--wf-color-text);
3648
+ }
3649
+
3650
+ .wf-toolcall-args {
3651
+ color: var(--wf-color-text-secondary);
3652
+ font-size: var(--wf-font-size-xs);
3653
+ word-break: break-all;
3654
+ }
3655
+
3656
+ .wf-toolcall-msg {
3657
+ color: var(--wf-color-text-secondary);
3658
+ font-size: var(--wf-font-size-xs);
3659
+ margin-top: var(--wf-gap-xs);
3660
+ }
3661
+
3662
+ .wf-toolcall-bar {
3663
+ height: 4px;
3664
+ border-radius: 2px;
3665
+ background: var(--wf-color-bg-tertiary);
3666
+ overflow: hidden;
3667
+ margin-top: var(--wf-gap-xs);
3668
+ }
3669
+
3670
+ .wf-toolcall-bar-fill {
3671
+ height: 100%;
3672
+ background: var(--wf-color-info);
3673
+ transition: width var(--wf-transition);
3674
+ }
3675
+
3676
+ .wf-toolcall-error {
3677
+ color: var(--wf-color-error);
3678
+ font-size: var(--wf-font-size-xs);
3679
+ margin-top: var(--wf-gap-xs);
3680
+ }
3681
+
3682
+ /* weifuwu/components — ApprovalCard */
3683
+
3684
+ .wf-approval {
3685
+ border: 1px solid var(--wf-color-warning);
3686
+ border-radius: var(--wf-radius);
3687
+ padding: var(--wf-space) var(--wf-space-md);
3688
+ font-family: var(--wf-font-sans);
3689
+ font-size: var(--wf-font-size-sm);
3690
+ line-height: var(--wf-line-height);
3691
+ background: var(--wf-color-warning-bg);
3692
+ }
3693
+
3694
+ .wf-approval--approved { border-color: var(--wf-color-success); background: var(--wf-color-success-bg); }
3695
+ .wf-approval--rejected { border-color: var(--wf-color-error); background: var(--wf-color-error-bg); }
3696
+ .wf-approval--timeout { border-color: var(--wf-color-border); background: var(--wf-color-bg-tertiary); }
3697
+
3698
+ .wf-approval-header {
3699
+ display: flex;
3700
+ align-items: center;
3701
+ gap: var(--wf-gap-xs);
3702
+ font-weight: var(--wf-font-weight-semibold);
3703
+ color: var(--wf-color-warning);
3704
+ }
3705
+
3706
+ .wf-approval--approved .wf-approval-header { color: var(--wf-color-success); }
3707
+ .wf-approval--rejected .wf-approval-header { color: var(--wf-color-error); }
3708
+ .wf-approval--timeout .wf-approval-header { color: var(--wf-color-text-secondary); }
3709
+
3710
+ .wf-approval-detail {
3711
+ margin-top: var(--wf-gap-xs);
3712
+ color: var(--wf-color-text);
3713
+ }
3714
+
3715
+ .wf-approval-name { font-weight: var(--wf-font-weight-medium); }
3716
+
3717
+ .wf-approval-args {
3718
+ color: var(--wf-color-text-secondary);
3719
+ font-size: var(--wf-font-size-xs);
3720
+ word-break: break-all;
3721
+ }
3722
+
3723
+ .wf-approval-reason {
3724
+ color: var(--wf-color-text-secondary);
3725
+ font-size: var(--wf-font-size-xs);
3726
+ margin-top: var(--wf-gap-xs);
3727
+ }
3728
+
3729
+ .wf-approval-actions {
3730
+ margin-top: var(--wf-gap-sm);
3731
+ display: flex;
3732
+ flex-direction: column;
3733
+ gap: var(--wf-gap-xs);
3734
+ }
3735
+
3736
+ .wf-approval-btns {
3737
+ display: flex;
3738
+ gap: var(--wf-gap-xs);
3739
+ }
3740
+
3741
+ .wf-approval-note-input {
3742
+ width: 100%;
3743
+ box-sizing: border-box;
3744
+ font-family: var(--wf-font-sans);
3745
+ font-size: var(--wf-font-size-xs);
3746
+ padding: var(--wf-control-pad-y) var(--wf-space);
3747
+ border: var(--wf-border-width) solid var(--wf-color-border);
3748
+ border-radius: var(--wf-radius);
3749
+ color: var(--wf-color-text);
3750
+ }
3751
+
3752
+ .wf-approval-status {
3753
+ margin-top: var(--wf-gap-xs);
3754
+ font-size: var(--wf-font-size-xs);
3755
+ color: var(--wf-color-text-secondary);
3756
+ }
3757
+
package/dist/index.d.ts CHANGED
@@ -21,6 +21,9 @@ export { userSystem } from './user/index.ts';
21
21
  export type { UserSystemOptions, UserInjected, AuthApi, RegisterInput } from './user/index.ts';
22
22
  export { queue } from './queue/index.ts';
23
23
  export type { QueueOptions, QueueClient, QueueInjected, QueueWorker, WorkerOptions, AddOptions, Job } from './queue/index.ts';
24
+ export { ai } from './ai/index.ts';
25
+ export type { AiOptions, AiInjected, AiClient, AiClientModule, ChatResponse, AgentConfig, AgentTool, AgentRunner, ToolContext, } from './ai/index.ts';
26
+ export type { WfStreamEvent, WfMessageStart, WfToken, WfUsage, WfDone, WfError, WfErrorCode, WfToolCall, WfToolResult, WfToolProgress, WfStep, WfApprovalRequest, WfApprovalResponse, WfApprovalDecision, ChatMessage, ChatParams, MessageRole, ToolCall, ToolDefinition, } from './ai/types.ts';
24
27
  export { ui } from './ui/index.ts';
25
28
  export { uiSsr } from './ui/ssr-page.ts';
26
29
  export type { UiSsrOptions } from './ui/ssr-page.ts';