@brainpilot/web 0.0.3 → 0.0.5
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/assets/index-C-8G4D4j.js +448 -0
- package/dist/assets/index-C501m5OS.css +1 -0
- package/dist/index.html +2 -2
- package/index.html +13 -0
- package/package.json +9 -3
- package/src/App.tsx +10 -0
- package/src/__tests__/api.test.ts +103 -0
- package/src/__tests__/messageGroups.test.ts +80 -0
- package/src/__tests__/newUiComponents.test.tsx +101 -0
- package/src/__tests__/newUiEvents.test.ts +236 -0
- package/src/components/chat/AskUserCard.tsx +123 -0
- package/src/components/chat/AutoRetryIndicator.tsx +71 -0
- package/src/components/chat/ComposerInput.tsx +73 -0
- package/src/components/chat/ComposerSendButton.tsx +26 -0
- package/src/components/chat/MarkdownMessage.tsx +24 -0
- package/src/components/chat/MessageStream.tsx +464 -0
- package/src/components/chat/PromptComposer.tsx +398 -0
- package/src/components/chat/SystemMessageBubble.tsx +46 -0
- package/src/components/demo/DemoFileTree.tsx +146 -0
- package/src/components/demo/DemoView.tsx +668 -0
- package/src/components/demo/TraceNodeModal.tsx +76 -0
- package/src/components/demo/demoBundle.ts +218 -0
- package/src/components/demo/demoCache.ts +42 -0
- package/src/components/files/FilePreviewView.tsx +153 -0
- package/src/components/files/FileSidebar.tsx +664 -0
- package/src/components/files/filePreview.ts +113 -0
- package/src/components/primitives/CustomSelect.tsx +200 -0
- package/src/components/primitives/IconButton.tsx +27 -0
- package/src/components/quota/DiskQuotaCriticalDialog.tsx +56 -0
- package/src/components/quota/DiskQuotaWarningDialog.tsx +65 -0
- package/src/components/quota/QuotaFileManager.tsx +197 -0
- package/src/components/search/SearchDialog.tsx +101 -0
- package/src/components/session/AgentNetwork.tsx +1240 -0
- package/src/components/session/AgentTraceViews.tsx +381 -0
- package/src/components/session/AnalyticsTab.tsx +386 -0
- package/src/components/session/GlobalOverview.tsx +108 -0
- package/src/components/session/NodeTooltip.tsx +127 -0
- package/src/components/session/TimelineTab.tsx +320 -0
- package/src/components/session/TraceGraphView.tsx +301 -0
- package/src/components/session/TraceNodeDetail.tsx +142 -0
- package/src/components/session/agentAnalytics.ts +397 -0
- package/src/components/session/agentNetworkShared.ts +329 -0
- package/src/components/session/traceLayout.ts +150 -0
- package/src/components/settings/SettingsDialog.tsx +719 -0
- package/src/components/shell/DesktopShell.tsx +236 -0
- package/src/components/shell/SandboxBuildingOverlay.tsx +73 -0
- package/src/components/shell/SandboxStatus.tsx +287 -0
- package/src/components/shell/TerminalDrawer.tsx +387 -0
- package/src/components/sidebar/Sidebar.tsx +187 -0
- package/src/config.ts +10 -0
- package/src/contexts/AppProviders.tsx +20 -0
- package/src/contexts/AuthContext.tsx +61 -0
- package/src/contexts/PreferencesContext.tsx +125 -0
- package/src/contexts/SSEContext.tsx +175 -0
- package/src/contexts/SandboxContext.tsx +310 -0
- package/src/contexts/SessionContext.tsx +608 -0
- package/src/contexts/draftStore.ts +103 -0
- package/src/contexts/messageFilters.ts +29 -0
- package/src/contexts/messageGroups.ts +77 -0
- package/src/contexts/messageReducer.ts +401 -0
- package/src/contexts/newUiEvents.ts +190 -0
- package/src/contracts/backend.ts +846 -0
- package/src/contracts/demoBundle.ts +83 -0
- package/src/i18n/messages/analytics.ts +96 -0
- package/src/i18n/messages/chat.ts +108 -0
- package/src/i18n/messages/contexts.ts +40 -0
- package/src/i18n/messages/demo.ts +80 -0
- package/src/i18n/messages/files.ts +82 -0
- package/src/i18n/messages/network.ts +186 -0
- package/src/i18n/messages/profile.ts +40 -0
- package/src/i18n/messages/quota.ts +36 -0
- package/src/i18n/messages/sandbox.ts +116 -0
- package/src/i18n/messages/search.ts +16 -0
- package/src/i18n/messages/settings.ts +184 -0
- package/src/i18n/messages/shell.ts +38 -0
- package/src/i18n/messages/sidebar.ts +52 -0
- package/src/i18n/messages/terminal.ts +22 -0
- package/src/i18n/messages/trace.ts +84 -0
- package/src/i18n/messages.ts +32 -0
- package/src/i18n/translate.ts +46 -0
- package/src/i18n/types.ts +15 -0
- package/src/i18n/useT.ts +15 -0
- package/src/main.tsx +13 -0
- package/src/mocks/backend.ts +722 -0
- package/src/styles/global.css +7429 -0
- package/src/styles/tokens.css +161 -0
- package/src/utils/api.ts +627 -0
- package/src/utils/download.ts +18 -0
- package/src/utils/format.ts +7 -0
- package/src/utils/zip.ts +119 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.app.json +22 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +13 -0
- package/vite.config.ts +13 -0
- package/dist/assets/index-Cd0Mi_WU.css +0 -1
- package/dist/assets/index-FGg-DeYR.js +0 -448
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Protocol is the single source of truth for wire/domain types.
|
|
3
|
+
//
|
|
4
|
+
// `@brainpilot/protocol` owns the canonical domain models (camelCase,
|
|
5
|
+
// post-normalize shape) and the AG-UI wire event union (snake_case). We import
|
|
6
|
+
// the domain types here and re-export them so the rest of `src/` keeps importing
|
|
7
|
+
// from "../contracts/backend" unchanged. This file is now a thin adapter: it
|
|
8
|
+
// keeps the `normalize*`/`serialize*` functions, the UI-only types the protocol
|
|
9
|
+
// does not model (Sandbox*, ChatMessage, raw API shapes, message filters), and
|
|
10
|
+
// the *post-normalize camelCase* AG-UI event/message shapes the components rely
|
|
11
|
+
// on (protocol models the snake_case wire shape, which differs — see below).
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
import type {
|
|
14
|
+
Session,
|
|
15
|
+
AgentStatus,
|
|
16
|
+
SessionStateSnapshot,
|
|
17
|
+
SettingsData,
|
|
18
|
+
McpServerEntry,
|
|
19
|
+
ModelHealth,
|
|
20
|
+
ProviderProfile,
|
|
21
|
+
FileEntry,
|
|
22
|
+
FileContent,
|
|
23
|
+
TraceNode,
|
|
24
|
+
TraceNodeStatus,
|
|
25
|
+
TraceParent,
|
|
26
|
+
TraceArtifact,
|
|
27
|
+
TraceTimestamp,
|
|
28
|
+
TraceGraph,
|
|
29
|
+
} from "@brainpilot/protocol";
|
|
30
|
+
|
|
31
|
+
// Re-export the canonical protocol domain types under their existing names so
|
|
32
|
+
// all `import { … } from "../contracts/backend"` sites continue to resolve.
|
|
33
|
+
export type {
|
|
34
|
+
Session,
|
|
35
|
+
AgentStatus,
|
|
36
|
+
SessionStateSnapshot,
|
|
37
|
+
SettingsData,
|
|
38
|
+
McpServerEntry,
|
|
39
|
+
ModelHealth,
|
|
40
|
+
ProviderProfile,
|
|
41
|
+
FileEntry,
|
|
42
|
+
FileContent,
|
|
43
|
+
TraceNode,
|
|
44
|
+
TraceNodeStatus,
|
|
45
|
+
TraceParent,
|
|
46
|
+
TraceArtifact,
|
|
47
|
+
TraceTimestamp,
|
|
48
|
+
TraceGraph,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type SandboxStatus =
|
|
52
|
+
| "creating"
|
|
53
|
+
| "running"
|
|
54
|
+
| "stopped"
|
|
55
|
+
| "error"
|
|
56
|
+
| "quota_exceeded"
|
|
57
|
+
| string;
|
|
58
|
+
|
|
59
|
+
export interface User {
|
|
60
|
+
id: string;
|
|
61
|
+
username: string;
|
|
62
|
+
createdAt: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface AuthToken {
|
|
66
|
+
accessToken: string;
|
|
67
|
+
tokenType: string;
|
|
68
|
+
user: User;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface Sandbox {
|
|
72
|
+
id: string;
|
|
73
|
+
name: string;
|
|
74
|
+
status: SandboxStatus;
|
|
75
|
+
port: number | null;
|
|
76
|
+
userId: string;
|
|
77
|
+
createdAt: string;
|
|
78
|
+
containerName?: string;
|
|
79
|
+
hostApiUrl?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface SandboxStats {
|
|
83
|
+
sandboxId: string;
|
|
84
|
+
sandboxName: string;
|
|
85
|
+
status: SandboxStatus;
|
|
86
|
+
memory: {
|
|
87
|
+
usedBytes: number;
|
|
88
|
+
limitBytes: number;
|
|
89
|
+
percent: number;
|
|
90
|
+
};
|
|
91
|
+
cpu: {
|
|
92
|
+
usedPercent: number;
|
|
93
|
+
quotaPercent: number;
|
|
94
|
+
onlineCpus: number;
|
|
95
|
+
};
|
|
96
|
+
pids: {
|
|
97
|
+
current: number;
|
|
98
|
+
limit: number | null;
|
|
99
|
+
};
|
|
100
|
+
disk: {
|
|
101
|
+
workspaceUsedBytes: number;
|
|
102
|
+
quotaBytes: number;
|
|
103
|
+
percentOfQuota: number;
|
|
104
|
+
};
|
|
105
|
+
gpu?: {
|
|
106
|
+
name?: string;
|
|
107
|
+
memoryUsedBytes?: number;
|
|
108
|
+
memoryTotalBytes?: number;
|
|
109
|
+
utilizationPercent?: number;
|
|
110
|
+
} | null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// FileEntry, FileContent, Session — now imported from @brainpilot/protocol (see top).
|
|
114
|
+
|
|
115
|
+
export interface ChatMessage {
|
|
116
|
+
id: string;
|
|
117
|
+
role: "user" | "assistant" | "system";
|
|
118
|
+
content: string;
|
|
119
|
+
createdAt: string;
|
|
120
|
+
agent?: string;
|
|
121
|
+
streaming?: boolean;
|
|
122
|
+
kind?: "text" | "thinking" | "tool" | "error" | "status" | "hook" | "system_message" | "ask_user" | "auto_retry";
|
|
123
|
+
toolName?: string;
|
|
124
|
+
toolInput?: unknown;
|
|
125
|
+
toolResult?: unknown;
|
|
126
|
+
reasoning?: string;
|
|
127
|
+
toolCallId?: string;
|
|
128
|
+
// Hook diagnostic event metadata — set when kind === "hook"
|
|
129
|
+
hookFamily?: string; // "expert_reply" | "principal_trace"
|
|
130
|
+
hookPhase?: string; // "reset_clean" | "reset_dirty" | "flag_set" | "reminder" | "fallback"
|
|
131
|
+
hookLevel?: string; // "debug" | "info" | "warning" | "error"
|
|
132
|
+
hookData?: Record<string, unknown>;
|
|
133
|
+
// ── 修正6 new-UI payloads (post-normalize, camelCase view shape) ──
|
|
134
|
+
// kind === "system_message": 4-level styled bubble (doc §6)
|
|
135
|
+
systemMessage?: SystemMessageView;
|
|
136
|
+
// kind === "ask_user": interactive user_input_request card (doc §6)
|
|
137
|
+
askUser?: AskUserView;
|
|
138
|
+
// kind === "auto_retry": auto-retry countdown + cancel indicator (doc §6)
|
|
139
|
+
autoRetry?: AutoRetryView;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** View-model for a `system_message` AG-UI event (post-normalize). */
|
|
143
|
+
export interface SystemMessageView {
|
|
144
|
+
level: "info" | "warning" | "error" | "fatal";
|
|
145
|
+
message: string;
|
|
146
|
+
details?: string;
|
|
147
|
+
agent?: string;
|
|
148
|
+
/** fatal events are non-recoverable; drives the emphasized red styling. */
|
|
149
|
+
recoverable: boolean;
|
|
150
|
+
timestamp?: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** View-model for a `user_input_request` AG-UI event (ask_user, post-normalize). */
|
|
154
|
+
export interface AskUserView {
|
|
155
|
+
requestId: string;
|
|
156
|
+
agent: string;
|
|
157
|
+
question: string;
|
|
158
|
+
options?: string[];
|
|
159
|
+
allowFreeText?: boolean;
|
|
160
|
+
timeoutSec?: number;
|
|
161
|
+
/** Set once the user has answered, so the card renders as resolved. */
|
|
162
|
+
answer?: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** View-model for an auto-retry indicator, surfaced from Pi `auto_retry_start`. */
|
|
166
|
+
export interface AutoRetryView {
|
|
167
|
+
attempt: number;
|
|
168
|
+
maxAttempts: number;
|
|
169
|
+
delayMs: number;
|
|
170
|
+
reason?: string;
|
|
171
|
+
/** Set once cancelled / superseded, so the countdown stops. */
|
|
172
|
+
cancelled?: boolean;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** A predicate that decides whether a message should be hidden from display. */
|
|
176
|
+
export interface MessageFilterRule {
|
|
177
|
+
id: string;
|
|
178
|
+
name: string;
|
|
179
|
+
description: string;
|
|
180
|
+
enabled: boolean;
|
|
181
|
+
/** Returns true if the message should be HIDDEN. */
|
|
182
|
+
test: (msg: ChatMessage, allMessages: ChatMessage[]) => boolean;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Serializable filter configuration (for persistence / settings). */
|
|
186
|
+
export interface MessageFilterConfig {
|
|
187
|
+
id: string;
|
|
188
|
+
enabled: boolean;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// TraceParent, TraceArtifact, TraceTimestamp, AgentStatus, SessionStateSnapshot,
|
|
192
|
+
// SettingsData, McpServerEntry, ModelHealth, ProviderProfile — now imported from
|
|
193
|
+
// @brainpilot/protocol (see top).
|
|
194
|
+
|
|
195
|
+
export interface ProviderCreate {
|
|
196
|
+
name: string;
|
|
197
|
+
baseUrl: string;
|
|
198
|
+
apiKey: string;
|
|
199
|
+
models?: string[];
|
|
200
|
+
icon?: string;
|
|
201
|
+
iconColor?: string;
|
|
202
|
+
notes?: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface ProviderUpdate {
|
|
206
|
+
name?: string;
|
|
207
|
+
baseUrl?: string;
|
|
208
|
+
apiKey?: string;
|
|
209
|
+
models?: string[];
|
|
210
|
+
icon?: string;
|
|
211
|
+
iconColor?: string;
|
|
212
|
+
notes?: string;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// TraceNodeStatus, TraceNode, TraceGraph — now imported from @brainpilot/protocol (see top).
|
|
216
|
+
|
|
217
|
+
export type SessionMessagePart =
|
|
218
|
+
| string
|
|
219
|
+
| { type?: "text"; text?: string }
|
|
220
|
+
| { type?: "thinking"; thinking?: string }
|
|
221
|
+
| { type?: "tool_use"; id?: string; name?: string; input?: unknown }
|
|
222
|
+
| { type?: "tool_result"; tool_use_id?: string; content?: unknown; is_error?: boolean };
|
|
223
|
+
|
|
224
|
+
export interface SessionMessageEntry {
|
|
225
|
+
type: string;
|
|
226
|
+
timestamp?: string;
|
|
227
|
+
uuid?: string;
|
|
228
|
+
isMeta?: boolean;
|
|
229
|
+
sourceToolUseID?: string;
|
|
230
|
+
message?: {
|
|
231
|
+
role?: string;
|
|
232
|
+
content?: string | SessionMessagePart[];
|
|
233
|
+
agent?: string;
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// TODO(dead-code): SessionEventsResponse / SessionEventEntry / normalizeSessionEvent
|
|
238
|
+
// are leftovers from the pre-AG-UI polling protocol (removed in 2026-05-18).
|
|
239
|
+
// Kept commented out for now; will be fully deleted once issue-4 fallback removal lands.
|
|
240
|
+
// export interface SessionEventsResponse {
|
|
241
|
+
// events: SessionEventEntry[];
|
|
242
|
+
// nextOffset: number;
|
|
243
|
+
// hasMore: boolean;
|
|
244
|
+
// }
|
|
245
|
+
//
|
|
246
|
+
// export interface SessionEventEntry {
|
|
247
|
+
// seq: number;
|
|
248
|
+
// timestamp: string;
|
|
249
|
+
// type: string;
|
|
250
|
+
// sessionId?: string;
|
|
251
|
+
// data: Record<string, unknown>;
|
|
252
|
+
// }
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* AG-UI message shape (as it appears in MESSAGES_SNAPSHOT.messages).
|
|
256
|
+
* Mirrors `Message` in /root/ag-ui/sdks/typescript/packages/core/src/types.ts.
|
|
257
|
+
*/
|
|
258
|
+
export interface AgUiMessage {
|
|
259
|
+
id: string;
|
|
260
|
+
role: string;
|
|
261
|
+
content?: string;
|
|
262
|
+
toolCalls?: Array<{ id: string; name: string; arguments: string }>;
|
|
263
|
+
toolCallId?: string;
|
|
264
|
+
error?: boolean;
|
|
265
|
+
// MAS extension: source agent for this message. Populated by `fold.py` for
|
|
266
|
+
// MESSAGES_SNAPSHOT so refreshes preserve Expert attribution. Camelized
|
|
267
|
+
// automatically from the wire's `agent_name` by `normalizeAgUiEvent`.
|
|
268
|
+
agentName?: string;
|
|
269
|
+
// MAS extension: true iff fold.py saw `*_START` but no matching `*_END`
|
|
270
|
+
// (and no `RUN_FINISHED/RUN_ERROR` terminator). Used by the snapshot path to
|
|
271
|
+
// resume the streaming indicator on the in-progress message after refresh.
|
|
272
|
+
unfinished?: boolean;
|
|
273
|
+
// MAS extension: message kind for snapshot path parity with live path.
|
|
274
|
+
kind?: string;
|
|
275
|
+
hookFamily?: string;
|
|
276
|
+
hookPhase?: string;
|
|
277
|
+
hookLevel?: string;
|
|
278
|
+
hookData?: Record<string, unknown>;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* AG-UI canonical event. Flat shape — no `data` wrapper. Field set varies by
|
|
283
|
+
* `type`; consumers should narrow before accessing event-specific fields.
|
|
284
|
+
*
|
|
285
|
+
* The full AG-UI EventType enum is defined upstream
|
|
286
|
+
* (`/root/ag-ui/sdks/typescript/packages/core/src/events.ts`); we only model
|
|
287
|
+
* the subset we emit.
|
|
288
|
+
*/
|
|
289
|
+
export interface AgUiEvent {
|
|
290
|
+
type: string;
|
|
291
|
+
runId?: string;
|
|
292
|
+
threadId?: string;
|
|
293
|
+
sessionId?: string;
|
|
294
|
+
agentName?: string;
|
|
295
|
+
|
|
296
|
+
// Text/reasoning messages
|
|
297
|
+
messageId?: string;
|
|
298
|
+
role?: string;
|
|
299
|
+
delta?: string;
|
|
300
|
+
|
|
301
|
+
// Tool calls
|
|
302
|
+
toolCallId?: string;
|
|
303
|
+
toolCallName?: string;
|
|
304
|
+
parentMessageId?: string;
|
|
305
|
+
content?: string; // TOOL_CALL_RESULT
|
|
306
|
+
isError?: boolean;
|
|
307
|
+
|
|
308
|
+
// RUN_*
|
|
309
|
+
message?: string; // RUN_ERROR
|
|
310
|
+
code?: string; // RUN_ERROR
|
|
311
|
+
result?: unknown; // RUN_FINISHED
|
|
312
|
+
parentRunId?: string;
|
|
313
|
+
|
|
314
|
+
// MESSAGES_SNAPSHOT
|
|
315
|
+
messages?: AgUiMessage[];
|
|
316
|
+
|
|
317
|
+
// CUSTOM
|
|
318
|
+
name?: string;
|
|
319
|
+
value?: unknown;
|
|
320
|
+
|
|
321
|
+
// Forward-compatible extras
|
|
322
|
+
[extra: string]: unknown;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Back-compat alias — components historically import `WebSocketEvent`. We keep
|
|
327
|
+
* the name as an alias of `AgUiEvent` so the rest of the codebase compiles
|
|
328
|
+
* without a sweeping rename.
|
|
329
|
+
*/
|
|
330
|
+
export type WebSocketEvent = AgUiEvent;
|
|
331
|
+
|
|
332
|
+
interface RawUser {
|
|
333
|
+
id?: string;
|
|
334
|
+
username?: string;
|
|
335
|
+
created_at?: string;
|
|
336
|
+
createdAt?: string;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
interface RawToken {
|
|
340
|
+
access_token?: string;
|
|
341
|
+
accessToken?: string;
|
|
342
|
+
token_type?: string;
|
|
343
|
+
tokenType?: string;
|
|
344
|
+
user?: RawUser;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
interface RawSandbox {
|
|
348
|
+
id?: string;
|
|
349
|
+
name?: string;
|
|
350
|
+
sandbox_name?: string;
|
|
351
|
+
status?: string;
|
|
352
|
+
port?: number | null;
|
|
353
|
+
user_id?: string;
|
|
354
|
+
userId?: string;
|
|
355
|
+
created_at?: string;
|
|
356
|
+
createdAt?: string;
|
|
357
|
+
container_name?: string;
|
|
358
|
+
containerName?: string;
|
|
359
|
+
host_api_url?: string;
|
|
360
|
+
hostApiUrl?: string;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
interface RawSession {
|
|
364
|
+
id?: string;
|
|
365
|
+
title?: string;
|
|
366
|
+
created_at?: string;
|
|
367
|
+
createdAt?: string;
|
|
368
|
+
updated_at?: string;
|
|
369
|
+
updatedAt?: string;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
interface RawFileEntry {
|
|
373
|
+
name?: string;
|
|
374
|
+
type?: string;
|
|
375
|
+
size?: number;
|
|
376
|
+
modified?: number | string;
|
|
377
|
+
permissions?: string;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
interface RawSettingsData {
|
|
381
|
+
model?: string;
|
|
382
|
+
api_key?: string;
|
|
383
|
+
apiKey?: string;
|
|
384
|
+
base_url?: string;
|
|
385
|
+
baseUrl?: string;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
interface RawModelHealth {
|
|
389
|
+
model?: string;
|
|
390
|
+
status?: string;
|
|
391
|
+
latency_ms?: number;
|
|
392
|
+
latencyMs?: number;
|
|
393
|
+
checked_at?: number;
|
|
394
|
+
checkedAt?: number;
|
|
395
|
+
error?: string;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
interface RawProviderProfile {
|
|
399
|
+
id?: string;
|
|
400
|
+
name?: string;
|
|
401
|
+
base_url?: string;
|
|
402
|
+
baseUrl?: string;
|
|
403
|
+
models?: string[];
|
|
404
|
+
icon?: string;
|
|
405
|
+
icon_color?: string;
|
|
406
|
+
iconColor?: string;
|
|
407
|
+
notes?: string;
|
|
408
|
+
is_active?: boolean;
|
|
409
|
+
isActive?: boolean;
|
|
410
|
+
api_key_masked?: string;
|
|
411
|
+
apiKeyMasked?: string;
|
|
412
|
+
created_at?: number;
|
|
413
|
+
createdAt?: number;
|
|
414
|
+
updated_at?: number;
|
|
415
|
+
updatedAt?: number;
|
|
416
|
+
health_status?: string;
|
|
417
|
+
healthStatus?: string;
|
|
418
|
+
health_checked_at?: number;
|
|
419
|
+
healthCheckedAt?: number;
|
|
420
|
+
model_health?: RawModelHealth[];
|
|
421
|
+
modelHealth?: RawModelHealth[];
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
type Dict = Record<string, unknown>;
|
|
425
|
+
|
|
426
|
+
function stringValue(value: unknown, fallback = ""): string {
|
|
427
|
+
return typeof value === "string" && value.length > 0 ? value : fallback;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function optionalString(value: unknown): string | undefined {
|
|
431
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function numberValue(value: unknown, fallback = 0): number {
|
|
435
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function optionalNumber(value: unknown): number | undefined {
|
|
439
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function isoValue(value: unknown): string {
|
|
443
|
+
return typeof value === "string" && value.length > 0 ? value : new Date().toISOString();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function asDict(value: unknown): Dict {
|
|
447
|
+
return value && typeof value === "object" && !Array.isArray(value) ? (value as Dict) : {};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function asOptionalUnknownRecord(value: unknown): Record<string, unknown> | undefined {
|
|
451
|
+
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function asOptionalRecord(value: unknown): Record<string, string> | undefined {
|
|
455
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
458
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
459
|
+
.filter((entry): entry is [string, string] => typeof entry[1] === "string");
|
|
460
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function normalizeStringArray(value: unknown): string[] {
|
|
464
|
+
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function camelizeKey(key: string): string {
|
|
468
|
+
return key.replace(/_([a-z])/g, (_, char: string) => char.toUpperCase());
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function camelizeObject(value: unknown): unknown {
|
|
472
|
+
if (Array.isArray(value)) {
|
|
473
|
+
return value.map(camelizeObject);
|
|
474
|
+
}
|
|
475
|
+
if (!value || typeof value !== "object") {
|
|
476
|
+
return value;
|
|
477
|
+
}
|
|
478
|
+
return Object.fromEntries(
|
|
479
|
+
Object.entries(value as Dict).map(([key, nested]) => [camelizeKey(key), camelizeObject(nested)]),
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function normalizeUser(raw: RawUser): User {
|
|
484
|
+
const username = stringValue(raw.username, "user");
|
|
485
|
+
return {
|
|
486
|
+
id: stringValue(raw.id, username),
|
|
487
|
+
username,
|
|
488
|
+
createdAt: isoValue(raw.createdAt ?? raw.created_at),
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export function normalizeToken(raw: RawToken): AuthToken {
|
|
493
|
+
return {
|
|
494
|
+
accessToken: stringValue(raw.accessToken ?? raw.access_token),
|
|
495
|
+
tokenType: stringValue(raw.tokenType ?? raw.token_type, "bearer"),
|
|
496
|
+
user: normalizeUser(raw.user ?? {}),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export function normalizeSandbox(raw: RawSandbox): Sandbox {
|
|
501
|
+
return {
|
|
502
|
+
id: stringValue(raw.id),
|
|
503
|
+
name: stringValue(raw.name ?? raw.sandbox_name, "default"),
|
|
504
|
+
status: stringValue(raw.status, "stopped"),
|
|
505
|
+
port: typeof raw.port === "number" ? raw.port : null,
|
|
506
|
+
userId: stringValue(raw.userId ?? raw.user_id),
|
|
507
|
+
createdAt: isoValue(raw.createdAt ?? raw.created_at),
|
|
508
|
+
containerName: optionalString(raw.containerName ?? raw.container_name),
|
|
509
|
+
hostApiUrl: optionalString(raw.hostApiUrl ?? raw.host_api_url),
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export function normalizeSandboxStats(rawValue: unknown): SandboxStats {
|
|
514
|
+
const raw = asDict(rawValue);
|
|
515
|
+
const memory = asDict(raw.memory);
|
|
516
|
+
const cpu = asDict(raw.cpu);
|
|
517
|
+
const pids = asDict(raw.pids);
|
|
518
|
+
const disk = asDict(raw.disk);
|
|
519
|
+
const gpu = raw.gpu === null || raw.gpu === undefined ? null : asDict(raw.gpu);
|
|
520
|
+
|
|
521
|
+
return {
|
|
522
|
+
sandboxId: stringValue(raw.sandboxId ?? raw.sandbox_id),
|
|
523
|
+
sandboxName: stringValue(raw.sandboxName ?? raw.sandbox_name, "default"),
|
|
524
|
+
status: stringValue(raw.status, "stopped"),
|
|
525
|
+
memory: {
|
|
526
|
+
usedBytes: numberValue(memory.usedBytes ?? memory.used_bytes),
|
|
527
|
+
limitBytes: numberValue(memory.limitBytes ?? memory.limit_bytes),
|
|
528
|
+
percent: numberValue(memory.percent),
|
|
529
|
+
},
|
|
530
|
+
cpu: {
|
|
531
|
+
usedPercent: numberValue(cpu.usedPercent ?? cpu.used_percent),
|
|
532
|
+
quotaPercent: numberValue(cpu.quotaPercent ?? cpu.quota_percent),
|
|
533
|
+
onlineCpus: numberValue(cpu.onlineCpus ?? cpu.online_cpus),
|
|
534
|
+
},
|
|
535
|
+
pids: {
|
|
536
|
+
current: numberValue(pids.current),
|
|
537
|
+
limit: typeof pids.limit === "number" ? pids.limit : null,
|
|
538
|
+
},
|
|
539
|
+
disk: {
|
|
540
|
+
workspaceUsedBytes: numberValue(disk.workspaceUsedBytes ?? disk.workspace_used_bytes),
|
|
541
|
+
quotaBytes: numberValue(disk.quotaBytes ?? disk.quota_bytes),
|
|
542
|
+
percentOfQuota: numberValue(disk.percentOfQuota ?? disk.percent_of_quota),
|
|
543
|
+
},
|
|
544
|
+
gpu: gpu
|
|
545
|
+
? {
|
|
546
|
+
name: optionalString(gpu.name),
|
|
547
|
+
memoryUsedBytes: numberValue(gpu.memoryUsedBytes ?? gpu.memory_used_bytes),
|
|
548
|
+
memoryTotalBytes: numberValue(gpu.memoryTotalBytes ?? gpu.memory_total_bytes),
|
|
549
|
+
utilizationPercent: numberValue(gpu.utilizationPercent ?? gpu.utilization_percent),
|
|
550
|
+
}
|
|
551
|
+
: null,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export function normalizeFileEntry(raw: RawFileEntry): FileEntry {
|
|
556
|
+
const modified = typeof raw.modified === "string" ? Number(raw.modified) : raw.modified;
|
|
557
|
+
return {
|
|
558
|
+
name: stringValue(raw.name),
|
|
559
|
+
type: raw.type === "folder" || raw.type === "symlink" ? raw.type : "file",
|
|
560
|
+
size: numberValue(raw.size),
|
|
561
|
+
modified: numberValue(modified),
|
|
562
|
+
permissions: stringValue(raw.permissions, ""),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function normalizeFileContent(rawValue: unknown): FileContent {
|
|
567
|
+
const raw = asDict(rawValue);
|
|
568
|
+
return {
|
|
569
|
+
path: stringValue(raw.path),
|
|
570
|
+
content: stringValue(raw.content),
|
|
571
|
+
size: numberValue(raw.size),
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export function normalizeSession(raw: RawSession): Session {
|
|
576
|
+
const id = stringValue(raw.id);
|
|
577
|
+
return {
|
|
578
|
+
id,
|
|
579
|
+
title: stringValue(raw.title, id ? `Session ${id.slice(0, 8)}` : "Untitled session"),
|
|
580
|
+
createdAt: isoValue(raw.createdAt ?? raw.created_at),
|
|
581
|
+
updatedAt: isoValue(raw.updatedAt ?? raw.updated_at ?? raw.createdAt ?? raw.created_at),
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export function normalizeSettings(raw: RawSettingsData): SettingsData {
|
|
586
|
+
return {
|
|
587
|
+
model: stringValue(raw.model),
|
|
588
|
+
apiKey: stringValue(raw.apiKey ?? raw.api_key),
|
|
589
|
+
baseUrl: stringValue(raw.baseUrl ?? raw.base_url),
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
export function serializeSettings(data: Partial<SettingsData>): Record<string, string> {
|
|
593
|
+
return {
|
|
594
|
+
...(data.model !== undefined ? { model: data.model } : {}),
|
|
595
|
+
...(data.apiKey !== undefined ? { api_key: data.apiKey } : {}),
|
|
596
|
+
...(data.baseUrl !== undefined ? { base_url: data.baseUrl } : {}),
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
export function normalizeMcpServer(rawValue: unknown): McpServerEntry {
|
|
602
|
+
const raw = asDict(rawValue);
|
|
603
|
+
return {
|
|
604
|
+
name: stringValue(raw.name),
|
|
605
|
+
type: raw.type === "stdio" || raw.type === "sse" ? raw.type : "http",
|
|
606
|
+
command: optionalString(raw.command),
|
|
607
|
+
args: Array.isArray(raw.args) ? raw.args.filter((arg): arg is string => typeof arg === "string") : undefined,
|
|
608
|
+
env: asOptionalRecord(raw.env),
|
|
609
|
+
url: optionalString(raw.url),
|
|
610
|
+
headers: asOptionalRecord(raw.headers),
|
|
611
|
+
timeout: optionalNumber(raw.timeout),
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export function serializeMcpConfig(config: Omit<McpServerEntry, "name">): Record<string, unknown> {
|
|
616
|
+
return Object.fromEntries(Object.entries(config).filter(([, value]) => value !== undefined && value !== ""));
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function normalizeModelHealth(raw: RawModelHealth | undefined): ModelHealth {
|
|
620
|
+
if (!raw || typeof raw !== "object") {
|
|
621
|
+
return { model: "", status: "unknown" };
|
|
622
|
+
}
|
|
623
|
+
return {
|
|
624
|
+
model: stringValue(raw.model),
|
|
625
|
+
status: (raw.status as ModelHealth["status"]) || "unknown",
|
|
626
|
+
latencyMs: optionalNumber(raw.latencyMs ?? raw.latency_ms),
|
|
627
|
+
checkedAt: optionalNumber(raw.checkedAt ?? raw.checked_at),
|
|
628
|
+
error: optionalString(raw.error),
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export function normalizeProviderProfile(raw: RawProviderProfile): ProviderProfile {
|
|
633
|
+
const modelHealthRaw = Array.isArray(raw.modelHealth)
|
|
634
|
+
? raw.modelHealth
|
|
635
|
+
: Array.isArray(raw.model_health)
|
|
636
|
+
? raw.model_health
|
|
637
|
+
: [];
|
|
638
|
+
return {
|
|
639
|
+
id: stringValue(raw.id),
|
|
640
|
+
name: stringValue(raw.name),
|
|
641
|
+
baseUrl: stringValue(raw.baseUrl ?? raw.base_url),
|
|
642
|
+
models: Array.isArray(raw.models) ? raw.models : [],
|
|
643
|
+
icon: stringValue(raw.icon, "circle"),
|
|
644
|
+
iconColor: stringValue(raw.iconColor ?? raw.icon_color, "#111111"),
|
|
645
|
+
notes: stringValue(raw.notes),
|
|
646
|
+
isActive: Boolean(raw.isActive ?? raw.is_active),
|
|
647
|
+
apiKeyMasked: stringValue(raw.apiKeyMasked ?? raw.api_key_masked),
|
|
648
|
+
createdAt: numberValue(raw.createdAt ?? raw.created_at),
|
|
649
|
+
updatedAt: numberValue(raw.updatedAt ?? raw.updated_at),
|
|
650
|
+
healthStatus: (raw.healthStatus ?? raw.health_status ?? "unknown") as ProviderProfile["healthStatus"],
|
|
651
|
+
healthCheckedAt: optionalNumber(raw.healthCheckedAt ?? raw.health_checked_at),
|
|
652
|
+
modelHealth: modelHealthRaw.map(normalizeModelHealth),
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
export function serializeProviderCreate(data: ProviderCreate): Record<string, unknown> {
|
|
657
|
+
return {
|
|
658
|
+
name: data.name,
|
|
659
|
+
base_url: data.baseUrl,
|
|
660
|
+
api_key: data.apiKey,
|
|
661
|
+
models: data.models,
|
|
662
|
+
icon: data.icon,
|
|
663
|
+
icon_color: data.iconColor,
|
|
664
|
+
notes: data.notes,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export function serializeProviderUpdate(data: ProviderUpdate): Record<string, unknown> {
|
|
669
|
+
return {
|
|
670
|
+
...(data.name !== undefined ? { name: data.name } : {}),
|
|
671
|
+
...(data.baseUrl !== undefined ? { base_url: data.baseUrl } : {}),
|
|
672
|
+
...(data.apiKey !== undefined ? { api_key: data.apiKey } : {}),
|
|
673
|
+
...(data.models !== undefined ? { models: data.models } : {}),
|
|
674
|
+
...(data.icon !== undefined ? { icon: data.icon } : {}),
|
|
675
|
+
...(data.iconColor !== undefined ? { icon_color: data.iconColor } : {}),
|
|
676
|
+
...(data.notes !== undefined ? { notes: data.notes } : {}),
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function normalizeTraceNode(rawValue: unknown): TraceNode {
|
|
681
|
+
const raw = asDict(rawValue);
|
|
682
|
+
const parents = Array.isArray(raw.parents)
|
|
683
|
+
? raw.parents.map((parent) => {
|
|
684
|
+
const item = asDict(parent);
|
|
685
|
+
return {
|
|
686
|
+
id: stringValue(item.id),
|
|
687
|
+
relation: optionalString(item.relation),
|
|
688
|
+
explanation: optionalString(item.explanation),
|
|
689
|
+
edgeType: optionalString(item.edgeType ?? item.edge_type),
|
|
690
|
+
};
|
|
691
|
+
}).filter((parent) => parent.id)
|
|
692
|
+
: [];
|
|
693
|
+
const artifacts = Array.isArray(raw.artifacts)
|
|
694
|
+
? raw.artifacts.map((artifact) => {
|
|
695
|
+
const item = asDict(artifact);
|
|
696
|
+
return {
|
|
697
|
+
path: stringValue(item.path),
|
|
698
|
+
type: optionalString(item.type),
|
|
699
|
+
};
|
|
700
|
+
}).filter((artifact) => artifact.path)
|
|
701
|
+
: [];
|
|
702
|
+
const description = optionalString(raw.description);
|
|
703
|
+
const timestamp = asDict(raw.timestamp);
|
|
704
|
+
const parentIds = normalizeStringArray(raw.parentIds ?? raw.parent_ids);
|
|
705
|
+
return {
|
|
706
|
+
id: stringValue(raw.id ?? raw.node_id),
|
|
707
|
+
title: stringValue(raw.title, stringValue(raw.id ?? raw.node_id, "Trace node")),
|
|
708
|
+
type: stringValue(raw.type ?? raw.node_type, "step"),
|
|
709
|
+
nodeType: optionalString(raw.nodeType ?? raw.node_type),
|
|
710
|
+
status: stringValue(raw.status, "done"),
|
|
711
|
+
agent: optionalString(raw.agent ?? raw.agent_name),
|
|
712
|
+
description,
|
|
713
|
+
summary: optionalString(raw.summary) ?? description,
|
|
714
|
+
content: optionalString(raw.content) ?? description,
|
|
715
|
+
reason: optionalString(raw.reason),
|
|
716
|
+
context: optionalString(raw.context),
|
|
717
|
+
parents,
|
|
718
|
+
artifacts,
|
|
719
|
+
parentIds: parentIds.length ? parentIds : parents.map((parent) => parent.id),
|
|
720
|
+
childIds: normalizeStringArray(raw.childIds ?? raw.child_ids),
|
|
721
|
+
createdAt: optionalString(raw.createdAt ?? raw.created_at),
|
|
722
|
+
updatedAt: optionalString(raw.updatedAt ?? raw.updated_at),
|
|
723
|
+
timestamp: {
|
|
724
|
+
createdAt: optionalString(timestamp.createdAt ?? timestamp.created_at),
|
|
725
|
+
startedAt: optionalString(timestamp.startedAt ?? timestamp.started_at),
|
|
726
|
+
completedAt: optionalString(timestamp.completedAt ?? timestamp.completed_at),
|
|
727
|
+
},
|
|
728
|
+
durationMs: optionalNumber(raw.durationMs ?? raw.duration_ms),
|
|
729
|
+
errorMessage: optionalString(raw.errorMessage ?? raw.error_message),
|
|
730
|
+
toolCalls: normalizeStringArray(raw.toolCalls ?? raw.tool_calls),
|
|
731
|
+
metadata: asOptionalUnknownRecord(raw.metadata),
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Normalize a wire SessionState (snake_case) into the camelCase frontend
|
|
737
|
+
* shape. Used by `api.sessions.state()` and by the `CUSTOM:session_state`
|
|
738
|
+
* SSE event consumer.
|
|
739
|
+
*/
|
|
740
|
+
export function normalizeSessionState(rawValue: unknown): SessionStateSnapshot {
|
|
741
|
+
const raw = asDict(rawValue);
|
|
742
|
+
const camelized = camelizeObject(raw) as Record<string, unknown>;
|
|
743
|
+
const rs = (camelized.runState ?? {}) as Record<string, unknown>;
|
|
744
|
+
const agentsRaw = Array.isArray(camelized.agents) ? camelized.agents : [];
|
|
745
|
+
const agents: AgentStatus[] = agentsRaw.map((entry) => {
|
|
746
|
+
const a = asDict(entry);
|
|
747
|
+
return {
|
|
748
|
+
name: stringValue(a.name),
|
|
749
|
+
status: stringValue(a.status, "idle"),
|
|
750
|
+
task: stringValue(a.task, ""),
|
|
751
|
+
updatedAt: optionalString(a.updatedAt),
|
|
752
|
+
alive: typeof a.alive === "boolean" ? a.alive : undefined,
|
|
753
|
+
};
|
|
754
|
+
});
|
|
755
|
+
return {
|
|
756
|
+
runState: {
|
|
757
|
+
active: rs.active === true,
|
|
758
|
+
runId: optionalString(rs.runId) ?? null,
|
|
759
|
+
},
|
|
760
|
+
agents,
|
|
761
|
+
lastActivityTs: stringValue(camelized.lastActivityTs, ""),
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
export function normalizeTraceGraph(rawValue: unknown): TraceGraph {
|
|
767
|
+
const raw = asDict(rawValue);
|
|
768
|
+
const meta = asDict(raw.meta);
|
|
769
|
+
const nodes = Array.isArray(raw.nodes) ? raw.nodes.map(normalizeTraceNode) : [];
|
|
770
|
+
const childIdsByParent = new Map<string, Set<string>>();
|
|
771
|
+
for (const node of nodes) {
|
|
772
|
+
for (const parentId of node.parentIds) {
|
|
773
|
+
const children = childIdsByParent.get(parentId) ?? new Set<string>();
|
|
774
|
+
children.add(node.id);
|
|
775
|
+
childIdsByParent.set(parentId, children);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return {
|
|
779
|
+
meta: {
|
|
780
|
+
...(camelizeObject(meta) as Record<string, unknown>),
|
|
781
|
+
sessionId: stringValue(meta.sessionId ?? meta.session_id),
|
|
782
|
+
userId: optionalString(meta.userId ?? meta.user_id),
|
|
783
|
+
projectName: optionalString(meta.projectName ?? meta.project_name),
|
|
784
|
+
currentFocus: optionalString(meta.currentFocus ?? meta.current_focus),
|
|
785
|
+
createdAt: optionalString(meta.createdAt ?? meta.created_at),
|
|
786
|
+
},
|
|
787
|
+
nodes: nodes.map((node) => ({
|
|
788
|
+
...node,
|
|
789
|
+
childIds: node.childIds.length ? node.childIds : Array.from(childIdsByParent.get(node.id) ?? []),
|
|
790
|
+
})),
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// TODO(dead-code): normalizeSessionEvent is a leftover from the pre-AG-UI polling protocol.
|
|
795
|
+
// Kept commented out for now; will be fully deleted once issue-4 fallback removal lands.
|
|
796
|
+
// export function normalizeSessionEvent(rawValue: unknown): SessionEventEntry {
|
|
797
|
+
// const raw = asDict(rawValue);
|
|
798
|
+
// const seq = numberValue(raw._seq ?? raw.seq, -1);
|
|
799
|
+
// const timestamp = stringValue(raw._ts ?? raw.timestamp, new Date().toISOString());
|
|
800
|
+
// const type = stringValue(raw.type, "unknown");
|
|
801
|
+
// const sessionId = optionalString(raw.session_id ?? raw.sessionId);
|
|
802
|
+
// const data = { ...raw };
|
|
803
|
+
// delete data._seq;
|
|
804
|
+
// delete data.seq;
|
|
805
|
+
// delete data._ts;
|
|
806
|
+
// delete data.timestamp;
|
|
807
|
+
// delete data.type;
|
|
808
|
+
// delete data.session_id;
|
|
809
|
+
// delete data.sessionId;
|
|
810
|
+
//
|
|
811
|
+
// return {
|
|
812
|
+
// seq,
|
|
813
|
+
// timestamp,
|
|
814
|
+
// type,
|
|
815
|
+
// sessionId,
|
|
816
|
+
// data: camelizeObject(data) as Dict,
|
|
817
|
+
// };
|
|
818
|
+
// }
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Normalize a raw AG-UI event JSON from the wire (snake_case) to the
|
|
822
|
+
* camelCase TypeScript shape. Fields are passed through 1:1; `_seq` is
|
|
823
|
+
* intentionally dropped — the new contract relies on AG-UI's
|
|
824
|
+
* messageId/toolCallId for state aggregation rather than transport-level
|
|
825
|
+
* sequence numbers.
|
|
826
|
+
*/
|
|
827
|
+
export function normalizeAgUiEvent(rawValue: unknown): AgUiEvent {
|
|
828
|
+
const raw = asDict(rawValue);
|
|
829
|
+
// The whole payload is flat — camelize every key, including nested messages.
|
|
830
|
+
const camelized = camelizeObject(raw) as Record<string, unknown>;
|
|
831
|
+
return camelized as AgUiEvent;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/** Back-compat alias for callers still using the old name. */
|
|
835
|
+
export const normalizeWebSocketEvent = normalizeAgUiEvent;
|
|
836
|
+
|
|
837
|
+
// TODO(dead-code): makeUserMessage is unused; remove once confirmed safe.
|
|
838
|
+
// export function makeUserMessage(content: string, sessionId: string): ChatMessage {
|
|
839
|
+
// return {
|
|
840
|
+
// id: crypto.randomUUID(),
|
|
841
|
+
// role: "user",
|
|
842
|
+
// content,
|
|
843
|
+
// createdAt: new Date().toISOString(),
|
|
844
|
+
// agent: "user",
|
|
845
|
+
// };
|
|
846
|
+
// }
|