@rebyteai/agent-react 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rebyte, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # @rebyteai/agent-react
2
+
3
+ Install the versioned package (no repository clone required):
4
+
5
+ ```sh
6
+ pnpm add @rebyteai/agent-react@0.2.0
7
+ ```
8
+
9
+
10
+ Headless React state for native Agents API Sessions. The browser calls your
11
+ same-origin application server, never the organization API directly.
12
+
13
+ ```tsx
14
+ import { createAgentSessionTransport, useAgentSession } from '@rebyteai/agent-react'
15
+ const transport = createAgentSessionTransport({ url: '/api/sessions' })
16
+ // Inside a React component:
17
+ const chat = useAgentSession({ transport, initialSessionId, onSession })
18
+ ```
19
+
20
+ Persist `chat.sessionId` via `onSession`, then pass it as `initialSessionId` on
21
+ reload. `send(text)` subscribes before posting input and resolves with a Turn.
22
+ `stop()` submits cancellation; the running send consumes the terminal event.
23
+ `reset()` starts a new conversation without deleting the old Session.
24
+ `upload(file)` creates the Session if needed and writes a file into its environment
25
+ (5 MiB maximum in this example). `artifacts` supplies immutable download URLs.
26
+
27
+ Each assistant message has a `turnId` and a presentation-only `projection`:
28
+ `textMessages`, `toolCalls`, `outputText`, and the received native `events`.
29
+ Built-in server functions are distinct from client functions. A completed client
30
+ result updates its matching call; the actual handoff is Session `requires_action`.
31
+
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.
37
+
38
+ Images are uploaded as Session files. The model can use hosted `view_image` to
39
+ inspect them; the upload itself is not an inline model image message. No browser
40
+ transport includes an organization API key.
package/dist/index.cjs ADDED
@@ -0,0 +1,382 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AgentTransportError: () => AgentTransportError,
24
+ createAgentSessionTransport: () => createAgentSessionTransport,
25
+ useAgentSession: () => useAgentSession
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/state.ts
30
+ var AgentTransportError = class extends Error {
31
+ status;
32
+ code;
33
+ body;
34
+ constructor(status, message, code, body) {
35
+ super(message);
36
+ this.name = "AgentTransportError";
37
+ this.status = status;
38
+ this.code = code;
39
+ this.body = body;
40
+ }
41
+ };
42
+ function createTurnState() {
43
+ return {
44
+ status: "idle",
45
+ turnId: null,
46
+ outputText: "",
47
+ textMessages: [],
48
+ error: null,
49
+ toolCalls: [],
50
+ events: []
51
+ };
52
+ }
53
+
54
+ // src/sessions.ts
55
+ var import_streaming = require("@rebyteai/agent-sdk/core/streaming");
56
+ function createAgentSessionTransport(options) {
57
+ const base = options.url.replace(/\/$/, "");
58
+ const request = options.fetch ?? globalThis.fetch;
59
+ async function checked(path2, init) {
60
+ const response = await request(`${base}${path2}`, init);
61
+ if (!response.ok) {
62
+ const body = await response.text();
63
+ let message = body;
64
+ try {
65
+ const parsed = JSON.parse(body);
66
+ if (typeof parsed?.error?.message === "string") message = parsed.error.message;
67
+ } catch {
68
+ }
69
+ throw new AgentTransportError(response.status, message, null, body);
70
+ }
71
+ return response;
72
+ }
73
+ const path = (id) => `/${encodeURIComponent(id)}`;
74
+ async function list(id, resource) {
75
+ return (await (await checked(`${path(id)}/${resource}`)).json()).data;
76
+ }
77
+ return {
78
+ async create() {
79
+ return (await checked("", { method: "POST" })).json();
80
+ },
81
+ async retrieve(id) {
82
+ return (await checked(path(id))).json();
83
+ },
84
+ async subscribe(id, signal) {
85
+ const controller = new AbortController();
86
+ const abort = () => controller.abort();
87
+ signal.addEventListener("abort", abort, { once: true });
88
+ if (signal.aborted) controller.abort();
89
+ let response;
90
+ try {
91
+ response = await checked(`${path(id)}/events`, { headers: { Accept: "text/event-stream" }, signal: controller.signal });
92
+ } catch (error) {
93
+ signal.removeEventListener("abort", abort);
94
+ throw error;
95
+ }
96
+ const stream = import_streaming.Stream.fromSSEResponse(response, controller);
97
+ return (async function* () {
98
+ try {
99
+ for await (const event of stream) yield event;
100
+ } finally {
101
+ signal.removeEventListener("abort", abort);
102
+ controller.abort();
103
+ }
104
+ })();
105
+ },
106
+ async submit(id, events, key) {
107
+ await checked(`${path(id)}/events`, { method: "POST", headers: { "Content-Type": "application/json", "Idempotency-Key": key }, body: JSON.stringify({ events }) });
108
+ },
109
+ items: (id) => list(id, "items"),
110
+ turns: (id) => list(id, "turns"),
111
+ artifacts: (id) => list(id, "artifacts"),
112
+ artifactURL: (id, artifactId) => `${base}${path(id)}/artifacts/${encodeURIComponent(artifactId)}/content`,
113
+ async upload(id, file, progress) {
114
+ if (file.size > 5 * 1024 * 1024) throw new Error("File exceeds the 5 MiB upload limit");
115
+ const url = `${base}${path(id)}/files?filename=${encodeURIComponent(file.name)}`;
116
+ const uploaded = await new Promise((resolve, reject) => {
117
+ const xhr = new XMLHttpRequest();
118
+ xhr.open("POST", url);
119
+ xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
120
+ xhr.upload.onprogress = (e) => {
121
+ if (e.lengthComputable) progress?.({ loaded: e.loaded, total: e.total, percent: Math.min(99, Math.round(e.loaded / e.total * 100)) });
122
+ };
123
+ xhr.onload = () => {
124
+ try {
125
+ const body = JSON.parse(xhr.responseText);
126
+ if (xhr.status < 200 || xhr.status >= 300) throw new Error(body.error?.message ?? `Upload failed: ${xhr.status}`);
127
+ resolve(body);
128
+ } catch (error) {
129
+ reject(error);
130
+ }
131
+ };
132
+ xhr.onerror = () => reject(new Error("Upload failed"));
133
+ xhr.onabort = () => reject(new Error("Upload cancelled"));
134
+ xhr.send(file);
135
+ });
136
+ if (uploaded.session_id !== id || typeof uploaded.path !== "string" || uploaded.size_bytes !== file.size) throw new Error("Upload returned an invalid Session file");
137
+ progress?.({ loaded: file.size, total: file.size, percent: 100 });
138
+ return { sessionId: id, path: uploaded.path, fileId: uploaded.path, filename: file.name, size: file.size, contentType: file.type || "application/octet-stream", inputType: file.type.startsWith("image/") ? "input_image" : "input_file" };
139
+ }
140
+ };
141
+ }
142
+ function projectSessionItem(state, item, index) {
143
+ if (item.type === "message" && item.role === "assistant") {
144
+ if (item.id === null) throw new Error("Persisted assistant message has no ID");
145
+ const value2 = {
146
+ id: item.id,
147
+ outputIndex: index,
148
+ status: item.status,
149
+ phase: item.phase,
150
+ text: item.content.filter((part) => part.type === "output_text").map((part) => part.text).join("")
151
+ };
152
+ const messages = state.textMessages.some((message) => message.id === item.id) ? state.textMessages.map((message) => message.id === item.id ? value2 : message) : [...state.textMessages, value2];
153
+ return { ...state, textMessages: messages, outputText: messages.map((message) => message.text).join("") };
154
+ }
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
+ 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);
158
+ const value = {
159
+ id: item.id,
160
+ outputIndex: index,
161
+ execution: item.type === "function_call" && !serverFunction ? "client" : "server",
162
+ callId: item.type === "function_call" ? item.call_id : null,
163
+ name: item.type === "command_execution" ? "exec_command" : item.name,
164
+ serverLabel: item.type === "command_execution" ? "Session Sandbox" : item.type === "mcp_call" ? item.server_label : serverFunction ? "Session runtime" : "client",
165
+ status: item.type === "function_call" && !serverFunction && item.status === "completed" ? "awaiting_output" : item.status,
166
+ arguments: item.type === "command_execution" ? item.command : JSON.stringify(item.arguments),
167
+ output: item.type === "command_execution" ? item.output : item.type === "mcp_call" && item.output != null ? JSON.stringify(item.output) : null,
168
+ error: item.type === "command_execution" && item.exit_code !== null && item.exit_code !== 0 ? `Exit code ${item.exit_code}` : item.type === "mcp_call" && item.error != null ? JSON.stringify(item.error) : null
169
+ };
170
+ return { ...state, toolCalls: state.toolCalls.some((tool) => tool.id === item.id) ? state.toolCalls.map((tool) => tool.id === item.id ? value : tool) : [...state.toolCalls, value] };
171
+ }
172
+ function reduceSessionEvent(state, event) {
173
+ let next = { ...state, events: [...state.events, event] };
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);
176
+ } else if (event.type === "agent.session.turn.output_text.delta") {
177
+ if (!next.textMessages.some((message) => message.id === event.item_id)) throw new Error("Text delta has no message");
178
+ const messages = next.textMessages.map((message) => message.id === event.item_id ? { ...message, text: message.text + event.delta } : message);
179
+ next = { ...next, textMessages: messages, outputText: messages.map((message) => message.text).join("") };
180
+ } else if (event.type === "agent.output.command_execution_output.delta") {
181
+ next.toolCalls = next.toolCalls.map((tool) => tool.id === event.item_id ? { ...tool, output: (tool.output ?? "") + event.delta } : tool);
182
+ }
183
+ return next;
184
+ }
185
+ function sessionItemState(items) {
186
+ return items.reduce((state, item, index) => projectSessionItem(state, item, index), createTurnState());
187
+ }
188
+
189
+ // src/use-agent-session.ts
190
+ var import_react = require("react");
191
+ function useAgentSession(options) {
192
+ const { transport, onSession } = options;
193
+ const [messages, setMessages] = (0, import_react.useState)([]);
194
+ const [status, setStatus] = (0, import_react.useState)("idle");
195
+ const [error, setError] = (0, import_react.useState)(null);
196
+ const [sessionId, setSessionId] = (0, import_react.useState)(options.initialSessionId ?? null);
197
+ const [artifacts, setArtifacts] = (0, import_react.useState)([]);
198
+ const idRef = (0, import_react.useRef)(sessionId);
199
+ const creating = (0, import_react.useRef)(null);
200
+ const active = (0, import_react.useRef)(null);
201
+ const cancelRequested = (0, import_react.useRef)(false);
202
+ const submitted = (0, import_react.useRef)(false);
203
+ const generation = (0, import_react.useRef)(0);
204
+ const busy = (0, import_react.useRef)(false);
205
+ const initial = (0, import_react.useRef)(options.initialSessionId);
206
+ const fail = (0, import_react.useCallback)((cause) => {
207
+ const failure = cause instanceof Error ? cause : new Error(String(cause));
208
+ setError(failure);
209
+ setStatus("error");
210
+ return failure;
211
+ }, []);
212
+ const ensure = (0, import_react.useCallback)(async () => {
213
+ if (idRef.current) return idRef.current;
214
+ const currentGeneration = generation.current;
215
+ const pending = creating.current ?? transport.create();
216
+ creating.current = pending;
217
+ try {
218
+ const session = await pending;
219
+ if (generation.current !== currentGeneration) throw new Error("Conversation changed during Session creation");
220
+ idRef.current = session.id;
221
+ setSessionId(session.id);
222
+ onSession?.(session.id);
223
+ return session.id;
224
+ } finally {
225
+ if (creating.current === pending) creating.current = null;
226
+ }
227
+ }, [transport, onSession]);
228
+ const refresh = (0, import_react.useCallback)(async (id) => {
229
+ const [items, turns, outputs] = await Promise.all([transport.items(id), transport.turns(id), transport.artifacts(id)]);
230
+ if (idRef.current !== id) return;
231
+ const restored = [];
232
+ for (const turn of turns) {
233
+ const turnItems = items.filter((item) => item.turn_id === turn.id);
234
+ for (const item of turnItems) {
235
+ 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
+ }
237
+ 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 } });
239
+ }
240
+ setMessages((previous) => restored.map((message) => {
241
+ const existing = previous.find((value) => value.turnId !== null && value.turnId === message.turnId);
242
+ return existing?.projection && message.projection ? { ...message, projection: { ...message.projection, events: existing.projection.events } } : message;
243
+ }));
244
+ setArtifacts(outputs.map((artifact) => ({ ...artifact, url: transport.artifactURL(id, artifact.id) })));
245
+ }, [transport]);
246
+ (0, import_react.useEffect)(() => {
247
+ const id = initial.current;
248
+ if (!id) return;
249
+ let cancelled = false;
250
+ busy.current = true;
251
+ setStatus("streaming");
252
+ void (async () => {
253
+ await refresh(id);
254
+ while (!cancelled) {
255
+ const session = await transport.retrieve(id);
256
+ if (session.status !== "in_progress") {
257
+ 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
+ break;
260
+ }
261
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
262
+ if (!cancelled) await refresh(id);
263
+ }
264
+ if (!cancelled) {
265
+ await refresh(id);
266
+ setStatus("idle");
267
+ }
268
+ })().catch((cause) => {
269
+ if (!cancelled) fail(cause);
270
+ }).finally(() => {
271
+ if (!cancelled) busy.current = false;
272
+ });
273
+ return () => {
274
+ cancelled = true;
275
+ active.current?.abort();
276
+ };
277
+ }, [transport, refresh, fail]);
278
+ const send = (0, import_react.useCallback)(async (value) => {
279
+ if (busy.current) throw new Error("A turn is already running");
280
+ const input = typeof value === "string" ? { text: value, attachments: [] } : value;
281
+ if (!input.text.trim() && !input.attachments.length) throw new Error("Input is required");
282
+ busy.current = true;
283
+ cancelRequested.current = false;
284
+ submitted.current = false;
285
+ setStatus("streaming");
286
+ setError(null);
287
+ const abort = new AbortController();
288
+ active.current = abort;
289
+ const assistantId = crypto.randomUUID();
290
+ let projection = createTurnState();
291
+ let id = null;
292
+ try {
293
+ id = await ensure();
294
+ abort.signal.throwIfAborted();
295
+ if (cancelRequested.current) {
296
+ setStatus("idle");
297
+ return null;
298
+ }
299
+ const text = [input.text.trim(), ...input.attachments.map((file) => {
300
+ if (!("sessionId" in file) || file.sessionId !== id || !("path" in file) || typeof file.path !== "string") throw new Error("Attachment belongs to another Session");
301
+ return `Attached file: ${JSON.stringify(file.path)}. Read this file in your Session environment.`;
302
+ })].filter(Boolean).join("\n");
303
+ setMessages((previous) => [...previous, { id: crypto.randomUUID(), role: "user", content: input.text, attachments: input.attachments, status: "completed", turnId: null, projection: null }, { id: assistantId, role: "assistant", content: "", status: "streaming", turnId: null, projection }]);
304
+ const events = await transport.subscribe(id, abort.signal);
305
+ abort.signal.throwIfAborted();
306
+ if (cancelRequested.current) {
307
+ setStatus("idle");
308
+ return null;
309
+ }
310
+ await transport.submit(id, [{ type: "agent.session.input.message", input: [{ role: "user", content: [{ type: "input_text", text }] }] }], crypto.randomUUID());
311
+ submitted.current = true;
312
+ if (cancelRequested.current) await transport.submit(id, [{ type: "agent.session.input.cancel" }], crypto.randomUUID());
313
+ for await (const event of events) {
314
+ if ("session_id" in event && event.session_id !== id) throw new Error("Event belongs to another Session");
315
+ projection = reduceSessionEvent(projection, event);
316
+ if ("turn_id" in event && typeof event.turn_id === "string") projection = { ...projection, turnId: event.turn_id };
317
+ 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
+ if (event.type === "agent.session.turn.completed" || event.type === "agent.session.turn.cancelled" || event.type === "agent.session.turn.failed") {
319
+ await refresh(id);
320
+ if (event.type === "agent.session.turn.failed") throw new Error(event.turn.error?.message ?? "Turn failed");
321
+ setStatus("idle");
322
+ return event.turn;
323
+ }
324
+ 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");
326
+ if (event.type === "error") throw new Error(event.error.message);
327
+ }
328
+ throw new Error("Session event stream disconnected. Reload to recover persisted output.");
329
+ } catch (cause) {
330
+ if (abort.signal.aborted) {
331
+ if (id) await refresh(id);
332
+ setStatus("idle");
333
+ return null;
334
+ }
335
+ throw fail(cause);
336
+ } finally {
337
+ abort.abort();
338
+ if (active.current === abort) active.current = null;
339
+ busy.current = false;
340
+ }
341
+ }, [ensure, fail, refresh, transport]);
342
+ const stop = (0, import_react.useCallback)(async () => {
343
+ const id = idRef.current;
344
+ cancelRequested.current = true;
345
+ try {
346
+ if (id && (submitted.current || active.current === null)) await transport.submit(id, [{ type: "agent.session.input.cancel" }], crypto.randomUUID());
347
+ } catch (cause) {
348
+ throw fail(cause);
349
+ }
350
+ }, [transport, fail]);
351
+ return {
352
+ messages,
353
+ status,
354
+ error,
355
+ sessionId,
356
+ artifacts,
357
+ send,
358
+ stop,
359
+ async upload(file, progress) {
360
+ const id = await ensure();
361
+ return transport.upload(id, file, progress);
362
+ },
363
+ reset() {
364
+ if (busy.current) throw new Error("Stop the active turn before starting a new Session");
365
+ generation.current++;
366
+ idRef.current = null;
367
+ creating.current = null;
368
+ setSessionId(null);
369
+ setMessages([]);
370
+ setArtifacts([]);
371
+ setError(null);
372
+ setStatus("idle");
373
+ onSession?.(null);
374
+ }
375
+ };
376
+ }
377
+ // Annotate the CommonJS export names for ESM import in node:
378
+ 0 && (module.exports = {
379
+ AgentTransportError,
380
+ createAgentSessionTransport,
381
+ useAgentSession
382
+ });
@@ -0,0 +1,107 @@
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';
7
+
8
+ interface AgentAttachment {
9
+ fileId: string;
10
+ filename: string;
11
+ contentType: string;
12
+ size: number;
13
+ inputType: 'input_file' | 'input_image';
14
+ }
15
+ interface AgentChatInput {
16
+ text: string;
17
+ attachments: AgentAttachment[];
18
+ }
19
+ interface AgentUploadProgress {
20
+ loaded: number;
21
+ total: number;
22
+ percent: number;
23
+ }
24
+ interface ToolCallState {
25
+ id: string;
26
+ outputIndex: number;
27
+ execution: 'server' | 'client';
28
+ callId: string | null;
29
+ name: string;
30
+ serverLabel: string;
31
+ status: 'in_progress' | 'completed' | 'incomplete' | 'failed' | 'awaiting_output';
32
+ arguments: string;
33
+ output: string | null;
34
+ error: string | null;
35
+ }
36
+ interface TextMessageState {
37
+ id: string;
38
+ outputIndex: number;
39
+ text: string;
40
+ phase: 'commentary' | 'final_answer' | null;
41
+ status: 'in_progress' | 'completed' | 'incomplete';
42
+ }
43
+ interface TurnState {
44
+ status: 'idle' | 'in_progress' | 'completed' | 'failed';
45
+ turnId: string | null;
46
+ outputText: string;
47
+ textMessages: TextMessageState[];
48
+ error: string | null;
49
+ toolCalls: ToolCallState[];
50
+ events: AgentSessionEvent[];
51
+ }
52
+ declare class AgentTransportError extends Error {
53
+ readonly status: number;
54
+ readonly code: string | null;
55
+ readonly body: unknown;
56
+ constructor(status: number, message: string, code: string | null, body: unknown);
57
+ }
58
+ interface AgentChatMessage {
59
+ id: string;
60
+ role: 'user' | 'assistant';
61
+ content: string;
62
+ attachments?: AgentAttachment[];
63
+ status: 'completed' | 'streaming' | 'failed' | 'cancelled';
64
+ turnId: string | null;
65
+ projection: TurnState | null;
66
+ }
67
+
68
+ interface SessionAttachment extends AgentAttachment {
69
+ sessionId: string;
70
+ path: string;
71
+ }
72
+ interface AgentSessionTransport {
73
+ create(): Promise<AgentSession>;
74
+ retrieve(id: string): Promise<AgentSession>;
75
+ subscribe(id: string, signal: AbortSignal): Promise<AsyncIterable<AgentSessionEvent>>;
76
+ submit(id: string, events: AgentSessionInputParam[], key: string): Promise<void>;
77
+ items(id: string): Promise<AgentSessionItem[]>;
78
+ turns(id: string): Promise<Turn[]>;
79
+ artifacts(id: string): Promise<SessionArtifact[]>;
80
+ artifactURL(sessionId: string, artifactId: string): string;
81
+ upload(id: string, file: File, progress?: (value: AgentUploadProgress) => void): Promise<SessionAttachment>;
82
+ }
83
+ declare function createAgentSessionTransport(options: {
84
+ url: string;
85
+ fetch?: typeof fetch;
86
+ }): AgentSessionTransport;
87
+
88
+ interface AgentSessionChat {
89
+ messages: AgentChatMessage[];
90
+ status: 'idle' | 'streaming' | 'error';
91
+ error: Error | null;
92
+ sessionId: string | null;
93
+ artifacts: Array<SessionArtifact & {
94
+ url: string;
95
+ }>;
96
+ send(input: string | AgentChatInput): Promise<Turn | null>;
97
+ upload(file: File, progress?: (value: AgentUploadProgress) => void): Promise<SessionAttachment>;
98
+ stop(): Promise<void>;
99
+ reset(): void;
100
+ }
101
+ declare function useAgentSession(options: {
102
+ transport: AgentSessionTransport;
103
+ initialSessionId?: string;
104
+ onSession?: (id: string | null) => void;
105
+ }): AgentSessionChat;
106
+
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 };
@@ -0,0 +1,107 @@
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';
7
+
8
+ interface AgentAttachment {
9
+ fileId: string;
10
+ filename: string;
11
+ contentType: string;
12
+ size: number;
13
+ inputType: 'input_file' | 'input_image';
14
+ }
15
+ interface AgentChatInput {
16
+ text: string;
17
+ attachments: AgentAttachment[];
18
+ }
19
+ interface AgentUploadProgress {
20
+ loaded: number;
21
+ total: number;
22
+ percent: number;
23
+ }
24
+ interface ToolCallState {
25
+ id: string;
26
+ outputIndex: number;
27
+ execution: 'server' | 'client';
28
+ callId: string | null;
29
+ name: string;
30
+ serverLabel: string;
31
+ status: 'in_progress' | 'completed' | 'incomplete' | 'failed' | 'awaiting_output';
32
+ arguments: string;
33
+ output: string | null;
34
+ error: string | null;
35
+ }
36
+ interface TextMessageState {
37
+ id: string;
38
+ outputIndex: number;
39
+ text: string;
40
+ phase: 'commentary' | 'final_answer' | null;
41
+ status: 'in_progress' | 'completed' | 'incomplete';
42
+ }
43
+ interface TurnState {
44
+ status: 'idle' | 'in_progress' | 'completed' | 'failed';
45
+ turnId: string | null;
46
+ outputText: string;
47
+ textMessages: TextMessageState[];
48
+ error: string | null;
49
+ toolCalls: ToolCallState[];
50
+ events: AgentSessionEvent[];
51
+ }
52
+ declare class AgentTransportError extends Error {
53
+ readonly status: number;
54
+ readonly code: string | null;
55
+ readonly body: unknown;
56
+ constructor(status: number, message: string, code: string | null, body: unknown);
57
+ }
58
+ interface AgentChatMessage {
59
+ id: string;
60
+ role: 'user' | 'assistant';
61
+ content: string;
62
+ attachments?: AgentAttachment[];
63
+ status: 'completed' | 'streaming' | 'failed' | 'cancelled';
64
+ turnId: string | null;
65
+ projection: TurnState | null;
66
+ }
67
+
68
+ interface SessionAttachment extends AgentAttachment {
69
+ sessionId: string;
70
+ path: string;
71
+ }
72
+ interface AgentSessionTransport {
73
+ create(): Promise<AgentSession>;
74
+ retrieve(id: string): Promise<AgentSession>;
75
+ subscribe(id: string, signal: AbortSignal): Promise<AsyncIterable<AgentSessionEvent>>;
76
+ submit(id: string, events: AgentSessionInputParam[], key: string): Promise<void>;
77
+ items(id: string): Promise<AgentSessionItem[]>;
78
+ turns(id: string): Promise<Turn[]>;
79
+ artifacts(id: string): Promise<SessionArtifact[]>;
80
+ artifactURL(sessionId: string, artifactId: string): string;
81
+ upload(id: string, file: File, progress?: (value: AgentUploadProgress) => void): Promise<SessionAttachment>;
82
+ }
83
+ declare function createAgentSessionTransport(options: {
84
+ url: string;
85
+ fetch?: typeof fetch;
86
+ }): AgentSessionTransport;
87
+
88
+ interface AgentSessionChat {
89
+ messages: AgentChatMessage[];
90
+ status: 'idle' | 'streaming' | 'error';
91
+ error: Error | null;
92
+ sessionId: string | null;
93
+ artifacts: Array<SessionArtifact & {
94
+ url: string;
95
+ }>;
96
+ send(input: string | AgentChatInput): Promise<Turn | null>;
97
+ upload(file: File, progress?: (value: AgentUploadProgress) => void): Promise<SessionAttachment>;
98
+ stop(): Promise<void>;
99
+ reset(): void;
100
+ }
101
+ declare function useAgentSession(options: {
102
+ transport: AgentSessionTransport;
103
+ initialSessionId?: string;
104
+ onSession?: (id: string | null) => void;
105
+ }): AgentSessionChat;
106
+
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 };
package/dist/index.js ADDED
@@ -0,0 +1,353 @@
1
+ // src/state.ts
2
+ var AgentTransportError = class extends Error {
3
+ status;
4
+ code;
5
+ body;
6
+ constructor(status, message, code, body) {
7
+ super(message);
8
+ this.name = "AgentTransportError";
9
+ this.status = status;
10
+ this.code = code;
11
+ this.body = body;
12
+ }
13
+ };
14
+ function createTurnState() {
15
+ return {
16
+ status: "idle",
17
+ turnId: null,
18
+ outputText: "",
19
+ textMessages: [],
20
+ error: null,
21
+ toolCalls: [],
22
+ events: []
23
+ };
24
+ }
25
+
26
+ // src/sessions.ts
27
+ import { Stream } from "@rebyteai/agent-sdk/core/streaming";
28
+ function createAgentSessionTransport(options) {
29
+ const base = options.url.replace(/\/$/, "");
30
+ const request = options.fetch ?? globalThis.fetch;
31
+ async function checked(path2, init) {
32
+ const response = await request(`${base}${path2}`, init);
33
+ if (!response.ok) {
34
+ const body = await response.text();
35
+ let message = body;
36
+ try {
37
+ const parsed = JSON.parse(body);
38
+ if (typeof parsed?.error?.message === "string") message = parsed.error.message;
39
+ } catch {
40
+ }
41
+ throw new AgentTransportError(response.status, message, null, body);
42
+ }
43
+ return response;
44
+ }
45
+ const path = (id) => `/${encodeURIComponent(id)}`;
46
+ async function list(id, resource) {
47
+ return (await (await checked(`${path(id)}/${resource}`)).json()).data;
48
+ }
49
+ return {
50
+ async create() {
51
+ return (await checked("", { method: "POST" })).json();
52
+ },
53
+ async retrieve(id) {
54
+ return (await checked(path(id))).json();
55
+ },
56
+ async subscribe(id, signal) {
57
+ const controller = new AbortController();
58
+ const abort = () => controller.abort();
59
+ signal.addEventListener("abort", abort, { once: true });
60
+ if (signal.aborted) controller.abort();
61
+ let response;
62
+ try {
63
+ response = await checked(`${path(id)}/events`, { headers: { Accept: "text/event-stream" }, signal: controller.signal });
64
+ } catch (error) {
65
+ signal.removeEventListener("abort", abort);
66
+ throw error;
67
+ }
68
+ const stream = Stream.fromSSEResponse(response, controller);
69
+ return (async function* () {
70
+ try {
71
+ for await (const event of stream) yield event;
72
+ } finally {
73
+ signal.removeEventListener("abort", abort);
74
+ controller.abort();
75
+ }
76
+ })();
77
+ },
78
+ async submit(id, events, key) {
79
+ await checked(`${path(id)}/events`, { method: "POST", headers: { "Content-Type": "application/json", "Idempotency-Key": key }, body: JSON.stringify({ events }) });
80
+ },
81
+ items: (id) => list(id, "items"),
82
+ turns: (id) => list(id, "turns"),
83
+ artifacts: (id) => list(id, "artifacts"),
84
+ artifactURL: (id, artifactId) => `${base}${path(id)}/artifacts/${encodeURIComponent(artifactId)}/content`,
85
+ async upload(id, file, progress) {
86
+ if (file.size > 5 * 1024 * 1024) throw new Error("File exceeds the 5 MiB upload limit");
87
+ const url = `${base}${path(id)}/files?filename=${encodeURIComponent(file.name)}`;
88
+ const uploaded = await new Promise((resolve, reject) => {
89
+ const xhr = new XMLHttpRequest();
90
+ xhr.open("POST", url);
91
+ xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
92
+ xhr.upload.onprogress = (e) => {
93
+ if (e.lengthComputable) progress?.({ loaded: e.loaded, total: e.total, percent: Math.min(99, Math.round(e.loaded / e.total * 100)) });
94
+ };
95
+ xhr.onload = () => {
96
+ try {
97
+ const body = JSON.parse(xhr.responseText);
98
+ if (xhr.status < 200 || xhr.status >= 300) throw new Error(body.error?.message ?? `Upload failed: ${xhr.status}`);
99
+ resolve(body);
100
+ } catch (error) {
101
+ reject(error);
102
+ }
103
+ };
104
+ xhr.onerror = () => reject(new Error("Upload failed"));
105
+ xhr.onabort = () => reject(new Error("Upload cancelled"));
106
+ xhr.send(file);
107
+ });
108
+ if (uploaded.session_id !== id || typeof uploaded.path !== "string" || uploaded.size_bytes !== file.size) throw new Error("Upload returned an invalid Session file");
109
+ progress?.({ loaded: file.size, total: file.size, percent: 100 });
110
+ return { sessionId: id, path: uploaded.path, fileId: uploaded.path, filename: file.name, size: file.size, contentType: file.type || "application/octet-stream", inputType: file.type.startsWith("image/") ? "input_image" : "input_file" };
111
+ }
112
+ };
113
+ }
114
+ function projectSessionItem(state, item, index) {
115
+ if (item.type === "message" && item.role === "assistant") {
116
+ if (item.id === null) throw new Error("Persisted assistant message has no ID");
117
+ const value2 = {
118
+ id: item.id,
119
+ outputIndex: index,
120
+ status: item.status,
121
+ phase: item.phase,
122
+ text: item.content.filter((part) => part.type === "output_text").map((part) => part.text).join("")
123
+ };
124
+ const messages = state.textMessages.some((message) => message.id === item.id) ? state.textMessages.map((message) => message.id === item.id ? value2 : message) : [...state.textMessages, value2];
125
+ return { ...state, textMessages: messages, outputText: messages.map((message) => message.text).join("") };
126
+ }
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
+ 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);
130
+ const value = {
131
+ id: item.id,
132
+ outputIndex: index,
133
+ execution: item.type === "function_call" && !serverFunction ? "client" : "server",
134
+ callId: item.type === "function_call" ? item.call_id : null,
135
+ name: item.type === "command_execution" ? "exec_command" : item.name,
136
+ serverLabel: item.type === "command_execution" ? "Session Sandbox" : item.type === "mcp_call" ? item.server_label : serverFunction ? "Session runtime" : "client",
137
+ status: item.type === "function_call" && !serverFunction && item.status === "completed" ? "awaiting_output" : item.status,
138
+ arguments: item.type === "command_execution" ? item.command : JSON.stringify(item.arguments),
139
+ output: item.type === "command_execution" ? item.output : item.type === "mcp_call" && item.output != null ? JSON.stringify(item.output) : null,
140
+ error: item.type === "command_execution" && item.exit_code !== null && item.exit_code !== 0 ? `Exit code ${item.exit_code}` : item.type === "mcp_call" && item.error != null ? JSON.stringify(item.error) : null
141
+ };
142
+ return { ...state, toolCalls: state.toolCalls.some((tool) => tool.id === item.id) ? state.toolCalls.map((tool) => tool.id === item.id ? value : tool) : [...state.toolCalls, value] };
143
+ }
144
+ function reduceSessionEvent(state, event) {
145
+ let next = { ...state, events: [...state.events, event] };
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);
148
+ } else if (event.type === "agent.session.turn.output_text.delta") {
149
+ if (!next.textMessages.some((message) => message.id === event.item_id)) throw new Error("Text delta has no message");
150
+ const messages = next.textMessages.map((message) => message.id === event.item_id ? { ...message, text: message.text + event.delta } : message);
151
+ next = { ...next, textMessages: messages, outputText: messages.map((message) => message.text).join("") };
152
+ } else if (event.type === "agent.output.command_execution_output.delta") {
153
+ next.toolCalls = next.toolCalls.map((tool) => tool.id === event.item_id ? { ...tool, output: (tool.output ?? "") + event.delta } : tool);
154
+ }
155
+ return next;
156
+ }
157
+ function sessionItemState(items) {
158
+ return items.reduce((state, item, index) => projectSessionItem(state, item, index), createTurnState());
159
+ }
160
+
161
+ // src/use-agent-session.ts
162
+ import { useCallback, useEffect, useRef, useState } from "react";
163
+ function useAgentSession(options) {
164
+ const { transport, onSession } = options;
165
+ const [messages, setMessages] = useState([]);
166
+ const [status, setStatus] = useState("idle");
167
+ const [error, setError] = useState(null);
168
+ const [sessionId, setSessionId] = useState(options.initialSessionId ?? null);
169
+ const [artifacts, setArtifacts] = useState([]);
170
+ const idRef = useRef(sessionId);
171
+ const creating = useRef(null);
172
+ const active = useRef(null);
173
+ const cancelRequested = useRef(false);
174
+ const submitted = useRef(false);
175
+ const generation = useRef(0);
176
+ const busy = useRef(false);
177
+ const initial = useRef(options.initialSessionId);
178
+ const fail = useCallback((cause) => {
179
+ const failure = cause instanceof Error ? cause : new Error(String(cause));
180
+ setError(failure);
181
+ setStatus("error");
182
+ return failure;
183
+ }, []);
184
+ const ensure = useCallback(async () => {
185
+ if (idRef.current) return idRef.current;
186
+ const currentGeneration = generation.current;
187
+ const pending = creating.current ?? transport.create();
188
+ creating.current = pending;
189
+ try {
190
+ const session = await pending;
191
+ if (generation.current !== currentGeneration) throw new Error("Conversation changed during Session creation");
192
+ idRef.current = session.id;
193
+ setSessionId(session.id);
194
+ onSession?.(session.id);
195
+ return session.id;
196
+ } finally {
197
+ if (creating.current === pending) creating.current = null;
198
+ }
199
+ }, [transport, onSession]);
200
+ const refresh = useCallback(async (id) => {
201
+ const [items, turns, outputs] = await Promise.all([transport.items(id), transport.turns(id), transport.artifacts(id)]);
202
+ if (idRef.current !== id) return;
203
+ const restored = [];
204
+ for (const turn of turns) {
205
+ const turnItems = items.filter((item) => item.turn_id === turn.id);
206
+ for (const item of turnItems) {
207
+ 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
+ }
209
+ 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 } });
211
+ }
212
+ setMessages((previous) => restored.map((message) => {
213
+ const existing = previous.find((value) => value.turnId !== null && value.turnId === message.turnId);
214
+ return existing?.projection && message.projection ? { ...message, projection: { ...message.projection, events: existing.projection.events } } : message;
215
+ }));
216
+ setArtifacts(outputs.map((artifact) => ({ ...artifact, url: transport.artifactURL(id, artifact.id) })));
217
+ }, [transport]);
218
+ useEffect(() => {
219
+ const id = initial.current;
220
+ if (!id) return;
221
+ let cancelled = false;
222
+ busy.current = true;
223
+ setStatus("streaming");
224
+ void (async () => {
225
+ await refresh(id);
226
+ while (!cancelled) {
227
+ const session = await transport.retrieve(id);
228
+ if (session.status !== "in_progress") {
229
+ 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
+ break;
232
+ }
233
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
234
+ if (!cancelled) await refresh(id);
235
+ }
236
+ if (!cancelled) {
237
+ await refresh(id);
238
+ setStatus("idle");
239
+ }
240
+ })().catch((cause) => {
241
+ if (!cancelled) fail(cause);
242
+ }).finally(() => {
243
+ if (!cancelled) busy.current = false;
244
+ });
245
+ return () => {
246
+ cancelled = true;
247
+ active.current?.abort();
248
+ };
249
+ }, [transport, refresh, fail]);
250
+ const send = useCallback(async (value) => {
251
+ if (busy.current) throw new Error("A turn is already running");
252
+ const input = typeof value === "string" ? { text: value, attachments: [] } : value;
253
+ if (!input.text.trim() && !input.attachments.length) throw new Error("Input is required");
254
+ busy.current = true;
255
+ cancelRequested.current = false;
256
+ submitted.current = false;
257
+ setStatus("streaming");
258
+ setError(null);
259
+ const abort = new AbortController();
260
+ active.current = abort;
261
+ const assistantId = crypto.randomUUID();
262
+ let projection = createTurnState();
263
+ let id = null;
264
+ try {
265
+ id = await ensure();
266
+ abort.signal.throwIfAborted();
267
+ if (cancelRequested.current) {
268
+ setStatus("idle");
269
+ return null;
270
+ }
271
+ const text = [input.text.trim(), ...input.attachments.map((file) => {
272
+ if (!("sessionId" in file) || file.sessionId !== id || !("path" in file) || typeof file.path !== "string") throw new Error("Attachment belongs to another Session");
273
+ return `Attached file: ${JSON.stringify(file.path)}. Read this file in your Session environment.`;
274
+ })].filter(Boolean).join("\n");
275
+ setMessages((previous) => [...previous, { id: crypto.randomUUID(), role: "user", content: input.text, attachments: input.attachments, status: "completed", turnId: null, projection: null }, { id: assistantId, role: "assistant", content: "", status: "streaming", turnId: null, projection }]);
276
+ const events = await transport.subscribe(id, abort.signal);
277
+ abort.signal.throwIfAborted();
278
+ if (cancelRequested.current) {
279
+ setStatus("idle");
280
+ return null;
281
+ }
282
+ await transport.submit(id, [{ type: "agent.session.input.message", input: [{ role: "user", content: [{ type: "input_text", text }] }] }], crypto.randomUUID());
283
+ submitted.current = true;
284
+ if (cancelRequested.current) await transport.submit(id, [{ type: "agent.session.input.cancel" }], crypto.randomUUID());
285
+ for await (const event of events) {
286
+ if ("session_id" in event && event.session_id !== id) throw new Error("Event belongs to another Session");
287
+ projection = reduceSessionEvent(projection, event);
288
+ if ("turn_id" in event && typeof event.turn_id === "string") projection = { ...projection, turnId: event.turn_id };
289
+ 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
+ if (event.type === "agent.session.turn.completed" || event.type === "agent.session.turn.cancelled" || event.type === "agent.session.turn.failed") {
291
+ await refresh(id);
292
+ if (event.type === "agent.session.turn.failed") throw new Error(event.turn.error?.message ?? "Turn failed");
293
+ setStatus("idle");
294
+ return event.turn;
295
+ }
296
+ 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");
298
+ if (event.type === "error") throw new Error(event.error.message);
299
+ }
300
+ throw new Error("Session event stream disconnected. Reload to recover persisted output.");
301
+ } catch (cause) {
302
+ if (abort.signal.aborted) {
303
+ if (id) await refresh(id);
304
+ setStatus("idle");
305
+ return null;
306
+ }
307
+ throw fail(cause);
308
+ } finally {
309
+ abort.abort();
310
+ if (active.current === abort) active.current = null;
311
+ busy.current = false;
312
+ }
313
+ }, [ensure, fail, refresh, transport]);
314
+ const stop = useCallback(async () => {
315
+ const id = idRef.current;
316
+ cancelRequested.current = true;
317
+ try {
318
+ if (id && (submitted.current || active.current === null)) await transport.submit(id, [{ type: "agent.session.input.cancel" }], crypto.randomUUID());
319
+ } catch (cause) {
320
+ throw fail(cause);
321
+ }
322
+ }, [transport, fail]);
323
+ return {
324
+ messages,
325
+ status,
326
+ error,
327
+ sessionId,
328
+ artifacts,
329
+ send,
330
+ stop,
331
+ async upload(file, progress) {
332
+ const id = await ensure();
333
+ return transport.upload(id, file, progress);
334
+ },
335
+ reset() {
336
+ if (busy.current) throw new Error("Stop the active turn before starting a new Session");
337
+ generation.current++;
338
+ idRef.current = null;
339
+ creating.current = null;
340
+ setSessionId(null);
341
+ setMessages([]);
342
+ setArtifacts([]);
343
+ setError(null);
344
+ setStatus("idle");
345
+ onSession?.(null);
346
+ }
347
+ };
348
+ }
349
+ export {
350
+ AgentTransportError,
351
+ createAgentSessionTransport,
352
+ useAgentSession
353
+ };
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@rebyteai/agent-react",
3
+ "version": "0.2.0",
4
+ "description": "Headless React hooks and transports for Rebyte Agents",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ReByteAI/rebyte-agent-toolkit.git",
9
+ "directory": "packages/react"
10
+ },
11
+ "homepage": "https://github.com/ReByteAI/rebyte-agent-toolkit#readme",
12
+ "bugs": "https://github.com/ReByteAI/rebyte-agent-toolkit/issues",
13
+ "engines": {
14
+ "node": ">=22"
15
+ },
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "main": "./dist/index.cjs",
23
+ "module": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js",
29
+ "require": "./dist/index.cjs"
30
+ }
31
+ },
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "registry": "https://registry.npmjs.org"
35
+ },
36
+ "peerDependencies": {
37
+ "react": ">=18.2.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/react": "^19.2.0",
41
+ "react": "^19.2.0",
42
+ "tsup": "^8.5.0",
43
+ "typescript": "^5.9.3"
44
+ },
45
+ "dependencies": {
46
+ "@rebyteai/agent-sdk": "0.2.0"
47
+ },
48
+ "scripts": {
49
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean --external react",
50
+ "typecheck": "tsc --noEmit"
51
+ }
52
+ }