@townco/ui 0.1.44 → 0.1.46
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 +6 -0
- package/dist/core/hooks/use-chat-messages.js +22 -1
- package/dist/core/hooks/use-chat-session.js +19 -3
- package/dist/core/hooks/use-tool-calls.d.ts +6 -0
- package/dist/core/schemas/chat.d.ts +18 -0
- package/dist/core/schemas/tool-call.d.ts +9 -0
- package/dist/core/schemas/tool-call.js +9 -0
- package/dist/core/store/chat-store.d.ts +14 -0
- package/dist/core/store/chat-store.js +3 -0
- package/dist/gui/components/Button.d.ts +1 -1
- package/dist/gui/components/ChatView.js +29 -15
- package/dist/gui/components/ContextUsageButton.d.ts +11 -3
- package/dist/gui/components/ContextUsageButton.js +22 -3
- package/dist/gui/components/MessageContent.d.ts +5 -0
- package/dist/gui/components/MessageContent.js +2 -55
- package/dist/gui/components/ToolCall.js +1 -1
- package/dist/sdk/client/acp-client.d.ts +12 -0
- package/dist/sdk/client/acp-client.js +19 -0
- package/dist/sdk/schemas/message.d.ts +14 -2
- package/dist/sdk/schemas/message.js +16 -0
- package/dist/sdk/schemas/session.d.ts +19 -6
- package/dist/sdk/schemas/session.js +1 -0
- package/dist/sdk/transports/http.d.ts +8 -0
- package/dist/sdk/transports/http.js +79 -8
- package/dist/sdk/transports/stdio.d.ts +8 -0
- package/dist/sdk/transports/stdio.js +28 -0
- package/dist/sdk/transports/types.d.ts +12 -0
- package/dist/tui/components/ToolCall.js +1 -1
- package/package.json +3 -3
- package/src/styles/global.css +15 -0
- package/dist/core/lib/logger.d.ts +0 -59
- package/dist/core/lib/logger.js +0 -191
- package/dist/tui/components/LogsPanel.d.ts +0 -5
- package/dist/tui/components/LogsPanel.js +0 -29
|
@@ -52,6 +52,12 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
|
|
|
52
52
|
outputTokens?: number | undefined;
|
|
53
53
|
totalTokens?: number | undefined;
|
|
54
54
|
} | undefined;
|
|
55
|
+
_meta?: {
|
|
56
|
+
truncationWarning?: string | undefined;
|
|
57
|
+
compactionAction?: "compacted" | "truncated" | undefined;
|
|
58
|
+
originalTokens?: number | undefined;
|
|
59
|
+
finalTokens?: number | undefined;
|
|
60
|
+
} | undefined;
|
|
55
61
|
}[] | undefined;
|
|
56
62
|
tokenUsage?: {
|
|
57
63
|
inputTokens?: number | undefined;
|
|
@@ -14,6 +14,7 @@ export function useChatMessages(client, startSession) {
|
|
|
14
14
|
const addMessage = useChatStore((state) => state.addMessage);
|
|
15
15
|
const updateMessage = useChatStore((state) => state.updateMessage);
|
|
16
16
|
const setError = useChatStore((state) => state.setError);
|
|
17
|
+
const setLatestContextSize = useChatStore((state) => state.setLatestContextSize);
|
|
17
18
|
/**
|
|
18
19
|
* Send a message to the agent
|
|
19
20
|
*/
|
|
@@ -78,8 +79,27 @@ export function useChatMessages(client, startSession) {
|
|
|
78
79
|
let accumulatedContent = "";
|
|
79
80
|
let streamCompleted = false;
|
|
80
81
|
for await (const chunk of messageStream) {
|
|
82
|
+
// Update context size if provided (check both _meta.context_size and direct context_size)
|
|
83
|
+
const chunkMeta = chunk._meta;
|
|
84
|
+
const contextSizeData = chunkMeta?.context_size || chunk.context_size;
|
|
85
|
+
if (contextSizeData != null) {
|
|
86
|
+
const contextSize = contextSizeData;
|
|
87
|
+
logger.info("✅ Received context_size from backend", {
|
|
88
|
+
context_size: contextSize,
|
|
89
|
+
totalEstimated: contextSize.totalEstimated,
|
|
90
|
+
source: chunkMeta?.context_size ? "_meta" : "direct",
|
|
91
|
+
});
|
|
92
|
+
setLatestContextSize(contextSize);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
logger.debug("Chunk does not have context_size", {
|
|
96
|
+
hasContextSize: "context_size" in chunk,
|
|
97
|
+
hasMeta: "_meta" in chunk,
|
|
98
|
+
metaKeys: chunkMeta ? Object.keys(chunkMeta) : null,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
81
101
|
if (chunk.tokenUsage) {
|
|
82
|
-
logger.debug("
|
|
102
|
+
logger.debug("Received tokenUsage from backend", {
|
|
83
103
|
tokenUsage: chunk.tokenUsage,
|
|
84
104
|
});
|
|
85
105
|
}
|
|
@@ -140,6 +160,7 @@ export function useChatMessages(client, startSession) {
|
|
|
140
160
|
setIsStreaming,
|
|
141
161
|
setStreamingStartTime,
|
|
142
162
|
setError,
|
|
163
|
+
setLatestContextSize,
|
|
143
164
|
]);
|
|
144
165
|
return {
|
|
145
166
|
messages,
|
|
@@ -14,11 +14,24 @@ export function useChatSession(client, initialSessionId) {
|
|
|
14
14
|
const clearMessages = useChatStore((state) => state.clearMessages);
|
|
15
15
|
const resetTokens = useChatStore((state) => state.resetTokens);
|
|
16
16
|
const addMessage = useChatStore((state) => state.addMessage);
|
|
17
|
+
const setLatestContextSize = useChatStore((state) => state.setLatestContextSize);
|
|
17
18
|
// Subscribe to session updates to handle replayed messages
|
|
18
19
|
useEffect(() => {
|
|
19
20
|
if (!client)
|
|
20
21
|
return;
|
|
21
22
|
const unsubscribe = client.onSessionUpdate((update) => {
|
|
23
|
+
// Extract context size from update metadata if available
|
|
24
|
+
const updateMeta = update._meta;
|
|
25
|
+
const contextSizeData = updateMeta?.context_size || update.context_size;
|
|
26
|
+
if (contextSizeData != null) {
|
|
27
|
+
const contextSize = contextSizeData;
|
|
28
|
+
logger.info("✅ Received context_size from session update", {
|
|
29
|
+
context_size: contextSize,
|
|
30
|
+
totalEstimated: contextSize.totalEstimated,
|
|
31
|
+
isReplay: updateMeta?.isReplay,
|
|
32
|
+
});
|
|
33
|
+
setLatestContextSize(contextSize);
|
|
34
|
+
}
|
|
22
35
|
// Handle replayed messages during session loading
|
|
23
36
|
if (update.message) {
|
|
24
37
|
logger.debug("Session update with message", {
|
|
@@ -64,7 +77,7 @@ export function useChatSession(client, initialSessionId) {
|
|
|
64
77
|
}
|
|
65
78
|
});
|
|
66
79
|
return unsubscribe;
|
|
67
|
-
}, [client, addMessage]);
|
|
80
|
+
}, [client, addMessage, setLatestContextSize]);
|
|
68
81
|
/**
|
|
69
82
|
* Connect to the agent (without creating a session)
|
|
70
83
|
*/
|
|
@@ -99,12 +112,15 @@ export function useChatSession(client, initialSessionId) {
|
|
|
99
112
|
try {
|
|
100
113
|
setConnectionStatus("connecting");
|
|
101
114
|
setError(null);
|
|
115
|
+
// Clear existing UI state before loading
|
|
116
|
+
clearMessages();
|
|
117
|
+
resetTokens(); // Clears latestContextSize temporarily
|
|
102
118
|
// Try to load session (this will also connect and replay messages)
|
|
103
119
|
const id = await client.loadSession(sessionIdToLoad);
|
|
104
120
|
setSessionId(id);
|
|
105
121
|
setConnectionStatus("connected");
|
|
106
|
-
//
|
|
107
|
-
|
|
122
|
+
// Messages and context size will be restored from backend during replay
|
|
123
|
+
// Backend sends context_size after replay completes (see adapter.ts:369-401)
|
|
108
124
|
logger.info("Session loaded successfully", { sessionId: id });
|
|
109
125
|
}
|
|
110
126
|
catch (error) {
|
|
@@ -50,6 +50,12 @@ export declare function useToolCalls(client: AcpClient | null): {
|
|
|
50
50
|
outputTokens?: number | undefined;
|
|
51
51
|
totalTokens?: number | undefined;
|
|
52
52
|
} | undefined;
|
|
53
|
+
_meta?: {
|
|
54
|
+
truncationWarning?: string | undefined;
|
|
55
|
+
compactionAction?: "compacted" | "truncated" | undefined;
|
|
56
|
+
originalTokens?: number | undefined;
|
|
57
|
+
finalTokens?: number | undefined;
|
|
58
|
+
} | undefined;
|
|
53
59
|
}[]>;
|
|
54
60
|
getToolCallsForSession: (sessionId: string) => ToolCall[];
|
|
55
61
|
};
|
|
@@ -74,6 +74,15 @@ export declare const DisplayMessage: z.ZodObject<{
|
|
|
74
74
|
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
75
75
|
totalTokens: z.ZodOptional<z.ZodNumber>;
|
|
76
76
|
}, z.core.$strip>>;
|
|
77
|
+
_meta: z.ZodOptional<z.ZodObject<{
|
|
78
|
+
truncationWarning: z.ZodOptional<z.ZodString>;
|
|
79
|
+
compactionAction: z.ZodOptional<z.ZodEnum<{
|
|
80
|
+
compacted: "compacted";
|
|
81
|
+
truncated: "truncated";
|
|
82
|
+
}>>;
|
|
83
|
+
originalTokens: z.ZodOptional<z.ZodNumber>;
|
|
84
|
+
finalTokens: z.ZodOptional<z.ZodNumber>;
|
|
85
|
+
}, z.core.$strip>>;
|
|
77
86
|
}, z.core.$strip>>>;
|
|
78
87
|
tokenUsage: z.ZodOptional<z.ZodObject<{
|
|
79
88
|
inputTokens: z.ZodOptional<z.ZodNumber>;
|
|
@@ -172,6 +181,15 @@ export declare const ChatSessionState: z.ZodObject<{
|
|
|
172
181
|
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
173
182
|
totalTokens: z.ZodOptional<z.ZodNumber>;
|
|
174
183
|
}, z.core.$strip>>;
|
|
184
|
+
_meta: z.ZodOptional<z.ZodObject<{
|
|
185
|
+
truncationWarning: z.ZodOptional<z.ZodString>;
|
|
186
|
+
compactionAction: z.ZodOptional<z.ZodEnum<{
|
|
187
|
+
compacted: "compacted";
|
|
188
|
+
truncated: "truncated";
|
|
189
|
+
}>>;
|
|
190
|
+
originalTokens: z.ZodOptional<z.ZodNumber>;
|
|
191
|
+
finalTokens: z.ZodOptional<z.ZodNumber>;
|
|
192
|
+
}, z.core.$strip>>;
|
|
175
193
|
}, z.core.$strip>>>;
|
|
176
194
|
tokenUsage: z.ZodOptional<z.ZodObject<{
|
|
177
195
|
inputTokens: z.ZodOptional<z.ZodNumber>;
|
|
@@ -125,6 +125,15 @@ export declare const ToolCallSchema: z.ZodObject<{
|
|
|
125
125
|
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
126
126
|
totalTokens: z.ZodOptional<z.ZodNumber>;
|
|
127
127
|
}, z.core.$strip>>;
|
|
128
|
+
_meta: z.ZodOptional<z.ZodObject<{
|
|
129
|
+
truncationWarning: z.ZodOptional<z.ZodString>;
|
|
130
|
+
compactionAction: z.ZodOptional<z.ZodEnum<{
|
|
131
|
+
compacted: "compacted";
|
|
132
|
+
truncated: "truncated";
|
|
133
|
+
}>>;
|
|
134
|
+
originalTokens: z.ZodOptional<z.ZodNumber>;
|
|
135
|
+
finalTokens: z.ZodOptional<z.ZodNumber>;
|
|
136
|
+
}, z.core.$strip>>;
|
|
128
137
|
}, z.core.$strip>;
|
|
129
138
|
export type ToolCall = z.infer<typeof ToolCallSchema>;
|
|
130
139
|
/**
|
|
@@ -101,6 +101,15 @@ export const ToolCallSchema = z.object({
|
|
|
101
101
|
completedAt: z.number().optional(),
|
|
102
102
|
/** Token usage metadata for this tool call */
|
|
103
103
|
tokenUsage: TokenUsageSchema.optional(),
|
|
104
|
+
/** Internal metadata (e.g., truncation warnings) */
|
|
105
|
+
_meta: z
|
|
106
|
+
.object({
|
|
107
|
+
truncationWarning: z.string().optional(),
|
|
108
|
+
compactionAction: z.enum(["compacted", "truncated"]).optional(),
|
|
109
|
+
originalTokens: z.number().optional(),
|
|
110
|
+
finalTokens: z.number().optional(),
|
|
111
|
+
})
|
|
112
|
+
.optional(),
|
|
104
113
|
});
|
|
105
114
|
/**
|
|
106
115
|
* Partial update for an existing tool call
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { type LogEntry } from "@townco/core";
|
|
2
2
|
import type { ConnectionStatus, DisplayMessage, InputState } from "../schemas/index.js";
|
|
3
3
|
import type { ToolCall, ToolCallUpdate } from "../schemas/tool-call.js";
|
|
4
|
+
/**
|
|
5
|
+
* Context size breakdown from agent
|
|
6
|
+
*/
|
|
7
|
+
export interface ContextSize {
|
|
8
|
+
systemPromptTokens: number;
|
|
9
|
+
userMessagesTokens: number;
|
|
10
|
+
assistantMessagesTokens: number;
|
|
11
|
+
toolInputTokens: number;
|
|
12
|
+
toolResultsTokens: number;
|
|
13
|
+
totalEstimated: number;
|
|
14
|
+
llmReportedInputTokens?: number;
|
|
15
|
+
}
|
|
4
16
|
/**
|
|
5
17
|
* Chat store state
|
|
6
18
|
*/
|
|
@@ -22,6 +34,7 @@ export interface ChatStore {
|
|
|
22
34
|
outputTokens: number;
|
|
23
35
|
totalTokens: number;
|
|
24
36
|
};
|
|
37
|
+
latestContextSize: ContextSize | null;
|
|
25
38
|
currentModel: string | null;
|
|
26
39
|
tokenDisplayMode: "context" | "input" | "output";
|
|
27
40
|
logs: LogEntry[];
|
|
@@ -49,6 +62,7 @@ export interface ChatStore {
|
|
|
49
62
|
outputTokens?: number;
|
|
50
63
|
totalTokens?: number;
|
|
51
64
|
}) => void;
|
|
65
|
+
setLatestContextSize: (contextSize: ContextSize | null) => void;
|
|
52
66
|
setCurrentModel: (model: string) => void;
|
|
53
67
|
resetTokens: () => void;
|
|
54
68
|
cycleTokenDisplayMode: () => void;
|
|
@@ -24,6 +24,7 @@ export const useChatStore = create((set) => ({
|
|
|
24
24
|
outputTokens: 0,
|
|
25
25
|
totalTokens: 0,
|
|
26
26
|
},
|
|
27
|
+
latestContextSize: null,
|
|
27
28
|
currentModel: "claude-sonnet-4-5-20250929", // Default model, TODO: get from server
|
|
28
29
|
tokenDisplayMode: "context", // Default to showing context (both billed and current)
|
|
29
30
|
logs: [],
|
|
@@ -278,6 +279,7 @@ export const useChatStore = create((set) => ({
|
|
|
278
279
|
(state.currentContext.outputTokens + (tokenUsage.outputTokens ?? 0)),
|
|
279
280
|
},
|
|
280
281
|
})),
|
|
282
|
+
setLatestContextSize: (contextSize) => set({ latestContextSize: contextSize }),
|
|
281
283
|
setCurrentModel: (model) => set({ currentModel: model }),
|
|
282
284
|
resetTokens: () => set({
|
|
283
285
|
totalBilled: {
|
|
@@ -290,6 +292,7 @@ export const useChatStore = create((set) => ({
|
|
|
290
292
|
outputTokens: 0,
|
|
291
293
|
totalTokens: 0,
|
|
292
294
|
},
|
|
295
|
+
latestContextSize: null,
|
|
293
296
|
}),
|
|
294
297
|
cycleTokenDisplayMode: () => set((state) => {
|
|
295
298
|
const modes = [
|
|
@@ -2,7 +2,7 @@ import { type VariantProps } from "class-variance-authority";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
declare const buttonVariants: (props?: ({
|
|
4
4
|
variant?: "default" | "link" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
|
|
5
|
-
size?: "default" | "
|
|
5
|
+
size?: "default" | "icon" | "sm" | "lg" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
8
8
|
asChild?: boolean;
|
|
@@ -42,6 +42,11 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
42
42
|
const error = useChatStore((state) => state.error);
|
|
43
43
|
const currentModel = useChatStore((state) => state.currentModel);
|
|
44
44
|
const [agentName, setAgentName] = useState("Agent");
|
|
45
|
+
const [agentDescription, setAgentDescription] = useState("This agent can help you with your tasks. Start a conversation by typing a message below.");
|
|
46
|
+
const [suggestedPrompts, setSuggestedPrompts] = useState([
|
|
47
|
+
"Search the web for the latest news on top tech company earnings, produce a summary for each company, and then a macro trend analysis of the tech industry. Use your todo list",
|
|
48
|
+
"What can you help me with?",
|
|
49
|
+
]);
|
|
45
50
|
const [isLargeScreen, setIsLargeScreen] = useState(typeof window !== "undefined" ? window.innerWidth >= 1024 : true);
|
|
46
51
|
const [placeholder, setPlaceholder] = useState("Type a message or / for commands...");
|
|
47
52
|
// Log connection status changes
|
|
@@ -51,15 +56,32 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
51
56
|
logger.error("Connection error occurred", { error });
|
|
52
57
|
}
|
|
53
58
|
}, [connectionStatus, error]);
|
|
54
|
-
// Get agent name from session metadata
|
|
59
|
+
// Get agent name from session metadata or client info
|
|
55
60
|
useEffect(() => {
|
|
56
|
-
if (client
|
|
61
|
+
if (client) {
|
|
62
|
+
// Try to get from current session first
|
|
57
63
|
const session = client.getCurrentSession();
|
|
58
64
|
if (session?.metadata?.agentName) {
|
|
59
65
|
setAgentName(session.metadata.agentName);
|
|
66
|
+
// We don't currently have description in session metadata, but we could add it
|
|
67
|
+
}
|
|
68
|
+
// Fallback to client info (if connected)
|
|
69
|
+
const agentInfo = client.getAgentInfo();
|
|
70
|
+
// Prefer displayName (human-readable) over name (programmatic)
|
|
71
|
+
if (agentInfo.displayName) {
|
|
72
|
+
setAgentName(agentInfo.displayName);
|
|
73
|
+
}
|
|
74
|
+
else if (agentInfo.name) {
|
|
75
|
+
setAgentName(agentInfo.name);
|
|
76
|
+
}
|
|
77
|
+
if (agentInfo.description) {
|
|
78
|
+
setAgentDescription(agentInfo.description);
|
|
79
|
+
}
|
|
80
|
+
if (agentInfo.suggestedPrompts && agentInfo.suggestedPrompts.length > 0) {
|
|
81
|
+
setSuggestedPrompts(agentInfo.suggestedPrompts);
|
|
60
82
|
}
|
|
61
83
|
}
|
|
62
|
-
}, [client, sessionId]);
|
|
84
|
+
}, [client, sessionId, connectionStatus]);
|
|
63
85
|
// Monitor screen size changes and update isLargeScreen state
|
|
64
86
|
useEffect(() => {
|
|
65
87
|
const mediaQuery = window.matchMedia("(min-width: 1024px)");
|
|
@@ -123,11 +145,8 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
123
145
|
favicon: "https://www.google.com/s2/favicons?domain=theverge.com&sz=32",
|
|
124
146
|
},
|
|
125
147
|
];
|
|
126
|
-
// Get the latest
|
|
127
|
-
const
|
|
128
|
-
.slice()
|
|
129
|
-
.reverse()
|
|
130
|
-
.find((msg) => msg.role === "assistant" && msg.tokenUsage)?.tokenUsage;
|
|
148
|
+
// Get the latest context size from the session context
|
|
149
|
+
const latestContextSize = useChatStore((state) => state.latestContextSize);
|
|
131
150
|
// Command menu items for chat input
|
|
132
151
|
const commandMenuItems = [
|
|
133
152
|
{
|
|
@@ -171,12 +190,7 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
171
190
|
},
|
|
172
191
|
},
|
|
173
192
|
];
|
|
174
|
-
return (_jsxs(ChatLayout.Root, { defaultPanelSize: "hidden", defaultActiveTab: "todo", children: [_jsxs(ChatLayout.Main, { children: [_jsx(AppChatHeader, { agentName: agentName, todos: todos, sources: sources, showHeader: messages.length > 0 }), connectionStatus === "error" && error && (_jsx("div", { className: "border-b border-destructive/20 bg-destructive/10 px-6 py-4", children: _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "mb-1 text-paragraph-sm font-semibold text-destructive", children: "Connection Error" }), _jsx("p", { className: "whitespace-pre-line text-paragraph-sm text-foreground", children: error })] }), _jsx("button", { type: "button", onClick: connect, className: "rounded-lg bg-destructive px-4 py-2 text-paragraph-sm font-medium text-destructive-foreground transition-colors hover:bg-destructive-hover", children: "Retry" })] }) })), _jsxs(ChatLayout.Body, { children: [_jsx(ChatLayout.Messages, { children: messages.length === 0 ? (_jsx(OpenFilesButton, { children: ({ openFiles }) => (_jsx("div", { className: "flex flex-1 items-center px-4", children: _jsx(ChatEmptyState, { title: agentName, description:
|
|
175
|
-
"Search the web for the latest news on top tech company earnings, produce a summary for each company, and then a macro trend analysis of the tech industry. Use your todo list",
|
|
176
|
-
"Explain how this works",
|
|
177
|
-
"Create a new feature",
|
|
178
|
-
"Review my changes",
|
|
179
|
-
], onPromptClick: (prompt) => {
|
|
193
|
+
return (_jsxs(ChatLayout.Root, { defaultPanelSize: "hidden", defaultActiveTab: "todo", children: [_jsxs(ChatLayout.Main, { children: [_jsx(AppChatHeader, { agentName: agentName, todos: todos, sources: sources, showHeader: messages.length > 0 }), connectionStatus === "error" && error && (_jsx("div", { className: "border-b border-destructive/20 bg-destructive/10 px-6 py-4", children: _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "mb-1 text-paragraph-sm font-semibold text-destructive", children: "Connection Error" }), _jsx("p", { className: "whitespace-pre-line text-paragraph-sm text-foreground", children: error })] }), _jsx("button", { type: "button", onClick: connect, className: "rounded-lg bg-destructive px-4 py-2 text-paragraph-sm font-medium text-destructive-foreground transition-colors hover:bg-destructive-hover", children: "Retry" })] }) })), _jsxs(ChatLayout.Body, { children: [_jsx(ChatLayout.Messages, { children: messages.length === 0 ? (_jsx(OpenFilesButton, { children: ({ openFiles }) => (_jsx("div", { className: "flex flex-1 items-center px-4", children: _jsx(ChatEmptyState, { title: agentName, description: agentDescription, suggestedPrompts: suggestedPrompts, onPromptClick: (prompt) => {
|
|
180
194
|
sendMessage(prompt);
|
|
181
195
|
setPlaceholder("Type a message or / for commands...");
|
|
182
196
|
logger.info("Prompt clicked", { prompt });
|
|
@@ -199,5 +213,5 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
199
213
|
previousMessage?.role === "assistant" ? "mt-2" : "mt-6";
|
|
200
214
|
}
|
|
201
215
|
return (_jsx(Message, { message: message, className: spacingClass, isLastMessage: index === messages.length - 1, children: _jsx(MessageContent, { message: message, thinkingDisplayStyle: "collapsible" }) }, message.id));
|
|
202
|
-
}) })) }), _jsx(ChatLayout.Footer, { children: _jsxs(ChatInputRoot, { client: client, startSession: startSession, children: [_jsx(ChatInputCommandMenu, { commands: commandMenuItems }), _jsx(ChatInputField, { placeholder: placeholder, autoFocus: true }), _jsxs(ChatInputToolbar, { children: [_jsxs("div", { className: "flex items-baseline gap-1", children: [_jsx(ChatInputActions, {}), _jsx(ChatInputAttachment, {}),
|
|
216
|
+
}) })) }), _jsx(ChatLayout.Footer, { children: _jsxs(ChatInputRoot, { client: client, startSession: startSession, children: [_jsx(ChatInputCommandMenu, { commands: commandMenuItems }), _jsx(ChatInputField, { placeholder: placeholder, autoFocus: true }), _jsxs(ChatInputToolbar, { children: [_jsxs("div", { className: "flex items-baseline gap-1", children: [_jsx(ChatInputActions, {}), _jsx(ChatInputAttachment, {}), latestContextSize != null && (_jsx(ContextUsageButton, { contextSize: latestContextSize, modelContextWindow: currentModel?.includes("claude") ? 200000 : 128000 }))] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(ChatInputVoiceInput, {}), _jsx(ChatInputSubmit, { children: _jsx(ArrowUp, { className: "size-4" }) })] })] })] }) })] })] }), isLargeScreen && (_jsx(ChatLayout.Aside, { breakpoint: "lg", children: _jsx(AsideTabs, {}) }))] }));
|
|
203
217
|
}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
export interface ContextSize {
|
|
3
|
+
systemPromptTokens: number;
|
|
4
|
+
userMessagesTokens: number;
|
|
5
|
+
assistantMessagesTokens: number;
|
|
6
|
+
toolInputTokens: number;
|
|
7
|
+
toolResultsTokens: number;
|
|
8
|
+
totalEstimated: number;
|
|
9
|
+
llmReportedInputTokens?: number;
|
|
10
|
+
}
|
|
2
11
|
export interface ContextUsageButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
formattedPercentage: string;
|
|
12
|
+
contextSize: ContextSize;
|
|
13
|
+
modelContextWindow: number;
|
|
6
14
|
}
|
|
7
15
|
export declare const ContextUsageButton: React.ForwardRefExoticComponent<ContextUsageButtonProps & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -3,9 +3,28 @@ import * as React from "react";
|
|
|
3
3
|
import { cn } from "../lib/utils.js";
|
|
4
4
|
import { Button } from "./Button.js";
|
|
5
5
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./Tooltip.js";
|
|
6
|
-
export const ContextUsageButton = React.forwardRef(({
|
|
7
|
-
//
|
|
6
|
+
export const ContextUsageButton = React.forwardRef(({ contextSize, modelContextWindow, className, ...props }, ref) => {
|
|
7
|
+
// Use max of estimated and LLM-reported tokens (LLM reported as fallback if higher)
|
|
8
|
+
const actualTokens = Math.max(contextSize.totalEstimated, contextSize.llmReportedInputTokens ?? 0);
|
|
9
|
+
const percentage = (actualTokens / modelContextWindow) * 100;
|
|
10
|
+
const formattedPercentage = `${percentage.toFixed(1)}%`;
|
|
11
|
+
// Clamp percentage between 0 and 100 for display
|
|
8
12
|
const clampedPercentage = Math.min(100, Math.max(0, percentage));
|
|
13
|
+
// Determine color based on percentage thresholds
|
|
14
|
+
const getColorClass = (pct) => {
|
|
15
|
+
if (pct < 50)
|
|
16
|
+
return "text-foreground"; // Normal text color
|
|
17
|
+
if (pct < 75)
|
|
18
|
+
return "text-yellow-500"; // Yellow
|
|
19
|
+
return "text-red-500"; // Red
|
|
20
|
+
};
|
|
21
|
+
const colorClass = getColorClass(percentage);
|
|
22
|
+
// Calculate percentage contribution for each element
|
|
23
|
+
const calculatePercentage = (tokens) => {
|
|
24
|
+
if (actualTokens === 0)
|
|
25
|
+
return "0.0%";
|
|
26
|
+
return `${((tokens / actualTokens) * 100).toFixed(1)}%`;
|
|
27
|
+
};
|
|
9
28
|
// SVG parameters
|
|
10
29
|
const size = 16;
|
|
11
30
|
const strokeWidth = 2;
|
|
@@ -13,6 +32,6 @@ export const ContextUsageButton = React.forwardRef(({ percentage, tokens, format
|
|
|
13
32
|
const center = size / 2;
|
|
14
33
|
const circumference = 2 * Math.PI * radius;
|
|
15
34
|
const offset = circumference - (clampedPercentage / 100) * circumference;
|
|
16
|
-
return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { ref: ref, type: "button", variant: "ghost", size: "icon", className: cn("rounded-full cursor-default", className), ...props, children: _jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "transform -rotate-90", children: [_jsx("circle", { cx: center, cy: center, r: radius, stroke: "currentColor", strokeWidth: strokeWidth, fill: "transparent", className: "opacity-20" }), _jsx("circle", { cx: center, cy: center, r: radius, stroke: "currentColor", strokeWidth: strokeWidth, fill: "transparent", strokeDasharray: circumference, strokeDashoffset: offset, strokeLinecap: "round", className: "transition-all duration-300 ease-in-out" })] }) }) }), _jsx(TooltipContent, { side: "top", align: "center", children: _jsxs("
|
|
35
|
+
return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { ref: ref, type: "button", variant: "ghost", size: "icon", className: cn("rounded-full cursor-default", colorClass, className), ...props, children: _jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "transform -rotate-90", children: [_jsx("circle", { cx: center, cy: center, r: radius, stroke: "currentColor", strokeWidth: strokeWidth, fill: "transparent", className: "opacity-20" }), _jsx("circle", { cx: center, cy: center, r: radius, stroke: "currentColor", strokeWidth: strokeWidth, fill: "transparent", strokeDasharray: circumference, strokeDashoffset: offset, strokeLinecap: "round", className: "transition-all duration-300 ease-in-out" })] }) }) }), _jsx(TooltipContent, { side: "top", align: "center", className: "p-3", children: _jsxs("div", { className: "space-y-2 font-mono text-xs", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "System Prompt:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.systemPromptTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.systemPromptTokens), ")"] })] })] }), _jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "User Messages:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.userMessagesTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.userMessagesTokens), ")"] })] })] }), _jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "Assistant Messages:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.assistantMessagesTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.assistantMessagesTokens), ")"] })] })] }), _jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "Tool Inputs:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.toolInputTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.toolInputTokens), ")"] })] })] }), _jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "Tool Results:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.toolResultsTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.toolResultsTokens), ")"] })] })] })] }), _jsx("div", { className: "border-t border-border pt-2", children: _jsxs("div", { className: cn("flex justify-end gap-2 font-semibold", colorClass), children: [_jsx("span", { children: actualTokens.toLocaleString() }), _jsxs("span", { children: ["(", formattedPercentage, ")"] })] }) })] }) })] }) }));
|
|
17
36
|
});
|
|
18
37
|
ContextUsageButton.displayName = "ContextUsageButton";
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { type VariantProps } from "class-variance-authority";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import type { DisplayMessage } from "./MessageList.js";
|
|
4
|
+
/**
|
|
5
|
+
* MessageContent component inspired by shadcn.io/ai
|
|
6
|
+
* Provides the content container with role-based styling
|
|
7
|
+
* Handles automatic rendering of thinking, waiting states, and content
|
|
8
|
+
*/
|
|
4
9
|
declare const messageContentVariants: (props?: ({
|
|
5
10
|
role?: "user" | "assistant" | "system" | null | undefined;
|
|
6
11
|
variant?: "default" | "outline" | "ghost" | null | undefined;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cva } from "class-variance-authority";
|
|
3
|
-
import { Loader2Icon } from "lucide-react";
|
|
4
3
|
import * as React from "react";
|
|
5
4
|
import { useChatStore } from "../../core/store/chat-store.js";
|
|
6
|
-
import { formatTokenPercentage } from "../../core/utils/model-context.js";
|
|
7
5
|
import { cn } from "../lib/utils.js";
|
|
8
6
|
import { Reasoning } from "./Reasoning.js";
|
|
9
7
|
import { Response } from "./Response.js";
|
|
@@ -13,57 +11,6 @@ import { ToolCall } from "./ToolCall.js";
|
|
|
13
11
|
* Provides the content container with role-based styling
|
|
14
12
|
* Handles automatic rendering of thinking, waiting states, and content
|
|
15
13
|
*/
|
|
16
|
-
// Synonyms of "thinking" in multiple languages
|
|
17
|
-
const THINKING_WORDS = [
|
|
18
|
-
"Thinking",
|
|
19
|
-
"Pensando",
|
|
20
|
-
"Pensant",
|
|
21
|
-
"Denkend",
|
|
22
|
-
"Pensando",
|
|
23
|
-
"考えている",
|
|
24
|
-
"생각 중",
|
|
25
|
-
"思考中",
|
|
26
|
-
"Размышляя",
|
|
27
|
-
"Düşünüyor",
|
|
28
|
-
"Myślący",
|
|
29
|
-
"Tänkande",
|
|
30
|
-
"Pensando",
|
|
31
|
-
"Ajatellen",
|
|
32
|
-
"Σκεπτόμενος",
|
|
33
|
-
"חושב",
|
|
34
|
-
"सोच रहा है",
|
|
35
|
-
"Berpikir",
|
|
36
|
-
];
|
|
37
|
-
const DOT_ANIMATIONS = ["...", "·..", ".·.", "..·", ".·.", "·.."];
|
|
38
|
-
function WaitingElapsedTime({ startTime }) {
|
|
39
|
-
const [elapsed, setElapsed] = React.useState(0);
|
|
40
|
-
const [thinkingWord, setThinkingWord] = React.useState(() => THINKING_WORDS[Math.floor(Math.random() * THINKING_WORDS.length)]);
|
|
41
|
-
const [dotIndex, setDotIndex] = React.useState(0);
|
|
42
|
-
React.useEffect(() => {
|
|
43
|
-
const interval = setInterval(() => {
|
|
44
|
-
const now = Date.now();
|
|
45
|
-
const elapsedMs = now - startTime;
|
|
46
|
-
setElapsed(elapsedMs);
|
|
47
|
-
}, 100);
|
|
48
|
-
return () => clearInterval(interval);
|
|
49
|
-
}, [startTime]);
|
|
50
|
-
React.useEffect(() => {
|
|
51
|
-
const wordInterval = setInterval(() => {
|
|
52
|
-
const randomIndex = Math.floor(Math.random() * THINKING_WORDS.length);
|
|
53
|
-
setThinkingWord(THINKING_WORDS[randomIndex]);
|
|
54
|
-
}, 1500);
|
|
55
|
-
return () => clearInterval(wordInterval);
|
|
56
|
-
}, []);
|
|
57
|
-
React.useEffect(() => {
|
|
58
|
-
const dotInterval = setInterval(() => {
|
|
59
|
-
setDotIndex((prev) => (prev + 1) % DOT_ANIMATIONS.length);
|
|
60
|
-
}, 100);
|
|
61
|
-
return () => clearInterval(dotInterval);
|
|
62
|
-
}, []);
|
|
63
|
-
const seconds = (elapsed / 1000).toFixed(1);
|
|
64
|
-
const animatedDots = DOT_ANIMATIONS[dotIndex];
|
|
65
|
-
return (_jsxs("span", { className: "text-muted-foreground text-paragraph-sm", children: [thinkingWord, animatedDots, " ", seconds, "s"] }));
|
|
66
|
-
}
|
|
67
14
|
const messageContentVariants = cva("w-full rounded-2xl text-[var(--font-size)] font-[var(--font-family)] leading-relaxed break-words transition-colors", {
|
|
68
15
|
variants: {
|
|
69
16
|
role: {
|
|
@@ -100,7 +47,7 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
100
47
|
const hasThinking = !!thinking;
|
|
101
48
|
// Check if waiting (streaming but no content yet)
|
|
102
49
|
const isWaiting = message.isStreaming && !message.content && message.role === "assistant";
|
|
103
|
-
content = (_jsxs(_Fragment, { children: [message.role === "assistant" && hasThinking && (_jsx(Reasoning, { content: thinking, isStreaming: message.isStreaming, mode: thinkingDisplayStyle, autoCollapse: true })), isWaiting && streamingStartTime && (
|
|
50
|
+
content = (_jsxs(_Fragment, { children: [message.role === "assistant" && hasThinking && (_jsx(Reasoning, { content: thinking, isStreaming: message.isStreaming, mode: thinkingDisplayStyle, autoCollapse: true })), isWaiting && streamingStartTime && (_jsx("div", { className: "flex items-center gap-2", children: _jsx("div", { className: "size-2.5 rounded-full bg-foreground animate-pulse-scale" }) })), message.role === "assistant" ? ((() => {
|
|
104
51
|
// Sort tool calls by content position
|
|
105
52
|
const sortedToolCalls = (message.toolCalls || [])
|
|
106
53
|
.slice()
|
|
@@ -137,5 +137,5 @@ export function ToolCall({ toolCall }) {
|
|
|
137
137
|
return (_jsxs("div", { className: "bg-neutral-900 text-neutral-100 p-2 rounded text-[11px] font-mono", children: ["Terminal: ", block.terminalId] }, getBlockKey()));
|
|
138
138
|
}
|
|
139
139
|
return null;
|
|
140
|
-
}), toolCall.error && (_jsxs("div", { className: "text-destructive font-mono text-[11px] mt-2", children: ["Error: ", toolCall.error] }))] })] })) : null, (toolCall.tokenUsage || toolCall.startedAt) && (_jsxs("div", { className: "p-2 bg-muted/50 border-t border-border flex flex-wrap gap-4 text-[10px] text-muted-foreground font-sans", children: [toolCall.tokenUsage && (_jsxs("div", { className: "flex gap-3", children: [toolCall.tokenUsage.inputTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Input:" }), toolCall.tokenUsage.inputTokens.toLocaleString()] })), toolCall.tokenUsage.outputTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Output:" }), toolCall.tokenUsage.outputTokens.toLocaleString()] })), toolCall.tokenUsage.totalTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Total:" }), toolCall.tokenUsage.totalTokens.toLocaleString()] }))] })), toolCall.startedAt && (_jsxs("div", { className: "flex gap-3 ml-auto", children: [_jsxs("span", { children: ["Started: ", new Date(toolCall.startedAt).toLocaleTimeString()] }), toolCall.completedAt && (_jsxs("span", { children: ["Completed:", " ", new Date(toolCall.completedAt).toLocaleTimeString(), " (", Math.round((toolCall.completedAt - toolCall.startedAt) / 1000), "s)"] }))] }))] }))] }))] }));
|
|
140
|
+
}), toolCall.error && (_jsxs("div", { className: "text-destructive font-mono text-[11px] mt-2", children: ["Error: ", toolCall.error] }))] })] })) : null, toolCall._meta?.truncationWarning && (_jsxs("div", { className: "mx-3 mt-3 mb-0 flex items-center gap-2 rounded-md bg-yellow-50 dark:bg-yellow-950/20 px-3 py-2 text-[11px] text-yellow-800 dark:text-yellow-200 border border-yellow-200 dark:border-yellow-900", children: [_jsx("span", { className: "text-yellow-600 dark:text-yellow-500", children: "\u26A0\uFE0F" }), _jsx("span", { children: toolCall._meta.truncationWarning })] })), (toolCall.tokenUsage || toolCall.startedAt) && (_jsxs("div", { className: "p-2 bg-muted/50 border-t border-border flex flex-wrap gap-4 text-[10px] text-muted-foreground font-sans", children: [toolCall.tokenUsage && (_jsxs("div", { className: "flex gap-3", children: [toolCall.tokenUsage.inputTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Input:" }), toolCall.tokenUsage.inputTokens.toLocaleString()] })), toolCall.tokenUsage.outputTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Output:" }), toolCall.tokenUsage.outputTokens.toLocaleString()] })), toolCall.tokenUsage.totalTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Total:" }), toolCall.tokenUsage.totalTokens.toLocaleString()] }))] })), toolCall.startedAt && (_jsxs("div", { className: "flex gap-3 ml-auto", children: [_jsxs("span", { children: ["Started: ", new Date(toolCall.startedAt).toLocaleTimeString()] }), toolCall.completedAt && (_jsxs("span", { children: ["Completed:", " ", new Date(toolCall.completedAt).toLocaleTimeString(), " (", Math.round((toolCall.completedAt - toolCall.startedAt) / 1000), "s)"] }))] }))] }))] }))] }));
|
|
141
141
|
}
|
|
@@ -76,6 +76,18 @@ export declare class AcpClient {
|
|
|
76
76
|
* Subscribe to errors
|
|
77
77
|
*/
|
|
78
78
|
onError(handler: (error: Error) => void): () => void;
|
|
79
|
+
/**
|
|
80
|
+
* Get agent information
|
|
81
|
+
* - displayName: Human-readable name for UI (preferred)
|
|
82
|
+
* - name: Programmatic name (fallback if displayName not set)
|
|
83
|
+
*/
|
|
84
|
+
getAgentInfo(): {
|
|
85
|
+
name?: string;
|
|
86
|
+
displayName?: string;
|
|
87
|
+
version?: string;
|
|
88
|
+
description?: string;
|
|
89
|
+
suggestedPrompts?: string[];
|
|
90
|
+
};
|
|
79
91
|
/**
|
|
80
92
|
* Create transport based on explicit configuration
|
|
81
93
|
*/
|
|
@@ -57,6 +57,7 @@ export class AcpClient {
|
|
|
57
57
|
if (transportSessionId) {
|
|
58
58
|
// Use the session ID from the transport
|
|
59
59
|
const now = new Date().toISOString();
|
|
60
|
+
const agentName = this.transport.getAgentInfo?.()?.name;
|
|
60
61
|
const session = {
|
|
61
62
|
id: transportSessionId,
|
|
62
63
|
status: "connected",
|
|
@@ -66,6 +67,7 @@ export class AcpClient {
|
|
|
66
67
|
messages: [],
|
|
67
68
|
metadata: {
|
|
68
69
|
startedAt: now,
|
|
70
|
+
agentName,
|
|
69
71
|
},
|
|
70
72
|
};
|
|
71
73
|
this.sessions.set(transportSessionId, session);
|
|
@@ -75,6 +77,7 @@ export class AcpClient {
|
|
|
75
77
|
// Fallback: generate session ID (for transports that don't auto-create sessions)
|
|
76
78
|
const sessionId = this.generateSessionId();
|
|
77
79
|
const now = new Date().toISOString();
|
|
80
|
+
const agentName = this.transport.getAgentInfo?.()?.name;
|
|
78
81
|
const session = {
|
|
79
82
|
id: sessionId,
|
|
80
83
|
status: "connecting",
|
|
@@ -84,6 +87,7 @@ export class AcpClient {
|
|
|
84
87
|
messages: [],
|
|
85
88
|
metadata: {
|
|
86
89
|
startedAt: now,
|
|
90
|
+
agentName,
|
|
87
91
|
},
|
|
88
92
|
};
|
|
89
93
|
this.sessions.set(sessionId, session);
|
|
@@ -116,6 +120,13 @@ export class AcpClient {
|
|
|
116
120
|
this.currentSessionId = sessionId;
|
|
117
121
|
// Load session from transport (will replay messages via session updates)
|
|
118
122
|
await this.transport.loadSession(sessionId);
|
|
123
|
+
// Update metadata with agent name if available
|
|
124
|
+
if (this.transport.getAgentInfo) {
|
|
125
|
+
const agentInfo = this.transport.getAgentInfo();
|
|
126
|
+
if (agentInfo.name && session.metadata) {
|
|
127
|
+
session.metadata.agentName = agentInfo.name;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
119
130
|
// Update session status
|
|
120
131
|
this.updateSessionStatus(sessionId, "connected");
|
|
121
132
|
return sessionId;
|
|
@@ -200,6 +211,14 @@ export class AcpClient {
|
|
|
200
211
|
this.errorHandlers.delete(handler);
|
|
201
212
|
};
|
|
202
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Get agent information
|
|
216
|
+
* - displayName: Human-readable name for UI (preferred)
|
|
217
|
+
* - name: Programmatic name (fallback if displayName not set)
|
|
218
|
+
*/
|
|
219
|
+
getAgentInfo() {
|
|
220
|
+
return this.transport.getAgentInfo?.() || {};
|
|
221
|
+
}
|
|
203
222
|
/**
|
|
204
223
|
* Create transport based on explicit configuration
|
|
205
224
|
*/
|