@reforma/types 1.3.0 → 1.5.0

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/index.js CHANGED
@@ -342,6 +342,8 @@ let SandboxClusterNodeStatusEnum = /* @__PURE__ */ function(SandboxClusterNodeSt
342
342
  // src/api/sandbox-clusters/sandbox-cluster-provider.enum.ts
343
343
  let SandboxClusterProviderEnum = /* @__PURE__ */ function(SandboxClusterProviderEnum) {
344
344
  SandboxClusterProviderEnum["rke2"] = "rke2";
345
+ /** Vercel Sandbox microVMs; overflow when no `rke2` cluster can take a sandbox. */
346
+ SandboxClusterProviderEnum["vercel"] = "vercel";
345
347
  return SandboxClusterProviderEnum;
346
348
  }({});
347
349
  // src/api/sandbox-clusters/sandbox-cluster-status.enum.ts
@@ -546,6 +548,35 @@ let McpRuntimeStatus = /* @__PURE__ */ function(McpRuntimeStatus) {
546
548
  McpRuntimeStatus["error"] = "error";
547
549
  return McpRuntimeStatus;
548
550
  }({});
551
+ // src/sandbox/ui-parts.ts
552
+ /** Whether the part is a data part (`data-*`). */
553
+ function isDataUIPart(part) {
554
+ return part.type.startsWith("data-");
555
+ }
556
+ /** Whether the part is a text part. */
557
+ function isTextUIPart(part) {
558
+ return part.type === "text";
559
+ }
560
+ /** Whether the part is a file part. */
561
+ function isFileUIPart(part) {
562
+ return part.type === "file";
563
+ }
564
+ /** Whether the part is a reasoning part. */
565
+ function isReasoningUIPart(part) {
566
+ return part.type === "reasoning";
567
+ }
568
+ /** Whether the part is a statically typed tool part (`tool-*`). */
569
+ function isStaticToolUIPart(part) {
570
+ return part.type.startsWith("tool-");
571
+ }
572
+ /** Whether the part is a static or dynamic tool part. */
573
+ function isToolUIPart(part) {
574
+ return isStaticToolUIPart(part) || part.type === "dynamic-tool";
575
+ }
576
+ /** Tool name of a static (`tool-<name>`) or dynamic tool part. */
577
+ function getToolName(part) {
578
+ return part.type === "dynamic-tool" ? part.toolName : part.type.split("-").slice(1).join("-");
579
+ }
549
580
  // src/sandbox/usage-metadata.ts
550
581
  /** Prompt-window buckets. Tool calls/results live in `conversation`, not `tools`. */
551
582
  const CONTEXT_USAGE_BUCKET_IDS = [
@@ -613,7 +644,14 @@ exports.allocLayerCite = allocLayerCite;
613
644
  exports.attachedLayerRefKey = attachedLayerRefKey;
614
645
  exports.codeEditorStateKey = codeEditorStateKey;
615
646
  exports.formatLayerCite = formatLayerCite;
647
+ exports.getToolName = getToolName;
648
+ exports.isDataUIPart = isDataUIPart;
649
+ exports.isFileUIPart = isFileUIPart;
650
+ exports.isReasoningUIPart = isReasoningUIPart;
616
651
  exports.isScreenshotTool = isScreenshotTool;
652
+ exports.isStaticToolUIPart = isStaticToolUIPart;
653
+ exports.isTextUIPart = isTextUIPart;
654
+ exports.isToolUIPart = isToolUIPart;
617
655
  exports.layerCiteBase = layerCiteBase;
618
656
  exports.normalizeAttachedLayerRef = normalizeAttachedLayerRef;
619
657
  exports.normalizeLayerSource = normalizeLayerSource;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@reforma/types",
3
3
  "author": "Reforma, Inc. <dev@reforma.ai>",
4
4
  "description": "Shared types between Reforma apps/packages",
5
- "version": "1.3.0",
5
+ "version": "1.5.0",
6
6
  "license": "UNLICENSED",
7
7
  "repository": {
8
8
  "type": "git",
@@ -81,6 +81,11 @@ export interface LlmUsageWebhookEvent {
81
81
  id?: string;
82
82
  litellm_call_id?: string;
83
83
  call_type?: string;
84
+ /**
85
+ * LiteLLM session id: `x-litellm-trace-id`, or `litellm_session_id` on the
86
+ * Gemini pass-through, which drops `spend_logs_metadata`. Client-controlled.
87
+ */
88
+ trace_id?: string | null;
84
89
  status?: string;
85
90
  /** Gateway-computed cost in USD for this request. */
86
91
  response_cost?: number;
@@ -33,6 +33,21 @@ export interface CreditOperation {
33
33
  /** Who spent the credits when this row is usage-linked; null for grants/purchases. */
34
34
  userName: string | null;
35
35
  userEmail: string | null;
36
+ /** The agent run behind a usage row; null for grants/purchases. */
37
+ usage: CreditUsageDetails | null;
38
+ }
39
+
40
+ /** The agent run a usage charge settled: where it ran and what the gateway saw. */
41
+ export interface CreditUsageDetails {
42
+ projectName: string | null;
43
+ threadTitle: string | null;
44
+ /** Gateway requests (model calls) in the run. */
45
+ requests: number;
46
+ inputTokens: number;
47
+ cacheReadTokens: number;
48
+ cacheWriteTokens: number;
49
+ outputTokens: number;
50
+ models: string[];
36
51
  }
37
52
 
38
53
  /** Paged customer ledger. */
@@ -8,8 +8,10 @@ export interface CreateSandboxCluster {
8
8
  region: string;
9
9
  apiUrl: string;
10
10
  ingressDomain: string;
11
- /** Plaintext kubeconfig YAML; encrypted at rest by the API. */
12
- kubeconfig: string;
11
+ /** Plaintext kubeconfig YAML; encrypted at rest by the API. Required for `rke2`. */
12
+ kubeconfig?: string;
13
13
  status?: SandboxClusterStatusEnum;
14
14
  maxSandboxes?: number;
15
+ /** Kubelet `Localhost` seccomp profile path for sandbox pods; omit for `Unconfined`. */
16
+ seccompProfile?: string | null;
15
17
  }
@@ -1,3 +1,5 @@
1
1
  export enum SandboxClusterProviderEnum {
2
- rke2 = 'rke2'
2
+ rke2 = 'rke2',
3
+ /** Vercel Sandbox microVMs; overflow when no `rke2` cluster can take a sandbox. */
4
+ vercel = 'vercel'
3
5
  }
@@ -12,6 +12,12 @@ export interface SandboxCluster {
12
12
  version: string | null;
13
13
  status: SandboxClusterStatusEnum;
14
14
  maxSandboxes: number;
15
+ /**
16
+ * Path of the kubelet `Localhost` seccomp profile the cluster's nodes carry,
17
+ * relative to the kubelet seccomp root. `null` runs sandbox pods
18
+ * `Unconfined`, for clusters where no profile is installed.
19
+ */
20
+ seccompProfile: string | null;
15
21
  lastHealthCheckAt: Date | null;
16
22
  healthCheckError: string | null;
17
23
  createdByUserId: number;
@@ -12,4 +12,6 @@ export interface UpdateSandboxCluster {
12
12
  kubeconfig?: string;
13
13
  status?: SandboxClusterStatusEnum;
14
14
  maxSandboxes?: number;
15
+ /** Kubelet `Localhost` seccomp profile path for sandbox pods; `null` for `Unconfined`. */
16
+ seccompProfile?: string | null;
15
17
  }
@@ -40,3 +40,27 @@ export interface ScreenshotUploadResponse {
40
40
  mediaType: 'image/png' | 'image/jpeg';
41
41
  size: number;
42
42
  }
43
+
44
+ /** Open a page without capturing it. Same URL rules and viewport defaults as a screenshot. */
45
+ export interface PageInspectRequest {
46
+ url: string;
47
+ headers?: Record<string, string>;
48
+ width?: number;
49
+ height?: number;
50
+ waitUntil?: ScreenshotWaitUntil;
51
+ /** Milliseconds to wait after the page loads. Default 500. Capped at 10000. */
52
+ delayMs?: number;
53
+ }
54
+
55
+ /** What the browser reported while the page loaded and settled. Each list keeps the first 20 entries. */
56
+ export interface PageInspection {
57
+ /** HTTP status of the document. Omitted when navigation failed. */
58
+ status?: number;
59
+ /** Navigation failure, such as a timeout. */
60
+ error?: string;
61
+ /** Uncaught exceptions thrown in the page. */
62
+ pageErrors: string[];
63
+ console: { type: 'error' | 'warn'; text: string }[];
64
+ /** Subresources that failed or answered 4xx/5xx. */
65
+ failedRequests: { url: string; status?: number; error?: string }[];
66
+ }
package/src/sandbox/ai.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ThreadPlan } from './plans';
2
2
  import type { ThreadTodo } from './todos';
3
3
  import type { ThreadBackgroundJob } from './tools/get-background-result';
4
- import type { ReformaUIMessage } from './ui-message';
4
+ import type { ReformaUIMessage, ReformaUserUIMessage } from './ui-message';
5
5
  import type { ReformaContextUsage } from './usage-metadata';
6
6
 
7
7
  export type ChatThreadSummaryDto = {
@@ -52,6 +52,63 @@ export type ChatRequestBody = {
52
52
  /** Keep messages up to and including this id; drop the tail before merge. */
53
53
  afterMessageId?: string;
54
54
  runConfig?: ChatRunConfig;
55
+ /** "Send now" on a queued item: the item leaves the queue once this turn is accepted. */
56
+ queueItemId?: string;
57
+ };
58
+
59
+ /** `steer` reaches the running agent at its next step; `next-turn` starts the next run. */
60
+ export type ChatQueueItemMode = 'steer' | 'next-turn';
61
+
62
+ /** User input waiting on a thread run. Owned by the sandbox, in memory only. */
63
+ export type ChatQueueItem = {
64
+ /** Client-chosen id; adding the same id twice is a no-op. */
65
+ id: string;
66
+ mode: ChatQueueItemMode;
67
+ /** Ready-to-send user message. */
68
+ message: ReformaUserUIMessage;
69
+ runConfig?: ChatRunConfig;
70
+ /** Opaque composer state the client needs to reopen the item for editing. */
71
+ draft?: unknown;
72
+ /** Open in an editor. A locked steer is not taken, and a locked head holds the queue. */
73
+ editing: boolean;
74
+ createdAt: number;
75
+ };
76
+
77
+ /** Why the queue stopped draining. Only "Send now" on an item clears it. */
78
+ export type ChatQueuePause = {
79
+ reason: 'stopped' | 'failed';
80
+ error?: string;
81
+ };
82
+
83
+ /** Steers first, then next turns, each in send order. */
84
+ export type ChatQueueState = {
85
+ items: ChatQueueItem[];
86
+ paused?: ChatQueuePause;
87
+ };
88
+
89
+ /**
90
+ * Sandbox sync value `chat-thread:<threadId>`: thread state without messages.
91
+ * A missing meta field means "unchanged": the idle snapshot of a thread the sandbox has not loaded carries none.
92
+ */
93
+ export type ChatThreadStateSnapshot = {
94
+ /** Set while a run is in flight, including a run parked on the user. */
95
+ runId?: string;
96
+ queue: ChatQueueState;
97
+ title?: string;
98
+ todos?: ThreadTodo[];
99
+ /** `null`: the thread has no plan. */
100
+ plan?: ThreadPlan | null;
101
+ /** Running background jobs. */
102
+ backgroundJobs?: ThreadBackgroundJob[];
103
+ contextUsage?: ReformaContextUsage;
104
+ };
105
+
106
+ /** POST `/ai/threads/:threadId/queue` */
107
+ export type ChatQueueAddRequest = Pick<ChatQueueItem, 'id' | 'mode' | 'message' | 'runConfig' | 'draft'>;
108
+
109
+ /** PATCH `/ai/threads/:threadId/queue/:itemId`. `index` is the position inside the item's mode section. */
110
+ export type ChatQueuePatchRequest = Partial<Pick<ChatQueueItem, 'mode' | 'message' | 'draft' | 'editing'>> & {
111
+ index?: number;
55
112
  };
56
113
 
57
114
  /**
@@ -69,6 +126,8 @@ export type ChatThreadDetailDto = {
69
126
  * The client may attempt stream resume (`GET …/chat/:threadId/stream`) only when this is true.
70
127
  */
71
128
  hasActiveStream: boolean;
129
+ /** In-flight run, including one parked on the user. */
130
+ runId?: string;
72
131
  /** Active todo list for this thread (empty when none). */
73
132
  todos: ThreadTodo[];
74
133
  /** Thread plan, when one exists. */
@@ -78,11 +137,3 @@ export type ChatThreadDetailDto = {
78
137
  /** Latest prompt-window breakdown; omitted before the first model step. */
79
138
  contextUsage?: ReformaContextUsage;
80
139
  };
81
-
82
- /** WS `thread-status` payload when a chat run starts or ends. */
83
- export type ThreadStatusEvent = {
84
- threadId: string;
85
- isActive: boolean;
86
- /** Set on subagent children so the client can ignore list refresh. */
87
- parentThreadId?: string;
88
- };
@@ -12,6 +12,7 @@ export * from './skills';
12
12
  export * from './todos';
13
13
  export * from './tools';
14
14
  export * from './ui-message';
15
+ export * from './ui-parts';
15
16
  export * from './usage-metadata';
16
17
  export * from './websocket';
17
18
  export * from './workspace';
@@ -7,6 +7,8 @@ export type AgentToolInput = {
7
7
  message: string;
8
8
  /** `new` starts a child. `resume` continues the latest of this name. Omit for the agent default. */
9
9
  resume?: 'new' | 'resume';
10
+ /** Run as a background job: a receipt now, the report as a notification later. Root agent only. */
11
+ background?: boolean;
10
12
  };
11
13
 
12
14
  export type AgentToolOutput = {
@@ -17,6 +19,8 @@ export type AgentToolOutput = {
17
19
  /** Present when the subagent profile used structured output. */
18
20
  data?: unknown;
19
21
  error?: string;
22
+ /** Set when the subagent runs in the background: the receipt's job id. The report arrives later. */
23
+ jobId?: string;
20
24
  };
21
25
 
22
26
  export type AgentUITool = {
@@ -0,0 +1,29 @@
1
+ /** Matches sandbox `CheckPage` tool schemas. */
2
+
3
+ export type CheckPageToolInput = {
4
+ /** Preview paths, such as `/` or `/reforma/stories/ui/button?variant=outline`. */
5
+ paths: string[];
6
+ delayMs?: number;
7
+ };
8
+
9
+ /** One visited path. Empty fields are omitted. */
10
+ export type CheckPageReport = {
11
+ path: string;
12
+ status?: number;
13
+ error?: string;
14
+ pageErrors?: string[];
15
+ console?: { type: 'error' | 'warn'; text: string }[];
16
+ failedRequests?: { url: string; status?: number; error?: string }[];
17
+ /** Dev server output written while the page loaded. */
18
+ serverLog?: string;
19
+ };
20
+
21
+ export type CheckPageToolOutput = {
22
+ pages?: CheckPageReport[];
23
+ error?: string;
24
+ };
25
+
26
+ export type CheckPageUITool = {
27
+ input: CheckPageToolInput;
28
+ output: CheckPageToolOutput;
29
+ };
@@ -6,12 +6,21 @@ export type BackgroundResult = {
6
6
  error?: string;
7
7
  };
8
8
 
9
+ /** A background subagent's job: which agent runs, in which child thread, and whether it waits on the user. */
10
+ export type ThreadBackgroundAgent = {
11
+ name: string;
12
+ childThreadId: string;
13
+ waitingForUser?: boolean;
14
+ };
15
+
9
16
  /** A background job on this thread. `output` / `error` appear when it finishes. */
10
17
  export type ThreadBackgroundJob = {
11
18
  id: string;
12
19
  description: string;
13
20
  output?: unknown;
14
21
  error?: string;
22
+ /** Set when the job is a subagent; commands and other work leave it out. */
23
+ agent?: ThreadBackgroundAgent;
15
24
  };
16
25
 
17
26
  /** Matches sandbox `GetBackgroundResult` tool schemas. */
@@ -3,6 +3,7 @@ export * from './ask-user-question';
3
3
  export * from './bash';
4
4
  export * from './build';
5
5
  export * from './capture-screenshot';
6
+ export * from './check-page';
6
7
  export * from './choose-design-direction';
7
8
  export * from './commit';
8
9
  export * from './commit-design-read';
@@ -10,7 +10,7 @@ export type ReadLintsDiagnostic = {
10
10
  endColumn?: number;
11
11
  severity: LintSeverity;
12
12
  message: string;
13
- source: 'biome' | 'typescript' | 'tokens';
13
+ source: 'biome' | 'typescript' | 'tokens' | 'stories';
14
14
  code?: string;
15
15
  };
16
16
 
@@ -4,14 +4,13 @@ import type { SelectedLayerContext } from '../layers';
4
4
  import type { CodeEditorState } from '../theia/bridge';
5
5
  import type { ReformaTurnGitCommit } from './git/commit';
6
6
  import type { VersionStatus } from './git/version';
7
- import type { ThreadPlan } from './plans';
8
- import type { ThreadTodo } from './todos';
9
7
  import type {
10
8
  AgentUITool,
11
9
  AskUserQuestionUITool,
12
10
  BashUITool,
13
11
  BuildUITool,
14
12
  CallDynamicToolUITool,
13
+ CheckPageUITool,
15
14
  ChooseDesignDirectionUITool,
16
15
  CommitDesignReadUITool,
17
16
  CommitUITool,
@@ -47,7 +46,6 @@ import type {
47
46
  RegisterLocalFontUITool,
48
47
  RemoveFileUITool,
49
48
  SkillUITool,
50
- ThreadBackgroundJob,
51
49
  TodoWriteUITool,
52
50
  WaitingUITool,
53
51
  WriteUITool,
@@ -161,6 +159,12 @@ export type ReformaChatMessageMetadata = {
161
159
  /** User stopped this assistant turn. Persisted with the message. */
162
160
  stopReason?: 'cancelled';
163
161
 
162
+ /**
163
+ * The user's ChatGPT plan or API key could not pay for this turn.
164
+ * The composer shows a limit plate instead of the generic error row.
165
+ */
166
+ payerFailure?: PayerFailure;
167
+
164
168
  /** Pre/Post/Stop hook context frozen for this assistant turn (server-tagged before persist). */
165
169
  turnHookContext?: string;
166
170
  };
@@ -181,7 +185,6 @@ export type RunCreditsSyncEntry = {
181
185
  */
182
186
 
183
187
  export type ThreadUpdateData = {
184
- title?: string;
185
188
  /** Emitted when the server assigns a new thread id (draft first message). */
186
189
  threadId?: string;
187
190
  };
@@ -212,21 +215,6 @@ export type ProjectUpdateData = {
212
215
  screenshotUrl?: string | null;
213
216
  };
214
217
 
215
- /** Transient UI stream payload (`data-thread-todos`); not stored on messages */
216
- export type ThreadTodosUpdateData = {
217
- todos: ThreadTodo[];
218
- };
219
-
220
- /** Transient UI stream payload (`data-thread-plan`); not stored on messages */
221
- export type ThreadPlanUpdateData = {
222
- plan: ThreadPlan;
223
- };
224
-
225
- /** Transient UI stream payload (`data-thread-background-jobs`); not stored on messages */
226
- export type ThreadBackgroundJobsUpdateData = {
227
- backgroundJobs: ThreadBackgroundJob[];
228
- };
229
-
230
218
  /**
231
219
  * Finished background commands, stored on the assistant message (`data-background-result`).
232
220
  * `text` is the model note frozen at delivery time.
@@ -237,6 +225,16 @@ export type BackgroundResultNoticeData = {
237
225
  text: string;
238
226
  };
239
227
 
228
+ /**
229
+ * A user message the running agent took between steps (`data-user-steer`), stored on the
230
+ * assistant message at that position. The model sees it as a user turn there.
231
+ */
232
+ export type UserSteerData = {
233
+ message: ReformaUserUIMessage;
234
+ /** Unix ms when the agent took the steer. Missing on parts stored before it was recorded. */
235
+ takenAt?: number;
236
+ };
237
+
240
238
  /** Transient UI stream payload (`data-version-status`); mirrors WS `version-status`. */
241
239
  export type VersionStatusData = VersionStatus;
242
240
 
@@ -307,15 +305,26 @@ export interface ReformaChatDataContextCompactionPayload {
307
305
  resumeMessageId?: string;
308
306
  }
309
307
 
308
+ /**
309
+ * ChatGPT plan or a user API key could not pay.
310
+ * Streamed as transient `data-payer-failure`, and saved on the assistant message.
311
+ */
312
+ export type PayerFailure = {
313
+ kind: 'limit' | 'access';
314
+ /** `chatgpt` is an OAuth plan. `api_key` is the user's provider key. */
315
+ payer: 'chatgpt' | 'api_key';
316
+ providerId: 'openai' | 'anthropic' | 'google';
317
+ /** Calendar day (`YYYY-MM-DD`) the provider named. Absent when they did not. */
318
+ resetAt?: string;
319
+ };
320
+
310
321
  /** Maps `data-*` suffix keys to payload shapes for {@link ReformaUIMessage}. */
311
322
  export type ReformaChatDataTypes = {
312
323
  'thread-update': ThreadUpdateData;
313
324
  'project-update': ProjectUpdateData;
314
325
  'version-status': VersionStatusData;
315
- 'thread-todos': ThreadTodosUpdateData;
316
- 'thread-plan': ThreadPlanUpdateData;
317
- 'thread-background-jobs': ThreadBackgroundJobsUpdateData;
318
326
  'background-result': BackgroundResultNoticeData;
327
+ 'user-steer': UserSteerData;
319
328
  'tool-metadata': ToolMetadataUpdateData;
320
329
  'client-tool': ClientToolRequestData;
321
330
  'tool-execution-progress': ReformaChatDataToolExecutionProgressPayload;
@@ -323,6 +332,7 @@ export type ReformaChatDataTypes = {
323
332
  'redacted-reasoning': ReformaChatDataRedactedReasoningPayload;
324
333
  'context-compaction': ReformaChatDataContextCompactionPayload;
325
334
  'subagent-status': SubagentStatusUpdateData;
335
+ 'payer-failure': PayerFailure;
326
336
  };
327
337
 
328
338
  // --- Tool UI parts (third `UIMessage` generic) ---
@@ -359,6 +369,7 @@ export type ReformaChatUITools = {
359
369
  MoveFile: MoveFileUITool;
360
370
  Navigate: NavigateUITool;
361
371
  PageScreenshot: PageScreenshotUITool;
372
+ CheckPage: CheckPageUITool;
362
373
  ReadPlan: ReadPlanUITool;
363
374
  ReadTodo: ReadTodoUITool;
364
375
  TodoWrite: TodoWriteUITool;
@@ -0,0 +1,49 @@
1
+ import type {
2
+ DataUIPart,
3
+ DynamicToolUIPart,
4
+ FileUIPart,
5
+ ReasoningUIPart,
6
+ TextUIPart,
7
+ ToolUIPart,
8
+ UIDataTypes,
9
+ UIMessagePart,
10
+ UITools,
11
+ } from 'ai' with { 'resolution-mode': 'import' };
12
+
13
+ // Same contracts as the `ai` part guards. Importing them from `ai` evaluates the whole
14
+ // SDK and its zod schemas, which chat UI outside the editor does not need.
15
+
16
+ /** Whether the part is a data part (`data-*`). */
17
+ export function isDataUIPart<DATA_TYPES extends UIDataTypes>(part: UIMessagePart<DATA_TYPES, UITools>): part is DataUIPart<DATA_TYPES> {
18
+ return part.type.startsWith('data-');
19
+ }
20
+
21
+ /** Whether the part is a text part. */
22
+ export function isTextUIPart(part: UIMessagePart<UIDataTypes, UITools>): part is TextUIPart {
23
+ return part.type === 'text';
24
+ }
25
+
26
+ /** Whether the part is a file part. */
27
+ export function isFileUIPart(part: UIMessagePart<UIDataTypes, UITools>): part is FileUIPart {
28
+ return part.type === 'file';
29
+ }
30
+
31
+ /** Whether the part is a reasoning part. */
32
+ export function isReasoningUIPart(part: UIMessagePart<UIDataTypes, UITools>): part is ReasoningUIPart {
33
+ return part.type === 'reasoning';
34
+ }
35
+
36
+ /** Whether the part is a statically typed tool part (`tool-*`). */
37
+ export function isStaticToolUIPart<TOOLS extends UITools>(part: UIMessagePart<UIDataTypes, TOOLS>): part is ToolUIPart<TOOLS> {
38
+ return part.type.startsWith('tool-');
39
+ }
40
+
41
+ /** Whether the part is a static or dynamic tool part. */
42
+ export function isToolUIPart<TOOLS extends UITools>(part: UIMessagePart<UIDataTypes, TOOLS>): part is ToolUIPart<TOOLS> | DynamicToolUIPart {
43
+ return isStaticToolUIPart(part) || part.type === 'dynamic-tool';
44
+ }
45
+
46
+ /** Tool name of a static (`tool-<name>`) or dynamic tool part. */
47
+ export function getToolName(part: ToolUIPart<UITools> | DynamicToolUIPart): string {
48
+ return part.type === 'dynamic-tool' ? part.toolName : part.type.split('-').slice(1).join('-');
49
+ }
@@ -34,7 +34,7 @@ export type TheiaHostMethods = {
34
34
  onAttachFiles: (files: readonly CodeEditorState[]) => void;
35
35
  /** Switch the parent shell/canvas mode, or activate commenting, while Theia holds keyboard focus. */
36
36
  setViewMode: (mode: TheiaHostViewMode) => void;
37
- /** Flip Code ↔ last persisted canvas mode (`exitCodeMode` / enter Code). */
37
+ /** Cycle Editor → Design → Code. Preview counts as Editor. */
38
38
  toggleCodeShell: () => void;
39
39
  };
40
40