@townco/ui 0.1.15 → 0.1.17

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.
Files changed (42) hide show
  1. package/dist/core/hooks/index.d.ts +1 -0
  2. package/dist/core/hooks/index.js +1 -0
  3. package/dist/core/hooks/use-chat-messages.d.ts +50 -11
  4. package/dist/core/hooks/use-chat-session.d.ts +5 -5
  5. package/dist/core/hooks/use-tool-calls.d.ts +52 -0
  6. package/dist/core/hooks/use-tool-calls.js +61 -0
  7. package/dist/core/schemas/chat.d.ts +166 -83
  8. package/dist/core/schemas/chat.js +27 -27
  9. package/dist/core/schemas/index.d.ts +1 -0
  10. package/dist/core/schemas/index.js +1 -0
  11. package/dist/core/schemas/tool-call.d.ts +174 -0
  12. package/dist/core/schemas/tool-call.js +130 -0
  13. package/dist/core/store/chat-store.d.ts +28 -28
  14. package/dist/core/store/chat-store.js +123 -59
  15. package/dist/gui/components/ChatLayout.js +11 -10
  16. package/dist/gui/components/Dialog.js +8 -84
  17. package/dist/gui/components/Label.js +2 -12
  18. package/dist/gui/components/MessageContent.js +4 -1
  19. package/dist/gui/components/Select.js +12 -118
  20. package/dist/gui/components/Tabs.js +4 -32
  21. package/dist/gui/components/ToolCall.d.ts +8 -0
  22. package/dist/gui/components/ToolCall.js +100 -0
  23. package/dist/gui/components/ToolCallList.d.ts +9 -0
  24. package/dist/gui/components/ToolCallList.js +22 -0
  25. package/dist/gui/components/index.d.ts +2 -0
  26. package/dist/gui/components/index.js +2 -0
  27. package/dist/gui/components/resizable.d.ts +7 -0
  28. package/dist/gui/components/resizable.js +7 -0
  29. package/dist/sdk/schemas/session.d.ts +390 -220
  30. package/dist/sdk/schemas/session.js +74 -29
  31. package/dist/sdk/transports/http.js +705 -472
  32. package/dist/sdk/transports/stdio.js +187 -32
  33. package/dist/tui/components/ChatView.js +19 -51
  34. package/dist/tui/components/MessageList.d.ts +2 -4
  35. package/dist/tui/components/MessageList.js +13 -37
  36. package/dist/tui/components/ToolCall.d.ts +9 -0
  37. package/dist/tui/components/ToolCall.js +41 -0
  38. package/dist/tui/components/ToolCallList.d.ts +8 -0
  39. package/dist/tui/components/ToolCallList.js +17 -0
  40. package/dist/tui/components/index.d.ts +2 -0
  41. package/dist/tui/components/index.js +2 -0
  42. package/package.json +4 -2
