@cybernetyx1/atlasflow-react 0.1.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 +18 -0
- package/README.md +67 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +235 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
PROPRIETARY SOFTWARE LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cybernetyx. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and its source code are the proprietary and confidential property
|
|
6
|
+
of the copyright holder. The software is original work authored independently.
|
|
7
|
+
|
|
8
|
+
No part of this software may be copied, reproduced, modified, published,
|
|
9
|
+
distributed, sublicensed, or sold in any form or by any means without the prior
|
|
10
|
+
written permission of the copyright holder, except as expressly permitted by a
|
|
11
|
+
separate written agreement.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
15
|
+
FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT
|
|
16
|
+
HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
17
|
+
OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE
|
|
18
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @cybernetyx1/atlasflow-react
|
|
2
|
+
|
|
3
|
+
React hooks and provider for wiring browser and app UIs to an AtlasFlow agent server.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @cybernetyx1/atlasflow-react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Part of the AtlasFlow monorepo. Proprietary.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Wrap your app in `AtlasFlowProvider` (pass either a prebuilt `client` or `options`). Then use `useAgent` to chat with an agent, `useRunWatch` to follow a durable run's event stream, and `useAtlasFlowClient` to reach the client directly.
|
|
16
|
+
|
|
17
|
+
Never put `ATLASFLOW_API_KEY` in browser code. Mint a short-lived scoped token on your server (see `@cybernetyx1/atlasflow-sdk`) and pass it to the provider.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { AtlasFlowProvider, useAgent } from "@cybernetyx1/atlasflow-react";
|
|
21
|
+
|
|
22
|
+
function Chat() {
|
|
23
|
+
const agent = useAgent({ agent: "hello", instanceId: "user-123" });
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<form
|
|
27
|
+
onSubmit={(event) => {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
const input = new FormData(event.currentTarget).get("message");
|
|
30
|
+
void agent.send(String(input ?? ""));
|
|
31
|
+
event.currentTarget.reset();
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
{agent.messages.map((message, index) => (
|
|
35
|
+
<p key={index}>{message.role}: {message.content}</p>
|
|
36
|
+
))}
|
|
37
|
+
<input name="message" />
|
|
38
|
+
<button disabled={agent.isStreaming}>Send</button>
|
|
39
|
+
</form>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function App({ token }: { token: string }) {
|
|
44
|
+
return (
|
|
45
|
+
<AtlasFlowProvider options={{ baseUrl: "https://agent.example", apiKey: token }}>
|
|
46
|
+
<Chat />
|
|
47
|
+
</AtlasFlowProvider>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { useRunWatch, useAtlasFlowClient } from "@cybernetyx1/atlasflow-react";
|
|
54
|
+
|
|
55
|
+
function RunStatus() {
|
|
56
|
+
const client = useAtlasFlowClient();
|
|
57
|
+
// Pass a run id or the RunAdmission returned by client.runs.dispatch(...).
|
|
58
|
+
const run = useRunWatch({ target: "run_abc123" });
|
|
59
|
+
return <pre>{run.status}: {run.events.length} events</pre>;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Exports: `AtlasFlowProvider`, `useAtlasFlowClient`, `useAgent`, `useRunWatch`, plus the `AgentChatMessage` / `AgentHookState` / `AgentStatus` types and the hook option/result types.
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
Proprietary. © 2026 Cybernetyx. See LICENSE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { AtlasEventLike, Usage, AtlasFlowClient, ClientOptions, AgentDispatchInput, RunWatchOptions, RunAdmission } from '@cybernetyx1/atlasflow-sdk';
|
|
3
|
+
|
|
4
|
+
type AgentStatus = "idle" | "streaming" | "success" | "error";
|
|
5
|
+
interface AgentChatMessage {
|
|
6
|
+
role: "user" | "assistant";
|
|
7
|
+
content: string;
|
|
8
|
+
runId?: string;
|
|
9
|
+
pending?: boolean;
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
interface AgentHookState {
|
|
13
|
+
status: AgentStatus;
|
|
14
|
+
messages: AgentChatMessage[];
|
|
15
|
+
events: AtlasEventLike[];
|
|
16
|
+
runId?: string;
|
|
17
|
+
data?: string;
|
|
18
|
+
result?: unknown;
|
|
19
|
+
usage?: Usage;
|
|
20
|
+
error?: Error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface AtlasFlowProviderProps {
|
|
24
|
+
client?: AtlasFlowClient;
|
|
25
|
+
options?: ClientOptions;
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
}
|
|
28
|
+
interface UseAgentOptions {
|
|
29
|
+
client?: AtlasFlowClient;
|
|
30
|
+
agent: string;
|
|
31
|
+
instanceId: string;
|
|
32
|
+
initialMessages?: AgentChatMessage[];
|
|
33
|
+
stream?: boolean;
|
|
34
|
+
}
|
|
35
|
+
interface UseAgentResult extends AgentHookState {
|
|
36
|
+
isStreaming: boolean;
|
|
37
|
+
send(input: string | AgentDispatchInput): Promise<void>;
|
|
38
|
+
reset(messages?: AgentChatMessage[]): void;
|
|
39
|
+
stop(): void;
|
|
40
|
+
}
|
|
41
|
+
interface RunWatchState {
|
|
42
|
+
status: "idle" | "watching" | "closed" | "error";
|
|
43
|
+
events: AtlasEventLike[];
|
|
44
|
+
error?: Error;
|
|
45
|
+
}
|
|
46
|
+
interface UseRunWatchOptions extends RunWatchOptions {
|
|
47
|
+
client?: AtlasFlowClient;
|
|
48
|
+
target?: string | RunAdmission | null;
|
|
49
|
+
}
|
|
50
|
+
declare function AtlasFlowProvider({ client, options, children }: AtlasFlowProviderProps): unknown;
|
|
51
|
+
declare function useAtlasFlowClient(client?: AtlasFlowClient): AtlasFlowClient;
|
|
52
|
+
declare function useAgent(options: UseAgentOptions): UseAgentResult;
|
|
53
|
+
declare function useRunWatch({ client: explicitClient, target, ...watchOptions }: UseRunWatchOptions): RunWatchState;
|
|
54
|
+
|
|
55
|
+
export { type AgentChatMessage, type AgentHookState, type AgentStatus, AtlasFlowProvider, type AtlasFlowProviderProps, type RunWatchState, type UseAgentOptions, type UseAgentResult, type UseRunWatchOptions, useAgent, useAtlasFlowClient, useRunWatch };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
createElement,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useReducer,
|
|
10
|
+
useRef
|
|
11
|
+
} from "react";
|
|
12
|
+
import {
|
|
13
|
+
createAtlasFlowClient
|
|
14
|
+
} from "@cybernetyx1/atlasflow-sdk";
|
|
15
|
+
|
|
16
|
+
// src/state.ts
|
|
17
|
+
function initialAgentState(messages = []) {
|
|
18
|
+
return { status: "idle", messages, events: [] };
|
|
19
|
+
}
|
|
20
|
+
function agentReducer(state, action) {
|
|
21
|
+
switch (action.type) {
|
|
22
|
+
case "reset":
|
|
23
|
+
return initialAgentState(action.messages ?? []);
|
|
24
|
+
case "start":
|
|
25
|
+
return {
|
|
26
|
+
...state,
|
|
27
|
+
status: "streaming",
|
|
28
|
+
error: void 0,
|
|
29
|
+
data: void 0,
|
|
30
|
+
result: void 0,
|
|
31
|
+
usage: void 0,
|
|
32
|
+
events: [],
|
|
33
|
+
messages: [
|
|
34
|
+
...state.messages,
|
|
35
|
+
{ role: "user", content: action.message },
|
|
36
|
+
{ role: "assistant", content: "", pending: true }
|
|
37
|
+
]
|
|
38
|
+
};
|
|
39
|
+
case "event":
|
|
40
|
+
return applyAgentEvent(state, action.event);
|
|
41
|
+
case "sync_result":
|
|
42
|
+
return replaceAssistant(state, {
|
|
43
|
+
status: "success",
|
|
44
|
+
runId: action.result.runId,
|
|
45
|
+
data: action.result.data,
|
|
46
|
+
result: action.result.result,
|
|
47
|
+
usage: action.result.usage,
|
|
48
|
+
content: action.result.data,
|
|
49
|
+
pending: false
|
|
50
|
+
});
|
|
51
|
+
case "error":
|
|
52
|
+
return replaceAssistant(state, {
|
|
53
|
+
status: "error",
|
|
54
|
+
error: action.error,
|
|
55
|
+
content: state.messages.at(-1)?.role === "assistant" ? state.messages.at(-1)?.content ?? "" : "",
|
|
56
|
+
pending: false,
|
|
57
|
+
messageError: action.error.message
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function applyAgentEvent(state, event) {
|
|
62
|
+
const events = [...state.events, event];
|
|
63
|
+
if (event.type === "text_delta") {
|
|
64
|
+
const delta = typeof event.delta === "string" ? event.delta : typeof event.text === "string" ? event.text : "";
|
|
65
|
+
return appendAssistant({ ...state, events }, delta);
|
|
66
|
+
}
|
|
67
|
+
if (event.type === "result") {
|
|
68
|
+
return replaceAssistant({
|
|
69
|
+
...state,
|
|
70
|
+
events,
|
|
71
|
+
status: "success",
|
|
72
|
+
runId: typeof event.runId === "string" ? event.runId : state.runId,
|
|
73
|
+
data: typeof event.text === "string" ? event.text : typeof event.data === "string" ? event.data : state.data,
|
|
74
|
+
result: "data" in event ? event.data : state.result,
|
|
75
|
+
usage: isUsage(event.usage) ? event.usage : state.usage
|
|
76
|
+
}, {
|
|
77
|
+
status: "success",
|
|
78
|
+
content: typeof event.text === "string" ? event.text : void 0,
|
|
79
|
+
pending: false
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (event.type === "run_error") {
|
|
83
|
+
const err = new Error(typeof event.message === "string" ? event.message : "run failed");
|
|
84
|
+
return replaceAssistant({ ...state, events }, { status: "error", error: err, pending: false, messageError: err.message });
|
|
85
|
+
}
|
|
86
|
+
return { ...state, events };
|
|
87
|
+
}
|
|
88
|
+
function appendAssistant(state, delta) {
|
|
89
|
+
if (!delta) return state;
|
|
90
|
+
const messages = [...state.messages];
|
|
91
|
+
const last = messages.at(-1);
|
|
92
|
+
if (last?.role === "assistant") {
|
|
93
|
+
messages[messages.length - 1] = { ...last, content: last.content + delta, pending: true };
|
|
94
|
+
} else {
|
|
95
|
+
messages.push({ role: "assistant", content: delta, pending: true });
|
|
96
|
+
}
|
|
97
|
+
return { ...state, messages, data: (state.data ?? "") + delta };
|
|
98
|
+
}
|
|
99
|
+
function replaceAssistant(state, patch) {
|
|
100
|
+
const messages = [...state.messages];
|
|
101
|
+
const last = messages.at(-1);
|
|
102
|
+
const content = patch.content ?? (last?.role === "assistant" ? last.content : "");
|
|
103
|
+
const assistant = {
|
|
104
|
+
role: "assistant",
|
|
105
|
+
content,
|
|
106
|
+
...patch.runId ?? state.runId ? { runId: patch.runId ?? state.runId } : {},
|
|
107
|
+
...patch.pending !== void 0 ? { pending: patch.pending } : {},
|
|
108
|
+
...patch.messageError ? { error: patch.messageError } : {}
|
|
109
|
+
};
|
|
110
|
+
if (last?.role === "assistant") messages[messages.length - 1] = assistant;
|
|
111
|
+
else messages.push(assistant);
|
|
112
|
+
const { content: _content, pending: _pending, messageError: _messageError, ...statePatch } = patch;
|
|
113
|
+
return { ...state, ...statePatch, messages };
|
|
114
|
+
}
|
|
115
|
+
function isUsage(value) {
|
|
116
|
+
if (!value || typeof value !== "object") return false;
|
|
117
|
+
const usage = value;
|
|
118
|
+
return typeof usage.totalTokens === "number" && typeof usage.costTotal === "number";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/index.ts
|
|
122
|
+
var AtlasFlowContext = createContext(void 0);
|
|
123
|
+
function AtlasFlowProvider({ client, options, children }) {
|
|
124
|
+
const resolved = useMemo(() => client ?? (options ? createAtlasFlowClient(options) : void 0), [client, options?.baseUrl, options?.apiKey, options?.fetch, options?.headers]);
|
|
125
|
+
if (!resolved) throw new Error("AtlasFlowProvider requires either client or options.");
|
|
126
|
+
return createElement(AtlasFlowContext.Provider, { value: resolved }, children);
|
|
127
|
+
}
|
|
128
|
+
function useAtlasFlowClient(client) {
|
|
129
|
+
const ctx = useContext(AtlasFlowContext);
|
|
130
|
+
const resolved = client ?? ctx;
|
|
131
|
+
if (!resolved) throw new Error("No AtlasFlow client found. Wrap your component in AtlasFlowProvider or pass { client }.");
|
|
132
|
+
return resolved;
|
|
133
|
+
}
|
|
134
|
+
function useAgent(options) {
|
|
135
|
+
const client = useAtlasFlowClient(options.client);
|
|
136
|
+
const [state, dispatch] = useReducer(agentReducer, initialAgentState(options.initialMessages));
|
|
137
|
+
const abortRef = useRef(null);
|
|
138
|
+
const stop = useCallback(() => {
|
|
139
|
+
abortRef.current?.abort();
|
|
140
|
+
abortRef.current = null;
|
|
141
|
+
}, []);
|
|
142
|
+
const reset = useCallback((messages = []) => {
|
|
143
|
+
stop();
|
|
144
|
+
dispatch({ type: "reset", messages });
|
|
145
|
+
}, [stop]);
|
|
146
|
+
const send = useCallback(
|
|
147
|
+
async (input) => {
|
|
148
|
+
stop();
|
|
149
|
+
const body = typeof input === "string" ? { message: input } : input;
|
|
150
|
+
const message = body.message ?? "";
|
|
151
|
+
dispatch({ type: "start", message });
|
|
152
|
+
const abort = new AbortController();
|
|
153
|
+
abortRef.current = abort;
|
|
154
|
+
try {
|
|
155
|
+
if (options.stream === false) {
|
|
156
|
+
const result = await client.agents.send(options.agent, options.instanceId, { ...body, message });
|
|
157
|
+
if (!abort.signal.aborted) dispatch({ type: "sync_result", result });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
for await (const event of client.agents.stream(options.agent, options.instanceId, { ...body, message, signal: abort.signal })) {
|
|
161
|
+
if (abort.signal.aborted) break;
|
|
162
|
+
dispatch({ type: "event", event });
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (!abort.signal.aborted) dispatch({ type: "error", error: normalizeError(err) });
|
|
166
|
+
} finally {
|
|
167
|
+
if (abortRef.current === abort) abortRef.current = null;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
[client, options.agent, options.instanceId, options.stream, stop]
|
|
171
|
+
);
|
|
172
|
+
useEffect(() => stop, [stop]);
|
|
173
|
+
return {
|
|
174
|
+
...state,
|
|
175
|
+
isStreaming: state.status === "streaming",
|
|
176
|
+
send,
|
|
177
|
+
reset,
|
|
178
|
+
stop
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function useRunWatch({ client: explicitClient, target, ...watchOptions }) {
|
|
182
|
+
const client = useAtlasFlowClient(explicitClient);
|
|
183
|
+
const [state, setState] = useReducer(runWatchReducer, { status: "idle", events: [] });
|
|
184
|
+
const serializedOptions = JSON.stringify({
|
|
185
|
+
offset: watchOptions.offset,
|
|
186
|
+
after: watchOptions.after,
|
|
187
|
+
limit: watchOptions.limit,
|
|
188
|
+
tail: watchOptions.tail,
|
|
189
|
+
reconnect: watchOptions.reconnect
|
|
190
|
+
});
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (!target) {
|
|
193
|
+
setState({ type: "reset" });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const abort = new AbortController();
|
|
197
|
+
setState({ type: "start" });
|
|
198
|
+
void (async () => {
|
|
199
|
+
try {
|
|
200
|
+
for await (const event of client.runs.watch(target, { ...watchOptions, signal: abort.signal })) {
|
|
201
|
+
if (abort.signal.aborted) break;
|
|
202
|
+
setState({ type: "event", event });
|
|
203
|
+
}
|
|
204
|
+
if (!abort.signal.aborted) setState({ type: "closed" });
|
|
205
|
+
} catch (err) {
|
|
206
|
+
if (!abort.signal.aborted) setState({ type: "error", error: normalizeError(err) });
|
|
207
|
+
}
|
|
208
|
+
})();
|
|
209
|
+
return () => abort.abort();
|
|
210
|
+
}, [client, target, serializedOptions]);
|
|
211
|
+
return state;
|
|
212
|
+
}
|
|
213
|
+
function runWatchReducer(state, action) {
|
|
214
|
+
switch (action.type) {
|
|
215
|
+
case "reset":
|
|
216
|
+
return { status: "idle", events: [] };
|
|
217
|
+
case "start":
|
|
218
|
+
return { status: "watching", events: [], error: void 0 };
|
|
219
|
+
case "event":
|
|
220
|
+
return { ...state, events: [...state.events, action.event] };
|
|
221
|
+
case "closed":
|
|
222
|
+
return { ...state, status: "closed" };
|
|
223
|
+
case "error":
|
|
224
|
+
return { ...state, status: "error", error: action.error };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function normalizeError(err) {
|
|
228
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
229
|
+
}
|
|
230
|
+
export {
|
|
231
|
+
AtlasFlowProvider,
|
|
232
|
+
useAgent,
|
|
233
|
+
useAtlasFlowClient,
|
|
234
|
+
useRunWatch
|
|
235
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cybernetyx1/atlasflow-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React provider and hooks for AtlasFlow agent chat and durable run event streams.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"author": "Cybernetyx",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Cybernetyx/atlasflow.git",
|
|
11
|
+
"directory": "packages/react"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@cybernetyx1/atlasflow-sdk": "0.1.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"test": "node --import tsx --test test/*.test.ts"
|
|
35
|
+
}
|
|
36
|
+
}
|