@deepnoodle/mobius 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -45
- package/dist/api/index.d.ts +117 -336
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/schema.d.ts +4620 -12987
- package/dist/api/schema.d.ts.map +1 -1
- package/dist/client.d.ts +64 -151
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +164 -368
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/signing.d.ts +43 -0
- package/dist/signing.d.ts.map +1 -0
- package/dist/signing.js +101 -0
- package/dist/signing.js.map +1 -0
- package/dist/webhook.d.ts +5 -17
- package/dist/webhook.d.ts.map +1 -1
- package/dist/webhook.js +38 -50
- package/dist/webhook.js.map +1 -1
- package/dist/worker.d.ts +41 -138
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +343 -463
- package/dist/worker.js.map +1 -1
- package/package.json +4 -4
package/dist/client.js
CHANGED
|
@@ -3,74 +3,24 @@ export { RateLimitError } from "./retry.js";
|
|
|
3
3
|
export const DEFAULT_BASE_URL = "https://api.mobiusops.ai";
|
|
4
4
|
export const DEFAULT_PROJECT = "default";
|
|
5
5
|
export const DEFAULT_NAMESPACE = DEFAULT_PROJECT;
|
|
6
|
-
export class LeaseLostError extends Error {
|
|
7
|
-
constructor(jobId) {
|
|
8
|
-
super(`lease lost for job ${jobId}`);
|
|
9
|
-
this.jobId = jobId;
|
|
10
|
-
this.name = "LeaseLostError";
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Thrown when the server returns HTTP 401 on a worker-loop request.
|
|
15
|
-
* The credential has been revoked mid-execution; the process needs
|
|
16
|
-
* to restart under a fresh credential. Distinct from
|
|
17
|
-
* {@link LeaseLostError} (409 — lease reclaimed by scheduler) because
|
|
18
|
-
* the remedy is operational, not workflow-level.
|
|
19
|
-
*/
|
|
20
6
|
export class AuthRevokedError extends Error {
|
|
21
|
-
constructor(
|
|
22
|
-
super(
|
|
23
|
-
? `mobius: credential revoked (job ${jobId})`
|
|
24
|
-
: "mobius: credential revoked");
|
|
25
|
-
this.jobId = jobId;
|
|
7
|
+
constructor() {
|
|
8
|
+
super("mobius: credential revoked");
|
|
26
9
|
this.name = "AuthRevokedError";
|
|
27
10
|
}
|
|
28
11
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Thrown when the server returns HTTP 409 with `worker_instance_conflict`
|
|
31
|
-
* on a claim. Another live process has already registered this
|
|
32
|
-
* `worker_instance_id` in the project under a different session token.
|
|
33
|
-
* Surfaces from {@link Worker.run} as a hard error so the operator
|
|
34
|
-
* notices the misconfiguration instead of the worker silently retrying —
|
|
35
|
-
* fix by configuring a unique instance ID per process or by relying on
|
|
36
|
-
* the SDK's auto-detection.
|
|
37
|
-
*/
|
|
38
|
-
export class WorkerInstanceConflictError extends Error {
|
|
39
|
-
constructor(workerInstanceId, projectHandle, message) {
|
|
40
|
-
super(message ??
|
|
41
|
-
(workerInstanceId
|
|
42
|
-
? `mobius: worker_instance_id ${JSON.stringify(workerInstanceId)} is already registered in project ${JSON.stringify(projectHandle)} by another live process; configure a unique instance ID per process or rely on auto-detection`
|
|
43
|
-
: "mobius: worker instance conflict"));
|
|
44
|
-
this.workerInstanceId = workerInstanceId;
|
|
45
|
-
this.projectHandle = projectHandle;
|
|
46
|
-
this.name = "WorkerInstanceConflictError";
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Thrown from {@link Client} construction when the API key or project
|
|
51
|
-
* options are malformed (e.g. a project-pinned key whose handle prefix
|
|
52
|
-
* doesn't match the server's handle regex, or a handle conflict
|
|
53
|
-
* between `WithProjectHandle` and the handle embedded in the key).
|
|
54
|
-
*/
|
|
55
12
|
export class ConfigError extends Error {
|
|
56
13
|
constructor(message) {
|
|
57
14
|
super(message);
|
|
58
15
|
this.name = "ConfigError";
|
|
59
16
|
}
|
|
60
17
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const slash = apiKey.indexOf("/");
|
|
67
|
-
if (slash < 0)
|
|
68
|
-
return null;
|
|
69
|
-
const handle = apiKey.slice(0, slash);
|
|
70
|
-
if (!HANDLE_RE.test(handle)) {
|
|
71
|
-
throw new ConfigError(`invalid project handle prefix in API key: ${JSON.stringify(handle)}`);
|
|
18
|
+
export class LeaseLostError extends Error {
|
|
19
|
+
constructor(jobId) {
|
|
20
|
+
super(`lease lost for job ${jobId}`);
|
|
21
|
+
this.jobId = jobId;
|
|
22
|
+
this.name = "LeaseLostError";
|
|
72
23
|
}
|
|
73
|
-
return handle;
|
|
74
24
|
}
|
|
75
25
|
export class PayloadTooLargeError extends Error {
|
|
76
26
|
constructor(jobId) {
|
|
@@ -79,12 +29,6 @@ export class PayloadTooLargeError extends Error {
|
|
|
79
29
|
this.name = "PayloadTooLargeError";
|
|
80
30
|
}
|
|
81
31
|
}
|
|
82
|
-
/**
|
|
83
|
-
* Legacy per-job rate-limit error raised by {@link Client.emitJobEvents}.
|
|
84
|
-
* Subclass of {@link RateLimitError} so callers catching the newer,
|
|
85
|
-
* transport-raised {@link RateLimitError} also catch this. New code
|
|
86
|
-
* should prefer {@link RateLimitError}.
|
|
87
|
-
*/
|
|
88
32
|
export class RateLimitedError extends RateLimitError {
|
|
89
33
|
constructor(jobId, retryAfter) {
|
|
90
34
|
super({
|
|
@@ -97,21 +41,34 @@ export class RateLimitedError extends RateLimitError {
|
|
|
97
41
|
this.jobId = jobId;
|
|
98
42
|
}
|
|
99
43
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
44
|
+
export class WorkerInstanceConflictError extends Error {
|
|
45
|
+
constructor(workerInstanceId, projectHandle, message) {
|
|
46
|
+
super(message ??
|
|
47
|
+
(workerInstanceId
|
|
48
|
+
? `mobius: worker_instance_id ${JSON.stringify(workerInstanceId)} is already registered in project ${JSON.stringify(projectHandle)} by another live process`
|
|
49
|
+
: "mobius: worker instance conflict"));
|
|
50
|
+
this.workerInstanceId = workerInstanceId;
|
|
51
|
+
this.projectHandle = projectHandle;
|
|
52
|
+
this.name = "WorkerInstanceConflictError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const HANDLE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
56
|
+
function extractHandleFromApiKey(apiKey) {
|
|
57
|
+
if (!apiKey.startsWith("mbx_") && !apiKey.startsWith("mbc_"))
|
|
58
|
+
return null;
|
|
59
|
+
const dot = apiKey.lastIndexOf(".");
|
|
60
|
+
if (dot < 0 || dot === apiKey.length - 1)
|
|
61
|
+
return null;
|
|
62
|
+
const handle = apiKey.slice(dot + 1);
|
|
63
|
+
if (!HANDLE_RE.test(handle)) {
|
|
64
|
+
throw new ConfigError(`invalid project handle suffix in API key: ${JSON.stringify(handle)}`);
|
|
65
|
+
}
|
|
66
|
+
return handle;
|
|
67
|
+
}
|
|
108
68
|
export class Client {
|
|
109
69
|
constructor(opts) {
|
|
110
70
|
this.baseURL = (opts.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
111
71
|
const explicitProject = opts.project ?? opts.namespace;
|
|
112
|
-
// Project-pinned keys arrive as "<handle>/mbx_<secret>". Split the
|
|
113
|
-
// handle off and use it as the project; any explicit project option
|
|
114
|
-
// must either match or be the default sentinel.
|
|
115
72
|
const handleInKey = extractHandleFromApiKey(opts.apiKey);
|
|
116
73
|
if (handleInKey != null) {
|
|
117
74
|
if (explicitProject != null &&
|
|
@@ -131,98 +88,109 @@ export class Client {
|
|
|
131
88
|
this.timeoutMs = opts.timeoutMs ?? 60000;
|
|
132
89
|
this.fetchFn = wrapFetchWithRetry((input, init) => globalThis.fetch(input, init), { maxRetries: opts.retry ?? DEFAULT_MAX_RETRIES });
|
|
133
90
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
91
|
+
workerSocketURL() {
|
|
92
|
+
const url = new URL(this.baseURL);
|
|
93
|
+
if (url.protocol === "http:")
|
|
94
|
+
url.protocol = "ws:";
|
|
95
|
+
if (url.protocol === "https:")
|
|
96
|
+
url.protocol = "wss:";
|
|
97
|
+
url.pathname = `${url.pathname.replace(/\/$/, "")}/v1/projects/${encodeURIComponent(this.project)}/workers/socket`;
|
|
98
|
+
url.search = "";
|
|
99
|
+
return url.toString();
|
|
100
|
+
}
|
|
101
|
+
async listAutomations(opts = {}) {
|
|
102
|
+
const resp = await this.request(withQuery("/v1/projects/:project/automations", opts), {
|
|
103
|
+
method: "GET",
|
|
104
|
+
});
|
|
105
|
+
return (await resp.json());
|
|
106
|
+
}
|
|
107
|
+
async getAutomation(handle) {
|
|
108
|
+
const resp = await this.request(`/v1/projects/:project/automations/${encodeURIComponent(handle)}`, { method: "GET" });
|
|
109
|
+
return (await resp.json());
|
|
110
|
+
}
|
|
111
|
+
async createAutomation(opts) {
|
|
112
|
+
const body = removeUndefined({
|
|
113
|
+
name: opts.name,
|
|
114
|
+
handle: opts.handle,
|
|
115
|
+
description: opts.description,
|
|
116
|
+
default_agent_id: opts.default_agent_id,
|
|
117
|
+
default_inputs: opts.default_inputs,
|
|
118
|
+
settings: opts.settings,
|
|
119
|
+
tags: opts.tags,
|
|
120
|
+
triggers: opts.triggers,
|
|
121
|
+
});
|
|
122
|
+
const resp = await this.request("/v1/projects/:project/automations", {
|
|
123
|
+
method: "POST",
|
|
124
|
+
body,
|
|
125
|
+
});
|
|
159
126
|
return (await resp.json());
|
|
160
127
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const resp = await this.request(`/v1/projects
|
|
164
|
-
if (resp.status === 401)
|
|
165
|
-
throw new AuthRevokedError(jobId);
|
|
166
|
-
if (resp.status === 409)
|
|
167
|
-
throw new LeaseLostError(jobId);
|
|
128
|
+
async updateAutomation(handle, opts) {
|
|
129
|
+
const body = removeUndefined(opts);
|
|
130
|
+
const resp = await this.request(`/v1/projects/:project/automations/${encodeURIComponent(handle)}`, { method: "PATCH", body });
|
|
168
131
|
return (await resp.json());
|
|
169
132
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const resp = await this.request(`/v1/projects/${encodeURIComponent(this.project)}/jobs/${encodeURIComponent(jobId)}/report`, { method: "POST", body: req });
|
|
173
|
-
if (resp.status === 401)
|
|
174
|
-
throw new AuthRevokedError(jobId);
|
|
175
|
-
if (resp.status === 409)
|
|
176
|
-
throw new LeaseLostError(jobId);
|
|
133
|
+
async deleteAutomation(handle) {
|
|
134
|
+
await this.request(`/v1/projects/:project/automations/${encodeURIComponent(handle)}`, { method: "DELETE" });
|
|
177
135
|
}
|
|
178
|
-
async
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const resp = await this.request(`/v1/projects/${encodeURIComponent(this.project)}/jobs/${encodeURIComponent(jobId)}/events`, { method: "POST", body: req });
|
|
182
|
-
if (resp.status === 401)
|
|
183
|
-
throw new AuthRevokedError(jobId);
|
|
184
|
-
if (resp.status === 409)
|
|
185
|
-
throw new LeaseLostError(jobId);
|
|
186
|
-
if (resp.status === 413)
|
|
187
|
-
throw new PayloadTooLargeError(jobId);
|
|
136
|
+
async listAutomationVersions(handle) {
|
|
137
|
+
const resp = await this.request(`/v1/projects/:project/automations/${encodeURIComponent(handle)}/versions`, { method: "GET" });
|
|
138
|
+
return (await resp.json());
|
|
188
139
|
}
|
|
189
|
-
async
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
events: [{ type: req.type, payload: req.payload }],
|
|
140
|
+
async createAutomationVersion(handle, spec, opts = {}) {
|
|
141
|
+
const body = removeUndefined({
|
|
142
|
+
spec: spec,
|
|
143
|
+
compiled_plan: opts.compiled_plan,
|
|
194
144
|
});
|
|
145
|
+
const resp = await this.request(`/v1/projects/:project/automations/${encodeURIComponent(handle)}/versions`, { method: "POST", body });
|
|
146
|
+
const version = (await resp.json());
|
|
147
|
+
if (opts.publish) {
|
|
148
|
+
await this.publishAutomationVersion(handle, version.version);
|
|
149
|
+
}
|
|
150
|
+
return version;
|
|
195
151
|
}
|
|
196
|
-
async
|
|
197
|
-
const resp = await this.request(`/v1/projects/${encodeURIComponent(
|
|
152
|
+
async publishAutomationVersion(handle, version) {
|
|
153
|
+
const resp = await this.request(`/v1/projects/:project/automations/${encodeURIComponent(handle)}/versions/${version}/publication`, { method: "POST" });
|
|
198
154
|
return (await resp.json());
|
|
199
155
|
}
|
|
200
|
-
async
|
|
201
|
-
|
|
156
|
+
async startRun(automationHandle, opts = {}) {
|
|
157
|
+
return this.startAutomationRun(automationHandle, opts);
|
|
158
|
+
}
|
|
159
|
+
async startAutomationRun(automationHandle, opts = {}) {
|
|
160
|
+
const body = removeUndefined({
|
|
161
|
+
inputs: opts.inputs,
|
|
162
|
+
source: opts.source,
|
|
163
|
+
external_id: opts.external_id,
|
|
164
|
+
});
|
|
165
|
+
const resp = await this.request(`/v1/projects/:project/automations/${encodeURIComponent(automationHandle)}/runs`, { method: "POST", body });
|
|
202
166
|
return (await resp.json());
|
|
203
167
|
}
|
|
204
168
|
async listRuns(opts = {}) {
|
|
205
|
-
const
|
|
206
|
-
|
|
169
|
+
const resp = await this.request(withQuery("/v1/projects/:project/runs", opts), {
|
|
170
|
+
method: "GET",
|
|
171
|
+
});
|
|
207
172
|
return (await resp.json());
|
|
208
173
|
}
|
|
209
174
|
async getRun(runId) {
|
|
210
|
-
const resp = await this.request(`/v1/projects
|
|
175
|
+
const resp = await this.request(`/v1/projects/:project/runs/${encodeURIComponent(runId)}`, { method: "GET" });
|
|
211
176
|
return (await resp.json());
|
|
212
177
|
}
|
|
213
|
-
async cancelRun(runId) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
await this.request(`/v1/projects/${encodeURIComponent(this.project)}/runs/${encodeURIComponent(runId)}/resumptions`, { method: "POST" });
|
|
178
|
+
async cancelRun(runId, reason) {
|
|
179
|
+
const body = removeUndefined({ reason });
|
|
180
|
+
const resp = await this.request(`/v1/projects/:project/runs/${encodeURIComponent(runId)}/cancellations`, { method: "POST", body });
|
|
181
|
+
return (await resp.json());
|
|
218
182
|
}
|
|
219
|
-
async
|
|
220
|
-
const
|
|
183
|
+
async signalRun(runId, stepKey, result) {
|
|
184
|
+
const body = removeUndefined({
|
|
185
|
+
step_key: stepKey,
|
|
186
|
+
result,
|
|
187
|
+
});
|
|
188
|
+
const resp = await this.request(`/v1/projects/:project/runs/${encodeURIComponent(runId)}/signals`, { method: "POST", body });
|
|
221
189
|
return (await resp.json());
|
|
222
190
|
}
|
|
223
191
|
async *watchRun(runId, opts = {}) {
|
|
224
|
-
const path = withQuery(`/v1/projects
|
|
225
|
-
const resp = await this.fetchFn(this.
|
|
192
|
+
const path = withQuery(`/v1/projects/:project/runs/${encodeURIComponent(runId)}/events.stream`, opts.since && opts.since > 0 ? { since_sequence: opts.since } : {});
|
|
193
|
+
const resp = await this.fetchFn(this.url(path), {
|
|
226
194
|
method: "GET",
|
|
227
195
|
headers: this.headers,
|
|
228
196
|
signal: opts.signal,
|
|
@@ -248,16 +216,11 @@ export class Client {
|
|
|
248
216
|
if (isTerminalRunStatus(run.status))
|
|
249
217
|
return run;
|
|
250
218
|
try {
|
|
251
|
-
for await (const ev of this.watchRun(runId, {
|
|
252
|
-
since
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (
|
|
256
|
-
since = ev.seq;
|
|
257
|
-
if (ev.type !== "run_updated")
|
|
258
|
-
continue;
|
|
259
|
-
const status = ev.data.status;
|
|
260
|
-
if (status === "completed" || status === "failed") {
|
|
219
|
+
for await (const ev of this.watchRun(runId, { ...opts, since })) {
|
|
220
|
+
if (ev.sequence > since)
|
|
221
|
+
since = ev.sequence;
|
|
222
|
+
const status = ev.payload?.status;
|
|
223
|
+
if (typeof status === "string" && isTerminalRunStatus(status)) {
|
|
261
224
|
return await this.getRun(runId);
|
|
262
225
|
}
|
|
263
226
|
}
|
|
@@ -269,60 +232,6 @@ export class Client {
|
|
|
269
232
|
await delay(reconnectDelayMs, opts.signal);
|
|
270
233
|
}
|
|
271
234
|
}
|
|
272
|
-
async listWorkflows(opts = {}) {
|
|
273
|
-
const path = withQuery(`/v1/projects/${encodeURIComponent(this.project)}/workflows`, opts);
|
|
274
|
-
const resp = await this.request(path, { method: "GET" });
|
|
275
|
-
return (await resp.json());
|
|
276
|
-
}
|
|
277
|
-
async getWorkflow(workflowId) {
|
|
278
|
-
const resp = await this.request(`/v1/projects/${encodeURIComponent(this.project)}/workflows/${encodeURIComponent(workflowId)}`, { method: "GET" });
|
|
279
|
-
return (await resp.json());
|
|
280
|
-
}
|
|
281
|
-
async createWorkflow(spec, opts = {}) {
|
|
282
|
-
const body = createWorkflowRequest(spec, opts);
|
|
283
|
-
const resp = await this.request(`/v1/projects/${encodeURIComponent(this.project)}/workflows`, { method: "POST", body });
|
|
284
|
-
return (await resp.json());
|
|
285
|
-
}
|
|
286
|
-
async updateWorkflow(workflowId, opts) {
|
|
287
|
-
const body = removeUndefined({
|
|
288
|
-
expected_version: opts.expected_version,
|
|
289
|
-
name: opts.name,
|
|
290
|
-
description: opts.description,
|
|
291
|
-
published_as_tool: opts.published_as_tool,
|
|
292
|
-
spec: opts.spec,
|
|
293
|
-
tags: opts.tags,
|
|
294
|
-
});
|
|
295
|
-
const resp = await this.request(`/v1/projects/${encodeURIComponent(this.project)}/workflows/${encodeURIComponent(workflowId)}`, { method: "PATCH", body });
|
|
296
|
-
return (await resp.json());
|
|
297
|
-
}
|
|
298
|
-
async ensureWorkflow(spec, opts = {}) {
|
|
299
|
-
const desired = normalizeWorkflowOptions(spec, opts);
|
|
300
|
-
const existing = await this.findWorkflow(desired);
|
|
301
|
-
if (!existing) {
|
|
302
|
-
return {
|
|
303
|
-
definition: await this.createWorkflow(spec, desired),
|
|
304
|
-
created: true,
|
|
305
|
-
updated: false,
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
const current = await this.getWorkflow(existing.id);
|
|
309
|
-
const update = workflowUpdateForDiff(current, spec, desired);
|
|
310
|
-
if (!update) {
|
|
311
|
-
return { definition: current, created: false, updated: false };
|
|
312
|
-
}
|
|
313
|
-
return {
|
|
314
|
-
definition: await this.updateWorkflow(current.id, update),
|
|
315
|
-
created: false,
|
|
316
|
-
updated: true,
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
async syncWorkflows(defs) {
|
|
320
|
-
const results = [];
|
|
321
|
-
for (const def of defs) {
|
|
322
|
-
results.push(await this.ensureWorkflow(def.spec, def.options ?? {}));
|
|
323
|
-
}
|
|
324
|
-
return results;
|
|
325
|
-
}
|
|
326
235
|
async request(path, opts) {
|
|
327
236
|
const timeout = AbortSignal.timeout(this.timeoutMs);
|
|
328
237
|
const signal = opts.signal ? anySignal(opts.signal, timeout) : timeout;
|
|
@@ -331,56 +240,37 @@ export class Client {
|
|
|
331
240
|
headers: this.headers,
|
|
332
241
|
signal,
|
|
333
242
|
};
|
|
334
|
-
if (opts.body != null)
|
|
243
|
+
if (opts.body != null)
|
|
335
244
|
init.body = JSON.stringify(opts.body);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
resp.status !== 401 &&
|
|
341
|
-
resp.status !== 409 &&
|
|
342
|
-
resp.status !== 413 &&
|
|
343
|
-
resp.status !== 429) {
|
|
245
|
+
const resp = await this.fetchFn(this.url(path), init);
|
|
246
|
+
if (!resp.ok && resp.status !== 204) {
|
|
247
|
+
if (resp.status === 401)
|
|
248
|
+
throw new AuthRevokedError();
|
|
344
249
|
const text = await resp.text().catch(() => "");
|
|
345
250
|
throw new Error(`mobius API ${opts.method} ${path}: HTTP ${resp.status}: ${text}`);
|
|
346
251
|
}
|
|
347
252
|
return resp;
|
|
348
253
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
throw new Error("mobius: ensure workflow requires a handle, name, or spec name");
|
|
352
|
-
}
|
|
353
|
-
let cursor = "";
|
|
354
|
-
for (;;) {
|
|
355
|
-
const page = await this.listWorkflows({ cursor, limit: 100 });
|
|
356
|
-
for (const item of page.items) {
|
|
357
|
-
if (desired.handle && item.handle === desired.handle)
|
|
358
|
-
return item;
|
|
359
|
-
if (!desired.handle && item.name === desired.name)
|
|
360
|
-
return item;
|
|
361
|
-
}
|
|
362
|
-
if (!page.has_more || !page.next_cursor)
|
|
363
|
-
return null;
|
|
364
|
-
cursor = page.next_cursor;
|
|
365
|
-
}
|
|
254
|
+
url(path) {
|
|
255
|
+
return this.baseURL + path.replace(":project", encodeURIComponent(this.project));
|
|
366
256
|
}
|
|
367
257
|
}
|
|
258
|
+
export function isTerminalRunStatus(status) {
|
|
259
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
260
|
+
}
|
|
368
261
|
function anySignal(...signals) {
|
|
369
262
|
const controller = new AbortController();
|
|
370
|
-
for (const
|
|
371
|
-
if (
|
|
372
|
-
controller.abort(
|
|
263
|
+
for (const signal of signals) {
|
|
264
|
+
if (signal.aborted) {
|
|
265
|
+
controller.abort(signal.reason);
|
|
373
266
|
break;
|
|
374
267
|
}
|
|
375
|
-
|
|
268
|
+
signal.addEventListener("abort", () => controller.abort(signal.reason), {
|
|
376
269
|
once: true,
|
|
377
270
|
});
|
|
378
271
|
}
|
|
379
272
|
return controller.signal;
|
|
380
273
|
}
|
|
381
|
-
export function isTerminalRunStatus(status) {
|
|
382
|
-
return status === "completed" || status === "failed";
|
|
383
|
-
}
|
|
384
274
|
function withQuery(path, params) {
|
|
385
275
|
const query = new URLSearchParams();
|
|
386
276
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -397,143 +287,49 @@ function withQuery(path, params) {
|
|
|
397
287
|
const qs = query.toString();
|
|
398
288
|
return qs ? `${path}?${qs}` : path;
|
|
399
289
|
}
|
|
400
|
-
function createWorkflowRequest(spec, opts) {
|
|
401
|
-
const normalized = normalizeWorkflowOptions(spec, opts);
|
|
402
|
-
return removeUndefined({
|
|
403
|
-
name: normalized.name,
|
|
404
|
-
handle: normalized.handle,
|
|
405
|
-
description: normalized.description,
|
|
406
|
-
published_as_tool: normalized.published_as_tool,
|
|
407
|
-
spec,
|
|
408
|
-
tags: normalized.tags,
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
function normalizeWorkflowOptions(spec, opts) {
|
|
412
|
-
return { ...opts, name: opts.name || spec.name };
|
|
413
|
-
}
|
|
414
|
-
function workflowUpdateForDiff(current, spec, desired) {
|
|
415
|
-
const update = {
|
|
416
|
-
expected_version: current.latest_version,
|
|
417
|
-
};
|
|
418
|
-
let changed = false;
|
|
419
|
-
if (desired.name && current.name !== desired.name) {
|
|
420
|
-
update.name = desired.name;
|
|
421
|
-
changed = true;
|
|
422
|
-
}
|
|
423
|
-
if (desired.description && current.description !== desired.description) {
|
|
424
|
-
update.description = desired.description;
|
|
425
|
-
changed = true;
|
|
426
|
-
}
|
|
427
|
-
if (desired.published_as_tool !== undefined &&
|
|
428
|
-
current.published_as_tool !== desired.published_as_tool) {
|
|
429
|
-
update.published_as_tool = desired.published_as_tool;
|
|
430
|
-
changed = true;
|
|
431
|
-
}
|
|
432
|
-
if (desired.tags !== undefined && !jsonEqual(current.tags, desired.tags)) {
|
|
433
|
-
update.tags = desired.tags;
|
|
434
|
-
changed = true;
|
|
435
|
-
}
|
|
436
|
-
if (!jsonEqual(current.spec, spec)) {
|
|
437
|
-
update.spec = spec;
|
|
438
|
-
changed = true;
|
|
439
|
-
}
|
|
440
|
-
return changed ? update : null;
|
|
441
|
-
}
|
|
442
|
-
function jsonEqual(a, b) {
|
|
443
|
-
return stableJsonStringify(a) === stableJsonStringify(b);
|
|
444
|
-
}
|
|
445
|
-
function stableJsonStringify(value) {
|
|
446
|
-
if (value === null || typeof value !== "object") {
|
|
447
|
-
return JSON.stringify(value);
|
|
448
|
-
}
|
|
449
|
-
if (Array.isArray(value)) {
|
|
450
|
-
return `[${value.map((item) => stableJsonStringify(item) ?? "null").join(",")}]`;
|
|
451
|
-
}
|
|
452
|
-
const obj = value;
|
|
453
|
-
const entries = Object.keys(obj)
|
|
454
|
-
.sort()
|
|
455
|
-
.filter((key) => obj[key] !== undefined)
|
|
456
|
-
.map((key) => `${JSON.stringify(key)}:${stableJsonStringify(obj[key])}`);
|
|
457
|
-
return `{${entries.join(",")}}`;
|
|
458
|
-
}
|
|
459
290
|
function removeUndefined(obj) {
|
|
460
|
-
return Object.fromEntries(Object.entries(obj).filter(([,
|
|
291
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
|
|
292
|
+
}
|
|
293
|
+
async function delay(ms, signal) {
|
|
294
|
+
if (ms <= 0)
|
|
295
|
+
return;
|
|
296
|
+
await new Promise((resolve, reject) => {
|
|
297
|
+
const timer = setTimeout(resolve, ms);
|
|
298
|
+
if (signal) {
|
|
299
|
+
signal.addEventListener("abort", () => {
|
|
300
|
+
clearTimeout(timer);
|
|
301
|
+
reject(signal.reason ?? new Error("aborted"));
|
|
302
|
+
}, { once: true });
|
|
303
|
+
}
|
|
304
|
+
});
|
|
461
305
|
}
|
|
462
306
|
async function* parseSSE(body) {
|
|
463
307
|
const reader = body.getReader();
|
|
464
308
|
const decoder = new TextDecoder();
|
|
465
309
|
let buffer = "";
|
|
466
|
-
|
|
467
|
-
let id = "";
|
|
468
|
-
let dataLines = [];
|
|
469
|
-
const dispatch = () => {
|
|
470
|
-
if (dataLines.length === 0) {
|
|
471
|
-
event = "";
|
|
472
|
-
return null;
|
|
473
|
-
}
|
|
474
|
-
const out = {
|
|
475
|
-
event: event || "message",
|
|
476
|
-
id,
|
|
477
|
-
data: dataLines.join("\n"),
|
|
478
|
-
};
|
|
479
|
-
event = "";
|
|
480
|
-
dataLines = [];
|
|
481
|
-
return out;
|
|
482
|
-
};
|
|
483
|
-
const processLine = (rawLine) => {
|
|
484
|
-
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
485
|
-
if (line === "")
|
|
486
|
-
return dispatch();
|
|
487
|
-
if (line.startsWith(":"))
|
|
488
|
-
return null;
|
|
489
|
-
const colon = line.indexOf(":");
|
|
490
|
-
const field = colon >= 0 ? line.slice(0, colon) : line;
|
|
491
|
-
let value = colon >= 0 ? line.slice(colon + 1) : "";
|
|
492
|
-
if (value.startsWith(" "))
|
|
493
|
-
value = value.slice(1);
|
|
494
|
-
if (field === "event")
|
|
495
|
-
event = value;
|
|
496
|
-
if (field === "id" && !value.includes("\0"))
|
|
497
|
-
id = value;
|
|
498
|
-
if (field === "data")
|
|
499
|
-
dataLines.push(value);
|
|
500
|
-
return null;
|
|
501
|
-
};
|
|
502
|
-
for (;;) {
|
|
503
|
-
const { value, done } = await reader.read();
|
|
504
|
-
if (done)
|
|
505
|
-
break;
|
|
506
|
-
buffer += decoder.decode(value, { stream: true });
|
|
310
|
+
try {
|
|
507
311
|
for (;;) {
|
|
508
|
-
const
|
|
509
|
-
if (
|
|
312
|
+
const { value, done } = await reader.read();
|
|
313
|
+
if (done)
|
|
510
314
|
break;
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
315
|
+
buffer += decoder.decode(value, { stream: true });
|
|
316
|
+
for (;;) {
|
|
317
|
+
const match = /\r?\n\r?\n/.exec(buffer);
|
|
318
|
+
if (!match)
|
|
319
|
+
break;
|
|
320
|
+
const raw = buffer.slice(0, match.index);
|
|
321
|
+
buffer = buffer.slice(match.index + match[0].length);
|
|
322
|
+
const data = raw
|
|
323
|
+
.split(/\r?\n/)
|
|
324
|
+
.filter((line) => line.startsWith("data:"))
|
|
325
|
+
.map((line) => line.slice(5).trimStart())
|
|
326
|
+
.join("\n");
|
|
327
|
+
yield { data };
|
|
328
|
+
}
|
|
516
329
|
}
|
|
517
330
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const evt = processLine(buffer);
|
|
521
|
-
if (evt)
|
|
522
|
-
yield evt;
|
|
331
|
+
finally {
|
|
332
|
+
reader.releaseLock();
|
|
523
333
|
}
|
|
524
|
-
const evt = dispatch();
|
|
525
|
-
if (evt)
|
|
526
|
-
yield evt;
|
|
527
|
-
}
|
|
528
|
-
function delay(ms, signal) {
|
|
529
|
-
if (signal?.aborted)
|
|
530
|
-
return Promise.reject(signal.reason);
|
|
531
|
-
return new Promise((resolve, reject) => {
|
|
532
|
-
const timeout = setTimeout(resolve, ms);
|
|
533
|
-
signal?.addEventListener("abort", () => {
|
|
534
|
-
clearTimeout(timeout);
|
|
535
|
-
reject(signal.reason);
|
|
536
|
-
}, { once: true });
|
|
537
|
-
});
|
|
538
334
|
}
|
|
539
335
|
//# sourceMappingURL=client.js.map
|