@@ -0,0 +1,174 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Tool call status lifecycle
4
+ */
5
+ export declare const ToolCallStatusSchema: z.ZodEnum<{
6
+ pending: "pending";
7
+ in_progress: "in_progress";
8
+ completed: "completed";
9
+ failed: "failed";
10
+ }>;
11
+ export type ToolCallStatus = z.infer<typeof ToolCallStatusSchema>;
12
+ /**
13
+ * Tool call categories for UI presentation
14
+ */
15
+ export declare const ToolCallKindSchema: z.ZodEnum<{
16
+ read: "read";
17
+ edit: "edit";
18
+ delete: "delete";
19
+ move: "move";
20
+ search: "search";
21
+ execute: "execute";
22
+ think: "think";
23
+ fetch: "fetch";
24
+ switch_mode: "switch_mode";
25
+ other: "other";
26
+ }>;
27
+ export type ToolCallKind = z.infer<typeof ToolCallKindSchema>;
28
+ /**
29
+ * File location with optional line number
30
+ */
31
+ export declare const FileLocationSchema: z.ZodObject<{
32
+ path: z.ZodString;
33
+ line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
34
+ }, z.core.$strip>;
35
+ export type FileLocation = z.infer<typeof FileLocationSchema>;
36
+ /**
37
+ * Token usage metadata for tracking LLM consumption
38
+ */
39
+ export declare const TokenUsageSchema: z.ZodObject<{
40
+ inputTokens: z.ZodOptional<z.ZodNumber>;
41
+ outputTokens: z.ZodOptional<z.ZodNumber>;
42
+ totalTokens: z.ZodOptional<z.ZodNumber>;
43
+ }, z.core.$strip>;
44
+ export type TokenUsage = z.infer<typeof TokenUsageSchema>;
45
+ /**
46
+ * Content block types for tool call results
47
+ */
48
+ export declare const ToolCallContentBlockSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
49
+ type: z.ZodLiteral<"content">;
50
+ content: z.ZodObject<{
51
+ type: z.ZodLiteral<"text">;
52
+ text: z.ZodString;
53
+ }, z.core.$strip>;
54
+ }, z.core.$strip>, z.ZodObject<{
55
+ type: z.ZodLiteral<"text">;
56
+ text: z.ZodString;
57
+ }, z.core.$strip>, z.ZodObject<{
58
+ type: z.ZodLiteral<"diff">;
59
+ path: z.ZodString;
60
+ oldText: z.ZodString;
61
+ newText: z.ZodString;
62
+ line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
63
+ }, z.core.$strip>, z.ZodObject<{
64
+ type: z.ZodLiteral<"terminal">;
65
+ terminalId: z.ZodString;
66
+ }, z.core.$strip>], "type">;
67
+ export type ToolCallContentBlock = z.infer<typeof ToolCallContentBlockSchema>;
68
+ /**
69
+ * Complete tool call state as displayed in the UI
70
+ */
71
+ export declare const ToolCallSchema: z.ZodObject<{
72
+ id: z.ZodString;
73
+ title: z.ZodString;
74
+ kind: z.ZodEnum<{
75
+ read: "read";
76
+ edit: "edit";
77
+ delete: "delete";
78
+ move: "move";
79
+ search: "search";
80
+ execute: "execute";
81
+ think: "think";
82
+ fetch: "fetch";
83
+ switch_mode: "switch_mode";
84
+ other: "other";
85
+ }>;
86
+ status: z.ZodEnum<{
87
+ pending: "pending";
88
+ in_progress: "in_progress";
89
+ completed: "completed";
90
+ failed: "failed";
91
+ }>;
92
+ locations: z.ZodOptional<z.ZodArray<z.ZodObject<{
93
+ path: z.ZodString;
94
+ line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
95
+ }, z.core.$strip>>>;
96
+ rawInput: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
97
+ rawOutput: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
98
+ content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
99
+ type: z.ZodLiteral<"content">;
100
+ content: z.ZodObject<{
101
+ type: z.ZodLiteral<"text">;
102
+ text: z.ZodString;
103
+ }, z.core.$strip>;
104
+ }, z.core.$strip>, z.ZodObject<{
105
+ type: z.ZodLiteral<"text">;
106
+ text: z.ZodString;
107
+ }, z.core.$strip>, z.ZodObject<{
108
+ type: z.ZodLiteral<"diff">;
109
+ path: z.ZodString;
110
+ oldText: z.ZodString;
111
+ newText: z.ZodString;
112
+ line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
113
+ }, z.core.$strip>, z.ZodObject<{
114
+ type: z.ZodLiteral<"terminal">;
115
+ terminalId: z.ZodString;
116
+ }, z.core.$strip>], "type">>>;
117
+ error: z.ZodOptional<z.ZodString>;
118
+ startedAt: z.ZodOptional<z.ZodNumber>;
119
+ completedAt: z.ZodOptional<z.ZodNumber>;
120
+ tokenUsage: z.ZodOptional<z.ZodObject<{
121
+ inputTokens: z.ZodOptional<z.ZodNumber>;
122
+ outputTokens: z.ZodOptional<z.ZodNumber>;
123
+ totalTokens: z.ZodOptional<z.ZodNumber>;
124
+ }, z.core.$strip>>;
125
+ }, z.core.$strip>;
126
+ export type ToolCall = z.infer<typeof ToolCallSchema>;
127
+ /**
128
+ * Partial update for an existing tool call
129
+ */
130
+ export declare const ToolCallUpdateSchema: z.ZodObject<{
131
+ id: z.ZodString;
132
+ status: z.ZodOptional<z.ZodEnum<{
133
+ pending: "pending";
134
+ in_progress: "in_progress";
135
+ completed: "completed";
136
+ failed: "failed";
137
+ }>>;
138
+ locations: z.ZodOptional<z.ZodArray<z.ZodObject<{
139
+ path: z.ZodString;
140
+ line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
141
+ }, z.core.$strip>>>;
142
+ rawOutput: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
143
+ content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
144
+ type: z.ZodLiteral<"content">;
145
+ content: z.ZodObject<{
146
+ type: z.ZodLiteral<"text">;
147
+ text: z.ZodString;
148
+ }, z.core.$strip>;
149
+ }, z.core.$strip>, z.ZodObject<{
150
+ type: z.ZodLiteral<"text">;
151
+ text: z.ZodString;
152
+ }, z.core.$strip>, z.ZodObject<{
153
+ type: z.ZodLiteral<"diff">;
154
+ path: z.ZodString;
155
+ oldText: z.ZodString;
156
+ newText: z.ZodString;
157
+ line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
158
+ }, z.core.$strip>, z.ZodObject<{
159
+ type: z.ZodLiteral<"terminal">;
160
+ terminalId: z.ZodString;
161
+ }, z.core.$strip>], "type">>>;
162
+ error: z.ZodOptional<z.ZodString>;
163
+ completedAt: z.ZodOptional<z.ZodNumber>;
164
+ tokenUsage: z.ZodOptional<z.ZodObject<{
165
+ inputTokens: z.ZodOptional<z.ZodNumber>;
166
+ outputTokens: z.ZodOptional<z.ZodNumber>;
167
+ totalTokens: z.ZodOptional<z.ZodNumber>;
168
+ }, z.core.$strip>>;
169
+ }, z.core.$strip>;
170
+ export type ToolCallUpdate = z.infer<typeof ToolCallUpdateSchema>;
171
+ /**
172
+ * Helper to merge a tool call update into an existing tool call
173
+ */
174
+ export declare function mergeToolCallUpdate(existing: ToolCall, update: ToolCallUpdate): ToolCall;
@@ -0,0 +1,130 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Tool call status lifecycle
4
+ */
5
+ export const ToolCallStatusSchema = z.enum([
6
+ "pending",
7
+ "in_progress",
8
+ "completed",
9
+ "failed",
10
+ ]);
11
+ /**
12
+ * Tool call categories for UI presentation
13
+ */
14
+ export const ToolCallKindSchema = z.enum([
15
+ "read",
16
+ "edit",
17
+ "delete",
18
+ "move",
19
+ "search",
20
+ "execute",
21
+ "think",
22
+ "fetch",
23
+ "switch_mode",
24
+ "other",
25
+ ]);
26
+ /**
27
+ * File location with optional line number
28
+ */
29
+ export const FileLocationSchema = z.object({
30
+ path: z.string(),
31
+ line: z.number().nullable().optional(),
32
+ });
33
+ /**
34
+ * Token usage metadata for tracking LLM consumption
35
+ */
36
+ export const TokenUsageSchema = z.object({
37
+ inputTokens: z.number().optional(),
38
+ outputTokens: z.number().optional(),
39
+ totalTokens: z.number().optional(),
40
+ });
41
+ /**
42
+ * Content block types for tool call results
43
+ */
44
+ export const ToolCallContentBlockSchema = z.discriminatedUnion("type", [
45
+ // ACP nested content format
46
+ z.object({
47
+ type: z.literal("content"),
48
+ content: z.object({
49
+ type: z.literal("text"),
50
+ text: z.string(),
51
+ }),
52
+ }),
53
+ // Direct text block (legacy)
54
+ z.object({
55
+ type: z.literal("text"),
56
+ text: z.string(),
57
+ }),
58
+ z.object({
59
+ type: z.literal("diff"),
60
+ path: z.string(),
61
+ oldText: z.string(),
62
+ newText: z.string(),
63
+ line: z.number().nullable().optional(),
64
+ }),
65
+ z.object({
66
+ type: z.literal("terminal"),
67
+ terminalId: z.string(),
68
+ }),
69
+ ]);
70
+ /**
71
+ * Complete tool call state as displayed in the UI
72
+ */
73
+ export const ToolCallSchema = z.object({
74
+ /** Unique identifier within the session */
75
+ id: z.string(),
76
+ /** Human-readable description of the operation */
77
+ title: z.string(),
78
+ /** Category for UI presentation */
79
+ kind: ToolCallKindSchema,
80
+ /** Current execution status */
81
+ status: ToolCallStatusSchema,
82
+ /** Affected file paths with optional line numbers */
83
+ locations: z.array(FileLocationSchema).optional(),
84
+ /** Raw parameters passed to the tool */
85
+ rawInput: z.record(z.string(), z.unknown()).optional(),
86
+ /** Raw response from the tool */
87
+ rawOutput: z.record(z.string(), z.unknown()).optional(),
88
+ /** Produced results (content blocks, diffs, terminal output) */
89
+ content: z.array(ToolCallContentBlockSchema).optional(),
90
+ /** Error message if status is 'failed' */
91
+ error: z.string().optional(),
92
+ /** Timestamp when the tool call started */
93
+ startedAt: z.number().optional(),
94
+ /** Timestamp when the tool call completed/failed */
95
+ completedAt: z.number().optional(),
96
+ /** Token usage metadata for this tool call */
97
+ tokenUsage: TokenUsageSchema.optional(),
98
+ });
99
+ /**
100
+ * Partial update for an existing tool call
101
+ */
102
+ export const ToolCallUpdateSchema = z.object({
103
+ id: z.string(),
104
+ status: ToolCallStatusSchema.optional(),
105
+ locations: z.array(FileLocationSchema).optional(),
106
+ rawOutput: z.record(z.string(), z.unknown()).optional(),
107
+ content: z.array(ToolCallContentBlockSchema).optional(),
108
+ error: z.string().optional(),
109
+ completedAt: z.number().optional(),
110
+ tokenUsage: TokenUsageSchema.optional(),
111
+ });
112
+ /**
113
+ * Helper to merge a tool call update into an existing tool call
114
+ */
115
+ export function mergeToolCallUpdate(existing, update) {
116
+ const merged = {
117
+ ...existing,
118
+ // Only update fields that are defined in the update
119
+ status: update.status ?? existing.status,
120
+ locations: update.locations ?? existing.locations,
121
+ rawOutput: update.rawOutput ?? existing.rawOutput,
122
+ content: update.content
123
+ ? [...(existing.content ?? []), ...update.content]
124
+ : existing.content,
125
+ error: update.error ?? existing.error,
126
+ completedAt: update.completedAt ?? existing.completedAt,
127
+ tokenUsage: update.tokenUsage ?? existing.tokenUsage,
128
+ };
129
+ return merged;
130
+ }
@@ -1,36 +1,36 @@
1
- import type {
2
- ConnectionStatus,
3
- DisplayMessage,
4
- InputState,
5
- } from "../schemas/index.js";
1
+ import type { ConnectionStatus, DisplayMessage, InputState } from "../schemas/index.js";
2
+ import type { ToolCall, ToolCallUpdate } from "../schemas/tool-call.js";
6
3
  /**
7
4
  * Chat store state
8
5
  */
