@perstack/react 0.0.53 → 0.0.55

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/src/index.js CHANGED
@@ -1,677 +1,661 @@
1
- import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
2
- import { BASE_SKILL_PREFIX, createBaseToolActivity, createGeneralToolActivity } from '@perstack/core';
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { BASE_SKILL_PREFIX, createBaseToolActivity, createGeneralToolActivity } from "@perstack/core";
3
3
 
4
- // src/hooks/use-job-stream.ts
5
-
6
- // src/utils/stream.ts
7
- var isAbortError = (err) => err instanceof DOMException && err.name === "AbortError";
4
+ //#region src/utils/stream.ts
5
+ const isAbortError = (err) => err instanceof DOMException && err.name === "AbortError";
6
+ /**
7
+ * Consumes an SSE stream with abort-safe iteration and error normalization.
8
+ *
9
+ * Handles the connection lifecycle shared by all streaming hooks:
10
+ * 1. Calls `connect` to obtain an async iterable
11
+ * 2. Iterates events, stopping on signal abort
12
+ * 3. Filters out AbortError (normal cleanup), surfaces all others
13
+ */
8
14
  async function consumeStream(connect, jobId, signal, onEvent) {
9
- const events = await connect(jobId, signal);
10
- for await (const event of events) {
11
- if (signal.aborted) break;
12
- onEvent(event);
13
- }
15
+ const events = await connect(jobId, signal);
16
+ for await (const event of events) {
17
+ if (signal.aborted) break;
18
+ onEvent(event);
19
+ }
14
20
  }
15
- var TOOL_RESULT_EVENT_TYPES = /* @__PURE__ */ new Set(["resolveToolResults", "attemptCompletion"]);
21
+
22
+ //#endregion
23
+ //#region src/utils/event-to-activity.ts
24
+ /**
25
+ * Converts a tool call and result to an Activity.
26
+ * Delegates to core's createBaseToolActivity/createGeneralToolActivity to avoid duplication.
27
+ */
16
28
  function toolToActivity(toolCall, toolResult, reasoning, meta) {
17
- const { skillName, toolName } = toolCall;
18
- const baseActivity = skillName.startsWith(BASE_SKILL_PREFIX) ? createBaseToolActivity(toolName, toolCall, toolResult, reasoning) : createGeneralToolActivity(skillName, toolName, toolCall, toolResult, reasoning);
19
- return {
20
- ...baseActivity,
21
- id: meta.id,
22
- expertKey: meta.expertKey,
23
- runId: meta.runId,
24
- previousActivityId: meta.previousActivityId,
25
- delegatedBy: meta.delegatedBy
26
- };
29
+ const { skillName, toolName } = toolCall;
30
+ return {
31
+ ...skillName.startsWith(BASE_SKILL_PREFIX) ? createBaseToolActivity(toolName, toolCall, toolResult, reasoning) : createGeneralToolActivity(skillName, toolName, toolCall, toolResult, reasoning),
32
+ id: meta.id,
33
+ expertKey: meta.expertKey,
34
+ runId: meta.runId,
35
+ previousActivityId: meta.previousActivityId,
36
+ delegatedBy: meta.delegatedBy
37
+ };
27
38
  }
28
39
  function createInitialActivityProcessState() {
29
- return {
30
- tools: /* @__PURE__ */ new Map(),
31
- runStates: /* @__PURE__ */ new Map()
32
- };
40
+ return {
41
+ tools: /* @__PURE__ */ new Map(),
42
+ runStates: /* @__PURE__ */ new Map()
43
+ };
33
44
  }
34
45
  function getOrCreateRunState(state, runId, expertKey) {
35
- let runState = state.runStates.get(runId);
36
- if (!runState) {
37
- runState = {
38
- expertKey,
39
- queryLogged: false,
40
- completionLogged: false,
41
- isComplete: false,
42
- pendingDelegateCount: 0
43
- };
44
- state.runStates.set(runId, runState);
45
- }
46
- return runState;
46
+ let runState = state.runStates.get(runId);
47
+ if (!runState) {
48
+ runState = {
49
+ expertKey,
50
+ queryLogged: false,
51
+ completionLogged: false,
52
+ isComplete: false,
53
+ pendingDelegateCount: 0
54
+ };
55
+ state.runStates.set(runId, runState);
56
+ }
57
+ return runState;
47
58
  }
48
- var isRunEvent = (event) => "type" in event && "expertKey" in event;
49
- var isCompleteStreamingReasoningEvent = (event) => "type" in event && event.type === "completeStreamingReasoning";
50
- var isToolCallsEvent = (event) => event.type === "callTools" && "toolCalls" in event;
51
- var isStopRunByDelegateEvent = (event) => event.type === "stopRunByDelegate" && "checkpoint" in event;
52
- var isStopRunByInteractiveToolEvent = (event) => event.type === "stopRunByInteractiveTool" && "checkpoint" in event;
53
- var isToolResultsEvent = (event) => event.type === "resolveToolResults" && "toolResults" in event;
54
- var isToolResultEvent = (event) => TOOL_RESULT_EVENT_TYPES.has(event.type) && "toolResult" in event;
59
+ const isRunEvent = (event) => "type" in event && "expertKey" in event;
60
+ const isCompleteStreamingReasoningEvent = (event) => "type" in event && event.type === "completeStreamingReasoning";
61
+ const isToolCallsEvent = (event) => event.type === "callTools" && "toolCalls" in event;
62
+ const isStopRunByDelegateEvent = (event) => event.type === "stopRunByDelegate" && "checkpoint" in event;
63
+ const isStopRunByInteractiveToolEvent = (event) => event.type === "stopRunByInteractiveTool" && "checkpoint" in event;
64
+ const isToolResultsEvent = (event) => event.type === "resolveToolResults" && "toolResults" in event;
65
+ /**
66
+ * Wraps multiple activities in a ParallelActivitiesGroup with shared reasoning.
67
+ * If only one activity, returns it directly.
68
+ */
55
69
  function wrapInGroupIfParallel(activities, reasoning, meta) {
56
- if (activities.length <= 1) {
57
- return activities;
58
- }
59
- const activitiesWithoutReasoning = activities.map((a) => {
60
- const { reasoning: _, ...rest } = a;
61
- return rest;
62
- });
63
- const group = {
64
- type: "parallelGroup",
65
- id: `parallel-${activities[0].id}`,
66
- expertKey: meta.expertKey,
67
- runId: meta.runId,
68
- reasoning,
69
- activities: activitiesWithoutReasoning
70
- };
71
- return [group];
70
+ if (activities.length <= 1) return activities;
71
+ const activitiesWithoutReasoning = activities.map((a) => {
72
+ const { reasoning: _, ...rest } = a;
73
+ return rest;
74
+ });
75
+ return [{
76
+ type: "parallelGroup",
77
+ id: `parallel-${activities[0].id}`,
78
+ expertKey: meta.expertKey,
79
+ runId: meta.runId,
80
+ reasoning,
81
+ activities: activitiesWithoutReasoning
82
+ }];
72
83
  }
