@reforma/types 1.4.0 → 1.6.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.6.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,
@@ -129,6 +126,9 @@ export type ReformaUserMessageMetadata = {
129
126
  optPlugins?: string[];
130
127
  };
131
128
 
129
+ /** A run that did not complete its turn. Resume continues the turn. */
130
+ export type ReformaStopReason = 'cancelled' | 'max_turns_exceeded' | 'provider_error' | 'internal_error';
131
+
132
132
  /**
133
133
  * Metadata on assistant Reforma chat UI messages (`UIMessage.metadata`), stream finish snapshots,
134
134
  * and persisted threads.
@@ -159,8 +159,14 @@ export type ReformaChatMessageMetadata = {
159
159
  runId?: string;
160
160
  /** Reforma credits billed for this run. Stamped after settle; denormalized from the API. */
161
161
  creditsCharged?: number;
162
- /** User stopped this assistant turn. Persisted with the message. */
163
- stopReason?: 'cancelled';
162
+ /** Why this assistant turn ended early: the user stopped it, or the run failed. Absent once a run completes it. */
163
+ stopReason?: ReformaStopReason;
164
+
165
+ /**
166
+ * The user's ChatGPT plan or API key could not pay for this turn.
167
+ * The composer shows a limit plate instead of the generic error row.
168
+ */
169
+ payerFailure?: PayerFailure;
164
170
 
165
171
  /** Pre/Post/Stop hook context frozen for this assistant turn (server-tagged before persist). */
166
172
  turnHookContext?: string;
@@ -182,7 +188,6 @@ export type RunCreditsSyncEntry = {
182
188
  */
183
189
 
184
190
  export type ThreadUpdateData = {
185
- title?: string;
186
191
  /** Emitted when the server assigns a new thread id (draft first message). */
187
192
  threadId?: string;
188
193
  };
@@ -213,21 +218,6 @@ export type ProjectUpdateData = {
213
218
  screenshotUrl?: string | null;
214
219
  };
215
220
 
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
221
  /**
232
222
  * Finished background commands, stored on the assistant message (`data-background-result`).
233
223
  * `text` is the model note frozen at delivery time.
@@ -238,6 +228,16 @@ export type BackgroundResultNoticeData = {
238
228
  text: string;
239
229
  };
240
230
 
231
+ /**
232
+ * A user message the running agent took between steps (`data-user-steer`), stored on the
233
+ * assistant message at that position. The model sees it as a user turn there.
234
+ */
235
+ export type UserSteerData = {
236
+ message: ReformaUserUIMessage;
237
+ /** Unix ms when the agent took the steer. Missing on parts stored before it was recorded. */
238
+ takenAt?: number;
239
+ };
240
+
241
241
  /** Transient UI stream payload (`data-version-status`); mirrors WS `version-status`. */
242
242
  export type VersionStatusData = VersionStatus;
243
243
 
@@ -308,15 +308,26 @@ export interface ReformaChatDataContextCompactionPayload {
308
308
  resumeMessageId?: string;
309
309
  }
310
310
 
311
+ /**
312
+ * ChatGPT plan or a user API key could not pay.
313
+ * Streamed as transient `data-payer-failure`, and saved on the assistant message.
314
+ */
315
+ export type PayerFailure = {
316
+ kind: 'limit' | 'access';
317
+ /** `chatgpt` is an OAuth plan. `api_key` is the user's provider key. */
318
+ payer: 'chatgpt' | 'api_key';
319
+ providerId: 'openai' | 'anthropic' | 'google';
320
+ /** Calendar day (`YYYY-MM-DD`) the provider named. Absent when they did not. */
321
+ resetAt?: string;
322
+ };
323
+
311
324
  /** Maps `data-*` suffix keys to payload shapes for {@link ReformaUIMessage}. */
312
325
  export type ReformaChatDataTypes = {
313
326
  'thread-update': ThreadUpdateData;
314
327
  'project-update': ProjectUpdateData;
315
328
  'version-status': VersionStatusData;
316
- 'thread-todos': ThreadTodosUpdateData;
317
- 'thread-plan': ThreadPlanUpdateData;
318
- 'thread-background-jobs': ThreadBackgroundJobsUpdateData;
319
329
  'background-result': BackgroundResultNoticeData;
330
+ 'user-steer': UserSteerData;
320
331
  'tool-metadata': ToolMetadataUpdateData;
321
332
  'client-tool': ClientToolRequestData;
322
333
  'tool-execution-progress': ReformaChatDataToolExecutionProgressPayload;
@@ -324,6 +335,7 @@ export type ReformaChatDataTypes = {
324
335
  'redacted-reasoning': ReformaChatDataRedactedReasoningPayload;
325
336
  'context-compaction': ReformaChatDataContextCompactionPayload;
326
337
  'subagent-status': SubagentStatusUpdateData;
338
+ 'payer-failure': PayerFailure;
327
339
  };
328
340
 
329
341
  // --- 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
+ }