9
6
  export interface ChatStore {
10
- connectionStatus: ConnectionStatus;
11
- sessionId: string | null;
12
- error: string | null;
13
- messages: DisplayMessage[];
14
- isStreaming: boolean;
15
- streamingStartTime: number | null;
16
- input: InputState;
17
- setConnectionStatus: (status: ConnectionStatus) => void;
18
- setSessionId: (id: string | null) => void;
19
- setError: (error: string | null) => void;
20
- addMessage: (message: DisplayMessage) => void;
21
- updateMessage: (id: string, updates: Partial<DisplayMessage>) => void;
22
- clearMessages: () => void;
23
- setIsStreaming: (streaming: boolean) => void;
24
- setStreamingStartTime: (time: number | null) => void;
25
- setInputValue: (value: string) => void;
26
- setInputSubmitting: (submitting: boolean) => void;
27
- addFileAttachment: (file: InputState["attachedFiles"][number]) => void;
28
- removeFileAttachment: (index: number) => void;
29
- clearInput: () => void;
7
+ connectionStatus: ConnectionStatus;
8
+ sessionId: string | null;
9
+ error: string | null;
10
+ messages: DisplayMessage[];
11
+ isStreaming: boolean;
12
+ streamingStartTime: number | null;
13
+ toolCalls: Record<string, ToolCall[]>;
14
+ input: InputState;
15
+ setConnectionStatus: (status: ConnectionStatus) => void;
16
+ setSessionId: (id: string | null) => void;
17
+ setError: (error: string | null) => void;
18
+ addMessage: (message: DisplayMessage) => void;
19
+ updateMessage: (id: string, updates: Partial<DisplayMessage>) => void;
20
+ clearMessages: () => void;
21
+ setIsStreaming: (streaming: boolean) => void;
22
+ setStreamingStartTime: (time: number | null) => void;
23
+ addToolCall: (sessionId: string, toolCall: ToolCall) => void;
24
+ updateToolCall: (sessionId: string, update: ToolCallUpdate) => void;
25
+ addToolCallToCurrentMessage: (toolCall: ToolCall) => void;
26
+ updateToolCallInCurrentMessage: (update: ToolCallUpdate) => void;
27
+ setInputValue: (value: string) => void;
28
+ setInputSubmitting: (submitting: boolean) => void;
29
+ addFileAttachment: (file: InputState["attachedFiles"][number]) => void;
30
+ removeFileAttachment: (index: number) => void;
31
+ clearInput: () => void;
30
32
  }
