@rebyteai/agent-react 0.3.0 → 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 +32 -5
- package/dist/index.cjs +35 -7
- package/dist/index.d.cts +19 -4
- package/dist/index.d.ts +19 -4
- package/dist/index.js +35 -7
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
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
|
|
@@ -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
|
|
33
|
-
polled until settled. Live disconnects report an error; reload recovers
|
|
34
|
-
without resending input.
|
|
35
|
-
|
|
36
|
-
|
|
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
|
@@ -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.
|
|
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 (
|
|
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"
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import { AgentSessionEvent, AgentSession, AgentSessionInputParam, AgentSessionItem } from 'openai/resources/beta/agents/agents';
|
|
1
|
+
import { AgentSessionEvent, AgentSession, AgentSessionInputParam, AgentSessionItem, AgentFunctionCallOutputParam } from 'openai/resources/beta/agents/agents';
|
|
2
2
|
export { AgentSession, AgentSessionEvent } from 'openai/resources/beta/agents/agents';
|
|
3
3
|
import { Turn } from 'openai/resources/beta/agents/sessions/turns';
|
|
4
4
|
export { Turn } from 'openai/resources/beta/agents/sessions/turns';
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
import { AgentSessionEvent, AgentSession, AgentSessionInputParam, AgentSessionItem } from 'openai/resources/beta/agents/agents';
|
|
1
|
+
import { AgentSessionEvent, AgentSession, AgentSessionInputParam, AgentSessionItem, AgentFunctionCallOutputParam } from 'openai/resources/beta/agents/agents';
|
|
2
2
|
export { AgentSession, AgentSessionEvent } from 'openai/resources/beta/agents/agents';
|
|
3
3
|
import { Turn } from 'openai/resources/beta/agents/sessions/turns';
|
|
4
4
|
export { Turn } from 'openai/resources/beta/agents/sessions/turns';
|
|
@@ -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
|
@@ -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.
|
|
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 (
|
|
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"
|
|
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.
|
|
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-
|
|
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-
|
|
12
|
-
"bugs": "https://github.com/ReByteAI/rebyte-agent-
|
|
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
|
},
|