@tangle-network/agent-app 0.6.0 → 0.7.1
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/{chunk-EO4IGDQD.js → chunk-4YTWB5MG.js} +70 -10
- package/dist/chunk-4YTWB5MG.js.map +1 -0
- package/dist/{chunk-HZZD3ZYD.js → chunk-OLCVUGGI.js} +2 -2
- package/dist/{chunk-JANT2G2E.js → chunk-QAQBR6KQ.js} +10 -3
- package/dist/chunk-QAQBR6KQ.js.map +1 -0
- package/dist/{chunk-GMFPCCQZ.js → chunk-SDOT7RNB.js} +152 -2
- package/dist/chunk-SDOT7RNB.js.map +1 -0
- package/dist/chunk-UIWB2F6N.js +1074 -0
- package/dist/chunk-UIWB2F6N.js.map +1 -0
- package/dist/eval/index.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +58 -4
- package/dist/missions/index.d.ts +698 -0
- package/dist/missions/index.js +45 -0
- package/dist/missions/index.js.map +1 -0
- package/dist/preset-cloudflare/index.d.ts +1 -1
- package/dist/runtime/index.d.ts +32 -7
- package/dist/runtime/index.js +2 -2
- package/dist/stream/index.d.ts +88 -1
- package/dist/stream/index.js +13 -1
- package/dist/tools/index.d.ts +10 -2
- package/dist/tools/index.js +2 -2
- package/dist/{types-CTOaTNtU.d.ts → types-By4B3K37.d.ts} +4 -0
- package/dist/web-react/index.d.ts +119 -2
- package/dist/web-react/index.js +257 -20
- package/dist/web-react/index.js.map +1 -1
- package/package.json +6 -1
- package/dist/chunk-EO4IGDQD.js.map +0 -1
- package/dist/chunk-GMFPCCQZ.js.map +0 -1
- package/dist/chunk-JANT2G2E.js.map +0 -1
- /package/dist/{chunk-HZZD3ZYD.js.map → chunk-OLCVUGGI.js.map} +0 -0
package/dist/web-react/index.js
CHANGED
|
@@ -1,5 +1,132 @@
|
|
|
1
1
|
// src/web-react/index.tsx
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
|
|
4
|
+
// src/web-react/chat-stream.ts
|
|
5
|
+
function dispatchChatStreamLine(line, cb) {
|
|
6
|
+
let receivedContent = false;
|
|
7
|
+
let turnId;
|
|
8
|
+
if (!line.trim()) return { receivedContent };
|
|
9
|
+
let parsed;
|
|
10
|
+
try {
|
|
11
|
+
parsed = JSON.parse(line);
|
|
12
|
+
} catch {
|
|
13
|
+
return { receivedContent };
|
|
14
|
+
}
|
|
15
|
+
if (parsed.kind === "tool_result") {
|
|
16
|
+
cb.onToolResult?.({
|
|
17
|
+
toolCallId: parsed.toolCallId,
|
|
18
|
+
toolName: parsed.toolName,
|
|
19
|
+
label: parsed.label,
|
|
20
|
+
outcome: parsed.outcome ?? parsed.result
|
|
21
|
+
});
|
|
22
|
+
return { receivedContent: true };
|
|
23
|
+
}
|
|
24
|
+
const evt = parsed.kind === "event" ? parsed.event : parsed;
|
|
25
|
+
if (!evt || typeof evt !== "object") return { receivedContent };
|
|
26
|
+
switch (evt.type) {
|
|
27
|
+
case "turn":
|
|
28
|
+
if (typeof evt.turnId === "string") turnId = evt.turnId;
|
|
29
|
+
break;
|
|
30
|
+
case "text":
|
|
31
|
+
if (typeof evt.text === "string") {
|
|
32
|
+
cb.onText?.(evt.text);
|
|
33
|
+
receivedContent = true;
|
|
34
|
+
}
|
|
35
|
+
break;
|
|
36
|
+
case "reasoning":
|
|
37
|
+
if (typeof evt.text === "string") {
|
|
38
|
+
cb.onReasoning?.(evt.text);
|
|
39
|
+
receivedContent = true;
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
case "tool_call": {
|
|
43
|
+
const call = evt.call ?? evt;
|
|
44
|
+
cb.onToolCall?.({
|
|
45
|
+
toolCallId: call.toolCallId ?? call.id,
|
|
46
|
+
toolName: String(call.toolName ?? call.name ?? "unknown"),
|
|
47
|
+
args: call.args ?? {}
|
|
48
|
+
});
|
|
49
|
+
receivedContent = true;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case "tool_result":
|
|
53
|
+
cb.onToolResult?.({
|
|
54
|
+
toolCallId: evt.toolCallId,
|
|
55
|
+
toolName: evt.toolName,
|
|
56
|
+
label: evt.label,
|
|
57
|
+
outcome: evt.outcome ?? evt.result
|
|
58
|
+
});
|
|
59
|
+
receivedContent = true;
|
|
60
|
+
break;
|
|
61
|
+
case "usage": {
|
|
62
|
+
const u = evt.usage;
|
|
63
|
+
if (u) cb.onUsage?.({ promptTokens: u.promptTokens ?? 0, completionTokens: u.completionTokens ?? 0 });
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "metadata":
|
|
67
|
+
cb.onMetadata?.(evt.data ?? {});
|
|
68
|
+
break;
|
|
69
|
+
case "error":
|
|
70
|
+
cb.onErrorEvent?.(String(evt.details ?? evt.error ?? "Unknown stream error"));
|
|
71
|
+
break;
|
|
72
|
+
default:
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
return { turnId, receivedContent };
|
|
76
|
+
}
|
|
77
|
+
async function consumeChatStream(body, cb) {
|
|
78
|
+
const reader = body.getReader();
|
|
79
|
+
const decoder = new TextDecoder();
|
|
80
|
+
let buffer = "";
|
|
81
|
+
let turnId = null;
|
|
82
|
+
let receivedContent = false;
|
|
83
|
+
const handle = (line) => {
|
|
84
|
+
const r = dispatchChatStreamLine(line, cb);
|
|
85
|
+
if (r.turnId) {
|
|
86
|
+
turnId = r.turnId;
|
|
87
|
+
cb.onTurnId?.(r.turnId);
|
|
88
|
+
}
|
|
89
|
+
if (r.receivedContent) receivedContent = true;
|
|
90
|
+
};
|
|
91
|
+
for (; ; ) {
|
|
92
|
+
const { done, value } = await reader.read();
|
|
93
|
+
if (done) {
|
|
94
|
+
if (buffer.trim()) handle(buffer);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
buffer += decoder.decode(value, { stream: true });
|
|
98
|
+
const lines = buffer.split("\n");
|
|
99
|
+
buffer = lines.pop() ?? "";
|
|
100
|
+
for (const line of lines) handle(line);
|
|
101
|
+
}
|
|
102
|
+
return { turnId, receivedContent };
|
|
103
|
+
}
|
|
104
|
+
async function streamChatTurn(opts) {
|
|
105
|
+
const res = await opts.start();
|
|
106
|
+
if (!res.ok || !res.body) {
|
|
107
|
+
const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
|
|
108
|
+
throw new Error(err.error ?? `HTTP ${res.status}`);
|
|
109
|
+
}
|
|
110
|
+
let turnId = null;
|
|
111
|
+
const cb = {
|
|
112
|
+
...opts.callbacks,
|
|
113
|
+
onTurnId: (id) => {
|
|
114
|
+
turnId = id;
|
|
115
|
+
opts.callbacks.onTurnId?.(id);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
try {
|
|
119
|
+
return await consumeChatStream(res.body, cb);
|
|
120
|
+
} catch (transportErr) {
|
|
121
|
+
if (!turnId || !opts.resume) throw transportErr;
|
|
122
|
+
opts.onResetForResume?.();
|
|
123
|
+
const resumed = await opts.resume(turnId, 0);
|
|
124
|
+
if (!resumed.ok || !resumed.body) throw transportErr;
|
|
125
|
+
return await consumeChatStream(resumed.body, cb);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/web-react/index.tsx
|
|
3
130
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
131
|
function ChevronDown({ className }) {
|
|
5
132
|
return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) });
|
|
@@ -199,19 +326,105 @@ function EffortPicker({ value, onChange }) {
|
|
|
199
326
|
)) })
|
|
200
327
|
] });
|
|
201
328
|
}
|
|
202
|
-
function
|
|
203
|
-
return /* @__PURE__ */
|
|
204
|
-
"div",
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
329
|
+
function RunDrillIn({ run, onClose }) {
|
|
330
|
+
return /* @__PURE__ */ jsxs("div", { className: "fixed inset-y-0 right-0 z-50 flex w-[480px] max-w-full flex-col border-l border-border bg-card shadow-xl", children: [
|
|
331
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-border px-4 py-3", children: [
|
|
332
|
+
/* @__PURE__ */ jsx(
|
|
333
|
+
"span",
|
|
334
|
+
{
|
|
335
|
+
className: `h-2 w-2 shrink-0 rounded-full ${run.status === "running" ? "bg-yellow-500" : run.status === "error" ? "bg-red-500" : "bg-green-500"}`
|
|
336
|
+
}
|
|
337
|
+
),
|
|
338
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
339
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold", children: run.title }),
|
|
340
|
+
/* @__PURE__ */ jsx("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: run.toolName })
|
|
341
|
+
] }),
|
|
342
|
+
/* @__PURE__ */ jsx(
|
|
343
|
+
"button",
|
|
344
|
+
{
|
|
345
|
+
type: "button",
|
|
346
|
+
onClick: onClose,
|
|
347
|
+
"aria-label": "Close",
|
|
348
|
+
className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground",
|
|
349
|
+
children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) })
|
|
350
|
+
}
|
|
351
|
+
)
|
|
352
|
+
] }),
|
|
353
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
|
|
354
|
+
run.steps.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No steps recorded yet." }),
|
|
355
|
+
run.steps.map((step, i) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border/60 bg-background", children: [
|
|
356
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5", children: [
|
|
357
|
+
/* @__PURE__ */ jsx("span", { className: `font-mono text-[11px] ${step.status === "error" ? "text-red-600" : "text-muted-foreground"}`, children: step.status === "error" ? "\u2717" : "$" }),
|
|
358
|
+
/* @__PURE__ */ jsx("code", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: step.label }),
|
|
359
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] text-muted-foreground/60", children: new Date(step.at).toLocaleTimeString() })
|
|
360
|
+
] }),
|
|
361
|
+
step.detail && /* @__PURE__ */ jsx("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap px-3 py-2 font-mono text-[11px] leading-relaxed text-muted-foreground", children: step.detail })
|
|
362
|
+
] }, i))
|
|
363
|
+
] }),
|
|
364
|
+
/* @__PURE__ */ jsx("p", { className: "border-t border-border px-4 py-2 text-[11px] text-muted-foreground/60", children: "Readonly drill-in. Follow up in the main chat." })
|
|
365
|
+
] });
|
|
366
|
+
}
|
|
367
|
+
function pendingApprovalOf(call) {
|
|
368
|
+
const outcome = call.result;
|
|
369
|
+
if (!outcome?.ok || outcome.result?.status !== "queued_for_approval" || !outcome.result.proposalId) return null;
|
|
370
|
+
return { proposalId: outcome.result.proposalId };
|
|
371
|
+
}
|
|
372
|
+
function ToolChips({
|
|
373
|
+
toolCalls,
|
|
374
|
+
approval,
|
|
375
|
+
onClick
|
|
376
|
+
}) {
|
|
377
|
+
return /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-col gap-1", children: toolCalls.map((tc) => {
|
|
378
|
+
const pending = tc.status === "done" ? pendingApprovalOf(tc) : null;
|
|
379
|
+
if (pending) {
|
|
380
|
+
return /* @__PURE__ */ jsxs(
|
|
381
|
+
"div",
|
|
382
|
+
{
|
|
383
|
+
className: "inline-flex w-fit items-center gap-2 rounded-md bg-amber-500/10 px-2.5 py-1 text-xs text-amber-700",
|
|
384
|
+
children: [
|
|
385
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono opacity-70", children: "\u23F8" }),
|
|
386
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: tc.name }),
|
|
387
|
+
/* @__PURE__ */ jsx("span", { className: "opacity-60", children: "awaiting approval" }),
|
|
388
|
+
approval && /* @__PURE__ */ jsxs("span", { className: "ml-1 inline-flex items-center gap-1", children: [
|
|
389
|
+
/* @__PURE__ */ jsx(
|
|
390
|
+
"button",
|
|
391
|
+
{
|
|
392
|
+
type: "button",
|
|
393
|
+
onClick: () => approval.onApprove(pending.proposalId, tc.id),
|
|
394
|
+
className: "rounded bg-green-600/90 px-2 py-0.5 text-[11px] font-semibold text-white transition hover:bg-green-600",
|
|
395
|
+
children: "Approve"
|
|
396
|
+
}
|
|
397
|
+
),
|
|
398
|
+
/* @__PURE__ */ jsx(
|
|
399
|
+
"button",
|
|
400
|
+
{
|
|
401
|
+
type: "button",
|
|
402
|
+
onClick: () => approval.onReject(pending.proposalId, tc.id),
|
|
403
|
+
className: "rounded border border-border bg-card px-2 py-0.5 text-[11px] font-medium text-foreground transition hover:bg-accent/30",
|
|
404
|
+
children: "Reject"
|
|
405
|
+
}
|
|
406
|
+
)
|
|
407
|
+
] })
|
|
408
|
+
]
|
|
409
|
+
},
|
|
410
|
+
tc.id
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
const Tag = onClick ? "button" : "div";
|
|
414
|
+
return /* @__PURE__ */ jsxs(
|
|
415
|
+
Tag,
|
|
416
|
+
{
|
|
417
|
+
...onClick ? { type: "button", onClick: () => onClick(tc) } : {},
|
|
418
|
+
className: `inline-flex w-fit items-center gap-2 rounded-md px-2.5 py-1 text-xs ${tc.status === "running" ? "bg-yellow-500/10 text-yellow-700" : tc.status === "error" ? "bg-red-500/10 text-red-700" : "bg-green-500/10 text-green-700"} ${onClick ? "cursor-pointer transition hover:ring-1 hover:ring-border" : ""}`,
|
|
419
|
+
children: [
|
|
420
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono opacity-70", children: tc.status === "running" ? "\u26A1" : tc.status === "error" ? "\u2717" : "\u2713" }),
|
|
421
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: tc.name }),
|
|
422
|
+
/* @__PURE__ */ jsx("span", { className: "opacity-60", children: tc.status === "running" ? "running\u2026" : tc.status === "error" ? "failed" : "done" })
|
|
423
|
+
]
|
|
424
|
+
},
|
|
425
|
+
tc.id
|
|
426
|
+
);
|
|
427
|
+
}) });
|
|
215
428
|
}
|
|
216
429
|
function ChatMessages({
|
|
217
430
|
messages,
|
|
@@ -220,7 +433,9 @@ function ChatMessages({
|
|
|
220
433
|
renderExtras,
|
|
221
434
|
userLabel = "User",
|
|
222
435
|
agentLabel = "Agent",
|
|
223
|
-
loading
|
|
436
|
+
loading,
|
|
437
|
+
approval,
|
|
438
|
+
onToolCallClick
|
|
224
439
|
}) {
|
|
225
440
|
const renderBody = renderMarkdown ?? ((content) => /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: content }));
|
|
226
441
|
const lastIsUser = messages[messages.length - 1]?.role === "user";
|
|
@@ -236,12 +451,29 @@ function ChatMessages({
|
|
|
236
451
|
formatTokensPerSecond(msg) && /* @__PURE__ */ jsx("span", { children: formatTokensPerSecond(msg) }),
|
|
237
452
|
formatModelCost(msg, models) && /* @__PURE__ */ jsx("span", { children: formatModelCost(msg, models) })
|
|
238
453
|
] }),
|
|
239
|
-
msg.reasoning && /* @__PURE__ */ jsxs(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
454
|
+
msg.reasoning && /* @__PURE__ */ jsxs(
|
|
455
|
+
"details",
|
|
456
|
+
{
|
|
457
|
+
className: "mb-2 rounded-md border border-border/40 bg-muted/30 px-3 py-2",
|
|
458
|
+
open: !msg.content,
|
|
459
|
+
children: [
|
|
460
|
+
/* @__PURE__ */ jsxs("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: [
|
|
461
|
+
msg.content ? "Thinking\u2026" : "Thinking",
|
|
462
|
+
!msg.content && /* @__PURE__ */ jsx("span", { className: "ml-1 inline-block animate-pulse", children: "\u25CF" })
|
|
463
|
+
] }),
|
|
464
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-sm text-muted-foreground/80", children: msg.reasoning })
|
|
465
|
+
]
|
|
466
|
+
}
|
|
467
|
+
),
|
|
243
468
|
/* @__PURE__ */ jsx("div", { className: "text-base leading-[1.75]", children: renderBody(msg.content) }),
|
|
244
|
-
msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx(
|
|
469
|
+
msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx(
|
|
470
|
+
ToolChips,
|
|
471
|
+
{
|
|
472
|
+
toolCalls: msg.toolCalls,
|
|
473
|
+
approval,
|
|
474
|
+
onClick: onToolCallClick ? (tc) => onToolCallClick(tc, msg) : void 0
|
|
475
|
+
}
|
|
476
|
+
),
|
|
245
477
|
renderExtras?.(msg)
|
|
246
478
|
] }, msg.id)
|
|
247
479
|
),
|
|
@@ -258,7 +490,12 @@ export {
|
|
|
258
490
|
ChatMessages,
|
|
259
491
|
EffortPicker,
|
|
260
492
|
ModelPicker,
|
|
493
|
+
RunDrillIn,
|
|
494
|
+
consumeChatStream,
|
|
495
|
+
dispatchChatStreamLine,
|
|
261
496
|
formatModelCost,
|
|
262
|
-
formatTokensPerSecond
|
|
497
|
+
formatTokensPerSecond,
|
|
498
|
+
pendingApprovalOf,
|
|
499
|
+
streamChatTurn
|
|
263
500
|
};
|
|
264
501
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/web-react/index.tsx"],"sourcesContent":["/**\n * `@tangle-network/agent-app/web-react` — the shared chat-shell components\n * every agent app's web UI hand-rolls: a model picker over the runtime's\n * model catalogue, a reasoning-effort selector, and a message thread with\n * User/Agent identity, per-message model + cost + tokens/sec metrics, tool\n * chips, and a collapsible thinking section.\n *\n * Works for BOTH chat shapes: router-backed copilots (LoopEvents from\n * `runtime/openai-stream`) and sandbox-backed chats — the thread renders\n * `ChatUiMessage`s; how they're produced is the app's business.\n *\n * Styling contract: Tailwind classes against the shared design tokens\n * (`bg-card`, `border-border`, `text-muted-foreground`, `bg-primary`, …) that\n * Tangle app shells define. No icon library — the few glyphs are inline SVGs.\n * Markdown and provider logos are injected (`renderMarkdown`,\n * `renderProviderBadge`) so this package stays dependency-free beyond React.\n */\n\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport type { CatalogModel } from '../runtime/model-catalog'\n\n// ── shared glyphs (no icon-library dependency) ────────────────────────────\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n )\n}\n\nfunction SearchGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n )\n}\n\nfunction SparkleGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1\" />\n </svg>\n )\n}\n\n/** Close an absolutely-positioned popover on outside mousedown. */\nfunction useClickOutside(onOutside: () => void) {\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n function handler(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) onOutside()\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n })\n return ref\n}\n\n// ── metrics helpers ───────────────────────────────────────────────────────\n\nexport interface ChatMessageMetrics {\n modelUsed?: string\n promptTokens?: number\n completionTokens?: number\n durationMs?: number\n}\n\n/** \"$0.0042\" from token counts × catalogue per-token pricing; null when unknown. */\nexport function formatModelCost(msg: ChatMessageMetrics, models: CatalogModel[]): string | null {\n if (msg.promptTokens == null && msg.completionTokens == null) return null\n const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing\n if (!pricing) return null\n const cost =\n (msg.promptTokens ?? 0) * Number(pricing.prompt ?? 0) +\n (msg.completionTokens ?? 0) * Number(pricing.completion ?? 0)\n if (!isFinite(cost) || cost <= 0) return null\n return cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`\n}\n\n/** \"38 tok/s\" from completion tokens over first-token→end duration; null when unknown. */\nexport function formatTokensPerSecond(msg: ChatMessageMetrics): string | null {\n if (msg.completionTokens == null || !msg.durationMs) return null\n return `${Math.round(msg.completionTokens / (msg.durationMs / 1000))} tok/s`\n}\n\n// ── ModelPicker ───────────────────────────────────────────────────────────\n\nexport interface ModelPickerProps {\n value: string\n onChange: (id: string) => void\n /** Catalogue models — from `GET`ing the app's catalogue route (see\n * `runtime/model-catalog`), plus any product-specific entries appended. */\n models: CatalogModel[]\n loading?: boolean\n /** Render a provider logo/badge; default is a generic sparkle. */\n renderProviderBadge?: (provider: string) => ReactNode\n /** Section label for `featured` models. */\n recommendedLabel?: string\n}\n\nfunction formatPrice(p?: string): string | undefined {\n if (!p) return undefined\n const n = Number(p)\n if (isNaN(n) || n === 0) return undefined\n const perM = n * 1_000_000\n return perM >= 1 ? `$${perM.toFixed(0)}/M` : `$${perM.toFixed(2)}/M`\n}\n\nfunction formatContext(len?: number): string | undefined {\n if (!len) return undefined\n if (len >= 1_000_000) return `${(len / 1_000_000).toFixed(1)}M ctx`\n if (len >= 1_000) return `${Math.round(len / 1_000)}K ctx`\n return `${len} ctx`\n}\n\nfunction SectionHeader({ children }: { children: ReactNode }) {\n return (\n <div className=\"px-3 pb-1 pt-3 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/70\">\n {children}\n </div>\n )\n}\n\nfunction ModelRow({\n model,\n selected,\n onSelect,\n renderProviderBadge,\n}: {\n model: CatalogModel\n selected: boolean\n onSelect: () => void\n renderProviderBadge?: (provider: string) => ReactNode\n}) {\n const price = formatPrice(model.pricing?.prompt)\n const ctx = formatContext(model.contextLength)\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n className={`flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition ${\n selected ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {renderProviderBadge ? renderProviderBadge(model.provider) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"truncate\">{model.name}</span>\n {!model.supportsTools && (\n <span className=\"shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\">\n no tools\n </span>\n )}\n <span className=\"ml-auto flex shrink-0 items-center gap-2 text-[11px] text-muted-foreground\">\n {ctx && <span>{ctx}</span>}\n {price && <span>{price}</span>}\n </span>\n </button>\n )\n}\n\n/**\n * Searchable model picker pill + popover: a featured/recommended section\n * first, then per-provider groups in catalogue order (the server already\n * sorts providers by tier).\n */\nexport function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel = 'Recommended' }: ModelPickerProps) {\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const containerRef = useClickOutside(() => setOpen(false))\n const inputRef = useRef<HTMLInputElement>(null)\n\n useEffect(() => {\n if (open) inputRef.current?.focus()\n }, [open])\n\n const selected = models.find((m) => m.id === value)\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (!q) return null\n return models.filter(\n (m) =>\n m.id.toLowerCase().includes(q) ||\n m.name.toLowerCase().includes(q) ||\n (m.description?.toLowerCase() ?? '').includes(q) ||\n m.provider.toLowerCase().includes(q),\n )\n }, [models, query])\n\n const sections = useMemo(() => {\n const recommended = models.filter((m) => m.featured)\n const byProvider: Array<{ provider: string; items: CatalogModel[] }> = []\n for (const m of models) {\n if (m.featured) continue\n const last = byProvider[byProvider.length - 1]\n if (last && last.provider === m.provider) last.items.push(m)\n else byProvider.push({ provider: m.provider, items: [m] })\n }\n return { recommended, byProvider }\n }, [models])\n\n const select = (id: string) => {\n onChange(id)\n setOpen(false)\n setQuery('')\n }\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n {selected && renderProviderBadge ? renderProviderBadge(selected.provider) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"max-w-[160px] truncate\">{selected?.name ?? value}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-[420px] overflow-hidden rounded-xl border border-border bg-card shadow-lg\">\n <div className=\"border-b border-border px-3 py-2\">\n <div className=\"flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2\">\n <SearchGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search models...\"\n className=\"flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n </div>\n </div>\n <div className=\"max-h-[400px] overflow-y-auto p-1 pb-2\">\n {loading && <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">Loading models...</div>}\n {!loading && filtered && (\n <>\n {filtered.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">No models match your search</div>\n )}\n {filtered.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {!loading && !filtered && (\n <>\n {sections.recommended.length > 0 && (\n <>\n <SectionHeader>{recommendedLabel}</SectionHeader>\n {sections.recommended.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {sections.byProvider.map((g) => (\n <div key={g.provider}>\n <SectionHeader>{g.provider}</SectionHeader>\n {g.items.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </div>\n ))}\n </>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\n// ── EffortPicker ──────────────────────────────────────────────────────────\n\nconst EFFORT_LEVELS = [\n { id: 'off', label: 'Off' },\n { id: 'low', label: 'Low' },\n { id: 'medium', label: 'Medium' },\n { id: 'high', label: 'High' },\n] as const\n\nexport interface EffortPickerProps {\n value: string\n onChange: (id: string) => void\n}\n\n/** Reasoning-effort selector pill, styled to match {@link ModelPicker}. Show\n * it only when the selected model `supportsReasoning`. */\nexport function EffortPicker({ value, onChange }: EffortPickerProps) {\n const [open, setOpen] = useState(false)\n const containerRef = useClickOutside(() => setOpen(false))\n const selected = EFFORT_LEVELS.find((l) => l.id === value) ?? EFFORT_LEVELS[2]\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n title=\"Reasoning effort\"\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <span>{selected.label}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-36 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-lg\">\n {EFFORT_LEVELS.map((l) => (\n <button\n key={l.id}\n type=\"button\"\n onClick={() => {\n onChange(l.id)\n setOpen(false)\n }}\n className={`flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${\n l.id === value ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {l.label}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n// ── ChatMessages ──────────────────────────────────────────────────────────\n\nexport interface ChatToolCallInfo {\n id: string\n name: string\n status: 'running' | 'done' | 'error'\n}\n\nexport interface ChatUiMessage extends ChatMessageMetrics {\n id: string\n role: 'user' | 'assistant' | 'system'\n content: string\n reasoning?: string\n toolCalls?: ChatToolCallInfo[]\n}\n\nexport interface ChatMessagesProps {\n messages: ChatUiMessage[]\n /** Catalogue models, for per-message cost from pricing. Pass [] to skip cost. */\n models?: CatalogModel[]\n /** Markdown renderer for assistant content; default renders pre-wrapped text. */\n renderMarkdown?: (content: string) => ReactNode\n /** Extra per-message content (artifacts, custom panels) appended after the body. */\n renderExtras?: (message: ChatUiMessage) => ReactNode\n userLabel?: string\n agentLabel?: string\n /** Render the trailing \"agent is thinking\" row. */\n loading?: boolean\n}\n\nfunction ToolChips({ toolCalls }: { toolCalls: ChatToolCallInfo[] }) {\n return (\n <div className=\"mt-2 flex flex-col gap-1\">\n {toolCalls.map((tc) => (\n <div\n key={tc.id}\n className={`inline-flex w-fit items-center gap-2 rounded-md px-2.5 py-1 text-xs ${\n tc.status === 'running'\n ? 'bg-yellow-500/10 text-yellow-700'\n : tc.status === 'error'\n ? 'bg-red-500/10 text-red-700'\n : 'bg-green-500/10 text-green-700'\n }`}\n >\n <span className=\"font-mono opacity-70\">{tc.status === 'running' ? '⚡' : tc.status === 'error' ? '✗' : '✓'}</span>\n <span className=\"font-medium\">{tc.name}</span>\n <span className=\"opacity-60\">{tc.status === 'running' ? 'running…' : tc.status === 'error' ? 'failed' : 'done'}</span>\n </div>\n ))}\n </div>\n )\n}\n\n/**\n * The message thread: one centered column; user messages are right-aligned\n * bubbles with a User label; agent messages carry an Agent meta line with\n * model id, tokens/sec, and cost, plus a collapsible thinking section and\n * tool-call chips.\n */\nexport function ChatMessages({\n messages,\n models = [],\n renderMarkdown,\n renderExtras,\n userLabel = 'User',\n agentLabel = 'Agent',\n loading,\n}: ChatMessagesProps) {\n const renderBody = renderMarkdown ?? ((content: string) => <p className=\"whitespace-pre-wrap\">{content}</p>)\n const lastIsUser = messages[messages.length - 1]?.role === 'user'\n return (\n <>\n {messages.map((msg) =>\n msg.role === 'user' ? (\n <div key={msg.id} className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"ml-auto w-fit max-w-[85%]\">\n <p className=\"mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">\n {userLabel}\n </p>\n <div className=\"rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed\">\n <p className=\"whitespace-pre-wrap\">{msg.content}</p>\n </div>\n </div>\n </div>\n ) : (\n <div key={msg.id} className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground/60\">\n <span className=\"font-semibold uppercase\">{agentLabel}</span>\n {msg.modelUsed && <span className=\"font-mono normal-case\">{msg.modelUsed}</span>}\n {formatTokensPerSecond(msg) && <span>{formatTokensPerSecond(msg)}</span>}\n {formatModelCost(msg, models) && <span>{formatModelCost(msg, models)}</span>}\n </div>\n {msg.reasoning && (\n <details className=\"mb-2 rounded-md border border-border/40 bg-muted/30 px-3 py-2\">\n <summary className=\"cursor-pointer select-none text-xs font-medium text-muted-foreground\">Thinking…</summary>\n <div className=\"mt-2 whitespace-pre-wrap text-sm text-muted-foreground/80\">{msg.reasoning}</div>\n </details>\n )}\n <div className=\"text-base leading-[1.75]\">{renderBody(msg.content)}</div>\n {msg.toolCalls && msg.toolCalls.length > 0 && <ToolChips toolCalls={msg.toolCalls} />}\n {renderExtras?.(msg)}\n </div>\n ),\n )}\n {loading && lastIsUser && (\n <div className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <p className=\"mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">{agentLabel}</p>\n <div className=\"flex items-center gap-2 text-base text-muted-foreground\">\n <svg className=\"h-4 w-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden>\n <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" strokeLinecap=\"round\" />\n </svg>\n Thinking...\n </div>\n </div>\n )}\n </>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/web-react/index.tsx","../../src/web-react/chat-stream.ts"],"sourcesContent":["/**\n * `@tangle-network/agent-app/web-react` — the shared chat-shell components\n * every agent app's web UI hand-rolls: a model picker over the runtime's\n * model catalogue, a reasoning-effort selector, and a message thread with\n * User/Agent identity, per-message model + cost + tokens/sec metrics, tool\n * chips, and a collapsible thinking section.\n *\n * Works for BOTH chat shapes: router-backed copilots (LoopEvents from\n * `runtime/openai-stream`) and sandbox-backed chats — the thread renders\n * `ChatUiMessage`s; how they're produced is the app's business.\n *\n * Styling contract: Tailwind classes against the shared design tokens\n * (`bg-card`, `border-border`, `text-muted-foreground`, `bg-primary`, …) that\n * Tangle app shells define. No icon library — the few glyphs are inline SVGs.\n * Markdown and provider logos are injected (`renderMarkdown`,\n * `renderProviderBadge`) so this package stays dependency-free beyond React.\n */\n\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\n\nexport * from './chat-stream'\nimport type { CatalogModel } from '../runtime/model-catalog'\n\n// ── shared glyphs (no icon-library dependency) ────────────────────────────\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n )\n}\n\nfunction SearchGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n )\n}\n\nfunction SparkleGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1\" />\n </svg>\n )\n}\n\n/** Close an absolutely-positioned popover on outside mousedown. */\nfunction useClickOutside(onOutside: () => void) {\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n function handler(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) onOutside()\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n })\n return ref\n}\n\n// ── metrics helpers ───────────────────────────────────────────────────────\n\nexport interface ChatMessageMetrics {\n modelUsed?: string\n promptTokens?: number\n completionTokens?: number\n durationMs?: number\n}\n\n/** \"$0.0042\" from token counts × catalogue per-token pricing; null when unknown. */\nexport function formatModelCost(msg: ChatMessageMetrics, models: CatalogModel[]): string | null {\n if (msg.promptTokens == null && msg.completionTokens == null) return null\n const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing\n if (!pricing) return null\n const cost =\n (msg.promptTokens ?? 0) * Number(pricing.prompt ?? 0) +\n (msg.completionTokens ?? 0) * Number(pricing.completion ?? 0)\n if (!isFinite(cost) || cost <= 0) return null\n return cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`\n}\n\n/** \"38 tok/s\" from completion tokens over first-token→end duration; null when unknown. */\nexport function formatTokensPerSecond(msg: ChatMessageMetrics): string | null {\n if (msg.completionTokens == null || !msg.durationMs) return null\n return `${Math.round(msg.completionTokens / (msg.durationMs / 1000))} tok/s`\n}\n\n// ── ModelPicker ───────────────────────────────────────────────────────────\n\nexport interface ModelPickerProps {\n value: string\n onChange: (id: string) => void\n /** Catalogue models — from `GET`ing the app's catalogue route (see\n * `runtime/model-catalog`), plus any product-specific entries appended. */\n models: CatalogModel[]\n loading?: boolean\n /** Render a provider logo/badge; default is a generic sparkle. */\n renderProviderBadge?: (provider: string) => ReactNode\n /** Section label for `featured` models. */\n recommendedLabel?: string\n}\n\nfunction formatPrice(p?: string): string | undefined {\n if (!p) return undefined\n const n = Number(p)\n if (isNaN(n) || n === 0) return undefined\n const perM = n * 1_000_000\n return perM >= 1 ? `$${perM.toFixed(0)}/M` : `$${perM.toFixed(2)}/M`\n}\n\nfunction formatContext(len?: number): string | undefined {\n if (!len) return undefined\n if (len >= 1_000_000) return `${(len / 1_000_000).toFixed(1)}M ctx`\n if (len >= 1_000) return `${Math.round(len / 1_000)}K ctx`\n return `${len} ctx`\n}\n\nfunction SectionHeader({ children }: { children: ReactNode }) {\n return (\n <div className=\"px-3 pb-1 pt-3 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/70\">\n {children}\n </div>\n )\n}\n\nfunction ModelRow({\n model,\n selected,\n onSelect,\n renderProviderBadge,\n}: {\n model: CatalogModel\n selected: boolean\n onSelect: () => void\n renderProviderBadge?: (provider: string) => ReactNode\n}) {\n const price = formatPrice(model.pricing?.prompt)\n const ctx = formatContext(model.contextLength)\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n className={`flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition ${\n selected ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {renderProviderBadge ? renderProviderBadge(model.provider) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"truncate\">{model.name}</span>\n {!model.supportsTools && (\n <span className=\"shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\">\n no tools\n </span>\n )}\n <span className=\"ml-auto flex shrink-0 items-center gap-2 text-[11px] text-muted-foreground\">\n {ctx && <span>{ctx}</span>}\n {price && <span>{price}</span>}\n </span>\n </button>\n )\n}\n\n/**\n * Searchable model picker pill + popover: a featured/recommended section\n * first, then per-provider groups in catalogue order (the server already\n * sorts providers by tier).\n */\nexport function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel = 'Recommended' }: ModelPickerProps) {\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const containerRef = useClickOutside(() => setOpen(false))\n const inputRef = useRef<HTMLInputElement>(null)\n\n useEffect(() => {\n if (open) inputRef.current?.focus()\n }, [open])\n\n const selected = models.find((m) => m.id === value)\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (!q) return null\n return models.filter(\n (m) =>\n m.id.toLowerCase().includes(q) ||\n m.name.toLowerCase().includes(q) ||\n (m.description?.toLowerCase() ?? '').includes(q) ||\n m.provider.toLowerCase().includes(q),\n )\n }, [models, query])\n\n const sections = useMemo(() => {\n const recommended = models.filter((m) => m.featured)\n const byProvider: Array<{ provider: string; items: CatalogModel[] }> = []\n for (const m of models) {\n if (m.featured) continue\n const last = byProvider[byProvider.length - 1]\n if (last && last.provider === m.provider) last.items.push(m)\n else byProvider.push({ provider: m.provider, items: [m] })\n }\n return { recommended, byProvider }\n }, [models])\n\n const select = (id: string) => {\n onChange(id)\n setOpen(false)\n setQuery('')\n }\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n {selected && renderProviderBadge ? renderProviderBadge(selected.provider) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"max-w-[160px] truncate\">{selected?.name ?? value}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-[420px] overflow-hidden rounded-xl border border-border bg-card shadow-lg\">\n <div className=\"border-b border-border px-3 py-2\">\n <div className=\"flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2\">\n <SearchGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search models...\"\n className=\"flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n </div>\n </div>\n <div className=\"max-h-[400px] overflow-y-auto p-1 pb-2\">\n {loading && <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">Loading models...</div>}\n {!loading && filtered && (\n <>\n {filtered.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">No models match your search</div>\n )}\n {filtered.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {!loading && !filtered && (\n <>\n {sections.recommended.length > 0 && (\n <>\n <SectionHeader>{recommendedLabel}</SectionHeader>\n {sections.recommended.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {sections.byProvider.map((g) => (\n <div key={g.provider}>\n <SectionHeader>{g.provider}</SectionHeader>\n {g.items.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </div>\n ))}\n </>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\n// ── EffortPicker ──────────────────────────────────────────────────────────\n\nconst EFFORT_LEVELS = [\n { id: 'off', label: 'Off' },\n { id: 'low', label: 'Low' },\n { id: 'medium', label: 'Medium' },\n { id: 'high', label: 'High' },\n] as const\n\nexport interface EffortPickerProps {\n value: string\n onChange: (id: string) => void\n}\n\n/** Reasoning-effort selector pill, styled to match {@link ModelPicker}. Show\n * it only when the selected model `supportsReasoning`. */\nexport function EffortPicker({ value, onChange }: EffortPickerProps) {\n const [open, setOpen] = useState(false)\n const containerRef = useClickOutside(() => setOpen(false))\n const selected = EFFORT_LEVELS.find((l) => l.id === value) ?? EFFORT_LEVELS[2]\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n title=\"Reasoning effort\"\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <span>{selected.label}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-36 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-lg\">\n {EFFORT_LEVELS.map((l) => (\n <button\n key={l.id}\n type=\"button\"\n onClick={() => {\n onChange(l.id)\n setOpen(false)\n }}\n className={`flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${\n l.id === value ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {l.label}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n// ── Tool run drill-in (retained runs) ─────────────────────────────────────\n\n/** One step of a retained tool run (e.g. a sandbox command + its output). */\nexport interface ToolRunStep {\n at: string\n label: string\n detail?: string\n status?: 'ok' | 'error'\n}\n\n/** A retained tool run keyed by the parent message's toolCallId. The product\n * persists these server-side (fail-closed: only ids its own loop created)\n * and serves them to the drill-in panel. */\nexport interface ToolRunRecord {\n toolCallId: string\n toolName: string\n title: string\n status: 'running' | 'complete' | 'error'\n steps: ToolRunStep[]\n}\n\nexport interface RunDrillInProps {\n run: ToolRunRecord\n onClose: () => void\n}\n\n/**\n * Readonly side panel showing a retained tool run's transcript — the\n * \"drill into what the sandbox actually did\" view. Follow-ups happen in the\n * main chat, never here.\n */\nexport function RunDrillIn({ run, onClose }: RunDrillInProps) {\n return (\n <div className=\"fixed inset-y-0 right-0 z-50 flex w-[480px] max-w-full flex-col border-l border-border bg-card shadow-xl\">\n <div className=\"flex items-center gap-2 border-b border-border px-4 py-3\">\n <span\n className={`h-2 w-2 shrink-0 rounded-full ${\n run.status === 'running' ? 'bg-yellow-500' : run.status === 'error' ? 'bg-red-500' : 'bg-green-500'\n }`}\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold\">{run.title}</p>\n <p className=\"truncate font-mono text-[11px] text-muted-foreground\">{run.toolName}</p>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close\"\n className=\"rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground\"\n >\n <svg className=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div className=\"flex-1 space-y-3 overflow-y-auto p-4\">\n {run.steps.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">No steps recorded yet.</p>\n )}\n {run.steps.map((step, i) => (\n <div key={i} className=\"rounded-lg border border-border/60 bg-background\">\n <div className=\"flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5\">\n <span className={`font-mono text-[11px] ${step.status === 'error' ? 'text-red-600' : 'text-muted-foreground'}`}>\n {step.status === 'error' ? '✗' : '$'}\n </span>\n <code className=\"min-w-0 flex-1 truncate font-mono text-xs\">{step.label}</code>\n <span className=\"shrink-0 text-[10px] text-muted-foreground/60\">\n {new Date(step.at).toLocaleTimeString()}\n </span>\n </div>\n {step.detail && (\n <pre className=\"max-h-48 overflow-auto whitespace-pre-wrap px-3 py-2 font-mono text-[11px] leading-relaxed text-muted-foreground\">\n {step.detail}\n </pre>\n )}\n </div>\n ))}\n </div>\n <p className=\"border-t border-border px-4 py-2 text-[11px] text-muted-foreground/60\">\n Readonly drill-in. Follow up in the main chat.\n </p>\n </div>\n )\n}\n\n// ── ChatMessages ──────────────────────────────────────────────────────────\n\nexport interface ChatToolCallInfo {\n id: string\n name: string\n status: 'running' | 'done' | 'error'\n /** The tool outcome (`{ok, result}` shape). When `result.status` is\n * 'queued_for_approval' the chip renders the approval state. */\n result?: unknown\n}\n\n/** Extract `{proposalId, status}` from a tool outcome when it is a proposal\n * awaiting human approval; null otherwise. */\nexport function pendingApprovalOf(call: ChatToolCallInfo): { proposalId: string } | null {\n const outcome = call.result as { ok?: boolean; result?: { status?: string; proposalId?: string } } | undefined\n if (!outcome?.ok || outcome.result?.status !== 'queued_for_approval' || !outcome.result.proposalId) return null\n return { proposalId: outcome.result.proposalId }\n}\n\nexport interface ChatUiMessage extends ChatMessageMetrics {\n id: string\n role: 'user' | 'assistant' | 'system'\n content: string\n reasoning?: string\n toolCalls?: ChatToolCallInfo[]\n}\n\nexport interface ChatMessagesProps {\n messages: ChatUiMessage[]\n /** Catalogue models, for per-message cost from pricing. Pass [] to skip cost. */\n models?: CatalogModel[]\n /** Markdown renderer for assistant content; default renders pre-wrapped text. */\n renderMarkdown?: (content: string) => ReactNode\n /** Extra per-message content (artifacts, custom panels) appended after the body. */\n renderExtras?: (message: ChatUiMessage) => ReactNode\n userLabel?: string\n agentLabel?: string\n /** Render the trailing \"agent is thinking\" row. */\n loading?: boolean\n /** Approve/Reject handlers for proposals awaiting approval. When omitted the\n * chip still shows \"awaiting approval\" but without action buttons. */\n approval?: ProposalApprovalHandlers\n /** Make tool chips clickable (e.g. open a {@link RunDrillIn} panel). */\n onToolCallClick?: (call: ChatToolCallInfo, message: ChatUiMessage) => void\n}\n\nexport interface ProposalApprovalHandlers {\n onApprove: (proposalId: string, toolCallId: string) => void | Promise<void>\n onReject: (proposalId: string, toolCallId: string) => void | Promise<void>\n}\n\nfunction ToolChips({\n toolCalls,\n approval,\n onClick,\n}: {\n toolCalls: ChatToolCallInfo[]\n approval?: ProposalApprovalHandlers\n onClick?: (call: ChatToolCallInfo) => void\n}) {\n return (\n <div className=\"mt-2 flex flex-col gap-1\">\n {toolCalls.map((tc) => {\n const pending = tc.status === 'done' ? pendingApprovalOf(tc) : null\n if (pending) {\n return (\n <div\n key={tc.id}\n className=\"inline-flex w-fit items-center gap-2 rounded-md bg-amber-500/10 px-2.5 py-1 text-xs text-amber-700\"\n >\n <span className=\"font-mono opacity-70\">⏸</span>\n <span className=\"font-medium\">{tc.name}</span>\n <span className=\"opacity-60\">awaiting approval</span>\n {approval && (\n <span className=\"ml-1 inline-flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => approval.onApprove(pending.proposalId, tc.id)}\n className=\"rounded bg-green-600/90 px-2 py-0.5 text-[11px] font-semibold text-white transition hover:bg-green-600\"\n >\n Approve\n </button>\n <button\n type=\"button\"\n onClick={() => approval.onReject(pending.proposalId, tc.id)}\n className=\"rounded border border-border bg-card px-2 py-0.5 text-[11px] font-medium text-foreground transition hover:bg-accent/30\"\n >\n Reject\n </button>\n </span>\n )}\n </div>\n )\n }\n const Tag = onClick ? 'button' : 'div'\n return (\n <Tag\n key={tc.id}\n {...(onClick ? { type: 'button' as const, onClick: () => onClick(tc) } : {})}\n className={`inline-flex w-fit items-center gap-2 rounded-md px-2.5 py-1 text-xs ${\n tc.status === 'running'\n ? 'bg-yellow-500/10 text-yellow-700'\n : tc.status === 'error'\n ? 'bg-red-500/10 text-red-700'\n : 'bg-green-500/10 text-green-700'\n } ${onClick ? 'cursor-pointer transition hover:ring-1 hover:ring-border' : ''}`}\n >\n <span className=\"font-mono opacity-70\">{tc.status === 'running' ? '⚡' : tc.status === 'error' ? '✗' : '✓'}</span>\n <span className=\"font-medium\">{tc.name}</span>\n <span className=\"opacity-60\">{tc.status === 'running' ? 'running…' : tc.status === 'error' ? 'failed' : 'done'}</span>\n </Tag>\n )\n })}\n </div>\n )\n}\n\n/**\n * The message thread: one centered column; user messages are right-aligned\n * bubbles with a User label; agent messages carry an Agent meta line with\n * model id, tokens/sec, and cost, plus a collapsible thinking section and\n * tool-call chips.\n */\nexport function ChatMessages({\n messages,\n models = [],\n renderMarkdown,\n renderExtras,\n userLabel = 'User',\n agentLabel = 'Agent',\n loading,\n approval,\n onToolCallClick,\n}: ChatMessagesProps) {\n const renderBody = renderMarkdown ?? ((content: string) => <p className=\"whitespace-pre-wrap\">{content}</p>)\n const lastIsUser = messages[messages.length - 1]?.role === 'user'\n return (\n <>\n {messages.map((msg) =>\n msg.role === 'user' ? (\n <div key={msg.id} className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"ml-auto w-fit max-w-[85%]\">\n <p className=\"mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">\n {userLabel}\n </p>\n <div className=\"rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed\">\n <p className=\"whitespace-pre-wrap\">{msg.content}</p>\n </div>\n </div>\n </div>\n ) : (\n <div key={msg.id} className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground/60\">\n <span className=\"font-semibold uppercase\">{agentLabel}</span>\n {msg.modelUsed && <span className=\"font-mono normal-case\">{msg.modelUsed}</span>}\n {formatTokensPerSecond(msg) && <span>{formatTokensPerSecond(msg)}</span>}\n {formatModelCost(msg, models) && <span>{formatModelCost(msg, models)}</span>}\n </div>\n {msg.reasoning && (\n <details\n className=\"mb-2 rounded-md border border-border/40 bg-muted/30 px-3 py-2\"\n open={!msg.content}\n >\n <summary className=\"cursor-pointer select-none text-xs font-medium text-muted-foreground\">\n {msg.content ? 'Thinking…' : 'Thinking'}\n {!msg.content && <span className=\"ml-1 inline-block animate-pulse\">●</span>}\n </summary>\n <div className=\"mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-sm text-muted-foreground/80\">\n {msg.reasoning}\n </div>\n </details>\n )}\n <div className=\"text-base leading-[1.75]\">{renderBody(msg.content)}</div>\n {msg.toolCalls && msg.toolCalls.length > 0 && (\n <ToolChips\n toolCalls={msg.toolCalls}\n approval={approval}\n onClick={onToolCallClick ? (tc) => onToolCallClick(tc, msg) : undefined}\n />\n )}\n {renderExtras?.(msg)}\n </div>\n ),\n )}\n {loading && lastIsUser && (\n <div className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <p className=\"mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">{agentLabel}</p>\n <div className=\"flex items-center gap-2 text-base text-muted-foreground\">\n <svg className=\"h-4 w-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden>\n <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" strokeLinecap=\"round\" />\n </svg>\n Thinking...\n </div>\n </div>\n )}\n </>\n )\n}\n","/**\n * Client-side chat-stream consumption — the NDJSON parse loop every agent\n * app's chat UI hand-rolls (and breaks). Normalizes the three line shapes the\n * agent-app chat routes emit:\n *\n * {kind:'event', event:{type:'text'|'reasoning'|'tool_call'|'usage', ...}}\n * {kind:'tool_result', toolCallId, toolName, label, outcome}\n * {type:'turn'|'metadata'|'error'|'turn_status', ...} (route-level)\n *\n * Replayed lines carry an extra `seq` — transparently ignored. Works for\n * router-backed and sandbox-backed chats alike: anything producing these\n * lines (live pump, queued follow, resume replay) feeds the same callbacks.\n */\n\nexport interface ChatStreamToolCall {\n toolCallId?: string\n toolName: string\n args: Record<string, unknown>\n}\n\nexport interface ChatStreamToolResult {\n toolCallId?: string\n toolName?: string\n label?: string\n outcome: { ok: boolean; result?: unknown; code?: string; message?: string }\n}\n\nexport interface ChatStreamCallbacks {\n onTurnId?: (turnId: string) => void\n onText?: (delta: string) => void\n onReasoning?: (delta: string) => void\n onToolCall?: (call: ChatStreamToolCall) => void\n onToolResult?: (result: ChatStreamToolResult) => void\n onUsage?: (usage: { promptTokens: number; completionTokens: number }) => void\n onMetadata?: (data: Record<string, unknown>) => void\n /** A loop-level error event (the turn failed server-side). */\n onErrorEvent?: (message: string) => void\n}\n\nexport interface ConsumeChatStreamResult {\n turnId: string | null\n /** True when any text/reasoning/tool activity was received. */\n receivedContent: boolean\n}\n\n/** Parse one NDJSON line into the callbacks. Exposed for tests. */\nexport function dispatchChatStreamLine(line: string, cb: ChatStreamCallbacks): {\n turnId?: string\n receivedContent: boolean\n} {\n let receivedContent = false\n let turnId: string | undefined\n if (!line.trim()) return { receivedContent }\n let parsed: Record<string, unknown>\n try {\n parsed = JSON.parse(line) as Record<string, unknown>\n } catch {\n return { receivedContent } // tolerate a torn line\n }\n\n if (parsed.kind === 'tool_result') {\n cb.onToolResult?.({\n toolCallId: parsed.toolCallId as string | undefined,\n toolName: parsed.toolName as string | undefined,\n label: parsed.label as string | undefined,\n outcome: (parsed.outcome ?? parsed.result) as ChatStreamToolResult['outcome'],\n })\n return { receivedContent: true }\n }\n\n const evt = (parsed.kind === 'event' ? parsed.event : parsed) as Record<string, unknown>\n if (!evt || typeof evt !== 'object') return { receivedContent }\n\n switch (evt.type) {\n case 'turn':\n if (typeof evt.turnId === 'string') turnId = evt.turnId\n break\n case 'text':\n if (typeof evt.text === 'string') {\n cb.onText?.(evt.text)\n receivedContent = true\n }\n break\n case 'reasoning':\n if (typeof evt.text === 'string') {\n cb.onReasoning?.(evt.text)\n receivedContent = true\n }\n break\n case 'tool_call': {\n const call = (evt.call ?? evt) as Record<string, unknown>\n cb.onToolCall?.({\n toolCallId: (call.toolCallId ?? call.id) as string | undefined,\n toolName: String(call.toolName ?? call.name ?? 'unknown'),\n args: (call.args ?? {}) as Record<string, unknown>,\n })\n receivedContent = true\n break\n }\n case 'tool_result':\n cb.onToolResult?.({\n toolCallId: evt.toolCallId as string | undefined,\n toolName: evt.toolName as string | undefined,\n label: evt.label as string | undefined,\n outcome: (evt.outcome ?? evt.result) as ChatStreamToolResult['outcome'],\n })\n receivedContent = true\n break\n case 'usage': {\n const u = evt.usage as { promptTokens?: number; completionTokens?: number } | undefined\n if (u) cb.onUsage?.({ promptTokens: u.promptTokens ?? 0, completionTokens: u.completionTokens ?? 0 })\n break\n }\n case 'metadata':\n cb.onMetadata?.((evt.data ?? {}) as Record<string, unknown>)\n break\n case 'error':\n cb.onErrorEvent?.(String(evt.details ?? evt.error ?? 'Unknown stream error'))\n break\n default:\n break // turn_status and unknown line types are non-content\n }\n return { turnId, receivedContent }\n}\n\n/** Drain one NDJSON body into the callbacks. Throws on transport failure\n * (caller decides whether to resume). */\nexport async function consumeChatStream(\n body: ReadableStream<Uint8Array>,\n cb: ChatStreamCallbacks,\n): Promise<ConsumeChatStreamResult> {\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let turnId: string | null = null\n let receivedContent = false\n\n const handle = (line: string) => {\n const r = dispatchChatStreamLine(line, cb)\n if (r.turnId) {\n turnId = r.turnId\n cb.onTurnId?.(r.turnId)\n }\n if (r.receivedContent) receivedContent = true\n }\n\n for (;;) {\n const { done, value } = await reader.read()\n if (done) {\n if (buffer.trim()) handle(buffer)\n break\n }\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) handle(line)\n }\n return { turnId, receivedContent }\n}\n\nexport interface StreamChatOptions {\n /** Start the turn (POST the chat request); must return a streaming Response. */\n start: () => Promise<Response>\n /** Re-attach to a turn after a transport drop (GET the resume route). */\n resume?: (turnId: string, fromSeq: number) => Promise<Response>\n callbacks: ChatStreamCallbacks\n /** Called before a resume replays from 0 so the UI can reset accumulated\n * turn state (text, reasoning, tool chips). */\n onResetForResume?: () => void\n}\n\n/**\n * Run one chat turn with automatic single-shot resume: if the transport drops\n * mid-turn and the server announced a turnId, reset and replay the buffered\n * turn. Server-side the turn keeps running either way (queued runner).\n */\nexport async function streamChatTurn(opts: StreamChatOptions): Promise<ConsumeChatStreamResult> {\n const res = await opts.start()\n if (!res.ok || !res.body) {\n const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` })) as { error?: string }\n throw new Error(err.error ?? `HTTP ${res.status}`)\n }\n let turnId: string | null = null\n const cb: ChatStreamCallbacks = {\n ...opts.callbacks,\n onTurnId: (id) => {\n turnId = id\n opts.callbacks.onTurnId?.(id)\n },\n }\n try {\n return await consumeChatStream(res.body, cb)\n } catch (transportErr) {\n if (!turnId || !opts.resume) throw transportErr\n opts.onResetForResume?.()\n const resumed = await opts.resume(turnId, 0)\n if (!resumed.ok || !resumed.body) throw transportErr\n return await consumeChatStream(resumed.body, cb)\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"packageManager": "pnpm@10.33.4",
|
|
5
5
|
"description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent\u2192app tool side channel, integration-hub client, per-workspace billing, and crypto \u2014 composed over the Tangle agent substrate through typed seams.",
|
|
6
6
|
"keywords": [
|
|
@@ -117,6 +117,11 @@
|
|
|
117
117
|
"import": "./dist/integrations/index.js",
|
|
118
118
|
"default": "./dist/integrations/index.js"
|
|
119
119
|
},
|
|
120
|
+
"./missions": {
|
|
121
|
+
"types": "./dist/missions/index.d.ts",
|
|
122
|
+
"import": "./dist/missions/index.js",
|
|
123
|
+
"default": "./dist/missions/index.js"
|
|
124
|
+
},
|
|
120
125
|
"./web": {
|
|
121
126
|
"types": "./dist/web/index.d.ts",
|
|
122
127
|
"import": "./dist/web/index.js",
|