31
33
  /**
32
34
  * Create chat store
33
35
  */
34
- export declare const useChatStore: import("zustand").UseBoundStore<
35
- import("zustand").StoreApi<ChatStore>
36
- >;
36
+ export declare const useChatStore: import("zustand").UseBoundStore<import("zustand").StoreApi<ChatStore>>;
@@ -1,65 +1,129 @@
1
1
  import { create } from "zustand";
2
+ import { mergeToolCallUpdate } from "../schemas/tool-call.js";
2
3
  /**
3
4
  * Create chat store
4
5
  */
5
6
  export const useChatStore = create((set) => ({
6
- // Initial state
7
- connectionStatus: "disconnected",
8
- sessionId: null,
9
- error: null,
10
- messages: [],
11
- isStreaming: false,
12
- streamingStartTime: null,
13
- input: {
14
- value: "",
15
- isSubmitting: false,
16
- attachedFiles: [],
17
- },
18
- // Actions
19
- setConnectionStatus: (status) => set({ connectionStatus: status }),
20
- setSessionId: (id) => set({ sessionId: id }),
21
- setError: (error) => set({ error }),
22
- addMessage: (message) =>
23
- set((state) => ({
24
- messages: [...state.messages, message],
25
- })),
26
- updateMessage: (id, updates) =>
27
- set((state) => ({
28
- messages: state.messages.map((msg) =>
29
- msg.id === id ? { ...msg, ...updates } : msg,
30
- ),
31
- })),
32
- clearMessages: () => set({ messages: [] }),
33
- setIsStreaming: (streaming) => set({ isStreaming: streaming }),
34
- setStreamingStartTime: (time) => set({ streamingStartTime: time }),
35
- setInputValue: (value) =>
36
- set((state) => ({
37
- input: { ...state.input, value },
38
- })),
39
- setInputSubmitting: (submitting) =>
40
- set((state) => ({
41
- input: { ...state.input, isSubmitting: submitting },
42
- })),
43
- addFileAttachment: (file) =>
44
- set((state) => ({
45
- input: {
46
- ...state.input,
47
- attachedFiles: [...state.input.attachedFiles, file],
48
- },
49
- })),
50
- removeFileAttachment: (index) =>
51
- set((state) => ({
52
- input: {
53
- ...state.input,
54
- attachedFiles: state.input.attachedFiles.filter((_, i) => i !== index),
55
- },
56
- })),
57
- clearInput: () =>
58
- set((_state) => ({
59
- input: {
60
- value: "",
61
- isSubmitting: false,
62
- attachedFiles: [],
63
- },
64
- })),
7
+ // Initial state
8
+ connectionStatus: "disconnected",
9
+ sessionId: null,
10
+ error: null,
11
+ messages: [],
12
+ isStreaming: false,
13
+ streamingStartTime: null,
14
+ toolCalls: {},
15
+ input: {
16
+ value: "",
17
+ isSubmitting: false,
18
+ attachedFiles: [],
19
+ },
20
+ // Actions
21
+ setConnectionStatus: (status) => set({ connectionStatus: status }),
22
+ setSessionId: (id) => set({ sessionId: id }),
23
+ setError: (error) => set({ error }),
24
+ addMessage: (message) => set((state) => ({
25
+ messages: [...state.messages, message],
26
+ })),
27
+ updateMessage: (id, updates) => set((state) => ({
28
+ messages: state.messages.map((msg) => msg.id === id ? { ...msg, ...updates } : msg),
29
+ })),
30
+ clearMessages: () => set({ messages: [] }),
31
+ setIsStreaming: (streaming) => set({ isStreaming: streaming }),
32
+ setStreamingStartTime: (time) => set({ streamingStartTime: time }),
33
+ addToolCall: (sessionId, toolCall) => set((state) => ({
34
+ toolCalls: {
35
+ ...state.toolCalls,
36
+ [sessionId]: [...(state.toolCalls[sessionId] || []), toolCall],
37
+ },
38
+ })),
39
+ addToolCallToCurrentMessage: (toolCall) => set((state) => {
40
+ // Find the most recent assistant message (which should be streaming)
41
+ const lastAssistantIndex = state.messages.findLastIndex((msg) => msg.role === "assistant");
42
+ if (lastAssistantIndex === -1) {
43
+ console.warn("No assistant message found to add tool call to");
44
+ return state;
45
+ }
46
+ const messages = [...state.messages];
47
+ const lastAssistantMsg = messages[lastAssistantIndex];
48
+ if (!lastAssistantMsg)
49
+ return state;
50
+ messages[lastAssistantIndex] = {
51
+ ...lastAssistantMsg,
52
+ toolCalls: [...(lastAssistantMsg.toolCalls || []), toolCall],
53
+ };
54
+ return { messages };
55
+ }),
56
+ updateToolCallInCurrentMessage: (update) => set((state) => {
57
+ // Find the most recent assistant message
58
+ const lastAssistantIndex = state.messages.findLastIndex((msg) => msg.role === "assistant");
59
+ if (lastAssistantIndex === -1) {
60
+ console.warn("No assistant message found to update tool call in");
61
+ return state;
62
+ }
63
+ const messages = [...state.messages];
64
+ const lastAssistantMsg = messages[lastAssistantIndex];
65
+ if (!lastAssistantMsg)
66
+ return state;
67
+ const toolCalls = lastAssistantMsg.toolCalls || [];
68
+ const existingIndex = toolCalls.findIndex((tc) => tc.id === update.id);
69
+ if (existingIndex === -1) {
70
+ console.warn(`Tool call ${update.id} not found in message`);
71
+ return state;
72
+ }
73
+ const existing = toolCalls[existingIndex];
74
+ if (!existing)
75
+ return state;
76
+ const updatedToolCalls = [...toolCalls];
77
+ updatedToolCalls[existingIndex] = mergeToolCallUpdate(existing, update);
78
+ messages[lastAssistantIndex] = {
79
+ ...lastAssistantMsg,
80
+ toolCalls: updatedToolCalls,
81
+ };
82
+ return { messages };
83
+ }),
84
+ updateToolCall: (sessionId, update) => set((state) => {
85
+ const sessionToolCalls = state.toolCalls[sessionId] || [];
86
+ const existingIndex = sessionToolCalls.findIndex((tc) => tc.id === update.id);
87
+ if (existingIndex === -1) {
88
+ // Tool call not found, ignore update
89
+ return state;
90
+ }
91
+ const existing = sessionToolCalls[existingIndex];
92
+ if (!existing) {
93
+ return state;
94
+ }
95
+ const updatedToolCalls = [...sessionToolCalls];
96
+ updatedToolCalls[existingIndex] = mergeToolCallUpdate(existing, update);
97
+ return {
98
+ toolCalls: {
99
+ ...state.toolCalls,
100
+ [sessionId]: updatedToolCalls,
101
+ },
102
+ };
103
+ }),
104
+ setInputValue: (value) => set((state) => ({
105
+ input: { ...state.input, value },
106
+ })),
107
+ setInputSubmitting: (submitting) => set((state) => ({
108
+ input: { ...state.input, isSubmitting: submitting },
109
+ })),
110
+ addFileAttachment: (file) => set((state) => ({
111
+ input: {
112
+ ...state.input,
113
+ attachedFiles: [...state.input.attachedFiles, file],
114
+ },
115
+ })),
116
+ removeFileAttachment: (index) => set((state) => ({
117
+ input: {
118
+ ...state.input,
119
+ attachedFiles: state.input.attachedFiles.filter((_, i) => i !== index),
120
+ },
121
+ })),
122
+ clearInput: () => set((_state) => ({
123
+ input: {
124
+ value: "",
125
+ isSubmitting: false,
126
+ attachedFiles: [],
127
+ },
128
+ })),
65
129
  }));
