@cybernetyx1/atlasflow-sdk 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 +45 -0
- package/dist/index.d.ts +304 -0
- package/dist/index.js +549 -0
- package/package.json +35 -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,45 @@
|
|
|
1
|
+
# @cybernetyx1/atlasflow-sdk
|
|
2
|
+
|
|
3
|
+
Typed HTTP client SDK for AtlasFlow agent servers — prompt agents, dispatch durable runs, stream events, and work with public streams.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @cybernetyx1/atlasflow-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Part of the AtlasFlow monorepo. Proprietary.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
`createAtlasFlowClient(options)` builds a client grouped into `agents`, `runs`, `workflows`, and `streams`.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createAtlasFlowClient } from "@cybernetyx1/atlasflow-sdk";
|
|
19
|
+
|
|
20
|
+
const client = createAtlasFlowClient({
|
|
21
|
+
baseUrl: "https://agent.example",
|
|
22
|
+
apiKey: process.env.ATLASFLOW_API_KEY, // server-side only
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Interactive prompt.
|
|
26
|
+
const reply = await client.agents.prompt("hello", "user-123", { message: "hi" });
|
|
27
|
+
console.log(reply.text);
|
|
28
|
+
|
|
29
|
+
// Durable run, then wait for completion.
|
|
30
|
+
const admission = await client.runs.dispatch("hello", { message: "do work" });
|
|
31
|
+
const record = await client.runs.wait(admission.runId);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// Stream run events as they happen.
|
|
36
|
+
for await (const event of client.agents.stream("hello", "user-123", { message: "hi" })) {
|
|
37
|
+
console.log(event.type);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Browser code must not use `ATLASFLOW_API_KEY`; mint a short-lived scoped token on your server and pass it as `apiKey`. Also exported: the `AtlasFlowError` class, the `AtlasFlowClient` type, and the request/response types (`AgentResult`, `RunRecord`, `RunWatchOptions`, stream option types, etc.).
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
Proprietary. © 2026 Cybernetyx. See LICENSE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @cybernetyx1/atlasflow-sdk — typed HTTP client for invoking deployed AtlasFlow agents.
|
|
3
|
+
*
|
|
4
|
+
* Dependency-free: uses the global `fetch`. Construct once with a base URL,
|
|
5
|
+
* then call agents / workflows / runs / admin.
|
|
6
|
+
*/
|
|
7
|
+
interface Usage {
|
|
8
|
+
inputTokens: number;
|
|
9
|
+
outputTokens: number;
|
|
10
|
+
cacheReadTokens: number;
|
|
11
|
+
cacheWriteTokens: number;
|
|
12
|
+
totalTokens: number;
|
|
13
|
+
costTotal: number;
|
|
14
|
+
}
|
|
15
|
+
interface AgentResult {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
runId: string;
|
|
18
|
+
/** The agent's final text reply. */
|
|
19
|
+
data: string;
|
|
20
|
+
/** Structured result, when the agent declared a result schema. */
|
|
21
|
+
result?: unknown;
|
|
22
|
+
usage: Usage;
|
|
23
|
+
}
|
|
24
|
+
interface AgentImageInput {
|
|
25
|
+
/** Base64 image bytes, or a data:image/...;base64,... URL. */
|
|
26
|
+
data: string;
|
|
27
|
+
/** Required unless `data` is a data URL that contains the MIME type. */
|
|
28
|
+
mimeType?: string;
|
|
29
|
+
}
|
|
30
|
+
interface AgentPromptInput {
|
|
31
|
+
message: string;
|
|
32
|
+
payload?: unknown;
|
|
33
|
+
images?: AgentImageInput[];
|
|
34
|
+
/** Abort an in-flight streaming request, useful for browser UI stop buttons. */
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}
|
|
37
|
+
interface AgentDispatchInput {
|
|
38
|
+
message?: string;
|
|
39
|
+
payload?: unknown;
|
|
40
|
+
images?: AgentImageInput[];
|
|
41
|
+
}
|
|
42
|
+
interface RunAdmission {
|
|
43
|
+
runId: string;
|
|
44
|
+
status: string;
|
|
45
|
+
/** Public event-stream URL owned by the server. */
|
|
46
|
+
streamUrl?: string;
|
|
47
|
+
/** Opaque offset to use for the first event read. */
|
|
48
|
+
offset?: string;
|
|
49
|
+
}
|
|
50
|
+
interface RunRecord {
|
|
51
|
+
runId: string;
|
|
52
|
+
agent: string;
|
|
53
|
+
instanceId: string;
|
|
54
|
+
status: "queued" | "running" | "waiting_approval" | "success" | "failed" | "interrupted";
|
|
55
|
+
result?: unknown;
|
|
56
|
+
error?: {
|
|
57
|
+
code: string;
|
|
58
|
+
message: string;
|
|
59
|
+
};
|
|
60
|
+
startedAt: number;
|
|
61
|
+
endedAt?: number;
|
|
62
|
+
}
|
|
63
|
+
interface AtlasEventLike {
|
|
64
|
+
type: string;
|
|
65
|
+
/** Opaque resume offset from the public event stream. */
|
|
66
|
+
offset?: string;
|
|
67
|
+
[key: string]: unknown;
|
|
68
|
+
}
|
|
69
|
+
interface RunEventsOptions {
|
|
70
|
+
/** Legacy numeric cursor; returns events with index greater than this value. */
|
|
71
|
+
after?: number;
|
|
72
|
+
/** Opaque cursor returned as event.offset / response.nextOffset. Use "-1", "now", or a prior offset. */
|
|
73
|
+
offset?: string;
|
|
74
|
+
/** Limit the number of events returned in one catch-up read. */
|
|
75
|
+
limit?: number;
|
|
76
|
+
/** On offset "-1", return only the most recent N events. */
|
|
77
|
+
tail?: number;
|
|
78
|
+
follow?: boolean;
|
|
79
|
+
signal?: AbortSignal;
|
|
80
|
+
}
|
|
81
|
+
interface RunWatchOptions extends Omit<RunEventsOptions, "follow"> {
|
|
82
|
+
/**
|
|
83
|
+
* Reconnect the live SSE leg after transient stream failures. Catch-up JSON
|
|
84
|
+
* reads still run before every live follow attempt, so delivered offsets are
|
|
85
|
+
* reconciled against durable history.
|
|
86
|
+
*/
|
|
87
|
+
reconnect?: boolean | {
|
|
88
|
+
attempts?: number;
|
|
89
|
+
delayMs?: number;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
interface RunEventsResult {
|
|
93
|
+
items: AtlasEventLike[];
|
|
94
|
+
nextOffset: string;
|
|
95
|
+
upToDate: boolean;
|
|
96
|
+
closed: boolean;
|
|
97
|
+
}
|
|
98
|
+
interface StreamMeta {
|
|
99
|
+
exists: boolean;
|
|
100
|
+
contentType?: string;
|
|
101
|
+
offset?: string;
|
|
102
|
+
cursor?: string;
|
|
103
|
+
streamClosed?: boolean;
|
|
104
|
+
ttlSeconds?: number;
|
|
105
|
+
expiresAt?: string;
|
|
106
|
+
etag?: string;
|
|
107
|
+
}
|
|
108
|
+
interface StreamWriteOptions {
|
|
109
|
+
contentType?: string;
|
|
110
|
+
body?: string | Uint8Array;
|
|
111
|
+
closed?: boolean;
|
|
112
|
+
ttlSeconds?: number;
|
|
113
|
+
expiresAt?: string;
|
|
114
|
+
/** Source stream path or /streams/* URL for Durable Streams fork creation. */
|
|
115
|
+
forkedFrom?: string;
|
|
116
|
+
/** Optional Durable Streams fork offset. Defaults to the source tail. */
|
|
117
|
+
forkOffset?: string;
|
|
118
|
+
}
|
|
119
|
+
interface StreamAppendOptions {
|
|
120
|
+
contentType?: string;
|
|
121
|
+
close?: boolean;
|
|
122
|
+
/** Optional Durable Streams writer coordination sequence, sent as Stream-Seq. */
|
|
123
|
+
seq?: string;
|
|
124
|
+
}
|
|
125
|
+
interface StreamReadOptions {
|
|
126
|
+
offset?: string;
|
|
127
|
+
cursor?: string;
|
|
128
|
+
live?: false | "long-poll";
|
|
129
|
+
timeoutMs?: number;
|
|
130
|
+
}
|
|
131
|
+
interface StreamJsonResult<T = unknown> {
|
|
132
|
+
items: T[];
|
|
133
|
+
nextOffset: string;
|
|
134
|
+
cursor?: string;
|
|
135
|
+
upToDate: boolean;
|
|
136
|
+
closed: boolean;
|
|
137
|
+
}
|
|
138
|
+
interface StreamSubscriptionCreateOptions {
|
|
139
|
+
type?: "pull-wake" | "webhook";
|
|
140
|
+
streams?: string[];
|
|
141
|
+
pattern?: string;
|
|
142
|
+
wakeStream?: string;
|
|
143
|
+
leaseTtlMs?: number;
|
|
144
|
+
webhookUrl?: string;
|
|
145
|
+
url?: string;
|
|
146
|
+
headers?: Record<string, string>;
|
|
147
|
+
description?: string;
|
|
148
|
+
}
|
|
149
|
+
interface StreamSubscriptionLink {
|
|
150
|
+
path: string;
|
|
151
|
+
linkType: "explicit" | "glob";
|
|
152
|
+
ackedOffset: string;
|
|
153
|
+
}
|
|
154
|
+
interface StreamSubscriptionInfo {
|
|
155
|
+
id: string;
|
|
156
|
+
subscriptionId: string;
|
|
157
|
+
type: "pull-wake" | "webhook";
|
|
158
|
+
pattern?: string;
|
|
159
|
+
streams: StreamSubscriptionLink[];
|
|
160
|
+
wakeStream?: string;
|
|
161
|
+
leaseTtlMs?: number;
|
|
162
|
+
webhookUrl?: string;
|
|
163
|
+
webhookLastSuccessAt?: string;
|
|
164
|
+
webhookLastError?: string;
|
|
165
|
+
createdAt: string;
|
|
166
|
+
status: string;
|
|
167
|
+
description?: string;
|
|
168
|
+
}
|
|
169
|
+
interface StreamSubscriptionStreamInfo extends StreamSubscriptionLink {
|
|
170
|
+
tailOffset: string;
|
|
171
|
+
hasPending: boolean;
|
|
172
|
+
}
|
|
173
|
+
interface StreamSubscriptionClaim {
|
|
174
|
+
wakeId: string;
|
|
175
|
+
generation: number;
|
|
176
|
+
token: string;
|
|
177
|
+
streams: StreamSubscriptionStreamInfo[];
|
|
178
|
+
leaseTtlMs: number;
|
|
179
|
+
}
|
|
180
|
+
interface StreamSubscriptionAckOptions {
|
|
181
|
+
wakeId: string;
|
|
182
|
+
generation: number;
|
|
183
|
+
acks?: Array<{
|
|
184
|
+
stream?: string;
|
|
185
|
+
path?: string;
|
|
186
|
+
offset: string;
|
|
187
|
+
}>;
|
|
188
|
+
done?: boolean;
|
|
189
|
+
}
|
|
190
|
+
interface ClientOptions {
|
|
191
|
+
baseUrl: string;
|
|
192
|
+
/**
|
|
193
|
+
* Bearer token for servers protected by requireApiKey().
|
|
194
|
+
* Server-side code may use ATLASFLOW_API_KEY. Browser code must use a
|
|
195
|
+
* short-lived scoped token minted by trusted server code with mintScopedToken().
|
|
196
|
+
*/
|
|
197
|
+
apiKey?: string;
|
|
198
|
+
headers?: Record<string, string>;
|
|
199
|
+
fetch?: typeof fetch;
|
|
200
|
+
}
|
|
201
|
+
declare class AtlasFlowError extends Error {
|
|
202
|
+
status: number;
|
|
203
|
+
code: string;
|
|
204
|
+
constructor(status: number, code: string, message: string);
|
|
205
|
+
}
|
|
206
|
+
declare function createAtlasFlowClient(options: ClientOptions): {
|
|
207
|
+
agents: {
|
|
208
|
+
/** Invoke an agent instance and await the full result. */
|
|
209
|
+
prompt(name: string, id: string, input: AgentPromptInput): Promise<AgentResult>;
|
|
210
|
+
/** Alias for prompt(), matching the send-style naming used by streaming agent clients. */
|
|
211
|
+
send(name: string, id: string, input: AgentPromptInput): Promise<AgentResult>;
|
|
212
|
+
/** Fire-and-forget: dispatch the agent in the background, get a runId now. */
|
|
213
|
+
dispatch(name: string, id: string, input?: AgentDispatchInput): Promise<RunAdmission>;
|
|
214
|
+
/** Stream events live as the agent works (Server-Sent Events). */
|
|
215
|
+
stream(name: string, id: string, input: AgentPromptInput): AsyncGenerator<AtlasEventLike>;
|
|
216
|
+
};
|
|
217
|
+
/** @deprecated Use `runs.dispatch` / `runs.dispatchAndWait` (same endpoints). */
|
|
218
|
+
workflows: {
|
|
219
|
+
/** Dispatch a durable workflow run; returns immediately with a runId. */
|
|
220
|
+
invoke(name: string, input?: {
|
|
221
|
+
payload?: unknown;
|
|
222
|
+
message?: string;
|
|
223
|
+
}): Promise<RunAdmission>;
|
|
224
|
+
/** Dispatch and block until the run finishes, returning its result. */
|
|
225
|
+
invokeAndWait(name: string, input?: {
|
|
226
|
+
payload?: unknown;
|
|
227
|
+
message?: string;
|
|
228
|
+
}): Promise<AgentResult>;
|
|
229
|
+
};
|
|
230
|
+
runs: {
|
|
231
|
+
/** Dispatch a durable run — an agent or an authored workflow — by name. */
|
|
232
|
+
dispatch(name: string, input?: AgentDispatchInput): Promise<RunAdmission & {
|
|
233
|
+
gate?: {
|
|
234
|
+
id: string;
|
|
235
|
+
title: string;
|
|
236
|
+
};
|
|
237
|
+
}>;
|
|
238
|
+
/** Dispatch an agent run and block until it finishes. */
|
|
239
|
+
dispatchAndWait(name: string, input?: AgentDispatchInput): Promise<AgentResult>;
|
|
240
|
+
/** Decide the gate a workflow run is parked at (approve resumes it). */
|
|
241
|
+
approve(runId: string, decision: {
|
|
242
|
+
approved: boolean;
|
|
243
|
+
gate?: string;
|
|
244
|
+
note?: string;
|
|
245
|
+
}): Promise<{
|
|
246
|
+
runId: string;
|
|
247
|
+
status: string;
|
|
248
|
+
gate?: {
|
|
249
|
+
id: string;
|
|
250
|
+
title: string;
|
|
251
|
+
};
|
|
252
|
+
data?: string;
|
|
253
|
+
}>;
|
|
254
|
+
get(runId: string): Promise<RunRecord>;
|
|
255
|
+
/** Poll until the run reaches a terminal status (success/failed/interrupted). */
|
|
256
|
+
wait(runId: string, opts?: {
|
|
257
|
+
intervalMs?: number;
|
|
258
|
+
timeoutMs?: number;
|
|
259
|
+
}): Promise<RunRecord>;
|
|
260
|
+
events(runId: string, opts?: Omit<RunEventsOptions, "follow" | "signal">): Promise<AtlasEventLike[]>;
|
|
261
|
+
eventPage(runId: string, opts?: Omit<RunEventsOptions, "follow" | "signal">): Promise<RunEventsResult>;
|
|
262
|
+
/** Stream persisted run events by run id. Reconnect with opts.after = last seen event index. */
|
|
263
|
+
stream(runId: string, opts?: RunEventsOptions): AsyncGenerator<AtlasEventLike>;
|
|
264
|
+
/**
|
|
265
|
+
* Reconciled durable event stream. Starts from a run id or dispatch
|
|
266
|
+
* admission, reads persisted pages until caught up, follows live SSE, then
|
|
267
|
+
* re-checks persisted history when the live stream closes or reconnects.
|
|
268
|
+
*/
|
|
269
|
+
watch(target: string | RunAdmission, opts?: RunWatchOptions): AsyncGenerator<AtlasEventLike>;
|
|
270
|
+
list(opts?: {
|
|
271
|
+
limit?: number;
|
|
272
|
+
}): Promise<RunRecord[]>;
|
|
273
|
+
};
|
|
274
|
+
streams: {
|
|
275
|
+
url(path: string): string;
|
|
276
|
+
head(path: string): Promise<StreamMeta>;
|
|
277
|
+
create(path: string, opts?: StreamWriteOptions): Promise<StreamMeta>;
|
|
278
|
+
append(path: string, body: string | Uint8Array, opts?: StreamAppendOptions): Promise<StreamMeta>;
|
|
279
|
+
close(path: string): Promise<StreamMeta>;
|
|
280
|
+
delete(path: string): Promise<void>;
|
|
281
|
+
readJson<T = unknown>(path: string, opts?: StreamReadOptions): Promise<StreamJsonResult<T>>;
|
|
282
|
+
subscriptions: {
|
|
283
|
+
create(id: string, opts: StreamSubscriptionCreateOptions): Promise<StreamSubscriptionInfo>;
|
|
284
|
+
get(id: string): Promise<StreamSubscriptionInfo>;
|
|
285
|
+
delete(id: string): Promise<void>;
|
|
286
|
+
addStreams(id: string, streams: string[]): Promise<void>;
|
|
287
|
+
removeStream(id: string, path: string): Promise<void>;
|
|
288
|
+
claim(id: string, worker: string): Promise<StreamSubscriptionClaim>;
|
|
289
|
+
ack(id: string, token: string, opts: StreamSubscriptionAckOptions): Promise<{
|
|
290
|
+
ok: true;
|
|
291
|
+
nextWake: boolean;
|
|
292
|
+
}>;
|
|
293
|
+
release(id: string, token: string, opts: Pick<StreamSubscriptionAckOptions, "wakeId" | "generation">): Promise<void>;
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
admin: {
|
|
297
|
+
agents(): Promise<{
|
|
298
|
+
name: string;
|
|
299
|
+
}[]>;
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
type AtlasFlowClient = ReturnType<typeof createAtlasFlowClient>;
|
|
303
|
+
|
|
304
|
+
export { type AgentDispatchInput, type AgentImageInput, type AgentPromptInput, type AgentResult, type AtlasEventLike, type AtlasFlowClient, AtlasFlowError, type ClientOptions, type RunAdmission, type RunEventsOptions, type RunEventsResult, type RunRecord, type RunWatchOptions, type StreamAppendOptions, type StreamJsonResult, type StreamMeta, type StreamReadOptions, type StreamSubscriptionAckOptions, type StreamSubscriptionClaim, type StreamSubscriptionCreateOptions, type StreamSubscriptionInfo, type StreamSubscriptionLink, type StreamSubscriptionStreamInfo, type StreamWriteOptions, type Usage, createAtlasFlowClient };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var AtlasFlowError = class extends Error {
|
|
3
|
+
constructor(status, code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.name = "AtlasFlowError";
|
|
8
|
+
}
|
|
9
|
+
status;
|
|
10
|
+
code;
|
|
11
|
+
};
|
|
12
|
+
function createAtlasFlowClient(options) {
|
|
13
|
+
const base = options.baseUrl.replace(/\/$/, "");
|
|
14
|
+
const doFetch = options.fetch ?? fetch;
|
|
15
|
+
const headers = {
|
|
16
|
+
"content-type": "application/json",
|
|
17
|
+
...options.apiKey ? { authorization: `Bearer ${options.apiKey}` } : {},
|
|
18
|
+
...options.headers
|
|
19
|
+
};
|
|
20
|
+
async function post(path, body) {
|
|
21
|
+
const res = await doFetch(`${base}${path}`, { method: "POST", headers, body: JSON.stringify(body) });
|
|
22
|
+
const json = await res.json();
|
|
23
|
+
if (!res.ok || json.error) {
|
|
24
|
+
throw new AtlasFlowError(res.status, json.error?.code ?? "request_failed", json.error?.message ?? res.statusText);
|
|
25
|
+
}
|
|
26
|
+
return json;
|
|
27
|
+
}
|
|
28
|
+
async function get(path) {
|
|
29
|
+
const res = await doFetch(`${base}${path}`, { headers });
|
|
30
|
+
if (!res.ok) throw new AtlasFlowError(res.status, "request_failed", res.statusText);
|
|
31
|
+
return await res.json();
|
|
32
|
+
}
|
|
33
|
+
async function getEventPageUrl(url, opts = {}) {
|
|
34
|
+
const res = await doFetch(withEventQuery(url, opts), { headers });
|
|
35
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
36
|
+
return await res.json();
|
|
37
|
+
}
|
|
38
|
+
async function* streamEventUrl(url, opts = {}) {
|
|
39
|
+
const res = await doFetch(withEventQuery(url, opts), {
|
|
40
|
+
headers: { ...headers, accept: "text/event-stream" },
|
|
41
|
+
signal: opts.signal
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
44
|
+
if (!res.body) throw new AtlasFlowError(res.status, "no_stream", "response has no body");
|
|
45
|
+
yield* parseSSE(res.body);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
agents: {
|
|
49
|
+
/** Invoke an agent instance and await the full result. */
|
|
50
|
+
prompt(name, id, input) {
|
|
51
|
+
return post(`/agents/${encodeURIComponent(name)}/${encodeURIComponent(id)}`, agentRequestBody(input));
|
|
52
|
+
},
|
|
53
|
+
/** Alias for prompt(), matching the send-style naming used by streaming agent clients. */
|
|
54
|
+
send(name, id, input) {
|
|
55
|
+
return post(`/agents/${encodeURIComponent(name)}/${encodeURIComponent(id)}`, agentRequestBody(input));
|
|
56
|
+
},
|
|
57
|
+
/** Fire-and-forget: dispatch the agent in the background, get a runId now. */
|
|
58
|
+
async dispatch(name, id, input = {}) {
|
|
59
|
+
const res = await doFetch(`${base}/agents/${encodeURIComponent(name)}/${encodeURIComponent(id)}`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { ...headers, "x-webhook": "true" },
|
|
62
|
+
body: JSON.stringify(agentRequestBody(input))
|
|
63
|
+
});
|
|
64
|
+
const json = await res.json();
|
|
65
|
+
if (!res.ok || json.error) throw new AtlasFlowError(res.status, json.error?.code ?? "request_failed", json.error?.message ?? res.statusText);
|
|
66
|
+
return json;
|
|
67
|
+
},
|
|
68
|
+
/** Stream events live as the agent works (Server-Sent Events). */
|
|
69
|
+
async *stream(name, id, input) {
|
|
70
|
+
const res = await doFetch(`${base}/agents/${encodeURIComponent(name)}/${encodeURIComponent(id)}`, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { ...headers, accept: "text/event-stream" },
|
|
73
|
+
body: JSON.stringify(agentRequestBody(input)),
|
|
74
|
+
signal: input.signal
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
let error;
|
|
78
|
+
try {
|
|
79
|
+
error = (await res.json()).error;
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
throw new AtlasFlowError(res.status, error?.code ?? "request_failed", error?.message ?? res.statusText);
|
|
83
|
+
}
|
|
84
|
+
if (!res.body) throw new AtlasFlowError(res.status, "no_stream", "response has no body");
|
|
85
|
+
yield* parseSSE(res.body);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
/** @deprecated Use `runs.dispatch` / `runs.dispatchAndWait` (same endpoints). */
|
|
89
|
+
workflows: {
|
|
90
|
+
/** Dispatch a durable workflow run; returns immediately with a runId. */
|
|
91
|
+
invoke(name, input = {}) {
|
|
92
|
+
return post(`/workflows/${encodeURIComponent(name)}`, input);
|
|
93
|
+
},
|
|
94
|
+
/** Dispatch and block until the run finishes, returning its result. */
|
|
95
|
+
invokeAndWait(name, input = {}) {
|
|
96
|
+
return post(`/workflows/${encodeURIComponent(name)}`, { ...input, wait: true });
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
runs: {
|
|
100
|
+
/** Dispatch a durable run — an agent or an authored workflow — by name. */
|
|
101
|
+
dispatch(name, input = {}) {
|
|
102
|
+
return post(`/runs/${encodeURIComponent(name)}`, agentRequestBody(input));
|
|
103
|
+
},
|
|
104
|
+
/** Dispatch an agent run and block until it finishes. */
|
|
105
|
+
dispatchAndWait(name, input = {}) {
|
|
106
|
+
return post(`/runs/${encodeURIComponent(name)}`, { ...agentRequestBody(input), wait: true });
|
|
107
|
+
},
|
|
108
|
+
/** Decide the gate a workflow run is parked at (approve resumes it). */
|
|
109
|
+
approve(runId, decision) {
|
|
110
|
+
return post(`/runs/${encodeURIComponent(runId)}/approve`, decision);
|
|
111
|
+
},
|
|
112
|
+
get(runId) {
|
|
113
|
+
return get(`/runs/${encodeURIComponent(runId)}?meta`);
|
|
114
|
+
},
|
|
115
|
+
/** Poll until the run reaches a terminal status (success/failed/interrupted). */
|
|
116
|
+
async wait(runId, opts = {}) {
|
|
117
|
+
const interval = opts.intervalMs ?? 250;
|
|
118
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 12e4);
|
|
119
|
+
for (; ; ) {
|
|
120
|
+
const run = await get(`/runs/${encodeURIComponent(runId)}?meta`);
|
|
121
|
+
if (run.status !== "queued" && run.status !== "running") return run;
|
|
122
|
+
if (Date.now() > deadline) throw new AtlasFlowError(408, "timeout", `run ${runId} did not finish in time`);
|
|
123
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
async events(runId, opts = {}) {
|
|
127
|
+
return (await get(`/runs/${encodeURIComponent(runId)}/events${eventQuery(opts)}`)).items;
|
|
128
|
+
},
|
|
129
|
+
async eventPage(runId, opts = {}) {
|
|
130
|
+
return get(`/runs/${encodeURIComponent(runId)}/events${eventQuery(opts)}`);
|
|
131
|
+
},
|
|
132
|
+
/** Stream persisted run events by run id. Reconnect with opts.after = last seen event index. */
|
|
133
|
+
async *stream(runId, opts = {}) {
|
|
134
|
+
yield* streamEventUrl(runEventUrl(base, runId), opts);
|
|
135
|
+
},
|
|
136
|
+
/**
|
|
137
|
+
* Reconciled durable event stream. Starts from a run id or dispatch
|
|
138
|
+
* admission, reads persisted pages until caught up, follows live SSE, then
|
|
139
|
+
* re-checks persisted history when the live stream closes or reconnects.
|
|
140
|
+
*/
|
|
141
|
+
async *watch(target, opts = {}) {
|
|
142
|
+
const source = runWatchSource(base, target);
|
|
143
|
+
let offset = opts.offset ?? (opts.after !== void 0 ? String(opts.after) : source.offset);
|
|
144
|
+
const limit = opts.limit ?? 1e3;
|
|
145
|
+
const reconnect = normalizeReconnect(opts.reconnect);
|
|
146
|
+
let failures = 0;
|
|
147
|
+
for (; ; ) {
|
|
148
|
+
for (; ; ) {
|
|
149
|
+
const page = await getEventPageUrl(source.streamUrl, { offset, limit, tail: opts.tail });
|
|
150
|
+
for (const event of page.items) {
|
|
151
|
+
offset = eventResumeOffset(event, page.nextOffset);
|
|
152
|
+
yield event;
|
|
153
|
+
}
|
|
154
|
+
if (page.closed && page.upToDate) return;
|
|
155
|
+
if (page.upToDate) break;
|
|
156
|
+
offset = page.nextOffset;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
for await (const event of streamEventUrl(source.streamUrl, { offset, follow: true, signal: opts.signal })) {
|
|
160
|
+
offset = eventResumeOffset(event, offset);
|
|
161
|
+
failures = 0;
|
|
162
|
+
yield event;
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (opts.signal?.aborted) throw err;
|
|
166
|
+
failures++;
|
|
167
|
+
if (!reconnect || failures > reconnect.attempts) throw err;
|
|
168
|
+
await sleep(reconnect.delayMs);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
async list(opts = {}) {
|
|
173
|
+
const q = opts.limit ? `?limit=${opts.limit}` : "";
|
|
174
|
+
return (await get(`/admin/runs${q}`)).items;
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
streams: {
|
|
178
|
+
url(path) {
|
|
179
|
+
return `${base}${streamPath(path)}`;
|
|
180
|
+
},
|
|
181
|
+
async head(path) {
|
|
182
|
+
const res = await doFetch(`${base}${streamPath(path)}`, { method: "HEAD", headers });
|
|
183
|
+
if (res.status === 404) return { exists: false };
|
|
184
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
185
|
+
return streamMeta(res);
|
|
186
|
+
},
|
|
187
|
+
async create(path, opts = {}) {
|
|
188
|
+
const res = await doFetch(`${base}${streamPath(path)}`, {
|
|
189
|
+
method: "PUT",
|
|
190
|
+
headers: streamRequestHeaders(headers, opts.contentType, {
|
|
191
|
+
...opts.closed ? { "Stream-Closed": "true" } : {},
|
|
192
|
+
...opts.ttlSeconds !== void 0 ? { "Stream-TTL": String(opts.ttlSeconds) } : {},
|
|
193
|
+
...opts.expiresAt !== void 0 ? { "Stream-Expires-At": opts.expiresAt } : {},
|
|
194
|
+
...opts.forkedFrom !== void 0 ? { "Stream-Forked-From": opts.forkedFrom } : {},
|
|
195
|
+
...opts.forkOffset !== void 0 ? { "Stream-Fork-Offset": opts.forkOffset } : {}
|
|
196
|
+
}),
|
|
197
|
+
body: opts.body
|
|
198
|
+
});
|
|
199
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
200
|
+
return streamMeta(res);
|
|
201
|
+
},
|
|
202
|
+
async append(path, body, opts = {}) {
|
|
203
|
+
const res = await doFetch(`${base}${streamPath(path)}`, {
|
|
204
|
+
method: "POST",
|
|
205
|
+
headers: streamRequestHeaders(headers, opts.contentType, {
|
|
206
|
+
...opts.close ? { "Stream-Closed": "true" } : {},
|
|
207
|
+
...opts.seq !== void 0 ? { "Stream-Seq": opts.seq } : {}
|
|
208
|
+
}),
|
|
209
|
+
body
|
|
210
|
+
});
|
|
211
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
212
|
+
return streamMeta(res);
|
|
213
|
+
},
|
|
214
|
+
async close(path) {
|
|
215
|
+
const res = await doFetch(`${base}${streamPath(path)}`, {
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: streamRequestHeaders(headers, void 0, { "Stream-Closed": "true" })
|
|
218
|
+
});
|
|
219
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
220
|
+
return streamMeta(res);
|
|
221
|
+
},
|
|
222
|
+
async delete(path) {
|
|
223
|
+
const res = await doFetch(`${base}${streamPath(path)}`, { method: "DELETE", headers });
|
|
224
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
225
|
+
},
|
|
226
|
+
async readJson(path, opts = {}) {
|
|
227
|
+
const res = await doFetch(`${base}${streamPath(path)}${streamReadQuery(opts)}`, { headers });
|
|
228
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
229
|
+
const text = await res.text();
|
|
230
|
+
const items = text ? JSON.parse(text) : [];
|
|
231
|
+
return {
|
|
232
|
+
items,
|
|
233
|
+
nextOffset: res.headers.get("Stream-Next-Offset") ?? opts.offset ?? "-1",
|
|
234
|
+
cursor: res.headers.get("Stream-Cursor") ?? void 0,
|
|
235
|
+
upToDate: res.headers.get("Stream-Up-To-Date")?.toLowerCase() === "true",
|
|
236
|
+
closed: res.headers.get("Stream-Closed")?.toLowerCase() === "true"
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
subscriptions: {
|
|
240
|
+
async create(id, opts) {
|
|
241
|
+
const type = opts.type ?? "pull-wake";
|
|
242
|
+
const res = await doFetch(`${base}${subscriptionPath(id)}`, {
|
|
243
|
+
method: "PUT",
|
|
244
|
+
headers,
|
|
245
|
+
body: JSON.stringify({
|
|
246
|
+
type,
|
|
247
|
+
streams: opts.streams ?? [],
|
|
248
|
+
...opts.pattern ? { pattern: opts.pattern } : {},
|
|
249
|
+
...opts.wakeStream ? { wake_stream: opts.wakeStream } : {},
|
|
250
|
+
...opts.leaseTtlMs !== void 0 ? { lease_ttl_ms: opts.leaseTtlMs } : {},
|
|
251
|
+
...opts.webhookUrl ?? opts.url ? { webhook_url: opts.webhookUrl ?? opts.url } : {},
|
|
252
|
+
...opts.headers ? { headers: opts.headers } : {},
|
|
253
|
+
...opts.description ? { description: opts.description } : {}
|
|
254
|
+
})
|
|
255
|
+
});
|
|
256
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
257
|
+
return subscriptionInfo(await res.json());
|
|
258
|
+
},
|
|
259
|
+
async get(id) {
|
|
260
|
+
const res = await doFetch(`${base}${subscriptionPath(id)}`, { headers });
|
|
261
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
262
|
+
return subscriptionInfo(await res.json());
|
|
263
|
+
},
|
|
264
|
+
async delete(id) {
|
|
265
|
+
const res = await doFetch(`${base}${subscriptionPath(id)}`, { method: "DELETE", headers });
|
|
266
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
267
|
+
},
|
|
268
|
+
async addStreams(id, streams) {
|
|
269
|
+
const res = await doFetch(`${base}${subscriptionPath(id)}/streams`, {
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers,
|
|
272
|
+
body: JSON.stringify({ streams })
|
|
273
|
+
});
|
|
274
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
275
|
+
},
|
|
276
|
+
async removeStream(id, path) {
|
|
277
|
+
const res = await doFetch(`${base}${subscriptionPath(id)}/streams/${path.split("/").map(encodeURIComponent).join("/")}`, {
|
|
278
|
+
method: "DELETE",
|
|
279
|
+
headers
|
|
280
|
+
});
|
|
281
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
282
|
+
},
|
|
283
|
+
async claim(id, worker) {
|
|
284
|
+
const res = await doFetch(`${base}${subscriptionPath(id)}/claim`, {
|
|
285
|
+
method: "POST",
|
|
286
|
+
headers,
|
|
287
|
+
body: JSON.stringify({ worker })
|
|
288
|
+
});
|
|
289
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
290
|
+
return subscriptionClaim(await res.json());
|
|
291
|
+
},
|
|
292
|
+
async ack(id, token, opts) {
|
|
293
|
+
const res = await doFetch(`${base}${subscriptionPath(id)}/ack`, {
|
|
294
|
+
method: "POST",
|
|
295
|
+
headers: headersWithoutContentType(headers, { "Subscription-Token": `Bearer ${token}`, "content-type": "application/json" }),
|
|
296
|
+
body: JSON.stringify(subscriptionAckBody(opts))
|
|
297
|
+
});
|
|
298
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
299
|
+
const json = await res.json();
|
|
300
|
+
return { ok: true, nextWake: json.next_wake };
|
|
301
|
+
},
|
|
302
|
+
async release(id, token, opts) {
|
|
303
|
+
const res = await doFetch(`${base}${subscriptionPath(id)}/release`, {
|
|
304
|
+
method: "POST",
|
|
305
|
+
headers: headersWithoutContentType(headers, { "Subscription-Token": `Bearer ${token}`, "content-type": "application/json" }),
|
|
306
|
+
body: JSON.stringify(subscriptionAckBody(opts))
|
|
307
|
+
});
|
|
308
|
+
if (!res.ok) throw await atlasErrorFromResponse(res);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
admin: {
|
|
313
|
+
async agents() {
|
|
314
|
+
return (await get(`/admin/agents`)).items;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function eventQuery(opts) {
|
|
320
|
+
const params = new URLSearchParams();
|
|
321
|
+
params.set("format", "atlas");
|
|
322
|
+
if (opts.offset !== void 0) params.set("offset", opts.offset);
|
|
323
|
+
else if (opts.after !== void 0) params.set("after", String(opts.after));
|
|
324
|
+
if (opts.follow !== void 0) params.set("follow", String(opts.follow));
|
|
325
|
+
if (opts.limit !== void 0) params.set("limit", String(opts.limit));
|
|
326
|
+
if (opts.tail !== void 0) params.set("tail", String(opts.tail));
|
|
327
|
+
const query = params.toString();
|
|
328
|
+
return query ? `?${query}` : "";
|
|
329
|
+
}
|
|
330
|
+
function withEventQuery(url, opts) {
|
|
331
|
+
const query = eventQuery(opts);
|
|
332
|
+
if (!query) return url;
|
|
333
|
+
return `${url}${url.includes("?") ? "&" : "?"}${query.slice(1)}`;
|
|
334
|
+
}
|
|
335
|
+
function runEventUrl(base, runId) {
|
|
336
|
+
return `${base}/runs/${encodeURIComponent(runId)}/events`;
|
|
337
|
+
}
|
|
338
|
+
function streamPath(path) {
|
|
339
|
+
return `/streams/${path.split("/").map(encodeURIComponent).join("/")}`;
|
|
340
|
+
}
|
|
341
|
+
function subscriptionPath(id) {
|
|
342
|
+
return `/streams/__ds/subscriptions/${encodeURIComponent(id)}`;
|
|
343
|
+
}
|
|
344
|
+
function streamReadQuery(opts) {
|
|
345
|
+
const params = new URLSearchParams();
|
|
346
|
+
params.set("offset", opts.offset ?? "-1");
|
|
347
|
+
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
348
|
+
if (opts.live) params.set("live", opts.live);
|
|
349
|
+
if (opts.timeoutMs !== void 0) params.set("timeout", String(opts.timeoutMs));
|
|
350
|
+
return `?${params.toString()}`;
|
|
351
|
+
}
|
|
352
|
+
function streamRequestHeaders(baseHeaders, contentType, extra) {
|
|
353
|
+
return {
|
|
354
|
+
...headersWithoutContentType(baseHeaders, extra),
|
|
355
|
+
...contentType ? { "content-type": contentType } : {}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function headersWithoutContentType(baseHeaders, extra) {
|
|
359
|
+
const out = {};
|
|
360
|
+
for (const [key, value] of Object.entries(baseHeaders)) {
|
|
361
|
+
if (key.toLowerCase() !== "content-type") out[key] = value;
|
|
362
|
+
}
|
|
363
|
+
return { ...out, ...extra ?? {} };
|
|
364
|
+
}
|
|
365
|
+
function streamMeta(res) {
|
|
366
|
+
const ttl = res.headers.get("Stream-TTL");
|
|
367
|
+
return {
|
|
368
|
+
exists: true,
|
|
369
|
+
contentType: res.headers.get("content-type") ?? void 0,
|
|
370
|
+
offset: res.headers.get("Stream-Next-Offset") ?? void 0,
|
|
371
|
+
cursor: res.headers.get("Stream-Cursor") ?? void 0,
|
|
372
|
+
streamClosed: res.headers.get("Stream-Closed")?.toLowerCase() === "true",
|
|
373
|
+
ttlSeconds: ttl == null ? void 0 : Number(ttl),
|
|
374
|
+
expiresAt: res.headers.get("Stream-Expires-At") ?? void 0,
|
|
375
|
+
etag: res.headers.get("etag") ?? void 0
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function subscriptionInfo(value) {
|
|
379
|
+
const raw = value;
|
|
380
|
+
return {
|
|
381
|
+
id: raw.id,
|
|
382
|
+
subscriptionId: raw.subscription_id,
|
|
383
|
+
type: raw.type,
|
|
384
|
+
pattern: raw.pattern,
|
|
385
|
+
streams: (raw.streams ?? []).map((stream) => ({
|
|
386
|
+
path: stream.path,
|
|
387
|
+
linkType: stream.link_type,
|
|
388
|
+
ackedOffset: stream.acked_offset
|
|
389
|
+
})),
|
|
390
|
+
wakeStream: raw.wake_stream,
|
|
391
|
+
leaseTtlMs: raw.lease_ttl_ms,
|
|
392
|
+
webhookUrl: raw.webhook_url,
|
|
393
|
+
webhookLastSuccessAt: raw.webhook_last_success_at,
|
|
394
|
+
webhookLastError: raw.webhook_last_error,
|
|
395
|
+
createdAt: raw.created_at,
|
|
396
|
+
status: raw.status,
|
|
397
|
+
description: raw.description
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function subscriptionClaim(value) {
|
|
401
|
+
const raw = value;
|
|
402
|
+
return {
|
|
403
|
+
wakeId: raw.wake_id,
|
|
404
|
+
generation: raw.generation,
|
|
405
|
+
token: raw.token,
|
|
406
|
+
streams: (raw.streams ?? []).map((stream) => ({
|
|
407
|
+
path: stream.path,
|
|
408
|
+
linkType: stream.link_type,
|
|
409
|
+
ackedOffset: stream.acked_offset,
|
|
410
|
+
tailOffset: stream.tail_offset,
|
|
411
|
+
hasPending: stream.has_pending
|
|
412
|
+
})),
|
|
413
|
+
leaseTtlMs: raw.lease_ttl_ms
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function subscriptionAckBody(opts) {
|
|
417
|
+
return {
|
|
418
|
+
wake_id: opts.wakeId,
|
|
419
|
+
generation: opts.generation,
|
|
420
|
+
...opts.acks ? { acks: opts.acks } : {},
|
|
421
|
+
...opts.done !== void 0 ? { done: opts.done } : {}
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function runWatchSource(base, target) {
|
|
425
|
+
if (typeof target === "string") return { streamUrl: runEventUrl(base, target), offset: "-1" };
|
|
426
|
+
return {
|
|
427
|
+
streamUrl: target.streamUrl ?? runEventUrl(base, target.runId),
|
|
428
|
+
offset: target.offset ?? "-1"
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function agentRequestBody(input) {
|
|
432
|
+
const { images, signal: _signal, ...rest } = input;
|
|
433
|
+
const body = { ...rest };
|
|
434
|
+
if (images !== void 0) body.images = images.map(agentImageBody);
|
|
435
|
+
return body;
|
|
436
|
+
}
|
|
437
|
+
function agentImageBody(image) {
|
|
438
|
+
const parsed = parseDataImage(image.data);
|
|
439
|
+
const mimeType = image.mimeType ?? parsed.mimeType;
|
|
440
|
+
return {
|
|
441
|
+
data: parsed.data,
|
|
442
|
+
...mimeType ? { mime_type: mimeType } : {}
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function parseDataImage(value) {
|
|
446
|
+
const trimmed = value.trim();
|
|
447
|
+
const match = /^data:([^;,]+)(?:;[^,]*)?;base64,(.*)$/is.exec(trimmed);
|
|
448
|
+
if (match) return { mimeType: match[1], data: match[2].replace(/\s/g, "") };
|
|
449
|
+
return { data: trimmed.replace(/\s/g, "") };
|
|
450
|
+
}
|
|
451
|
+
function eventResumeOffset(event, fallback) {
|
|
452
|
+
if (typeof event.offset === "string") return event.offset;
|
|
453
|
+
if (typeof event.index === "number") return String(event.index);
|
|
454
|
+
return fallback;
|
|
455
|
+
}
|
|
456
|
+
function normalizeReconnect(reconnect) {
|
|
457
|
+
if (!reconnect) return void 0;
|
|
458
|
+
if (reconnect === true) return { attempts: 3, delayMs: 250 };
|
|
459
|
+
return { attempts: reconnect.attempts ?? 3, delayMs: reconnect.delayMs ?? 250 };
|
|
460
|
+
}
|
|
461
|
+
async function atlasErrorFromResponse(res) {
|
|
462
|
+
let error;
|
|
463
|
+
try {
|
|
464
|
+
error = (await res.json()).error;
|
|
465
|
+
} catch {
|
|
466
|
+
}
|
|
467
|
+
return new AtlasFlowError(res.status, error?.code ?? "request_failed", error?.message ?? res.statusText);
|
|
468
|
+
}
|
|
469
|
+
function sleep(ms) {
|
|
470
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
471
|
+
}
|
|
472
|
+
async function* parseSSE(body) {
|
|
473
|
+
const reader = body.getReader();
|
|
474
|
+
const decoder = new TextDecoder();
|
|
475
|
+
let buffer = "";
|
|
476
|
+
const frame = { data: [] };
|
|
477
|
+
for (; ; ) {
|
|
478
|
+
const { done, value } = await reader.read();
|
|
479
|
+
if (done) break;
|
|
480
|
+
buffer += decoder.decode(value, { stream: true });
|
|
481
|
+
for (; ; ) {
|
|
482
|
+
const eol = findLineEnd(buffer, false);
|
|
483
|
+
if (!eol) break;
|
|
484
|
+
const line = buffer.slice(0, eol.index);
|
|
485
|
+
buffer = buffer.slice(eol.next);
|
|
486
|
+
const event2 = readSseLine(frame, line);
|
|
487
|
+
if (event2) yield event2;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
buffer += decoder.decode();
|
|
491
|
+
for (; ; ) {
|
|
492
|
+
const eol = findLineEnd(buffer, true);
|
|
493
|
+
if (!eol) break;
|
|
494
|
+
const line = buffer.slice(0, eol.index);
|
|
495
|
+
buffer = buffer.slice(eol.next);
|
|
496
|
+
const event2 = readSseLine(frame, line);
|
|
497
|
+
if (event2) yield event2;
|
|
498
|
+
}
|
|
499
|
+
if (buffer) {
|
|
500
|
+
const event2 = readSseLine(frame, buffer);
|
|
501
|
+
if (event2) yield event2;
|
|
502
|
+
}
|
|
503
|
+
const event = finishSseFrame(frame);
|
|
504
|
+
if (event) yield event;
|
|
505
|
+
}
|
|
506
|
+
function findLineEnd(buffer, eof) {
|
|
507
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
508
|
+
const ch = buffer[i];
|
|
509
|
+
if (ch === "\n") return { index: i, next: i + 1 };
|
|
510
|
+
if (ch === "\r") {
|
|
511
|
+
if (i === buffer.length - 1 && !eof) return void 0;
|
|
512
|
+
return { index: i, next: buffer[i + 1] === "\n" ? i + 2 : i + 1 };
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return void 0;
|
|
516
|
+
}
|
|
517
|
+
function readSseLine(frame, rawLine) {
|
|
518
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
519
|
+
if (line === "") return finishSseFrame(frame);
|
|
520
|
+
if (line.startsWith(":")) return void 0;
|
|
521
|
+
const colon = line.indexOf(":");
|
|
522
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
523
|
+
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
524
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
525
|
+
if (field === "data") frame.data.push(value);
|
|
526
|
+
else if (field === "id") frame.id = value;
|
|
527
|
+
return void 0;
|
|
528
|
+
}
|
|
529
|
+
function finishSseFrame(frame) {
|
|
530
|
+
if (!frame.data.length) {
|
|
531
|
+
frame.id = void 0;
|
|
532
|
+
return void 0;
|
|
533
|
+
}
|
|
534
|
+
const data = frame.data.join("\n");
|
|
535
|
+
const id = frame.id;
|
|
536
|
+
frame.data = [];
|
|
537
|
+
frame.id = void 0;
|
|
538
|
+
try {
|
|
539
|
+
const parsed = JSON.parse(data);
|
|
540
|
+
if (id && parsed.offset === void 0) parsed.offset = id;
|
|
541
|
+
return parsed;
|
|
542
|
+
} catch {
|
|
543
|
+
return void 0;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
export {
|
|
547
|
+
AtlasFlowError,
|
|
548
|
+
createAtlasFlowClient
|
|
549
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cybernetyx1/atlasflow-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Typed HTTP client for invoking AtlasFlow agents, streams, runs, and events.",
|
|
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/sdk"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.10.0",
|
|
24
|
+
"tsup": "^8.3.5",
|
|
25
|
+
"typescript": "^5.7.2"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"test": "node --import tsx --test test/*.test.ts"
|
|
34
|
+
}
|
|
35
|
+
}
|