@rebyteai/agent-react 0.2.4 → 0.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/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # @rebyteai/agent-react
2
2
 
3
+ The function-wait state and `submitToolResult` below require version 0.4.0 or later.
4
+
3
5
  Install the versioned package (no repository clone required):
4
6
 
5
7
  ```sh
6
- pnpm add @rebyteai/agent-react@0.2.0
8
+ pnpm add @rebyteai/agent-react
7
9
  ```
8
10
 
9
11
 
@@ -29,11 +31,36 @@ Each assistant message has a `turnId` and a presentation-only `projection`:
29
31
  Built-in server functions are distinct from client functions. A completed client
30
32
  result updates its matching call; the actual handoff is Session `requires_action`.
31
33
 
32
- History is restored from persisted Items and Turns. A recovered active Session is
33
- polled until settled. Live disconnects report an error; reload recovers output
34
- without resending input. The hook reports client-tool waiting as an error and does
35
- not run application handlers or submit their outputs. For those workflows, use
36
- the [Rebyte SDK recipe](../../examples/agents-api/README.md) or Commerce adapter.
34
+ History is restored from persisted Items and Turns. A recovered active or waiting
35
+ Session is polled until settled. Live disconnects report an error; reload recovers
36
+ output without resending input.
37
+
38
+ ## Application functions
39
+
40
+ `chat.status === 'requires_action'` is a normal waiting state. Read
41
+ `chat.requiredActions`; execute only these authoritative calls, never every
42
+ `function_call` Item. The hook leaves execution to your application and continues
43
+ observing while an external backend worker supplies results.
44
+
45
+ An application handler can explicitly return a saved result through the hook:
46
+
47
+ ```tsx
48
+ // Called by your application's function handler, not on every React render.
49
+ await chat.submitToolResult({
50
+ turn_id: action.turn_id, call_id: action.call_id,
51
+ success: true, output: JSON.stringify(result),
52
+ })
53
+ // Failure: { turn_id, call_id, success: false, error: 'Lookup failed' }
54
+ ```
55
+
56
+ The method submits one result per request and reuses a stable per-call idempotency
57
+ key. Persist side effects and results by `call_id` in your application; a reload
58
+ or repeated notification is not authorization to execute a side effect twice.
59
+ Both ordinary functions and functions inside Dynamic Workflow use this path.
60
+ `send()` remains pending through handoffs until the Turn settles. `stop()` cancels
61
+ waiting work too. Reload restores the pending actions and observes subsequent
62
+ results; it never automatically executes your handler. See the
63
+ [Agents API recipes](../../examples/agents-api/README.md) for backend consumers.
37
64
 
38
65
  Images are uploaded as Session files. The model can use hosted `view_image` to
39
66
  inspect them; the upload itself is not an inline model image message. No browser
package/dist/index.cjs CHANGED
@@ -52,7 +52,7 @@ function createTurnState() {
52
52
  }
53
53
 
54
54
  // src/sessions.ts