84
+ /**
85
+ * Processes a RunEvent and produces Activity or ParallelActivitiesGroup items.
86
+ * Only processes RunEvent (state machine transitions), not RuntimeEvent.
87
+ *
88
+ * @param state - Mutable processing state
89
+ * @param event - The event to process
90
+ * @param addActivity - Callback to add a new Activity or ParallelActivitiesGroup
91
+ */
73
92
  function processRunEventToActivity(state, event, addActivity) {
74
- if (isCompleteStreamingReasoningEvent(event)) {
75
- const reasoningEvent = event;
76
- const { runId, text, expertKey } = reasoningEvent;
77
- const runState2 = state.runStates.get(runId);
78
- if (runState2) {
79
- runState2.completedReasoning = text;
80
- } else {
81
- state.runStates.set(runId, {
82
- expertKey,
83
- queryLogged: false,
84
- completionLogged: false,
85
- isComplete: false,
86
- pendingDelegateCount: 0,
87
- completedReasoning: text
88
- });
89
- }
90
- return;
91
- }
92
- if (!isRunEvent(event)) {
93
- return;
94
- }
95
- const runState = getOrCreateRunState(state, event.runId, event.expertKey);
96
- if (event.type === "startRun") {
97
- const startRunEvent = event;
98
- const userMessage = startRunEvent.inputMessages.find((m) => m.type === "userMessage");
99
- const queryText = userMessage?.contents?.find((c) => c.type === "textPart")?.text;
100
- if (!runState.delegatedBy) {
101
- const delegatedByInfo = startRunEvent.initialCheckpoint?.delegatedBy;
102
- if (delegatedByInfo) {
103
- runState.delegatedBy = {
104
- expertKey: delegatedByInfo.expert.key,
105
- runId: delegatedByInfo.runId
106
- };
107
- }
108
- }
109
- const isDelegationReturn = startRunEvent.initialCheckpoint?.status === "stoppedByDelegate" || startRunEvent.initialCheckpoint?.status === "stoppedByInteractiveTool";
110
- if (queryText && !runState.queryLogged && !isDelegationReturn) {
111
- const activityId = `query-${event.runId}`;
112
- addActivity({
113
- type: "query",
114
- id: activityId,
115
- expertKey: event.expertKey,
116
- runId: event.runId,
117
- previousActivityId: runState.lastActivityId,
118
- delegatedBy: runState.delegatedBy,
119
- text: queryText
120
- });
121
- runState.lastActivityId = activityId;
122
- runState.queryLogged = true;
123
- }
124
- return;
125
- }
126
- if (event.type === "resumeFromStop") {
127
- if (!runState.delegatedBy) {
128
- const resumeEvent = event;
129
- const delegatedByInfo = resumeEvent.checkpoint?.delegatedBy;
130
- if (delegatedByInfo) {
131
- runState.delegatedBy = {
132
- expertKey: delegatedByInfo.expert.key,
133
- runId: delegatedByInfo.runId
134
- };
135
- }
136
- }
137
- return;
138
- }
139
- if (event.type === "retry") {
140
- const retryEvent = event;
141
- const activityId = `retry-${event.id}`;
142
- addActivity({
143
- type: "retry",
144
- id: activityId,
145
- expertKey: event.expertKey,
146
- runId: event.runId,
147
- previousActivityId: runState.lastActivityId,
148
- delegatedBy: runState.delegatedBy,
149
- reasoning: runState.completedReasoning,
150
- error: retryEvent.reason,
151
- message: ""
152
- });
153
- runState.lastActivityId = activityId;
154
- runState.completedReasoning = void 0;
155
- return;
156
- }
157
- if (event.type === "completeRun") {
158
- if (!runState.completionLogged) {
159
- const text = event.text ?? "";
160
- const activityId = `completion-${event.runId}`;
161
- addActivity({
162
- type: "complete",
163
- id: activityId,
164
- expertKey: event.expertKey,
165
- runId: event.runId,
166
- previousActivityId: runState.lastActivityId,
167
- delegatedBy: runState.delegatedBy,
168
- reasoning: runState.completedReasoning,
169
- text
170
- });
171
- runState.lastActivityId = activityId;
172
- runState.completionLogged = true;
173
- runState.isComplete = true;
174
- runState.completedReasoning = void 0;
175
- }
176
- return;
177
- }
178
- if (event.type === "stopRunByError") {
179
- const errorEvent = event;
180
- const activityId = `error-${event.id}`;
181
- addActivity({
182
- type: "error",
183
- id: activityId,
184
- expertKey: event.expertKey,
185
- runId: event.runId,
186
- previousActivityId: runState.lastActivityId,
187
- delegatedBy: runState.delegatedBy,
188
- errorName: errorEvent.error.name,
189
- error: errorEvent.error.message,
190
- isRetryable: errorEvent.error.isRetryable
191
- });
192
- runState.lastActivityId = activityId;
193
- runState.isComplete = true;
194
- runState.completedReasoning = void 0;
195
- return;
196
- }
197
- if (isToolCallsEvent(event)) {
198
- for (const toolCall of event.toolCalls) {
199
- if (!state.tools.has(toolCall.id)) {
200
- state.tools.set(toolCall.id, { id: toolCall.id, toolCall, logged: false });
201
- }
202
- }
203
- }
204
- if (isStopRunByDelegateEvent(event)) {
205
- const reasoning = runState.completedReasoning;
206
- const delegations = event.checkpoint.delegateTo ?? [];
207
- runState.pendingDelegateCount += delegations.length;
208
- const delegateActivities = [];
209
- for (const delegation of delegations) {
210
- const existingTool = state.tools.get(delegation.toolCallId);
211
- if (!existingTool || !existingTool.logged) {
212
- if (existingTool) {
213
- existingTool.logged = true;
214
- }
215
- const activityId = `delegate-${delegation.toolCallId}`;
216
- delegateActivities.push({
217
- type: "delegate",
218
- id: activityId,
219
- expertKey: event.expertKey,
220
- runId: event.runId,
221
- previousActivityId: runState.lastActivityId,
222
- delegatedBy: runState.delegatedBy,
223
- delegateExpertKey: delegation.expert.key,
224
- query: delegation.query,
225
- reasoning
226
- });
227
- runState.lastActivityId = activityId;
228
- }
229
- }
230
- const wrapped = wrapInGroupIfParallel(delegateActivities, reasoning, {
231
- expertKey: event.expertKey,
232
- runId: event.runId,
233
- delegatedBy: runState.delegatedBy
234
- });
235
- for (const item of wrapped) {
236
- addActivity(item);
237
- }
238
- runState.completedReasoning = void 0;
239
- return;
240
- }
241
- if (isStopRunByInteractiveToolEvent(event)) {
242
- const reasoning = runState.completedReasoning;
243
- const pendingToolCalls = event.checkpoint.pendingToolCalls ?? [];
244
- const interactiveActivities = [];
245
- for (const toolCall of pendingToolCalls) {
246
- const existingTool = state.tools.get(toolCall.id);
247
- if (!existingTool || !existingTool.logged) {
248
- if (existingTool) {
249
- existingTool.logged = true;
250
- }
251
- const activityId = `interactive-${toolCall.id}`;
252
- interactiveActivities.push({
253
- type: "interactiveTool",
254
- id: activityId,
255
- expertKey: event.expertKey,
256
- runId: event.runId,
257
- previousActivityId: runState.lastActivityId,
258
- delegatedBy: runState.delegatedBy,
259
- skillName: toolCall.skillName,
260
- toolName: toolCall.toolName,
261
- args: toolCall.args,
262
- reasoning
263
- });
264
- runState.lastActivityId = activityId;
265
- }
266
- }
267
- const wrapped = wrapInGroupIfParallel(interactiveActivities, reasoning, {
268
- expertKey: event.expertKey,
269
- runId: event.runId,
270
- delegatedBy: runState.delegatedBy
271
- });
272
- for (const item of wrapped) {
273
- addActivity(item);
274
- }
275
- runState.completedReasoning = void 0;
276
- return;
277
- }
278
- if (isToolResultsEvent(event)) {
279
- const reasoning = runState.completedReasoning;
280
- const toolActivities = [];
281
- for (const toolResult of event.toolResults) {
282
- const tool = state.tools.get(toolResult.id);
283
- if (tool && !tool.logged) {
284
- const activityId = `action-${tool.id}`;
285
- const activity = toolToActivity(tool.toolCall, toolResult, reasoning, {
286
- id: activityId,
287
- expertKey: event.expertKey,
288
- runId: event.runId,
289
- previousActivityId: runState.lastActivityId,
290
- delegatedBy: runState.delegatedBy
291
- });
292
- toolActivities.push(activity);
293
- runState.lastActivityId = activityId;
294
- tool.logged = true;
295
- }
296
- }
297
- if (toolActivities.length > 0) {
298
- const wrapped = wrapInGroupIfParallel(toolActivities, reasoning, {
299
- expertKey: event.expertKey,
300
- runId: event.runId,
301
- delegatedBy: runState.delegatedBy
302
- });
303
- for (const item of wrapped) {
304
- addActivity(item);
305
- }
306
- runState.completedReasoning = void 0;
307
- }
308
- } else if (isToolResultEvent(event)) {
309
- const { toolResult } = event;
310
- const tool = state.tools.get(toolResult.id);
311
- if (tool && !tool.logged) {
312
- const activityId = `action-${tool.id}`;
313
- const activity = toolToActivity(tool.toolCall, toolResult, runState.completedReasoning, {
314
- id: activityId,
315
- expertKey: event.expertKey,
316
- runId: event.runId,
317
- previousActivityId: runState.lastActivityId,
318
- delegatedBy: runState.delegatedBy
319
- });
320
- addActivity(activity);
321
- runState.lastActivityId = activityId;
322
- tool.logged = true;
323
- runState.completedReasoning = void 0;
324
- }
325
- }
93
+ if (isCompleteStreamingReasoningEvent(event)) {
94
+ const { runId, text, expertKey } = event;
95
+ const runState = state.runStates.get(runId);
96
+ if (runState) runState.completedReasoning = text;
97
+ else state.runStates.set(runId, {
98
+ expertKey,
99
+ queryLogged: false,
100
+ completionLogged: false,
101
+ isComplete: false,
102
+ pendingDelegateCount: 0,
103
+ completedReasoning: text
104
+ });
105
+ return;
106
+ }
107
+ if (!isRunEvent(event)) return;
108
+ const runState = getOrCreateRunState(state, event.runId, event.expertKey);
109
+ if (event.type === "startRun") {
110
+ const startRunEvent = event;
111
+ const queryText = startRunEvent.inputMessages.find((m) => m.type === "userMessage")?.contents?.find((c) => c.type === "textPart")?.text;
112
+ if (!runState.delegatedBy) {
113
+ const delegatedByInfo = startRunEvent.initialCheckpoint?.delegatedBy;
114
+ if (delegatedByInfo) runState.delegatedBy = {
115
+ expertKey: delegatedByInfo.expert.key,
116
+ runId: delegatedByInfo.runId
117
+ };
118
+ }
119
+ const isDelegationReturn = startRunEvent.initialCheckpoint?.status === "stoppedByDelegate" || startRunEvent.initialCheckpoint?.status === "stoppedByInteractiveTool";
120
+ if (queryText && !runState.queryLogged && !isDelegationReturn) {
121
+ const activityId = `query-${event.runId}`;
122
+ addActivity({
123
+ type: "query",
124
+ id: activityId,
125
+ expertKey: event.expertKey,
126
+ runId: event.runId,
127
+ previousActivityId: runState.lastActivityId,
128
+ delegatedBy: runState.delegatedBy,
129
+ text: queryText
130
+ });
131
+ runState.lastActivityId = activityId;
132
+ runState.queryLogged = true;
133
+ }
134
+ return;
135
+ }
136
+ if (event.type === "resumeFromStop") {
137
+ if (!runState.delegatedBy) {
138
+ const delegatedByInfo = event.checkpoint?.delegatedBy;
139
+ if (delegatedByInfo) runState.delegatedBy = {
140
+ expertKey: delegatedByInfo.expert.key,
141
+ runId: delegatedByInfo.runId
142
+ };
143
+ }
144
+ return;
145
+ }
146
+ if (event.type === "retry") {
147
+ const retryEvent = event;
148
+ const activityId = `retry-${event.id}`;
149
+ addActivity({
150
+ type: "retry",
151
+ id: activityId,
152
+ expertKey: event.expertKey,
153
+ runId: event.runId,
154
+ previousActivityId: runState.lastActivityId,
155
+ delegatedBy: runState.delegatedBy,
156
+ reasoning: runState.completedReasoning,
157
+ error: retryEvent.reason,
158
+ message: ""
159
+ });
160
+ runState.lastActivityId = activityId;
161
+ runState.completedReasoning = void 0;
162
+ return;
163
+ }
164
+ if (event.type === "completeRun") {
165
+ if (!runState.completionLogged) {
166
+ const text = event.text ?? "";
167
+ const activityId = `completion-${event.runId}`;
168
+ addActivity({
169
+ type: "complete",
170
+ id: activityId,
171
+ expertKey: event.expertKey,
172
+ runId: event.runId,
173
+ previousActivityId: runState.lastActivityId,
174
+ delegatedBy: runState.delegatedBy,
175
+ reasoning: runState.completedReasoning,
176
+ text
177
+ });
178
+ runState.lastActivityId = activityId;
179
+ runState.completionLogged = true;
180
+ runState.isComplete = true;
181
+ runState.completedReasoning = void 0;
182
+ }
183
+ return;
184
+ }
185
+ if (event.type === "stopRunByError") {
186
+ const errorEvent = event;
187
+ const activityId = `error-${event.id}`;
188
+ addActivity({
189
+ type: "error",
190
+ id: activityId,
191
+ expertKey: event.expertKey,
192
+ runId: event.runId,
193
+ previousActivityId: runState.lastActivityId,
194
+ delegatedBy: runState.delegatedBy,
195
+ errorName: errorEvent.error.name,
196
+ error: errorEvent.error.message,
197
+ isRetryable: errorEvent.error.isRetryable
198
+ });
199
+ runState.lastActivityId = activityId;
200
+ runState.isComplete = true;
201
+ runState.completedReasoning = void 0;
202
+ return;
203
+ }
204
+ if (isToolCallsEvent(event)) {
205
+ for (const toolCall of event.toolCalls) if (!state.tools.has(toolCall.id)) state.tools.set(toolCall.id, {
206
+ id: toolCall.id,
207
+ toolCall,
208
+ logged: false
209
+ });
210
+ }
211
+ if (isStopRunByDelegateEvent(event)) {
212
+ const reasoning = runState.completedReasoning;
213
+ const delegations = event.checkpoint.delegateTo ?? [];
214
+ runState.pendingDelegateCount += delegations.length;
215
+ const delegateActivities = [];
216
+ for (const delegation of delegations) {
217
+ const existingTool = state.tools.get(delegation.toolCallId);
218
+ if (!existingTool || !existingTool.logged) {
219
+ if (existingTool) existingTool.logged = true;
220
+ const activityId = `delegate-${delegation.toolCallId}`;
221
+ delegateActivities.push({
222
+ type: "delegate",
223
+ id: activityId,
224
+ expertKey: event.expertKey,
225
+ runId: event.runId,
226
+ previousActivityId: runState.lastActivityId,
227
+ delegatedBy: runState.delegatedBy,
228
+ delegateExpertKey: delegation.expert.key,
229
+ query: delegation.query,
230
+ reasoning
231
+ });
232
+ runState.lastActivityId = activityId;
233
+ }
234
+ }
235
+ const wrapped = wrapInGroupIfParallel(delegateActivities, reasoning, {
236
+ expertKey: event.expertKey,
237
+ runId: event.runId,
238
+ delegatedBy: runState.delegatedBy
239
+ });
240
+ for (const item of wrapped) addActivity(item);
241
+ runState.completedReasoning = void 0;
242
+ return;
243
+ }
244
+ if (isStopRunByInteractiveToolEvent(event)) {
245
+ const reasoning = runState.completedReasoning;
246
+ const pendingToolCalls = event.checkpoint.pendingToolCalls ?? [];
247
+ const interactiveActivities = [];
248
+ for (const toolCall of pendingToolCalls) {
249
+ const existingTool = state.tools.get(toolCall.id);
250
+ if (!existingTool || !existingTool.logged) {
251
+ if (existingTool) existingTool.logged = true;
252
+ const activityId = `interactive-${toolCall.id}`;
253
+ interactiveActivities.push({
254
+ type: "interactiveTool",
255
+ id: activityId,
256
+ expertKey: event.expertKey,
257
+ runId: event.runId,
258
+ previousActivityId: runState.lastActivityId,
259
+ delegatedBy: runState.delegatedBy,
260
+ skillName: toolCall.skillName,
261
+ toolName: toolCall.toolName,
262
+ args: toolCall.args,
263
+ reasoning
264
+ });
265
+ runState.lastActivityId = activityId;
266
+ }
267
+ }
268
+ const wrapped = wrapInGroupIfParallel(interactiveActivities, reasoning, {
269
+ expertKey: event.expertKey,
270
+ runId: event.runId,
271
+ delegatedBy: runState.delegatedBy
272
+ });
273
+ for (const item of wrapped) addActivity(item);
274
+ runState.completedReasoning = void 0;
275
+ return;
276
+ }
277
+ if (isToolResultsEvent(event)) {
278
+ const reasoning = runState.completedReasoning;
279
+ const toolActivities = [];
280
+ for (const toolResult of event.toolResults) {
281
+ const tool = state.tools.get(toolResult.id);
282
+ if (tool && !tool.logged) {
283
+ const activityId = `action-${tool.id}`;
284
+ const activity = toolToActivity(tool.toolCall, toolResult, reasoning, {
285
+ id: activityId,
286
+ expertKey: event.expertKey,
287
+ runId: event.runId,
288
+ previousActivityId: runState.lastActivityId,
289
+ delegatedBy: runState.delegatedBy
290
+ });
291
+ toolActivities.push(activity);
292
+ runState.lastActivityId = activityId;
293
+ tool.logged = true;
294
+ }
295
+ }
296
+ if (toolActivities.length > 0) {
297
+ const wrapped = wrapInGroupIfParallel(toolActivities, reasoning, {
298
+ expertKey: event.expertKey,
299
+ runId: event.runId,
300
+ delegatedBy: runState.delegatedBy
301
+ });
302
+ for (const item of wrapped) addActivity(item);
303
+ runState.completedReasoning = void 0;
304
+ }
305
+ }
326
306
  }
