@townco/ui 0.1.77 → 0.1.78
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/core/hooks/use-chat-messages.d.ts +4 -4
- package/dist/core/hooks/use-chat-messages.js +4 -1
- package/dist/core/hooks/use-chat-session.d.ts +1 -1
- package/dist/core/hooks/use-chat-session.js +5 -1
- package/dist/core/hooks/use-subagent-stream.js +6 -6
- package/dist/core/hooks/use-tool-calls.d.ts +3 -3
- package/dist/core/hooks/use-tool-calls.js +1 -1
- package/dist/core/schemas/chat.d.ts +10 -10
- package/dist/core/schemas/tool-call.d.ts +8 -8
- package/dist/core/store/chat-store.js +1 -0
- package/dist/core/utils/tool-summary.js +8 -3
- package/dist/core/utils/tool-verbiage.js +1 -1
- package/dist/gui/components/AppSidebar.d.ts +1 -1
- package/dist/gui/components/AppSidebar.js +4 -3
- package/dist/gui/components/Button.d.ts +1 -1
- package/dist/gui/components/ChatEmptyState.js +1 -1
- package/dist/gui/components/ChatHeader.d.ts +1 -28
- package/dist/gui/components/ChatHeader.js +4 -71
- package/dist/gui/components/ChatLayout.d.ts +6 -2
- package/dist/gui/components/ChatLayout.js +82 -33
- package/dist/gui/components/ChatView.js +28 -45
- package/dist/gui/components/ContextUsageButton.d.ts +0 -1
- package/dist/gui/components/ContextUsageButton.js +10 -3
- package/dist/gui/components/HookNotification.js +2 -1
- package/dist/gui/components/MessageContent.js +24 -160
- package/dist/gui/components/SessionHistory.js +1 -2
- package/dist/gui/components/SessionHistoryItem.js +1 -1
- package/dist/gui/components/Sidebar.js +27 -42
- package/dist/gui/components/SubAgentDetails.js +10 -14
- package/dist/gui/components/TodoSubline.js +1 -0
- package/dist/gui/components/ToolOperation.js +16 -75
- package/dist/gui/components/WorkProgress.js +5 -3
- package/dist/gui/components/index.d.ts +0 -1
- package/dist/gui/components/resizable.d.ts +1 -1
- package/dist/gui/constants.d.ts +6 -0
- package/dist/gui/constants.js +8 -0
- package/dist/gui/hooks/index.d.ts +1 -0
- package/dist/gui/hooks/index.js +1 -0
- package/dist/gui/hooks/use-lock-body-scroll.d.ts +7 -0
- package/dist/gui/hooks/use-lock-body-scroll.js +29 -0
- package/dist/gui/lib/motion.d.ts +12 -0
- package/dist/gui/lib/motion.js +69 -0
- package/dist/sdk/schemas/message.d.ts +2 -2
- package/dist/sdk/schemas/session.d.ts +18 -18
- package/dist/sdk/transports/http.d.ts +1 -1
- package/dist/sdk/transports/http.js +9 -0
- package/dist/sdk/transports/stdio.js +2 -2
- package/dist/sdk/transports/types.d.ts +11 -0
- package/dist/sdk/transports/types.js +28 -1
- package/package.json +3 -5
- package/dist/gui/components/InvokingGroup.d.ts +0 -9
- package/dist/gui/components/InvokingGroup.js +0 -16
- package/dist/gui/components/SubagentStream.d.ts +0 -23
- package/dist/gui/components/SubagentStream.js +0 -98
- package/dist/gui/components/ToolCall.d.ts +0 -8
- package/dist/gui/components/ToolCall.js +0 -234
- package/dist/gui/components/ToolCallGroup.d.ts +0 -8
- package/dist/gui/components/ToolCallGroup.js +0 -29
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import JsonView from "@uiw/react-json-view";
|
|
3
|
-
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
|
4
3
|
import { AlertCircle, BrainCircuit, CheckSquare, ChevronDown, ChevronRight, CircleDot, Cloud, Edit, FileText, Globe, Image, Link, ListVideo, Search, Wrench, } from "lucide-react";
|
|
5
4
|
import React, { useEffect, useState } from "react";
|
|
6
|
-
import {
|
|
5
|
+
import { getGroupDisplayState, getToolCallDisplayState, getToolCallStateVerbiage, isPreliminaryToolCall, } from "../../core/utils/tool-call-state.js";
|
|
7
6
|
import { generateSmartSummary } from "../../core/utils/tool-summary.js";
|
|
8
|
-
import { expandCollapseVariants, fadeInVariants, getDuration, getTransition, motionDuration, motionEasing, rotateVariants, shimmerTransition, standardTransition, } from "../lib/motion.js";
|
|
9
|
-
import { cn } from "../lib/utils.js";
|
|
10
7
|
import * as ChatLayout from "./ChatLayout.js";
|
|
11
8
|
import { SubAgentDetails } from "./SubAgentDetails.js";
|
|
12
9
|
import { useTheme } from "./ThemeProvider.js";
|
|
@@ -35,8 +32,6 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
35
32
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
36
33
|
const [isMinimized, setIsMinimized] = useState(false);
|
|
37
34
|
const [userInteracted, setUserInteracted] = useState(false);
|
|
38
|
-
const { resolvedTheme } = useTheme();
|
|
39
|
-
const shouldReduceMotion = useReducedMotion();
|
|
40
35
|
// For single tool call, extract it
|
|
41
36
|
const singleToolCall = toolCalls.length === 1 ? toolCalls[0] : null;
|
|
42
37
|
const isTodoWrite = singleToolCall?.title === "todo_write";
|
|
@@ -63,7 +58,6 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
63
58
|
const isCompleted = displayState === "completed";
|
|
64
59
|
const isFailed = displayState === "failed";
|
|
65
60
|
const isSelecting = displayState === "selecting";
|
|
66
|
-
const isActive = displayState === "executing";
|
|
67
61
|
// Auto-minimize when completed (only if user hasn't manually interacted)
|
|
68
62
|
useEffect(() => {
|
|
69
63
|
if (autoMinimize && isCompleted && !isMinimized && !userInteracted) {
|
|
@@ -136,64 +130,14 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
136
130
|
const displayText = getDisplayText();
|
|
137
131
|
// For preliminary/selecting states, show simple non-expandable text
|
|
138
132
|
if (isSelecting && !isGrouped) {
|
|
139
|
-
return (_jsx(
|
|
140
|
-
backgroundPosition: ["200% 0", "-200% 0"],
|
|
141
|
-
}, transition: {
|
|
142
|
-
...shimmerTransition,
|
|
143
|
-
duration: getDuration(shouldReduceMotion ?? false, 1.5),
|
|
144
|
-
}, style: {
|
|
145
|
-
backgroundImage: "linear-gradient(90deg, transparent 5%, rgba(255, 255, 255, 0.75) 25%, transparent 35%)",
|
|
146
|
-
backgroundSize: "200% 100%",
|
|
147
|
-
}, children: _jsx("span", { className: "text-paragraph-sm text-text-secondary/50", children: displayText }) }));
|
|
133
|
+
return (_jsx("div", { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", children: _jsx("span", { className: "text-paragraph-sm text-text-secondary/50", children: displayText }) }));
|
|
148
134
|
}
|
|
149
135
|
// If it's a grouped preliminary (selecting) state
|
|
150
136
|
if (isSelecting && isGrouped) {
|
|
151
|
-
return (_jsxs(
|
|
152
|
-
backgroundPosition: ["200% 0", "-200% 0"],
|
|
153
|
-
}, transition: {
|
|
154
|
-
...shimmerTransition,
|
|
155
|
-
duration: getDuration(shouldReduceMotion ?? false, 1.5),
|
|
156
|
-
}, style: {
|
|
157
|
-
backgroundImage: "linear-gradient(90deg, transparent 5%, rgba(255, 255, 255, 0.75) 25%, transparent 35%)",
|
|
158
|
-
backgroundSize: "200% 100%",
|
|
159
|
-
}, children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "text-text-secondary/70", children: _jsx(ListVideo, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70", children: "Selecting tools" }), _jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-text-secondary/70", children: toolCalls.length })] }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: displayText })] }));
|
|
137
|
+
return (_jsxs("div", { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "text-text-secondary/70", children: _jsx(ListVideo, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70", children: "Selecting tools" }), _jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-text-secondary/70", children: toolCalls.length })] }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: displayText })] }));
|
|
160
138
|
}
|
|
161
139
|
// Full display (for single tool call or expanded group, includes minimized state)
|
|
162
|
-
return (_jsxs(
|
|
163
|
-
filter: "blur(12px)",
|
|
164
|
-
opacity: 0,
|
|
165
|
-
y: 12,
|
|
166
|
-
}, animate: {
|
|
167
|
-
filter: "blur(0px)",
|
|
168
|
-
opacity: 1,
|
|
169
|
-
y: 0,
|
|
170
|
-
}, exit: {
|
|
171
|
-
filter: "blur(12px)",
|
|
172
|
-
opacity: 0,
|
|
173
|
-
y: -12,
|
|
174
|
-
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
175
|
-
duration: 0.5,
|
|
176
|
-
ease: motionEasing.smooth,
|
|
177
|
-
}), children: [_jsxs(motion.button, { type: "button", className: "flex flex-col items-start gap-0.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit rounded-md px-1 -mx-1", onClick: handleHeaderClick, "aria-expanded": isTodoWrite ? undefined : isExpanded, animate: isActive || isSelecting
|
|
178
|
-
? {
|
|
179
|
-
backgroundPosition: ["200% 0", "-200% 0"],
|
|
180
|
-
}
|
|
181
|
-
: {}, transition: isActive || isSelecting
|
|
182
|
-
? {
|
|
183
|
-
...shimmerTransition,
|
|
184
|
-
duration: getDuration(shouldReduceMotion ?? false, 1.5),
|
|
185
|
-
}
|
|
186
|
-
: {}, style: isActive || isSelecting
|
|
187
|
-
? {
|
|
188
|
-
backgroundImage: "linear-gradient(90deg, transparent 5%, rgba(255, 255, 255, 0.75) 25%, transparent 35%)",
|
|
189
|
-
backgroundSize: "200% 100%",
|
|
190
|
-
}
|
|
191
|
-
: {}, children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsxs("span", { className: "text-paragraph-sm text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: [isGrouped && _jsx("span", { className: "mr-1", children: "Parallel operation" }), !isGrouped && displayText] }), isGrouped && (_jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-text-secondary/70", children: toolCalls.length })), isFailed && _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }), isTodoWrite ? (_jsx(ChevronRight, { className: "h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors" })) : (_jsx(motion.div, { animate: {
|
|
192
|
-
rotate: isExpanded ? 180 : 0,
|
|
193
|
-
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
194
|
-
duration: motionDuration.normal,
|
|
195
|
-
ease: motionEasing.smooth,
|
|
196
|
-
}), className: "h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: _jsx(ChevronDown, { className: "h-3 w-3" }) }))] }), !isGrouped &&
|
|
140
|
+
return (_jsxs("div", { className: "flex flex-col my-4", children: [_jsxs("button", { type: "button", className: "flex flex-col items-start gap-0.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit rounded-md px-1 -mx-1", onClick: handleHeaderClick, "aria-expanded": isTodoWrite ? undefined : isExpanded, children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsxs("span", { className: "text-paragraph-sm text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: [isGrouped && _jsx("span", { className: "mr-1", children: "Parallel operation" }), !isGrouped && displayText] }), isGrouped && (_jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-text-secondary/70", children: toolCalls.length })), isFailed && _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }), isTodoWrite ? (_jsx(ChevronRight, { className: "h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors" })) : (_jsx("div", { className: `h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-all duration-200 ${isExpanded ? "rotate-180" : ""}`, children: _jsx(ChevronDown, { className: "h-3 w-3" }) }))] }), !isGrouped &&
|
|
197
141
|
singleToolCall &&
|
|
198
142
|
(isTodoWrite && singleToolCall.rawInput?.todos ? (_jsx(TodoSubline, { todos: singleToolCall.rawInput.todos, className: "text-paragraph-sm text-text-secondary/70 pl-4.5" })) : singleToolCall.subline ? (_jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: singleToolCall.subline })) : null), !isGrouped && toolHookNotification && (_jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: toolHookNotification.status === "triggered"
|
|
199
143
|
? "Compacting response..."
|
|
@@ -218,19 +162,16 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
218
162
|
return (_jsxs("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: [compactedCount, " response", compactedCount > 1 ? "s" : "", " ", "compacted"] }));
|
|
219
163
|
}
|
|
220
164
|
return null;
|
|
221
|
-
})()] }), !isTodoWrite && isSubagentCall && singleToolCall && (_jsx("div", { className: "pl-4.5 mt-2", children: _jsx(SubAgentDetails, { port: singleToolCall.subagentPort, sessionId: singleToolCall.subagentSessionId, parentStatus: singleToolCall.status, agentName: singleToolCall.rawInput?.agentName, query: singleToolCall.rawInput?.query, isExpanded: isSubagentExpanded, onExpandChange: setIsSubagentExpanded, storedMessages: singleToolCall.subagentMessages, isReplay: isReplaySubagent }) })),
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
singleToolCall && (_jsx(ToolOperationDetails, { toolCall: singleToolCall, ...(toolHookNotification
|
|
232
|
-
? { hookNotification: toolHookNotification }
|
|
233
|
-
: {}) }))) }, "expanded-content")) })] }));
|
|
165
|
+
})()] }), !isTodoWrite && isSubagentCall && singleToolCall && (_jsx("div", { className: "pl-4.5 mt-2", children: _jsx(SubAgentDetails, { port: singleToolCall.subagentPort, sessionId: singleToolCall.subagentSessionId, parentStatus: singleToolCall.status, agentName: singleToolCall.rawInput?.agentName, query: singleToolCall.rawInput?.query, isExpanded: isSubagentExpanded, onExpandChange: setIsSubagentExpanded, storedMessages: singleToolCall.subagentMessages, isReplay: isReplaySubagent }) })), !isTodoWrite && isExpanded && (_jsx("div", { className: "mt-1", children: isGrouped ? (
|
|
166
|
+
// Render individual tool calls in group
|
|
167
|
+
_jsx("div", { className: "flex flex-col gap-4 mt-2", children: toolCalls.map((toolCall) => {
|
|
168
|
+
const hookNotification = hookNotifications.find((n) => n.toolCallId === toolCall.id);
|
|
169
|
+
return (_jsx(GroupedToolCallItem, { toolCall: toolCall, ...(hookNotification ? { hookNotification } : {}) }, toolCall.id));
|
|
170
|
+
}) })) : (
|
|
171
|
+
// Render single tool call details
|
|
172
|
+
singleToolCall && (_jsx(ToolOperationDetails, { toolCall: singleToolCall, ...(toolHookNotification
|
|
173
|
+
? { hookNotification: toolHookNotification }
|
|
174
|
+
: {}) }))) }))] }));
|
|
234
175
|
}
|
|
235
176
|
/**
|
|
236
177
|
* Component to render a single tool call within a grouped parallel operation
|
|
@@ -290,7 +231,7 @@ function ToolOperationDetails({ toolCall, hookNotification, }) {
|
|
|
290
231
|
"--w-rjv-type-nan-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
|
|
291
232
|
"--w-rjv-type-undefined-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
|
|
292
233
|
};
|
|
293
|
-
return (_jsxs("div", { className: "text-sm border border-border rounded-lg bg-card overflow-hidden w-full", children: [toolCall.locations && toolCall.locations.length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary uppercase tracking-wider mb-1.5 font-sans", children: "Files" }), _jsx("ul", { className: "space-y-1", children: toolCall.locations.map((loc) => (_jsxs("li", { className: "font-mono text-[11px] text-foreground bg-muted px-1.5 py-0.5 rounded w-fit", children: [loc.path, loc.line !== null && loc.line !== undefined && `:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })), toolCall.rawInput && Object.keys(toolCall.rawInput).length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary uppercase tracking-wider mb-1.5 font-sans", children: "Input" }), _jsx("div", { className: "text-[11px] font-mono text-foreground", children: _jsx(JsonView, { value: toolCall.rawInput, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: jsonStyle }) })] })), ((toolCall.content && toolCall.content.length > 0) ||
|
|
234
|
+
return (_jsxs("div", { className: "text-sm border border-border rounded-lg bg-card overflow-hidden w-full", children: [toolCall.locations && toolCall.locations.length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary uppercase tracking-wider mb-1.5 font-sans", children: "Files" }), _jsx("ul", { className: "space-y-1", children: toolCall.locations.map((loc) => (_jsxs("li", { className: "font-mono text-[11px] text-foreground bg-muted px-1.5 py-0.5 rounded w-fit", children: [loc.path, loc.line !== null && loc.line !== undefined && `:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })), toolCall.rawInput && Object.keys(toolCall.rawInput).length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary uppercase tracking-wider mb-1.5 font-sans", children: "Input" }), _jsx("div", { className: "text-[11px] font-mono text-foreground", children: _jsx(JsonView, { value: toolCall.rawInput, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, shortenTextAfterLength: 80, style: jsonStyle }) })] })), ((toolCall.content && toolCall.content.length > 0) ||
|
|
294
235
|
toolCall.error) && (_jsxs("div", { className: "p-3 border-b border-border last:border-0", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary uppercase tracking-wider mb-1.5 font-sans", children: "Output" }), _jsxs("div", { className: "space-y-2 text-[11px] text-foreground", children: [toolCall.content?.map((block, idx) => {
|
|
295
236
|
// Generate a stable key based on content
|
|
296
237
|
const getBlockKey = () => {
|
|
@@ -314,7 +255,7 @@ function ToolOperationDetails({ toolCall, hookNotification, }) {
|
|
|
314
255
|
try {
|
|
315
256
|
const parsed = JSON.parse(text);
|
|
316
257
|
if (typeof parsed === "object" && parsed !== null) {
|
|
317
|
-
return (_jsx("div", { className: "text-[11px]", children: _jsx(JsonView, { value: parsed, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: jsonStyle }) }, key));
|
|
258
|
+
return (_jsx("div", { className: "text-[11px]", children: _jsx(JsonView, { value: parsed, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, shortenTextAfterLength: 80, style: jsonStyle }) }, key));
|
|
318
259
|
}
|
|
319
260
|
}
|
|
320
261
|
catch {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { isPreliminaryToolCall } from "../../core/utils/tool-call-state.js";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
5
|
import { Reasoning } from "./Reasoning.js";
|
|
6
6
|
import { ToolOperation } from "./ToolOperation.js";
|
|
@@ -22,6 +22,7 @@ export function WorkProgress({ thinking, isThinkingStreaming = false, toolCalls
|
|
|
22
22
|
result.push({ type: "selecting", toolCalls: selectingGroup });
|
|
23
23
|
}
|
|
24
24
|
else {
|
|
25
|
+
// biome-ignore lint/style/noNonNullAssertion: else branch ensures array has exactly one element
|
|
25
26
|
result.push({ type: "single", toolCall: selectingGroup[0] });
|
|
26
27
|
}
|
|
27
28
|
selectingGroup = [];
|
|
@@ -64,14 +65,15 @@ export function WorkProgress({ thinking, isThinkingStreaming = false, toolCalls
|
|
|
64
65
|
if (!hasThinking && !hasToolCalls) {
|
|
65
66
|
return null;
|
|
66
67
|
}
|
|
67
|
-
return (_jsxs("div", { className: cn("work-progress", className), children: [hasThinking && (_jsx(Reasoning, { content: thinking, isStreaming: isThinkingStreaming, mode: thinkingDisplayStyle, autoCollapse: autoCollapseThinking })), groupedToolCalls.map((group,
|
|
68
|
+
return (_jsxs("div", { className: cn("work-progress", className), children: [hasThinking && (_jsx(Reasoning, { content: thinking, isStreaming: isThinkingStreaming, mode: thinkingDisplayStyle, autoCollapse: autoCollapseThinking })), groupedToolCalls.map((group, _index) => {
|
|
68
69
|
if (group.type === "batch") {
|
|
69
70
|
// Parallel operations group
|
|
70
71
|
return (_jsx(ToolOperation, { toolCalls: group.toolCalls, isGrouped: true, autoMinimize: true }, `batch-${group.batchId}`));
|
|
71
72
|
}
|
|
72
73
|
if (group.type === "selecting") {
|
|
73
74
|
// Multiple selecting operations
|
|
74
|
-
|
|
75
|
+
const selectingKey = group.toolCalls.map((tc) => tc.id).join("-");
|
|
76
|
+
return (_jsx(ToolOperation, { toolCalls: group.toolCalls, isGrouped: true, autoMinimize: false }, `selecting-${selectingKey}`));
|
|
75
77
|
}
|
|
76
78
|
// Single tool call
|
|
77
79
|
return (_jsx(ToolOperation, { toolCalls: [group.toolCall], isGrouped: false, autoMinimize: true }, group.toolCall.id));
|
|
@@ -6,7 +6,6 @@ export { AppSidebar, type AppSidebarProps, } from "./AppSidebar.js";
|
|
|
6
6
|
export { Button, type ButtonProps, buttonVariants } from "./Button.js";
|
|
7
7
|
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "./Card.js";
|
|
8
8
|
export { ChatEmptyState, type ChatEmptyStateProps } from "./ChatEmptyState.js";
|
|
9
|
-
export type { ConnectionStatus } from "./ChatHeader.js";
|
|
10
9
|
export * as ChatHeader from "./ChatHeader.js";
|
|
11
10
|
export { Actions as ChatInputActions, Attachment as ChatInputAttachment, type ChatInputActionsProps, type ChatInputAttachmentProps, type ChatInputCommandMenuProps, type ChatInputFieldProps, type ChatInputRootProps, type ChatInputSubmitProps, type ChatInputToolbarProps, type ChatInputVoiceInputProps, CommandMenu as ChatInputCommandMenu, type CommandMenuItem, Field as ChatInputField, Root as ChatInputRoot, Submit as ChatInputSubmit, Toolbar as ChatInputToolbar, VoiceInput as ChatInputVoiceInput, } from "./ChatInput.js";
|
|
12
11
|
export * as ChatLayout from "./ChatLayout.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ResizablePrimitive from "react-resizable-panels";
|
|
2
|
-
declare const ResizablePanelGroup:
|
|
2
|
+
declare const ResizablePanelGroup: React.FC<React.ComponentProps<typeof ResizablePrimitive.PanelGroup>>;
|
|
3
3
|
declare const ResizablePanel: ({ className, ...props }: React.ComponentProps<typeof ResizablePrimitive.Panel>) => import("react/jsx-runtime").JSX.Element;
|
|
4
4
|
declare const ResizableHandle: ({ withHandle, className, ...props }: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
|
5
5
|
withHandle?: boolean;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const SIDEBAR_WIDTH_DESKTOP = 256;
|
|
2
|
+
export declare const SIDEBAR_WIDTH_MOBILE = "calc(100vw - 104px)";
|
|
3
|
+
export declare const SIDEBAR_TAP_ZONE = 104;
|
|
4
|
+
export declare const ASIDE_WIDTH_DEFAULT = 450;
|
|
5
|
+
export declare const ASIDE_WIDTH_MIN = 250;
|
|
6
|
+
export declare const ASIDE_WIDTH_MAX = 800;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Sidebar constants
|
|
2
|
+
export const SIDEBAR_WIDTH_DESKTOP = 256;
|
|
3
|
+
export const SIDEBAR_WIDTH_MOBILE = "calc(100vw - 104px)";
|
|
4
|
+
export const SIDEBAR_TAP_ZONE = 104;
|
|
5
|
+
// Aside panel constants
|
|
6
|
+
export const ASIDE_WIDTH_DEFAULT = 450;
|
|
7
|
+
export const ASIDE_WIDTH_MIN = 250;
|
|
8
|
+
export const ASIDE_WIDTH_MAX = 800;
|
package/dist/gui/hooks/index.js
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Hook to lock body scroll when a modal/drawer is open
|
|
4
|
+
* Primarily used for mobile sidebar overlay to prevent background scrolling
|
|
5
|
+
*
|
|
6
|
+
* @param lock - Whether to lock the body scroll
|
|
7
|
+
*/
|
|
8
|
+
export function useLockBodyScroll(lock) {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!lock)
|
|
11
|
+
return;
|
|
12
|
+
// Store current scroll position
|
|
13
|
+
const scrollY = window.scrollY;
|
|
14
|
+
// Lock body scroll
|
|
15
|
+
document.body.style.position = "fixed";
|
|
16
|
+
document.body.style.top = `-${scrollY}px`;
|
|
17
|
+
document.body.style.width = "100%";
|
|
18
|
+
document.body.style.overflow = "hidden";
|
|
19
|
+
return () => {
|
|
20
|
+
// Unlock body scroll
|
|
21
|
+
document.body.style.position = "";
|
|
22
|
+
document.body.style.top = "";
|
|
23
|
+
document.body.style.width = "";
|
|
24
|
+
document.body.style.overflow = "";
|
|
25
|
+
// Restore scroll position
|
|
26
|
+
window.scrollTo(0, scrollY);
|
|
27
|
+
};
|
|
28
|
+
}, [lock]);
|
|
29
|
+
}
|
package/dist/gui/lib/motion.d.ts
CHANGED
|
@@ -53,3 +53,15 @@ export declare function getTransition(shouldReduceMotion: boolean, transition?:
|
|
|
53
53
|
* Get duration with reduced motion consideration
|
|
54
54
|
*/
|
|
55
55
|
export declare function getDuration(shouldReduceMotion: boolean, duration?: number): number;
|
|
56
|
+
export declare const sidebarTransition: Transition;
|
|
57
|
+
export declare const sidebarMobileTransition: Transition;
|
|
58
|
+
export declare const sidebarDesktopVariants: Variants;
|
|
59
|
+
export declare const sidebarMobileVariants: Variants;
|
|
60
|
+
export declare const sidebarContentVariants: Variants;
|
|
61
|
+
export declare const sidebarContentTransition: Transition;
|
|
62
|
+
export declare const backdropVariants: Variants;
|
|
63
|
+
export declare const asideDesktopVariants: Variants;
|
|
64
|
+
export declare const asideMobileVariants: Variants;
|
|
65
|
+
export declare const asideContentVariants: Variants;
|
|
66
|
+
export declare const asideContentTransition: Transition;
|
|
67
|
+
export declare const asideMobileTransition: Transition;
|
package/dist/gui/lib/motion.js
CHANGED
|
@@ -215,3 +215,72 @@ export function getTransition(shouldReduceMotion, transition = standardTransitio
|
|
|
215
215
|
export function getDuration(shouldReduceMotion, duration = motionDuration.normal) {
|
|
216
216
|
return shouldReduceMotion ? 0.01 : duration;
|
|
217
217
|
}
|
|
218
|
+
// ============================================================================
|
|
219
|
+
// Sidebar Animations (AppSidebar)
|
|
220
|
+
// ============================================================================
|
|
221
|
+
export const sidebarTransition = {
|
|
222
|
+
duration: 0.5,
|
|
223
|
+
ease: motionEasing.smooth,
|
|
224
|
+
};
|
|
225
|
+
export const sidebarMobileTransition = {
|
|
226
|
+
duration: 0.3,
|
|
227
|
+
ease: motionEasing.smooth,
|
|
228
|
+
};
|
|
229
|
+
// Desktop: Slide animation (fixed width, no reflow)
|
|
230
|
+
export const sidebarDesktopVariants = {
|
|
231
|
+
initial: { x: "-100%" },
|
|
232
|
+
animate: { x: 0 },
|
|
233
|
+
exit: { x: "-100%" },
|
|
234
|
+
};
|
|
235
|
+
// Mobile: Slide animation (overlay from left)
|
|
236
|
+
export const sidebarMobileVariants = {
|
|
237
|
+
initial: { x: "-100%" },
|
|
238
|
+
animate: { x: 0 },
|
|
239
|
+
exit: { x: "-100%" },
|
|
240
|
+
};
|
|
241
|
+
// Sidebar content fade-in (snappy, only on open)
|
|
242
|
+
export const sidebarContentVariants = {
|
|
243
|
+
initial: { opacity: 0, x: -20 },
|
|
244
|
+
animate: { opacity: 1, x: 0 },
|
|
245
|
+
};
|
|
246
|
+
export const sidebarContentTransition = {
|
|
247
|
+
duration: 0.5,
|
|
248
|
+
ease: motionEasing.smooth,
|
|
249
|
+
delay: 0.25,
|
|
250
|
+
};
|
|
251
|
+
// Backdrop animation for mobile overlay
|
|
252
|
+
export const backdropVariants = {
|
|
253
|
+
initial: { opacity: 0 },
|
|
254
|
+
animate: { opacity: 1 },
|
|
255
|
+
exit: { opacity: 0 },
|
|
256
|
+
};
|
|
257
|
+
// ============================================================================
|
|
258
|
+
// Aside Panel Animations (ChatLayout.Aside)
|
|
259
|
+
// ============================================================================
|
|
260
|
+
// Desktop: Width-based slide animation (inline panel, main content reflows)
|
|
261
|
+
export const asideDesktopVariants = {
|
|
262
|
+
initial: { width: 0 },
|
|
263
|
+
animate: { width: "auto" },
|
|
264
|
+
exit: { width: 0 },
|
|
265
|
+
};
|
|
266
|
+
// Mobile: Slide animation from right (full-screen overlay)
|
|
267
|
+
export const asideMobileVariants = {
|
|
268
|
+
initial: { x: "100%" },
|
|
269
|
+
animate: { x: 0 },
|
|
270
|
+
exit: { x: "100%" },
|
|
271
|
+
};
|
|
272
|
+
// Aside content fade-in (delayed, snappy)
|
|
273
|
+
export const asideContentVariants = {
|
|
274
|
+
initial: { opacity: 0, x: 20 },
|
|
275
|
+
animate: { opacity: 1, x: 0 },
|
|
276
|
+
};
|
|
277
|
+
export const asideContentTransition = {
|
|
278
|
+
duration: 0.35,
|
|
279
|
+
ease: motionEasing.smooth,
|
|
280
|
+
delay: 0.1,
|
|
281
|
+
};
|
|
282
|
+
// Mobile transition (faster, matches sidebar)
|
|
283
|
+
export const asideMobileTransition = {
|
|
284
|
+
duration: 0.3,
|
|
285
|
+
ease: motionEasing.smooth,
|
|
286
|
+
};
|
|
@@ -13,9 +13,9 @@ export type MessageRole = z.infer<typeof MessageRole>;
|
|
|
13
13
|
* Content type for messages
|
|
14
14
|
*/
|
|
15
15
|
export declare const ContentType: z.ZodEnum<{
|
|
16
|
+
file: "file";
|
|
16
17
|
text: "text";
|
|
17
18
|
image: "image";
|
|
18
|
-
file: "file";
|
|
19
19
|
tool_call: "tool_call";
|
|
20
20
|
tool_result: "tool_result";
|
|
21
21
|
}>;
|
|
@@ -25,9 +25,9 @@ export type ContentType = z.infer<typeof ContentType>;
|
|
|
25
25
|
*/
|
|
26
26
|
export declare const BaseContent: z.ZodObject<{
|
|
27
27
|
type: z.ZodEnum<{
|
|
28
|
+
file: "file";
|
|
28
29
|
text: "text";
|
|
29
30
|
image: "image";
|
|
30
|
-
file: "file";
|
|
31
31
|
tool_call: "tool_call";
|
|
32
32
|
tool_result: "tool_result";
|
|
33
33
|
}>;
|
|
@@ -5,11 +5,11 @@ import { z } from "zod";
|
|
|
5
5
|
export declare const SessionStatus: z.ZodEnum<{
|
|
6
6
|
error: "error";
|
|
7
7
|
active: "active";
|
|
8
|
-
|
|
8
|
+
idle: "idle";
|
|
9
9
|
connecting: "connecting";
|
|
10
10
|
connected: "connected";
|
|
11
|
-
idle: "idle";
|
|
12
11
|
streaming: "streaming";
|
|
12
|
+
disconnected: "disconnected";
|
|
13
13
|
}>;
|
|
14
14
|
export type SessionStatus = z.infer<typeof SessionStatus>;
|
|
15
15
|
/**
|
|
@@ -42,11 +42,11 @@ export declare const Session: z.ZodObject<{
|
|
|
42
42
|
status: z.ZodEnum<{
|
|
43
43
|
error: "error";
|
|
44
44
|
active: "active";
|
|
45
|
-
|
|
45
|
+
idle: "idle";
|
|
46
46
|
connecting: "connecting";
|
|
47
47
|
connected: "connected";
|
|
48
|
-
idle: "idle";
|
|
49
48
|
streaming: "streaming";
|
|
49
|
+
disconnected: "disconnected";
|
|
50
50
|
}>;
|
|
51
51
|
config: z.ZodObject<{
|
|
52
52
|
agentPath: z.ZodString;
|
|
@@ -118,11 +118,11 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
118
118
|
status: z.ZodOptional<z.ZodEnum<{
|
|
119
119
|
error: "error";
|
|
120
120
|
active: "active";
|
|
121
|
-
|
|
121
|
+
idle: "idle";
|
|
122
122
|
connecting: "connecting";
|
|
123
123
|
connected: "connected";
|
|
124
|
-
idle: "idle";
|
|
125
124
|
streaming: "streaming";
|
|
125
|
+
disconnected: "disconnected";
|
|
126
126
|
}>>;
|
|
127
127
|
message: z.ZodOptional<z.ZodObject<{
|
|
128
128
|
id: z.ZodString;
|
|
@@ -197,9 +197,9 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
197
197
|
other: "other";
|
|
198
198
|
}>;
|
|
199
199
|
status: z.ZodEnum<{
|
|
200
|
-
completed: "completed";
|
|
201
200
|
pending: "pending";
|
|
202
201
|
in_progress: "in_progress";
|
|
202
|
+
completed: "completed";
|
|
203
203
|
failed: "failed";
|
|
204
204
|
}>;
|
|
205
205
|
contentPosition: z.ZodOptional<z.ZodNumber>;
|
|
@@ -265,9 +265,9 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
265
265
|
prettyName: z.ZodOptional<z.ZodString>;
|
|
266
266
|
icon: z.ZodOptional<z.ZodString>;
|
|
267
267
|
status: z.ZodEnum<{
|
|
268
|
-
completed: "completed";
|
|
269
268
|
pending: "pending";
|
|
270
269
|
in_progress: "in_progress";
|
|
270
|
+
completed: "completed";
|
|
271
271
|
failed: "failed";
|
|
272
272
|
}>;
|
|
273
273
|
content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -310,9 +310,9 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
310
310
|
prettyName: z.ZodOptional<z.ZodString>;
|
|
311
311
|
icon: z.ZodOptional<z.ZodString>;
|
|
312
312
|
status: z.ZodEnum<{
|
|
313
|
-
completed: "completed";
|
|
314
313
|
pending: "pending";
|
|
315
314
|
in_progress: "in_progress";
|
|
315
|
+
completed: "completed";
|
|
316
316
|
failed: "failed";
|
|
317
317
|
}>;
|
|
318
318
|
content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -355,11 +355,11 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
355
355
|
status: z.ZodOptional<z.ZodEnum<{
|
|
356
356
|
error: "error";
|
|
357
357
|
active: "active";
|
|
358
|
-
|
|
358
|
+
idle: "idle";
|
|
359
359
|
connecting: "connecting";
|
|
360
360
|
connected: "connected";
|
|
361
|
-
idle: "idle";
|
|
362
361
|
streaming: "streaming";
|
|
362
|
+
disconnected: "disconnected";
|
|
363
363
|
}>>;
|
|
364
364
|
message: z.ZodOptional<z.ZodObject<{
|
|
365
365
|
id: z.ZodString;
|
|
@@ -412,9 +412,9 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
412
412
|
toolCallUpdate: z.ZodObject<{
|
|
413
413
|
id: z.ZodString;
|
|
414
414
|
status: z.ZodOptional<z.ZodEnum<{
|
|
415
|
-
completed: "completed";
|
|
416
415
|
pending: "pending";
|
|
417
416
|
in_progress: "in_progress";
|
|
417
|
+
completed: "completed";
|
|
418
418
|
failed: "failed";
|
|
419
419
|
}>>;
|
|
420
420
|
title: z.ZodOptional<z.ZodString>;
|
|
@@ -473,9 +473,9 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
473
473
|
prettyName: z.ZodOptional<z.ZodString>;
|
|
474
474
|
icon: z.ZodOptional<z.ZodString>;
|
|
475
475
|
status: z.ZodEnum<{
|
|
476
|
-
completed: "completed";
|
|
477
476
|
pending: "pending";
|
|
478
477
|
in_progress: "in_progress";
|
|
478
|
+
completed: "completed";
|
|
479
479
|
failed: "failed";
|
|
480
480
|
}>;
|
|
481
481
|
content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -518,9 +518,9 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
518
518
|
prettyName: z.ZodOptional<z.ZodString>;
|
|
519
519
|
icon: z.ZodOptional<z.ZodString>;
|
|
520
520
|
status: z.ZodEnum<{
|
|
521
|
-
completed: "completed";
|
|
522
521
|
pending: "pending";
|
|
523
522
|
in_progress: "in_progress";
|
|
523
|
+
completed: "completed";
|
|
524
524
|
failed: "failed";
|
|
525
525
|
}>;
|
|
526
526
|
content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -562,11 +562,11 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
562
562
|
status: z.ZodOptional<z.ZodEnum<{
|
|
563
563
|
error: "error";
|
|
564
564
|
active: "active";
|
|
565
|
-
|
|
565
|
+
idle: "idle";
|
|
566
566
|
connecting: "connecting";
|
|
567
567
|
connected: "connected";
|
|
568
|
-
idle: "idle";
|
|
569
568
|
streaming: "streaming";
|
|
569
|
+
disconnected: "disconnected";
|
|
570
570
|
}>>;
|
|
571
571
|
message: z.ZodOptional<z.ZodObject<{
|
|
572
572
|
id: z.ZodString;
|
|
@@ -627,11 +627,11 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
627
627
|
status: z.ZodOptional<z.ZodEnum<{
|
|
628
628
|
error: "error";
|
|
629
629
|
active: "active";
|
|
630
|
-
|
|
630
|
+
idle: "idle";
|
|
631
631
|
connecting: "connecting";
|
|
632
632
|
connected: "connected";
|
|
633
|
-
idle: "idle";
|
|
634
633
|
streaming: "streaming";
|
|
634
|
+
disconnected: "disconnected";
|
|
635
635
|
}>>;
|
|
636
636
|
message: z.ZodOptional<z.ZodObject<{
|
|
637
637
|
id: z.ZodString;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Message, MessageChunk, SessionUpdate } from "../schemas/index.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type HttpTransportOptions, type SessionSummary, type Transport } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* HTTP transport implementation using ACP over HTTP + SSE
|
|
5
5
|
* Uses POST /rpc for client->agent messages and GET /events (SSE) for agent->client
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as acp from "@agentclientprotocol/sdk";
|
|
2
2
|
import { createLogger } from "@townco/core";
|
|
3
|
+
import { httpTransportOptionsSchema, } from "./types.js";
|
|
3
4
|
const logger = createLogger("http-transport");
|
|
4
5
|
/**
|
|
5
6
|
* HTTP transport implementation using ACP over HTTP + SSE
|
|
@@ -24,6 +25,14 @@ export class HttpTransport {
|
|
|
24
25
|
isInReplayMode = false; // True during session replay, ignores non-replay streaming
|
|
25
26
|
agentInfo;
|
|
26
27
|
constructor(options) {
|
|
28
|
+
// Validate options at the boundary using Zod
|
|
29
|
+
const validationResult = httpTransportOptionsSchema.safeParse(options);
|
|
30
|
+
if (!validationResult.success) {
|
|
31
|
+
const errorMessages = validationResult.error.issues
|
|
32
|
+
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
33
|
+
.join("; ");
|
|
34
|
+
throw new Error(`Invalid HTTP transport configuration: ${errorMessages}`);
|
|
35
|
+
}
|
|
27
36
|
// Ensure baseUrl doesn't end with a slash
|
|
28
37
|
this.options = { ...options, baseUrl: options.baseUrl.replace(/\/$/, "") };
|
|
29
38
|
}
|
|
@@ -180,7 +180,7 @@ export class StdioTransport {
|
|
|
180
180
|
}),
|
|
181
181
|
startedAt: Date.now(),
|
|
182
182
|
};
|
|
183
|
-
const
|
|
183
|
+
const _sessionUpdate = {
|
|
184
184
|
type: "tool_call",
|
|
185
185
|
sessionId,
|
|
186
186
|
status: "active",
|
|
@@ -265,7 +265,7 @@ export class StdioTransport {
|
|
|
265
265
|
? Date.now()
|
|
266
266
|
: undefined,
|
|
267
267
|
};
|
|
268
|
-
const
|
|
268
|
+
const _sessionUpdate = {
|
|
269
269
|
type: "tool_call_update",
|
|
270
270
|
sessionId,
|
|
271
271
|
status: "active",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import type { Message, MessageChunk, SessionUpdate } from "../schemas/index.js";
|
|
2
3
|
/**
|
|
3
4
|
* Agent tool information exposed via initialize response
|
|
@@ -127,6 +128,16 @@ export interface StdioTransportOptions {
|
|
|
127
128
|
workingDirectory?: string;
|
|
128
129
|
timeout?: number;
|
|
129
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Zod schema for validating HTTP transport options
|
|
133
|
+
* Validates that baseUrl is a valid URL at the boundary
|
|
134
|
+
*/
|
|
135
|
+
export declare const httpTransportOptionsSchema: z.ZodObject<{
|
|
136
|
+
baseUrl: z.ZodString;
|
|
137
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
138
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
139
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
140
|
+
}, z.core.$strip>;
|
|
130
141
|
/**
|
|
131
142
|
* HTTP transport options
|
|
132
143
|
*/
|
|
@@ -1 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for validating HTTP transport options
|
|
4
|
+
* Validates that baseUrl is a valid URL at the boundary
|
|
5
|
+
*/
|
|
6
|
+
export const httpTransportOptionsSchema = z.object({
|
|
7
|
+
baseUrl: z
|
|
8
|
+
.string()
|
|
9
|
+
.url("Invalid base URL. Must be a valid URL (e.g., http://localhost:3000)")
|
|
10
|
+
.refine((url) => {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = new URL(url);
|
|
13
|
+
// Check for common mistakes like double ports (e.g., http://localhost:3102:3100)
|
|
14
|
+
if (parsed.port && parsed.hostname.includes(":")) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}, {
|
|
23
|
+
message: "Invalid URL format. Check for typos like duplicate ports (e.g., http://localhost:3102:3100)",
|
|
24
|
+
}),
|
|
25
|
+
apiKey: z.string().optional(),
|
|
26
|
+
timeout: z.number().optional(),
|
|
27
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
28
|
+
});
|