55
- var import_streaming = require("@rebyteai/agent-sdk/core/streaming");
55
+ var import_streaming = require("openai/core/streaming");
56
56
  function createAgentSessionTransport(options) {
57
57
  const base = options.url.replace(/\/$/, "");
58
58
  const request = options.fetch ?? globalThis.fetch;
@@ -154,7 +154,7 @@ function projectSessionItem(state, item, index) {
154
154
  }
155
155
  if (item.type === "function_call_output") return { ...state, toolCalls: state.toolCalls.map((tool) => tool.callId === item.call_id ? { ...tool, status: item.status, output: typeof item.output === "string" ? item.output : JSON.stringify(item.output), error: item.error } : tool) };
156
156
  if (item.type !== "command_execution" && item.type !== "mcp_call" && item.type !== "function_call") return state;
157
- const serverFunction = item.type === "function_call" && ["write_stdin", "apply_patch", "view_image", "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource", "rebyte_web_search"].includes(item.name);
157
+ const serverFunction = item.type === "function_call" && ["write_stdin", "apply_patch", "view_image", "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource", "rebyte_web_search", "tool_search", "run_code"].includes(item.name);
158
158
  const value = {
159
159
  id: item.id,
160
160
  outputIndex: index,
@@ -172,7 +172,8 @@ function projectSessionItem(state, item, index) {
172
172
  function reduceSessionEvent(state, event) {
173
173
  let next = { ...state, events: [...state.events, event] };
174
174
  if (event.type === "agent.session.turn.item.added" || event.type === "agent.session.turn.item.done") {
175
- if (event.output_index !== null) next = projectSessionItem(next, event.item, event.output_index);
175
+ if (event.item.type === "function_call_output") next = projectSessionItem(next, event.item, 0);
176
+ else if (event.output_index !== null) next = projectSessionItem(next, event.item, event.output_index);
176
177
  } else if (event.type === "agent.session.turn.output_text.delta") {
177
178
  if (!next.textMessages.some((message) => message.id === event.item_id)) throw new Error("Text delta has no message");
178
179
  const messages = next.textMessages.map((message) => message.id === event.item_id ? { ...message, text: message.text + event.delta } : message);
@@ -195,6 +196,7 @@ function useAgentSession(options) {
195
196
  const [error, setError] = (0, import_react.useState)(null);
196
197
  const [sessionId, setSessionId] = (0, import_react.useState)(options.initialSessionId ?? null);
197
198
  const [artifacts, setArtifacts] = (0, import_react.useState)([]);
199
+ const [requiredActions, setRequiredActions] = (0, import_react.useState)([]);
198
200
  const idRef = (0, import_react.useRef)(sessionId);
199
201
  const creating = (0, import_react.useRef)(null);
200
202
  const active = (0, import_react.useRef)(null);
@@ -235,7 +237,7 @@ function useAgentSession(options) {
235
237
  if (item.type === "message" && item.role === "user" && item.id !== null) restored.push({ id: item.id, role: "user", content: item.content.filter((part) => part.type === "input_text").map((part) => part.text).join("\n"), status: "completed", turnId: null, projection: null });
236
238
  }
237
239
  const projection = sessionItemState(turnItems);
238
- restored.push({ id: turn.id, role: "assistant", content: projection.textMessages.map((item) => item.text).join("\n\n"), status: turn.status === "failed" ? "failed" : turn.status === "cancelled" ? "cancelled" : turn.status === "completed" ? "completed" : "streaming", turnId: turn.id, projection: { ...projection, turnId: turn.id } });
240
+ restored.push({ id: turn.id, role: "assistant", content: projection.textMessages.map((item) => item.text).join("\n\n"), status: turn.status === "failed" ? "failed" : turn.status === "cancelled" ? "cancelled" : turn.status === "completed" ? "completed" : turn.status === "waiting" ? "waiting" : "streaming", turnId: turn.id, projection: { ...projection, turnId: turn.id } });
239
241
  }
240
242
  setMessages((previous) => restored.map((message) => {
241
243
  const existing = previous.find((value) => value.turnId !== null && value.turnId === message.turnId);
@@ -245,7 +247,9 @@ function useAgentSession(options) {
245
247
  }, [transport]);
246
248
  (0, import_react.useEffect)(() => {
247
249
  const id = initial.current;
248
- if (!id) return;
250
+ if (!id) return () => {
251
+ active.current?.abort();
252
+ };
249
253
  let cancelled = false;
250
254
  busy.current = true;
251
255
  setStatus("streaming");
@@ -253,11 +257,13 @@ function useAgentSession(options) {
253
257
  await refresh(id);
254
258
  while (!cancelled) {
255
259
  const session = await transport.retrieve(id);
256
- if (session.status !== "in_progress") {
260
+ if (cancelled) return;
261
+ setRequiredActions(session.required_actions);
262
+ if (session.status !== "in_progress" && session.status !== "requires_action") {
257
263
  if (session.status === "failed") throw new Error(session.error ?? "Session failed");
258
- if (session.status === "requires_action") throw new Error("Session is waiting for a client tool result");
259
264
  break;
260
265
  }
266
+ setStatus(session.status === "requires_action" ? "requires_action" : "streaming");
261
267
  await new Promise((resolve) => setTimeout(resolve, 1e3));
262
268
  if (!cancelled) await refresh(id);
263
269
  }
@@ -284,6 +290,7 @@ function useAgentSession(options) {
284
290
  submitted.current = false;
285
291
  setStatus("streaming");
286
292
  setError(null);
293
+ setRequiredActions([]);
287
294
  const abort = new AbortController();
288
295
  active.current = abort;
289
296
  const assistantId = crypto.randomUUID();
@@ -317,12 +324,18 @@ function useAgentSession(options) {
317
324
  setMessages((previous) => previous.map((message) => message.id === assistantId ? { ...message, content: projection.textMessages.map((item) => item.text).join("\n\n"), turnId: projection.turnId, projection } : message));
318
325
  if (event.type === "agent.session.turn.completed" || event.type === "agent.session.turn.cancelled" || event.type === "agent.session.turn.failed") {
319
326
  await refresh(id);
327
+ setRequiredActions([]);
320
328
  if (event.type === "agent.session.turn.failed") throw new Error(event.turn.error?.message ?? "Turn failed");
321
329
  setStatus("idle");
322
330
  return event.turn;
323
331
  }
324
332
  if (event.type === "agent.session.failed") throw new Error(event.session.error ?? "Session failed");
325
- if (event.type === "agent.session.requires_action") throw new Error("Session is waiting for a client tool result");
333
+ if (event.type === "agent.session.requires_action" || event.type === "agent.session.in_progress") {
334
+ const waiting = event.type === "agent.session.requires_action";
335
+ setRequiredActions(event.session.required_actions);
336
+ setStatus(waiting ? "requires_action" : "streaming");
337
+ setMessages((previous) => previous.map((message) => message.id === assistantId ? { ...message, status: waiting ? "waiting" : "streaming" } : message));
338
+ }
326
339
  if (event.type === "error") throw new Error(event.error.message);
327
340
  }
328
341
  throw new Error("Session event stream disconnected. Reload to recover persisted output.");
@@ -348,12 +361,26 @@ function useAgentSession(options) {
348
361
  throw fail(cause);
349
362
  }
350
363
  }, [transport, fail]);
364
+ const submitToolResult = (0, import_react.useCallback)(async (result) => {
365
+ const id = idRef.current;
366
+ if (!id) throw new Error("No Session is waiting for a function result");
367
+ setError(null);
368
+ try {
369
+ await transport.submit(id, [{ type: "agent.session.input.tool_result", ...result }], `agent-result:${result.turn_id}:${result.call_id}`);
370
+ } catch (cause) {
371
+ const failure = cause instanceof Error ? cause : new Error(String(cause));
372
+ setError(failure);
373
+ throw failure;
374
+ }
375
+ }, [transport]);
351
376
  return {
352
377
  messages,
353
378
  status,
354
379
  error,
355
380
  sessionId,
356
381
  artifacts,
382
+ requiredActions,
383
+ submitToolResult,
357
384
  send,
358
385
  stop,
359
386
  async upload(file, progress) {
@@ -368,6 +395,7 @@ function useAgentSession(options) {
368
395
  setSessionId(null);
369
396
  setMessages([]);
370
397
  setArtifacts([]);
398
+ setRequiredActions([]);
371
399
  setError(null);
372
400
  setStatus("idle");
373
401
  onSession?.(null);
package/dist/index.d.cts CHANGED
@@ -1,9 +1,9 @@
1
- import { AgentSessionEvent, AgentSession, AgentSessionInputParam, AgentSessionItem } from '@rebyteai/agent-sdk/resources/beta/agents/agents';
2
- export { AgentSession, AgentSessionEvent } from '@rebyteai/agent-sdk/resources/beta/agents/agents';
3
- import { Turn } from '@rebyteai/agent-sdk/resources/beta/agents/sessions/turns';
4
- export { Turn } from '@rebyteai/agent-sdk/resources/beta/agents/sessions/turns';
5
- import { SessionArtifact } from '@rebyteai/agent-sdk/resources/beta/agents/sessions/artifacts';
6
- export { SessionArtifact } from '@rebyteai/agent-sdk/resources/beta/agents/sessions/artifacts';
1
+ import { AgentSessionEvent, AgentSession, AgentSessionInputParam, AgentSessionItem, AgentFunctionCallOutputParam } from 'openai/resources/beta/agents/agents';
2
+ export { AgentSession, AgentSessionEvent } from 'openai/resources/beta/agents/agents';
3
+ import { Turn } from 'openai/resources/beta/agents/sessions/turns';
4
+ export { Turn } from 'openai/resources/beta/agents/sessions/turns';
5
+ import { SessionArtifact } from 'openai/resources/beta/agents/sessions/artifacts';
6
+ export { SessionArtifact } from 'openai/resources/beta/agents/sessions/artifacts';
7
7
 
8
8
  interface AgentAttachment {
9
9
  fileId: string;
@@ -60,7 +60,7 @@ interface AgentChatMessage {
60
60
  role: 'user' | 'assistant';
61
61
  content: string;
62
62
  attachments?: AgentAttachment[];
63
- status: 'completed' | 'streaming' | 'failed' | 'cancelled';
63
+ status: 'completed' | 'streaming' | 'waiting' | 'failed' | 'cancelled';
64
64
  turnId: string | null;
65
65
  projection: TurnState | null;
66
66
  }
@@ -85,14 +85,29 @@ declare function createAgentSessionTransport(options: {
85
85
  fetch?: typeof fetch;
86
86
  }): AgentSessionTransport;
87
87
 
88
+ type AgentRequiredAction = AgentSession['required_actions'][number];
89
+ /** One result per request also supports functions called inside Dynamic Workflow. */
90
+ type AgentToolResult = {
91
+ turn_id: string;
92
+ call_id: string;
93
+ } & ({
94
+ success: true;
95
+ output: AgentFunctionCallOutputParam;
96
+ } | {
97
+ success: false;
98
+ error: string;
99
+ });
88
100
  interface AgentSessionChat {
89
101
  messages: AgentChatMessage[];
90
- status: 'idle' | 'streaming' | 'error';
102
+ status: 'idle' | 'streaming' | 'requires_action' | 'error';
91
103
  error: Error | null;
92
104
  sessionId: string | null;
93
105
  artifacts: Array<SessionArtifact & {
94
106
  url: string;
95
107
  }>;
108
+ requiredActions: AgentRequiredAction[];
109
+ /** Submit an already executed function's result; the application owns execution and deduplication. */
110
+ submitToolResult(result: AgentToolResult): Promise<void>;
96
111
  send(input: string | AgentChatInput): Promise<Turn | null>;
97
112
  upload(file: File, progress?: (value: AgentUploadProgress) => void): Promise<SessionAttachment>;
98
113
  stop(): Promise<void>;
@@ -104,4 +119,4 @@ declare function useAgentSession(options: {
104
119
  onSession?: (id: string | null) => void;
105
120
  }): AgentSessionChat;
106
121
 
107
- export { type AgentAttachment, type AgentChatInput, type AgentChatMessage, type AgentSessionChat, type AgentSessionTransport, AgentTransportError, type AgentUploadProgress, type SessionAttachment, type TextMessageState, type ToolCallState, type TurnState, createAgentSessionTransport, useAgentSession };
122
+ export { type AgentAttachment, type AgentChatInput, type AgentChatMessage, type AgentRequiredAction, type AgentSessionChat, type AgentSessionTransport, type AgentToolResult, AgentTransportError, type AgentUploadProgress, type SessionAttachment, type TextMessageState, type ToolCallState, type TurnState, createAgentSessionTransport, useAgentSession };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { AgentSessionEvent, AgentSession, AgentSessionInputParam, AgentSessionItem } from '@rebyteai/agent-sdk/resources/beta/agents/agents';
2
- export { AgentSession, AgentSessionEvent } from '@rebyteai/agent-sdk/resources/beta/agents/agents';
3
- import { Turn } from '@rebyteai/agent-sdk/resources/beta/agents/sessions/turns';
4
- export { Turn } from '@rebyteai/agent-sdk/resources/beta/agents/sessions/turns';
5
- import { SessionArtifact } from '@rebyteai/agent-sdk/resources/beta/agents/sessions/artifacts';
6
- export { SessionArtifact } from '@rebyteai/agent-sdk/resources/beta/agents/sessions/artifacts';
1
+ import { AgentSessionEvent, AgentSession, AgentSessionInputParam, AgentSessionItem, AgentFunctionCallOutputParam } from 'openai/resources/beta/agents/agents';
2
+ export { AgentSession, AgentSessionEvent } from 'openai/resources/beta/agents/agents';
3
+ import { Turn } from 'openai/resources/beta/agents/sessions/turns';
4
+ export { Turn } from 'openai/resources/beta/agents/sessions/turns';
5
+ import { SessionArtifact } from 'openai/resources/beta/agents/sessions/artifacts';
6
+ export { SessionArtifact } from 'openai/resources/beta/agents/sessions/artifacts';
7
7
 
8
8
  interface AgentAttachment {
9
9
  fileId: string;
@@ -60,7 +60,7 @@ interface AgentChatMessage {
60
60
  role: 'user' | 'assistant';
61
61
  content: string;
62
62
  attachments?: AgentAttachment[];
63
- status: 'completed' | 'streaming' | 'failed' | 'cancelled';
63
+ status: 'completed' | 'streaming' | 'waiting' | 'failed' | 'cancelled';
64
64
  turnId: string | null;
65
65
  projection: TurnState | null;
66
66
  }
@@ -85,14 +85,29 @@ declare function createAgentSessionTransport(options: {
85
85
  fetch?: typeof fetch;
86
86
  }): AgentSessionTransport;
87
87
 
88
+ type AgentRequiredAction = AgentSession['required_actions'][number];
89
+ /** One result per request also supports functions called inside Dynamic Workflow. */
90
+ type AgentToolResult = {
91
+ turn_id: string;
92
+ call_id: string;
93
+ } & ({
94
+ success: true;
95
+ output: AgentFunctionCallOutputParam;
96
+ } | {
97
+ success: false;
98
+ error: string;
99
+ });
88
100
  interface AgentSessionChat {
89
101
  messages: AgentChatMessage[];
90
- status: 'idle' | 'streaming' | 'error';
102
+ status: 'idle' | 'streaming' | 'requires_action' | 'error';
91
103
  error: Error | null;
92
104
  sessionId: string | null;
93
105
  artifacts: Array<SessionArtifact & {
94
106
  url: string;
95
107
  }>;
108
+ requiredActions: AgentRequiredAction[];
109
+ /** Submit an already executed function's result; the application owns execution and deduplication. */
110
+ submitToolResult(result: AgentToolResult): Promise<void>;
96
111
  send(input: string | AgentChatInput): Promise<Turn | null>;
97
112
  upload(file: File, progress?: (value: AgentUploadProgress) => void): Promise<SessionAttachment>;
98
113
  stop(): Promise<void>;
@@ -104,4 +119,4 @@ declare function useAgentSession(options: {
104
119
  onSession?: (id: string | null) => void;
105
120
  }): AgentSessionChat;
106
121
 
107
- export { type AgentAttachment, type AgentChatInput, type AgentChatMessage, type AgentSessionChat, type AgentSessionTransport, AgentTransportError, type AgentUploadProgress, type SessionAttachment, type TextMessageState, type ToolCallState, type TurnState, createAgentSessionTransport, useAgentSession };
122
+ export { type AgentAttachment, type AgentChatInput, type AgentChatMessage, type AgentRequiredAction, type AgentSessionChat, type AgentSessionTransport, type AgentToolResult, AgentTransportError, type AgentUploadProgress, type SessionAttachment, type TextMessageState, type ToolCallState, type TurnState, createAgentSessionTransport, useAgentSession };
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ function createTurnState() {
24
24
  }
25
25
 
26
26
  // src/sessions.ts
27
- import { Stream } from "@rebyteai/agent-sdk/core/streaming";
27
+ import { Stream } from "openai/core/streaming";
28
28
  function createAgentSessionTransport(options) {
29
29
  const base = options.url.replace(/\/$/, "");
30
30
  const request = options.fetch ?? globalThis.fetch;
@@ -126,7 +126,7 @@ function projectSessionItem(state, item, index) {
126
126
  }
127
127
  if (item.type === "function_call_output") return { ...state, toolCalls: state.toolCalls.map((tool) => tool.callId === item.call_id ? { ...tool, status: item.status, output: typeof item.output === "string" ? item.output : JSON.stringify(item.output), error: item.error } : tool) };
128
128
  if (item.type !== "command_execution" && item.type !== "mcp_call" && item.type !== "function_call") return state;
129
- const serverFunction = item.type === "function_call" && ["write_stdin", "apply_patch", "view_image", "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource", "rebyte_web_search"].includes(item.name);
129
+ const serverFunction = item.type === "function_call" && ["write_stdin", "apply_patch", "view_image", "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource", "rebyte_web_search", "tool_search", "run_code"].includes(item.name);
130
130
  const value = {
131
131
  id: item.id,
132
132
  outputIndex: index,
@@ -144,7 +144,8 @@ function projectSessionItem(state, item, index) {
144
144
  function reduceSessionEvent(state, event) {
145
145
  let next = { ...state, events: [...state.events, event] };
146
146
  if (event.type === "agent.session.turn.item.added" || event.type === "agent.session.turn.item.done") {
147
- if (event.output_index !== null) next = projectSessionItem(next, event.item, event.output_index);
147
+ if (event.item.type === "function_call_output") next = projectSessionItem(next, event.item, 0);
148
+ else if (event.output_index !== null) next = projectSessionItem(next, event.item, event.output_index);
148
149
  } else if (event.type === "agent.session.turn.output_text.delta") {
149
150
  if (!next.textMessages.some((message) => message.id === event.item_id)) throw new Error("Text delta has no message");
150
151
  const messages = next.textMessages.map((message) => message.id === event.item_id ? { ...message, text: message.text + event.delta } : message);
@@ -167,6 +168,7 @@ function useAgentSession(options) {
167
168
  const [error, setError] = useState(null);
168
169
  const [sessionId, setSessionId] = useState(options.initialSessionId ?? null);
169
170
  const [artifacts, setArtifacts] = useState([]);
171
+ const [requiredActions, setRequiredActions] = useState([]);
170
172
  const idRef = useRef(sessionId);
171
173
  const creating = useRef(null);
172
174
  const active = useRef(null);
@@ -207,7 +209,7 @@ function useAgentSession(options) {
207
209
  if (item.type === "message" && item.role === "user" && item.id !== null) restored.push({ id: item.id, role: "user", content: item.content.filter((part) => part.type === "input_text").map((part) => part.text).join("\n"), status: "completed", turnId: null, projection: null });
208
210
  }
209
211
  const projection = sessionItemState(turnItems);
210
- restored.push({ id: turn.id, role: "assistant", content: projection.textMessages.map((item) => item.text).join("\n\n"), status: turn.status === "failed" ? "failed" : turn.status === "cancelled" ? "cancelled" : turn.status === "completed" ? "completed" : "streaming", turnId: turn.id, projection: { ...projection, turnId: turn.id } });
212
+ restored.push({ id: turn.id, role: "assistant", content: projection.textMessages.map((item) => item.text).join("\n\n"), status: turn.status === "failed" ? "failed" : turn.status === "cancelled" ? "cancelled" : turn.status === "completed" ? "completed" : turn.status === "waiting" ? "waiting" : "streaming", turnId: turn.id, projection: { ...projection, turnId: turn.id } });
211
213
  }
212
214
  setMessages((previous) => restored.map((message) => {
213
215
  const existing = previous.find((value) => value.turnId !== null && value.turnId === message.turnId);
@@ -217,7 +219,9 @@ function useAgentSession(options) {
217
219
  }, [transport]);
218
220
  useEffect(() => {
219
221
  const id = initial.current;
220
- if (!id) return;
222
+ if (!id) return () => {
223
+ active.current?.abort();
224
+ };
221
225
  let cancelled = false;
222
226
  busy.current = true;
223
227
  setStatus("streaming");
@@ -225,11 +229,13 @@ function useAgentSession(options) {
225
229
  await refresh(id);
226
230
  while (!cancelled) {
227
231
  const session = await transport.retrieve(id);
228
- if (session.status !== "in_progress") {
232
+ if (cancelled) return;
233
+ setRequiredActions(session.required_actions);
234
+ if (session.status !== "in_progress" && session.status !== "requires_action") {
229
235
  if (session.status === "failed") throw new Error(session.error ?? "Session failed");
230
- if (session.status === "requires_action") throw new Error("Session is waiting for a client tool result");
231
236
  break;
232
237
  }
238
+ setStatus(session.status === "requires_action" ? "requires_action" : "streaming");
233
239
  await new Promise((resolve) => setTimeout(resolve, 1e3));
234
240
  if (!cancelled) await refresh(id);
235
241
  }
@@ -256,6 +262,7 @@ function useAgentSession(options) {
256
262
  submitted.current = false;
257
263
  setStatus("streaming");
258
264
  setError(null);
265
+ setRequiredActions([]);
259
266
  const abort = new AbortController();
260
267
  active.current = abort;
261
268
  const assistantId = crypto.randomUUID();
@@ -289,12 +296,18 @@ function useAgentSession(options) {
289
296
  setMessages((previous) => previous.map((message) => message.id === assistantId ? { ...message, content: projection.textMessages.map((item) => item.text).join("\n\n"), turnId: projection.turnId, projection } : message));
290
297
  if (event.type === "agent.session.turn.completed" || event.type === "agent.session.turn.cancelled" || event.type === "agent.session.turn.failed") {
291
298
  await refresh(id);
299
+ setRequiredActions([]);
292
300
  if (event.type === "agent.session.turn.failed") throw new Error(event.turn.error?.message ?? "Turn failed");
293
301
  setStatus("idle");
294
302
  return event.turn;
295
303
  }
296
304
  if (event.type === "agent.session.failed") throw new Error(event.session.error ?? "Session failed");
297
- if (event.type === "agent.session.requires_action") throw new Error("Session is waiting for a client tool result");
305
+ if (event.type === "agent.session.requires_action" || event.type === "agent.session.in_progress") {
306
+ const waiting = event.type === "agent.session.requires_action";
307
+ setRequiredActions(event.session.required_actions);
308
+ setStatus(waiting ? "requires_action" : "streaming");
309
+ setMessages((previous) => previous.map((message) => message.id === assistantId ? { ...message, status: waiting ? "waiting" : "streaming" } : message));
310
+ }
298
311
  if (event.type === "error") throw new Error(event.error.message);
299
312
  }
300
313
  throw new Error("Session event stream disconnected. Reload to recover persisted output.");
@@ -320,12 +333,26 @@ function useAgentSession(options) {
320
333
  throw fail(cause);
321
334
  }
322
335
  }, [transport, fail]);
336
+ const submitToolResult = useCallback(async (result) => {
337
+ const id = idRef.current;
338
+ if (!id) throw new Error("No Session is waiting for a function result");
339
+ setError(null);
340
+ try {
341
+ await transport.submit(id, [{ type: "agent.session.input.tool_result", ...result }], `agent-result:${result.turn_id}:${result.call_id}`);
342
+ } catch (cause) {
343
+ const failure = cause instanceof Error ? cause : new Error(String(cause));
344
+ setError(failure);
345
+ throw failure;
346
+ }
347
+ }, [transport]);
323
348
  return {
324
349
  messages,
325
350
  status,
326
351
  error,
327
352
  sessionId,
328
353
  artifacts,
354
+ requiredActions,
355
+ submitToolResult,
329
356
  send,
330
357
  stop,
331
358
  async upload(file, progress) {
@@ -340,6 +367,7 @@ function useAgentSession(options) {
340
367
  setSessionId(null);
341
368
  setMessages([]);
342
369
  setArtifacts([]);
370
+ setRequiredActions([]);
343
371
  setError(null);
344
372
  setStatus("idle");
345
373
  onSession?.(null);
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@rebyteai/agent-react",
3
- "version": "0.2.4",
3
+ "version": "0.6.0",
4
4
  "description": "Headless React hooks and transports for Rebyte Agents",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/ReByteAI/rebyte-agent-toolkit.git",
8
+ "url": "git+https://github.com/ReByteAI/rebyte-agent-sdk.git",
9
9
  "directory": "packages/react"
10
10
  },
11
- "homepage": "https://github.com/ReByteAI/rebyte-agent-toolkit#readme",
12
- "bugs": "https://github.com/ReByteAI/rebyte-agent-toolkit/issues",
11
+ "homepage": "https://github.com/ReByteAI/rebyte-agent-sdk#readme",
12
+ "bugs": "https://github.com/ReByteAI/rebyte-agent-sdk/issues",
13
13
  "engines": {
14
14
  "node": ">=22"
15
15
  },
@@ -43,7 +43,7 @@
43
43
  "typescript": "^5.9.3"
44
44
  },
45
45
  "dependencies": {
46
- "@rebyteai/agent-sdk": "0.2.4"
46
+ "openai": "7.15.0"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "tsup src/index.ts --format esm,cjs --dts --clean --external react",