@reforma/types 1.4.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.4.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,8 @@ 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
15
  /** Kubelet `Localhost` seccomp profile path for sandbox pods; omit for `Unconfined`. */
@@ -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
  }
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 = {
@@ -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. */
@@ -4,8 +4,6 @@ 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,
@@ -48,7 +46,6 @@ import type {
48
46
  RegisterLocalFontUITool,
49
47
  RemoveFileUITool,
50
48
  SkillUITool,
51
- ThreadBackgroundJob,
52
49
  TodoWriteUITool,
53
50
  WaitingUITool,
54
51
  WriteUITool,
@@ -162,6 +159,12 @@ export type ReformaChatMessageMetadata = {
162
159
  /** User stopped this assistant turn. Persisted with the message. */
163
160
  stopReason?: 'cancelled';
164
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
+
165
168
  /** Pre/Post/Stop hook context frozen for this assistant turn (server-tagged before persist). */
166
169
  turnHookContext?: string;
167
170
  };
@@ -182,7 +185,6 @@ export type RunCreditsSyncEntry = {
182
185
  */
183
186
 
184
187
  export type ThreadUpdateData = {
185
- title?: string;
186
188
  /** Emitted when the server assigns a new thread id (draft first message). */
187
189
  threadId?: string;
188
190
  };
@@ -213,21 +215,6 @@ export type ProjectUpdateData = {
213
215
  screenshotUrl?: string | null;
214
216
  };
215
217
 
216
- /** Transient UI stream payload (`data-thread-todos`); not stored on messages */
217
- export type ThreadTodosUpdateData = {
218
- todos: ThreadTodo[];
219
- };
220
-
221
- /** Transient UI stream payload (`data-thread-plan`); not stored on messages */
222
- export type ThreadPlanUpdateData = {
223
- plan: ThreadPlan;
224
- };
225
-
226
- /** Transient UI stream payload (`data-thread-background-jobs`); not stored on messages */
227
- export type ThreadBackgroundJobsUpdateData = {
228
- backgroundJobs: ThreadBackgroundJob[];
229
- };
230
-
231
218
  /**
232
219
  * Finished background commands, stored on the assistant message (`data-background-result`).
233
220
  * `text` is the model note frozen at delivery time.
@@ -238,6 +225,16 @@ export type BackgroundResultNoticeData = {
238
225
  text: string;
239
226
  };
240
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
+
241
238
  /** Transient UI stream payload (`data-version-status`); mirrors WS `version-status`. */
242
239
  export type VersionStatusData = VersionStatus;
243
240
 
@@ -308,15 +305,26 @@ export interface ReformaChatDataContextCompactionPayload {
308
305
  resumeMessageId?: string;
309
306
  }
310
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
+
311
321
  /** Maps `data-*` suffix keys to payload shapes for {@link ReformaUIMessage}. */
312
322
  export type ReformaChatDataTypes = {
313
323
  'thread-update': ThreadUpdateData;
314
324
  'project-update': ProjectUpdateData;
315
325
  'version-status': VersionStatusData;
316
- 'thread-todos': ThreadTodosUpdateData;
317
- 'thread-plan': ThreadPlanUpdateData;
318
- 'thread-background-jobs': ThreadBackgroundJobsUpdateData;
319
326
  'background-result': BackgroundResultNoticeData;
327
+ 'user-steer': UserSteerData;
320
328
  'tool-metadata': ToolMetadataUpdateData;
321
329
  'client-tool': ClientToolRequestData;
322
330
  'tool-execution-progress': ReformaChatDataToolExecutionProgressPayload;
@@ -324,6 +332,7 @@ export type ReformaChatDataTypes = {
324
332
  'redacted-reasoning': ReformaChatDataRedactedReasoningPayload;
325
333
  'context-compaction': ReformaChatDataContextCompactionPayload;
326
334
  'subagent-status': SubagentStatusUpdateData;
335
+ 'payer-failure': PayerFailure;
327
336
  };
328
337
 
329
338
  // --- Tool UI parts (third `UIMessage` generic) ---
@@ -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
+ }