327
307
 
328
- // src/hooks/use-run.ts
329
- var STREAMING_EVENT_TYPES = /* @__PURE__ */ new Set([
330
- "startStreamingReasoning",
331
- "streamReasoning",
332
- "completeStreamingReasoning",
333
- "startStreamingRunResult",
334
- "streamRunResult",
335
- "completeStreamingRunResult"
308
+ //#endregion
309
+ //#region src/hooks/use-run.ts
310
+ const STREAMING_EVENT_TYPES = new Set([
311
+ "startStreamingReasoning",
312
+ "streamReasoning",
313
+ "completeStreamingReasoning",
314
+ "startStreamingRunResult",
315
+ "streamRunResult",
316
+ "completeStreamingRunResult"
336
317
  ]);
337
- var isStreamingEvent = (event) => "type" in event && "expertKey" in event && STREAMING_EVENT_TYPES.has(event.type);
318
+ const isStreamingEvent = (event) => "type" in event && "expertKey" in event && STREAMING_EVENT_TYPES.has(event.type);
338
319
  function processStreamingEvent(event, prevState) {
339
- const { runId, expertKey } = event;
340
- switch (event.type) {
341
- case "startStreamingReasoning": {
342
- return {
343
- newState: {
344
- ...prevState,
345
- runs: {
346
- ...prevState.runs,
347
- [runId]: {
348
- expertKey,
349
- reasoning: "",
350
- isReasoningActive: true
351
- }
352
- }
353
- },
354
- handled: true
355
- };
356
- }
357
- case "streamReasoning": {
358
- return {
359
- newState: {
360
- ...prevState,
361
- runs: {
362
- ...prevState.runs,
363
- [runId]: {
364
- ...prevState.runs[runId],
365
- expertKey,
366
- reasoning: (prevState.runs[runId]?.reasoning ?? "") + event.delta
367
- }
368
- }
369
- },
370
- handled: true
371
- };
372
- }
373
- case "completeStreamingReasoning": {
374
- return {
375
- newState: {
376
- ...prevState,
377
- runs: {
378
- ...prevState.runs,
379
- [runId]: {
380
- ...prevState.runs[runId],
381
- expertKey,
382
- isReasoningActive: false
383
- }
384
- }
385
- },
386
- handled: false
387
- };
388
- }
389
- case "startStreamingRunResult": {
390
- return {
391
- newState: {
392
- ...prevState,
393
- runs: {
394
- ...prevState.runs,
395
- [runId]: {
396
- expertKey,
397
- reasoning: void 0,
398
- isReasoningActive: false,
399
- runResult: "",
400
- isRunResultActive: true
401
- }
402
- }
403
- },
404
- handled: true
405
- };
406
- }
407
- case "streamRunResult": {
408
- return {
409
- newState: {
410
- ...prevState,
411
- runs: {
412
- ...prevState.runs,
413
- [runId]: {
414
- ...prevState.runs[runId],
415
- expertKey,
416
- runResult: (prevState.runs[runId]?.runResult ?? "") + event.delta
417
- }
418
- }
419
- },
420
- handled: true
421
- };
422
- }
423
- case "completeStreamingRunResult": {
424
- return {
425
- newState: {
426
- ...prevState,
427
- runs: {
428
- ...prevState.runs,
429
- [runId]: {
430
- ...prevState.runs[runId],
431
- expertKey,
432
- isRunResultActive: false
433
- }
434
- }
435
- },
436
- handled: true
437
- };
438
- }
439
- default:
440
- return { newState: prevState, handled: false };
441
- }
320
+ const { runId, expertKey } = event;
321
+ switch (event.type) {
322
+ case "startStreamingReasoning": return {
323
+ newState: {
324
+ ...prevState,
325
+ runs: {
326
+ ...prevState.runs,
327
+ [runId]: {
328
+ expertKey,
329
+ reasoning: "",
330
+ isReasoningActive: true
331
+ }
332
+ }
333
+ },
334
+ handled: true
335
+ };
336
+ case "streamReasoning": return {
337
+ newState: {
338
+ ...prevState,
339
+ runs: {
340
+ ...prevState.runs,
341
+ [runId]: {
342
+ ...prevState.runs[runId],
343
+ expertKey,
344
+ reasoning: (prevState.runs[runId]?.reasoning ?? "") + event.delta
345
+ }
346
+ }
347
+ },
348
+ handled: true
349
+ };
350
+ case "completeStreamingReasoning": return {
351
+ newState: {
352
+ ...prevState,
353
+ runs: {
354
+ ...prevState.runs,
355
+ [runId]: {
356
+ ...prevState.runs[runId],
357
+ expertKey,
358
+ isReasoningActive: false
359
+ }
360
+ }
361
+ },
362
+ handled: false
363
+ };
364
+ case "startStreamingRunResult": return {
365
+ newState: {
366
+ ...prevState,
367
+ runs: {
368
+ ...prevState.runs,
369
+ [runId]: {
370
+ expertKey,
371
+ reasoning: void 0,
372
+ isReasoningActive: false,
373
+ runResult: "",
374
+ isRunResultActive: true
375
+ }
376
+ }
377
+ },
378
+ handled: true
379
+ };
380
+ case "streamRunResult": return {
381
+ newState: {
382
+ ...prevState,
383
+ runs: {
384
+ ...prevState.runs,
385
+ [runId]: {
386
+ ...prevState.runs[runId],
387
+ expertKey,
388
+ runResult: (prevState.runs[runId]?.runResult ?? "") + event.delta
389
+ }
390
+ }
391
+ },
392
+ handled: true
393
+ };
394
+ case "completeStreamingRunResult": return {
395
+ newState: {
396
+ ...prevState,
397
+ runs: {
398
+ ...prevState.runs,
399
+ [runId]: {
400
+ ...prevState.runs[runId],
401
+ expertKey,
402
+ isRunResultActive: false
403
+ }
404
+ }
405
+ },
406
+ handled: true
407
+ };
408
+ default: return {
409
+ newState: prevState,
410
+ handled: false
411
+ };
412
+ }
442
413
  }
414
+ /**
415
+ * Hook for managing Run state from RunEvent stream.
416
+ *
417
+ * Architecture:
418
+ * - ExpertStateEvent → ActivityOrGroup[] (accumulated, append-only)
419
+ * - StreamingEvent → StreamingState (latest only, for display)
420
+ *
421
+ * IMPORTANT: activities are append-only and never cleared.
422
+ * This is required for compatibility with Ink's <Static> component.
423
+ */
443
424
  function useRun() {
444
- const [activities, setActivities] = useState([]);
445
- const [streaming, setStreaming] = useState({ runs: {} });
446
- const [eventCount, setEventCount] = useState(0);
447
- const [isComplete, setIsComplete] = useState(false);
448
- const stateRef = useRef(createInitialActivityProcessState());
449
- const clearStreaming = useCallback(() => {
450
- setStreaming({ runs: {} });
451
- }, []);
452
- const handleStreamingEvent = useCallback((event) => {
453
- let handled = false;
454
- setStreaming((prev) => {
455
- const result = processStreamingEvent(event, prev);
456
- handled = result.handled;
457
- return result.newState;
458
- });
459
- return handled;
460
- }, []);
461
- const processEvent = useCallback((event) => {
462
- const newActivities = [];
463
- const addActivity = (activity) => newActivities.push(activity);
464
- processRunEventToActivity(stateRef.current, event, addActivity);
465
- if (newActivities.length > 0) {
466
- setActivities((prev) => [...prev, ...newActivities]);
467
- }
468
- const rootRunComplete = Array.from(stateRef.current.runStates.values()).some(
469
- (rs) => rs.isComplete && !rs.delegatedBy
470
- );
471
- setIsComplete(rootRunComplete);
472
- }, []);
473
- const clearRunStreaming = useCallback((runId) => {
474
- setStreaming((prev) => {
475
- const { [runId]: _, ...rest } = prev.runs;
476
- return { runs: rest };
477
- });
478
- }, []);
479
- const addEvent = useCallback(
480
- (event) => {
481
- if (isStreamingEvent(event)) {
482
- const handled = handleStreamingEvent(event);
483
- if (handled) {
484
- setEventCount((prev) => prev + 1);
485
- return;
486
- }
487
- }
488
- if ("type" in event && "runId" in event && (event.type === "completeRun" || event.type === "stopRunByError")) {
489
- clearRunStreaming(event.runId);
490
- }
491
- processEvent(event);
492
- setEventCount((prev) => prev + 1);
493
- },
494
- [handleStreamingEvent, clearRunStreaming, processEvent]
495
- );
496
- const appendHistoricalEvents = useCallback((historicalEvents) => {
497
- const newActivities = [];
498
- const addActivity = (activity) => newActivities.push(activity);
499
- for (const event of historicalEvents) {
500
- processRunEventToActivity(stateRef.current, event, addActivity);
501
- }
502
- if (newActivities.length > 0) {
503
- setActivities((prev) => [...prev, ...newActivities]);
504
- }
505
- setEventCount((prev) => prev + historicalEvents.length);
506
- const rootRunComplete = Array.from(stateRef.current.runStates.values()).some(
507
- (rs) => rs.isComplete && !rs.delegatedBy
508
- );
509
- setIsComplete(rootRunComplete);
510
- }, []);
511
- return {
512
- activities,
513
- streaming,
514
- isComplete,
515
- eventCount,
516
- addEvent,
517
- appendHistoricalEvents,
518
- clearStreaming
519
- };
425
+ const [activities, setActivities] = useState([]);
426
+ const [streaming, setStreaming] = useState({ runs: {} });
427
+ const [eventCount, setEventCount] = useState(0);
428
+ const [isComplete, setIsComplete] = useState(false);
429
+ const stateRef = useRef(createInitialActivityProcessState());
430
+ const clearStreaming = useCallback(() => {
431
+ setStreaming({ runs: {} });
432
+ }, []);
433
+ const handleStreamingEvent = useCallback((event) => {
434
+ let handled = false;
435
+ setStreaming((prev) => {
436
+ const result = processStreamingEvent(event, prev);
437
+ handled = result.handled;
438
+ return result.newState;
439
+ });
440
+ return handled;
441
+ }, []);
442
+ const processEvent = useCallback((event) => {
443
+ const newActivities = [];
444
+ const addActivity = (activity) => newActivities.push(activity);
445
+ processRunEventToActivity(stateRef.current, event, addActivity);
446
+ if (newActivities.length > 0) setActivities((prev) => [...prev, ...newActivities]);
447
+ setIsComplete(Array.from(stateRef.current.runStates.values()).some((rs) => rs.isComplete && !rs.delegatedBy));
448
+ }, []);
449
+ const clearRunStreaming = useCallback((runId) => {
450
+ setStreaming((prev) => {
451
+ const { [runId]: _, ...rest } = prev.runs;
452
+ return { runs: rest };
453
+ });
454
+ }, []);
455
+ return {
456
+ activities,
457
+ streaming,
458
+ isComplete,
459
+ eventCount,
460
+ addEvent: useCallback((event) => {
461
+ if (isStreamingEvent(event)) {
462
+ if (handleStreamingEvent(event)) {
463
+ setEventCount((prev) => prev + 1);
464
+ return;
465
+ }
466
+ }
467
+ if ("type" in event && "runId" in event && (event.type === "completeRun" || event.type === "stopRunByError")) clearRunStreaming(event.runId);
468
+ processEvent(event);
469
+ setEventCount((prev) => prev + 1);
470
+ }, [
471
+ handleStreamingEvent,
472
+ clearRunStreaming,
473
+ processEvent
474
+ ]),
475
+ appendHistoricalEvents: useCallback((historicalEvents) => {
476
+ const newActivities = [];
477
+ const addActivity = (activity) => newActivities.push(activity);
478
+ for (const event of historicalEvents) processRunEventToActivity(stateRef.current, event, addActivity);
479
+ if (newActivities.length > 0) setActivities((prev) => [...prev, ...newActivities]);
480
+ setEventCount((prev) => prev + historicalEvents.length);
481
+ setIsComplete(Array.from(stateRef.current.runStates.values()).some((rs) => rs.isComplete && !rs.delegatedBy));
482
+ }, []),
483
+ clearStreaming
484
+ };
520
485
  }
521
486
 
522
- // src/hooks/use-job-stream.ts
487
+ //#endregion
488
+ //#region src/hooks/use-job-stream.ts
523
489
  function useJobStream(options) {
524
- const { jobId, connect, enabled = true } = options;
525
- const shouldConnect = Boolean(jobId && enabled);
526
- const { activities, streaming, addEvent } = useRun();
527
- const [isConnected, setIsConnected] = useState(false);
528
- const [error, setError] = useState(null);
529
- const addEventRef = useRef(addEvent);
530
- addEventRef.current = addEvent;
531
- const connectRef = useRef(connect);
532
- connectRef.current = connect;
533
- useEffect(() => {
534
- if (!shouldConnect || !jobId) {
535
- setIsConnected(false);
536
- return;
537
- }
538
- const controller = new AbortController();
539
- async function run() {
540
- setError(null);
541
- setIsConnected(true);
542
- try {
543
- await consumeStream(connectRef.current, jobId, controller.signal, (event) => {
544
- addEventRef.current(event);
545
- });
546
- } catch (err) {
547
- if (isAbortError(err)) return;
548
- setError(err instanceof Error ? err : new Error(String(err)));
549
- } finally {
550
- setIsConnected(false);
551
- }
552
- }
553
- run();
554
- return () => {
555
- controller.abort();
556
- };
557
- }, [shouldConnect, jobId]);
558
- const latestActivity = useMemo(
559
- () => activities.length > 0 ? activities[activities.length - 1] : null,
560
- [activities]
561
- );
562
- return { activities, streaming, latestActivity, isConnected, error };
490
+ const { jobId, connect, enabled = true } = options;
491
+ const shouldConnect = Boolean(jobId && enabled);
492
+ const { activities, streaming, addEvent } = useRun();
493
+ const [isConnected, setIsConnected] = useState(false);
494
+ const [error, setError] = useState(null);
495
+ const addEventRef = useRef(addEvent);
496
+ addEventRef.current = addEvent;
497
+ const connectRef = useRef(connect);
498
+ connectRef.current = connect;
499
+ useEffect(() => {
500
+ if (!shouldConnect || !jobId) {
501
+ setIsConnected(false);
502
+ return;
503
+ }
504
+ const controller = new AbortController();
505
+ async function run() {
506
+ setError(null);
507
+ setIsConnected(true);
508
+ try {
509
+ await consumeStream(connectRef.current, jobId, controller.signal, (event) => {
510
+ addEventRef.current(event);
511
+ });
512
+ } catch (err) {
513
+ if (isAbortError(err)) return;
514
+ setError(err instanceof Error ? err : new Error(String(err)));
515
+ } finally {
516
+ setIsConnected(false);
517
+ }
518
+ }
519
+ run();
520
+ return () => {
521
+ controller.abort();
522
+ };
523
+ }, [shouldConnect, jobId]);
524
+ return {
525
+ activities,
526
+ streaming,
527
+ latestActivity: useMemo(() => activities.length > 0 ? activities[activities.length - 1] : null, [activities]),
528
+ isConnected,
529
+ error
530
+ };
563
531
  }
532
+
533
+ //#endregion
534
+ //#region src/hooks/use-job-streams.ts
564
535
  function useJobStreams(options) {
565
- const { jobs, connect } = options;
566
- const [states, setStates] = useState(/* @__PURE__ */ new Map());
567
- const controllersRef = useRef(/* @__PURE__ */ new Map());
568
- const activityStateRef = useRef(/* @__PURE__ */ new Map());
569
- const connectRef = useRef(connect);
570
- connectRef.current = connect;
571
- const connectToJob = useCallback(async (jobId, signal) => {
572
- setStates((prev) => {
573
- const next = new Map(prev);
574
- next.set(jobId, { latestActivity: null, isConnected: true });
575
- return next;
576
- });
577
- let state = activityStateRef.current.get(jobId);
578
- if (!state) {
579
- state = createInitialActivityProcessState();
580
- activityStateRef.current.set(jobId, state);
581
- }
582
- try {
583
- await consumeStream(connectRef.current, jobId, signal, (event) => {
584
- let latestActivity = null;
585
- processRunEventToActivity(state, event, (activity) => {
586
- latestActivity = activity;
587
- });
588
- if (latestActivity) {
589
- setStates((prev) => {
590
- const next = new Map(prev);
591
- next.set(jobId, { latestActivity, isConnected: true });
592
- return next;
593
- });
594
- }
595
- });
596
- } catch (err) {
597
- if (isAbortError(err)) return;
598
- console.error(`Stream connection failed for job ${jobId}:`, err);
599
- } finally {
600
- setStates((prev) => {
601
- const current = prev.get(jobId);
602
- if (!current) return prev;
603
- const next = new Map(prev);
604
- next.set(jobId, { ...current, isConnected: false });
605
- return next;
606
- });
607
- }
608
- }, []);
609
- useEffect(() => {
610
- const enabledIds = new Set(jobs.filter((j) => j.enabled).map((j) => j.id));
611
- for (const [jobId, controller] of controllersRef.current) {
612
- if (!enabledIds.has(jobId)) {
613
- controller.abort();
614
- controllersRef.current.delete(jobId);
615
- activityStateRef.current.delete(jobId);
616
- setStates((prev) => {
617
- const next = new Map(prev);
618
- next.delete(jobId);
619
- return next;
620
- });
621
- }
622
- }
623
- for (const jobId of enabledIds) {
624
- if (!controllersRef.current.has(jobId)) {
625
- const controller = new AbortController();
626
- controllersRef.current.set(jobId, controller);
627
- connectToJob(jobId, controller.signal);
628
- }
629
- }
630
- }, [jobs, connectToJob]);
631
- useEffect(() => {
632
- return () => {
633
- for (const controller of controllersRef.current.values()) {
634
- controller.abort();
635
- }
636
- controllersRef.current.clear();
637
- activityStateRef.current.clear();
638
- };
639
- }, []);
640
- return states;
536
+ const { jobs, connect } = options;
537
+ const [states, setStates] = useState(/* @__PURE__ */ new Map());
538
+ const controllersRef = useRef(/* @__PURE__ */ new Map());
539
+ const activityStateRef = useRef(/* @__PURE__ */ new Map());
540
+ const connectRef = useRef(connect);
541
+ connectRef.current = connect;
542
+ const connectToJob = useCallback(async (jobId, signal) => {
543
+ setStates((prev) => {
544
+ const next = new Map(prev);
545
+ next.set(jobId, {
546
+ latestActivity: null,
547
+ isConnected: true
548
+ });
549
+ return next;
550
+ });
551
+ let state = activityStateRef.current.get(jobId);
552
+ if (!state) {
553
+ state = createInitialActivityProcessState();
554
+ activityStateRef.current.set(jobId, state);
555
+ }
556
+ try {
557
+ await consumeStream(connectRef.current, jobId, signal, (event) => {
558
+ let latestActivity = null;
559
+ processRunEventToActivity(state, event, (activity) => {
560
+ latestActivity = activity;
561
+ });
562
+ if (latestActivity) setStates((prev) => {
563
+ const next = new Map(prev);
564
+ next.set(jobId, {
565
+ latestActivity,
566
+ isConnected: true
567
+ });
568
+ return next;
569
+ });
570
+ });
571
+ } catch (err) {
572
+ if (isAbortError(err)) return;
573
+ console.error(`Stream connection failed for job ${jobId}:`, err);
574
+ } finally {
575
+ setStates((prev) => {
576
+ const current = prev.get(jobId);
577
+ if (!current) return prev;
578
+ const next = new Map(prev);
579
+ next.set(jobId, {
580
+ ...current,
581
+ isConnected: false
582
+ });
583
+ return next;
584
+ });
585
+ }
586
+ }, []);
587
+ useEffect(() => {
588
+ const enabledIds = new Set(jobs.filter((j) => j.enabled).map((j) => j.id));
589
+ for (const [jobId, controller] of controllersRef.current) if (!enabledIds.has(jobId)) {
590
+ controller.abort();
591
+ controllersRef.current.delete(jobId);
592
+ activityStateRef.current.delete(jobId);
593
+ setStates((prev) => {
594
+ const next = new Map(prev);
595
+ next.delete(jobId);
596
+ return next;
597
+ });
598
+ }
599
+ for (const jobId of enabledIds) if (!controllersRef.current.has(jobId)) {
600
+ const controller = new AbortController();
601
+ controllersRef.current.set(jobId, controller);
602
+ connectToJob(jobId, controller.signal);
603
+ }
604
+ }, [jobs, connectToJob]);
605
+ useEffect(() => {
606
+ return () => {
607
+ for (const controller of controllersRef.current.values()) controller.abort();
608
+ controllersRef.current.clear();
609
+ activityStateRef.current.clear();
610
+ };
611
+ }, []);
612
+ return states;
641
613
  }
642
614
 
643
- // src/utils/group-by-run.ts
615
+ //#endregion
616
+ //#region src/utils/group-by-run.ts
617
+ /**
618
+ * Extract base properties from ActivityOrGroup for grouping.
619
+ */
644
620
  function getActivityProps(activityOrGroup) {
645
- if (activityOrGroup.type === "parallelGroup") {
646
- const firstActivity = activityOrGroup.activities[0];
647
- return {
648
- runId: activityOrGroup.runId,
649
- expertKey: activityOrGroup.expertKey,
650
- delegatedBy: firstActivity?.delegatedBy
651
- };
652
- }
653
- return activityOrGroup;
621
+ if (activityOrGroup.type === "parallelGroup") {
622
+ const firstActivity = activityOrGroup.activities[0];
623
+ return {
624
+ runId: activityOrGroup.runId,
625
+ expertKey: activityOrGroup.expertKey,
626
+ delegatedBy: firstActivity?.delegatedBy
627
+ };
628
+ }
629
+ return activityOrGroup;
654
630
  }
631
+ /**
632
+ * Groups activities by their runId while preserving order.
633
+ *
634
+ * This function groups activities so that each run can be displayed in its own
635
+ * visual section (e.g., separate <Static> components in Ink).
636
+ *
637
+ * @param activities - Array of activities or groups to group
638
+ * @returns Array of RunGroup objects, ordered by first appearance
639
+ */
655
640
  function groupActivitiesByRun(activities) {
656
- const groupMap = /* @__PURE__ */ new Map();
657
- const order = [];
658
- for (const activityOrGroup of activities) {
659
- const { runId, expertKey, delegatedBy } = getActivityProps(activityOrGroup);
660
- if (!groupMap.has(runId)) {
661
- groupMap.set(runId, {
662
- runId,
663
- expertKey,
664
- activities: [],
665
- delegatedBy
666
- });
667
- order.push(runId);
668
- }
669
- const group = groupMap.get(runId);
670
- group.activities.push(activityOrGroup);
671
- }
672
- return order.map((runId) => groupMap.get(runId));
641
+ const groupMap = /* @__PURE__ */ new Map();
642
+ const order = [];
643
+ for (const activityOrGroup of activities) {
644
+ const { runId, expertKey, delegatedBy } = getActivityProps(activityOrGroup);
645
+ if (!groupMap.has(runId)) {
646
+ groupMap.set(runId, {
647
+ runId,
648
+ expertKey,
649
+ activities: [],
650
+ delegatedBy
651
+ });
652
+ order.push(runId);
653
+ }
654
+ groupMap.get(runId).activities.push(activityOrGroup);
655
+ }
656
+ return order.map((runId) => groupMap.get(runId));
673
657
  }
674
658
 
659
+ //#endregion
675
660
  export { createInitialActivityProcessState, groupActivitiesByRun, processRunEventToActivity, toolToActivity, useJobStream, useJobStreams, useRun };
676
- //# sourceMappingURL=index.js.map
677
661
  //# sourceMappingURL=index.js.map