@@ -1,7 +1,8 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { ArrowDown } from "lucide-react";
3
3
  import * as React from "react";
4
4
  import { cn } from "../lib/utils.js";
5
+ import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "./resizable.js";
5
6
  import { Toaster } from "./Sonner.js";
6
7
  const ChatLayoutContext = React.createContext(undefined);
7
8
  const useChatLayoutContext = () => {
@@ -22,7 +23,7 @@ const ChatLayoutRoot = React.forwardRef(({ defaultSidebarOpen = false, defaultPa
22
23
  setPanelSize,
23
24
  activeTab,
24
25
  setActiveTab,
25
- }, children: _jsx("div", { ref: ref, className: cn("flex h-screen flex-row bg-background text-foreground", className), ...props, children: children }) }));
26
+ }, children: _jsx("div", { ref: ref, className: cn("flex h-screen flex-row bg-background text-foreground", className), ...props, children: _jsx(ResizablePanelGroup, { direction: "horizontal", className: "flex-1", children: children }) }) }));
26
27
  });
27
28
  ChatLayoutRoot.displayName = "ChatLayout.Root";
28
29
  const ChatLayoutHeader = React.forwardRef(({ className, children, ...props }, ref) => {
@@ -30,7 +31,7 @@ const ChatLayoutHeader = React.forwardRef(({ className, children, ...props }, re
30
31
  });
31
32
  ChatLayoutHeader.displayName = "ChatLayout.Header";
32
33
  const ChatLayoutMain = React.forwardRef(({ className, children, ...props }, ref) => {
33
- return (_jsx("div", { ref: ref, className: cn("flex flex-1 flex-col overflow-hidden", className), ...props, children: children }));
34
+ return (_jsx(ResizablePanel, { defaultSize: 75, minSize: 50, children: _jsx("div", { ref: ref, className: cn("flex flex-1 flex-col overflow-hidden h-full", className), ...props, children: children }) }));
34
35
  });
35
36
  ChatLayoutMain.displayName = "ChatLayout.Main";
36
37
  const ChatLayoutBody = React.forwardRef(({ showToaster = true, className, children, ...props }, ref) => {
@@ -90,13 +91,13 @@ const ChatLayoutAside = React.forwardRef(({ breakpoint = "lg", className, childr
90
91
  // Hidden state - don't render
91
92
  if (panelSize === "hidden")
92
93
  return null;
93
- return (_jsx("div", { ref: ref, className: cn(
94
- // Hidden by default, visible at breakpoint
95
- "hidden border-l border-border bg-card overflow-y-auto transition-all duration-300",
96
- // Breakpoint visibility
97
- breakpoint === "md" && "md:block", breakpoint === "lg" && "lg:block", breakpoint === "xl" && "xl:block", breakpoint === "2xl" && "2xl:block",
98
- // Size variants
99
- panelSize === "small" && "w-80", panelSize === "large" && "w-lg", className), ...props, children: children }));
94
+ return (_jsxs(_Fragment, { children: [_jsx(ResizableHandle, { withHandle: true }), _jsx(ResizablePanel, { defaultSize: 25, minSize: 15, maxSize: 50, children: _jsx("div", { ref: ref, className: cn(
95
+ // Hidden by default, visible at breakpoint
96
+ "hidden h-full border-l border-border bg-card overflow-y-auto transition-all duration-300",
97
+ // Breakpoint visibility
98
+ breakpoint === "md" && "md:block", breakpoint === "lg" && "lg:block", breakpoint === "xl" && "xl:block", breakpoint === "2xl" && "2xl:block",
99
+ // Size variants - width is now controlled by ResizablePanel
100
+ className), ...props, children: children }) })] }));
100
101
  });
101
102
  ChatLayoutAside.displayName = "ChatLayout.Aside";
102
103
  /* -------------------------------------------------------------------------------------------------