@lunnoa/client 0.0.0-development

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/dist/index.cjs ADDED
@@ -0,0 +1,2141 @@
1
+ 'use strict';
2
+
3
+ // src/core/auth.ts
4
+ var API_KEY_PREFIX = "lna_";
5
+ function isBrowserEnvironment() {
6
+ return typeof window !== "undefined";
7
+ }
8
+ function assertApiKeyAllowedInEnvironment(apiKey) {
9
+ if (isBrowserEnvironment()) {
10
+ throw new Error(
11
+ "Refusing to use a Lunnoa API key (lna_...) in a browser environment. API keys are server-side credentials: anyone opening dev tools could read the key and act as the service account across its whole workspace. Either call the Lunnoa API from your own backend which holds the key (backend-for-frontend pattern), or authenticate the end user with a Lunnoa user JWT and pass it as `accessToken`."
12
+ );
13
+ }
14
+ if (!apiKey.startsWith(API_KEY_PREFIX)) {
15
+ throw new Error(
16
+ `Invalid Lunnoa API key: expected it to start with "${API_KEY_PREFIX}". If you are passing a user JWT, use the \`accessToken\` option instead of \`apiKey\`.`
17
+ );
18
+ }
19
+ }
20
+
21
+ // src/core/errors.ts
22
+ var LunnoaApiError = class extends Error {
23
+ /** HTTP status code of the failed response. */
24
+ status;
25
+ /** Parsed JSON error body when the server returned one, otherwise the raw text. */
26
+ body;
27
+ /** HTTP method of the failed request. */
28
+ method;
29
+ /** Path of the failed request (without the base URL). */
30
+ path;
31
+ constructor(args) {
32
+ const detail = extractMessage(args.body);
33
+ super(
34
+ `Lunnoa API request failed: ${args.method} ${args.path} \u2192 ${args.status}${detail ? ` (${detail})` : ""}`
35
+ );
36
+ this.name = "LunnoaApiError";
37
+ this.status = args.status;
38
+ this.body = args.body;
39
+ this.method = args.method;
40
+ this.path = args.path;
41
+ }
42
+ /** True for 401 responses (missing, revoked, or expired credential). */
43
+ get isUnauthorized() {
44
+ return this.status === 401;
45
+ }
46
+ /** True for 403 responses (missing permission or feature not licensed). */
47
+ get isForbidden() {
48
+ return this.status === 403;
49
+ }
50
+ /** True for 404 responses. */
51
+ get isNotFound() {
52
+ return this.status === 404;
53
+ }
54
+ /** True for 429 responses (rate limited). */
55
+ get isRateLimited() {
56
+ return this.status === 429;
57
+ }
58
+ };
59
+ function extractMessage(body) {
60
+ if (body && typeof body === "object") {
61
+ const record = body;
62
+ const candidate = record.message ?? record.error;
63
+ if (typeof candidate === "string") {
64
+ return candidate;
65
+ }
66
+ if (Array.isArray(candidate)) {
67
+ return candidate.filter((m) => typeof m === "string").join("; ");
68
+ }
69
+ }
70
+ if (typeof body === "string" && body.length > 0 && body.length < 300) {
71
+ return body;
72
+ }
73
+ return void 0;
74
+ }
75
+
76
+ // src/core/http.ts
77
+ var HttpClient = class {
78
+ baseUrl;
79
+ tokenStore;
80
+ /** Underlying fetch (shared with AI SDK transports when needed). */
81
+ fetch;
82
+ apiKey;
83
+ accessToken;
84
+ autoRefresh;
85
+ defaultHeaders;
86
+ refreshInFlight = null;
87
+ constructor(options) {
88
+ if (!options.baseUrl) {
89
+ throw new Error("LunnoaClient requires a baseUrl.");
90
+ }
91
+ if (!options.apiKey && !options.accessToken && !options.tokenStore) {
92
+ throw new Error(
93
+ "LunnoaClient requires an apiKey (server-side), an accessToken (user JWT), or a tokenStore (Pattern B)."
94
+ );
95
+ }
96
+ if (options.apiKey) {
97
+ assertApiKeyAllowedInEnvironment(options.apiKey);
98
+ }
99
+ if (typeof options.accessToken === "string" && options.accessToken.startsWith(API_KEY_PREFIX)) {
100
+ throw new Error(
101
+ "The accessToken option received an lna_ API key. API keys must be passed as `apiKey` (and never in a browser); accessToken is for user JWTs only."
102
+ );
103
+ }
104
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
105
+ this.apiKey = options.apiKey;
106
+ this.accessToken = options.accessToken;
107
+ this.tokenStore = options.tokenStore;
108
+ this.autoRefresh = options.autoRefresh ?? Boolean(options.tokenStore && !options.apiKey);
109
+ this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
110
+ this.defaultHeaders = options.headers ?? {};
111
+ }
112
+ async resolveAuthHeader() {
113
+ if (this.apiKey) {
114
+ return `Bearer ${this.apiKey}`;
115
+ }
116
+ if (this.tokenStore) {
117
+ const fromStore = await this.tokenStore.getAccessToken();
118
+ if (!fromStore) {
119
+ throw new Error(
120
+ "No access token in the token store. Call lunnoa.auth.login(...) first, or seed the store with setTokens / createMemoryTokenStore({ accessToken })."
121
+ );
122
+ }
123
+ return `Bearer ${fromStore}`;
124
+ }
125
+ const token = typeof this.accessToken === "function" ? await this.accessToken() : this.accessToken;
126
+ if (!token) {
127
+ throw new Error("accessToken resolved to an empty value.");
128
+ }
129
+ return `Bearer ${token}`;
130
+ }
131
+ buildUrl(path, query) {
132
+ const url = new URL(this.baseUrl + path);
133
+ if (query) {
134
+ for (const [key, value] of Object.entries(query)) {
135
+ if (value !== void 0 && value !== null) {
136
+ url.searchParams.set(key, String(value));
137
+ }
138
+ }
139
+ }
140
+ return url.toString();
141
+ }
142
+ async request(method, path, options = {}) {
143
+ const url = this.buildUrl(path, options.query);
144
+ const headers = {
145
+ ...this.defaultHeaders,
146
+ ...options.headers
147
+ };
148
+ if (!options.skipAuth) {
149
+ headers.Authorization = await this.resolveAuthHeader();
150
+ }
151
+ const init = { method, headers, signal: options.signal };
152
+ if (options.body !== void 0) {
153
+ headers["Content-Type"] = "application/json";
154
+ init.body = JSON.stringify(options.body);
155
+ }
156
+ const response = await this.fetch(url, init);
157
+ if (!response.ok && response.status === 401 && !options.skipAuth && !options._retriedAfterRefresh && this.autoRefresh && this.tokenStore && !this.apiKey) {
158
+ const refreshed = await this.tryRefreshAccessToken();
159
+ if (refreshed) {
160
+ return this.request(method, path, {
161
+ ...options,
162
+ _retriedAfterRefresh: true
163
+ });
164
+ }
165
+ }
166
+ if (!response.ok) {
167
+ const body = await parseBodySafely(response);
168
+ throw new LunnoaApiError({
169
+ status: response.status,
170
+ body,
171
+ method,
172
+ path
173
+ });
174
+ }
175
+ if (options.raw) {
176
+ return response;
177
+ }
178
+ if (response.status === 204) {
179
+ return void 0;
180
+ }
181
+ return await parseBodySafely(response);
182
+ }
183
+ get(path, options) {
184
+ return this.request("GET", path, options);
185
+ }
186
+ post(path, options) {
187
+ return this.request("POST", path, options);
188
+ }
189
+ patch(path, options) {
190
+ return this.request("PATCH", path, options);
191
+ }
192
+ put(path, options) {
193
+ return this.request("PUT", path, options);
194
+ }
195
+ delete(path, options) {
196
+ return this.request("DELETE", path, options);
197
+ }
198
+ /**
199
+ * Single-flight refresh using the token store. Returns false when refresh
200
+ * is impossible or fails (store is cleared on failure).
201
+ */
202
+ async tryRefreshAccessToken() {
203
+ if (!this.tokenStore) {
204
+ return false;
205
+ }
206
+ if (!this.refreshInFlight) {
207
+ this.refreshInFlight = this.performRefresh().finally(() => {
208
+ this.refreshInFlight = null;
209
+ });
210
+ }
211
+ return this.refreshInFlight;
212
+ }
213
+ async performRefresh() {
214
+ const refreshToken = await this.tokenStore.getRefreshToken();
215
+ if (!refreshToken) {
216
+ return false;
217
+ }
218
+ try {
219
+ const url = this.buildUrl("/api/auth/refresh-token");
220
+ const response = await this.fetch(url, {
221
+ method: "POST",
222
+ headers: { "Content-Type": "application/json" },
223
+ body: JSON.stringify({ refreshToken })
224
+ });
225
+ if (!response.ok) {
226
+ await this.tokenStore.clear();
227
+ return false;
228
+ }
229
+ const body = await parseBodySafely(response);
230
+ if (!body?.access_token) {
231
+ await this.tokenStore.clear();
232
+ return false;
233
+ }
234
+ await this.tokenStore.setTokens({ accessToken: body.access_token });
235
+ return true;
236
+ } catch {
237
+ await this.tokenStore.clear();
238
+ return false;
239
+ }
240
+ }
241
+ };
242
+ async function parseBodySafely(response) {
243
+ const text = await response.text();
244
+ if (!text) {
245
+ return void 0;
246
+ }
247
+ try {
248
+ return JSON.parse(text);
249
+ } catch {
250
+ return text;
251
+ }
252
+ }
253
+
254
+ // src/streaming/sse.ts
255
+ async function* parseSseStream(stream) {
256
+ for await (const event of parseSseEvents(stream)) {
257
+ if (event.data !== null) {
258
+ yield event.data;
259
+ }
260
+ }
261
+ }
262
+ async function* parseSseEvents(stream) {
263
+ const reader = stream.getReader();
264
+ const decoder = new TextDecoder("utf-8", { fatal: false });
265
+ let buffer = "";
266
+ try {
267
+ while (true) {
268
+ const { done, value } = await reader.read();
269
+ if (done) {
270
+ break;
271
+ }
272
+ buffer += decoder.decode(value, { stream: true });
273
+ buffer = buffer.replace(/\r\n/g, "\n");
274
+ let boundary = buffer.indexOf("\n\n");
275
+ while (boundary !== -1) {
276
+ const rawEvent = buffer.slice(0, boundary);
277
+ buffer = buffer.slice(boundary + 2);
278
+ boundary = buffer.indexOf("\n\n");
279
+ const parsed = extractEvent(rawEvent);
280
+ if (parsed.data !== null || parsed.event !== "message") {
281
+ yield parsed;
282
+ }
283
+ }
284
+ }
285
+ const remaining = buffer.trim();
286
+ if (remaining) {
287
+ const parsed = extractEvent(remaining);
288
+ if (parsed.data !== null || parsed.event !== "message") {
289
+ yield parsed;
290
+ }
291
+ }
292
+ } finally {
293
+ reader.releaseLock();
294
+ }
295
+ }
296
+ function extractEvent(rawEvent) {
297
+ const dataLines = [];
298
+ let eventName = "message";
299
+ for (const line of rawEvent.split("\n")) {
300
+ if (line.startsWith(":")) {
301
+ continue;
302
+ }
303
+ if (line.startsWith("event:")) {
304
+ let value = line.slice("event:".length);
305
+ if (value.startsWith(" ")) {
306
+ value = value.slice(1);
307
+ }
308
+ if (value) {
309
+ eventName = value;
310
+ }
311
+ continue;
312
+ }
313
+ if (line.startsWith("data:")) {
314
+ let value = line.slice("data:".length);
315
+ if (value.startsWith(" ")) {
316
+ value = value.slice(1);
317
+ }
318
+ dataLines.push(value);
319
+ }
320
+ }
321
+ return {
322
+ event: eventName,
323
+ data: dataLines.length === 0 ? null : dataLines.join("\n")
324
+ };
325
+ }
326
+
327
+ // src/streaming/agent-chat.ts
328
+ var AgentChatStream = class {
329
+ constructor(response, options) {
330
+ this.response = response;
331
+ this.options = options;
332
+ this.runId = response.headers.get("X-Task-Run-Id");
333
+ }
334
+ response;
335
+ options;
336
+ /**
337
+ * Server run id of this turn (`X-Task-Run-Id` response header), when
338
+ * provided. Useful for diagnostics.
339
+ */
340
+ runId;
341
+ consumed = false;
342
+ [Symbol.asyncIterator]() {
343
+ return this.chunks();
344
+ }
345
+ async *chunks() {
346
+ if (this.consumed) {
347
+ throw new Error("This AgentChatStream has already been consumed.");
348
+ }
349
+ this.consumed = true;
350
+ const body = this.response.body;
351
+ if (!body) {
352
+ return;
353
+ }
354
+ for await (const data of parseSseStream(body)) {
355
+ if (this.options?.signal?.aborted) {
356
+ return;
357
+ }
358
+ if (data === "[DONE]") {
359
+ return;
360
+ }
361
+ let parsed;
362
+ try {
363
+ parsed = JSON.parse(data);
364
+ } catch {
365
+ continue;
366
+ }
367
+ if (parsed && typeof parsed === "object" && "type" in parsed) {
368
+ yield parsed;
369
+ }
370
+ }
371
+ }
372
+ /**
373
+ * The same chunks as a web `ReadableStream`, ready for the AI SDK's
374
+ * `readUIMessageStream({ stream })` to assemble into `UIMessage` snapshots.
375
+ */
376
+ toUIMessageStream() {
377
+ const iterator = this.chunks();
378
+ return new ReadableStream({
379
+ async pull(controller) {
380
+ const { done, value } = await iterator.next();
381
+ if (done) {
382
+ controller.close();
383
+ } else {
384
+ controller.enqueue(value);
385
+ }
386
+ },
387
+ async cancel() {
388
+ await iterator.return();
389
+ }
390
+ });
391
+ }
392
+ /** Collects the full text of the turn (concatenated `text-delta` chunks). */
393
+ async text() {
394
+ let text = "";
395
+ for await (const chunk of this.chunks()) {
396
+ if (chunk.type === "text-delta" && typeof chunk.delta === "string") {
397
+ text += chunk.delta;
398
+ }
399
+ }
400
+ return text;
401
+ }
402
+ };
403
+ var AgentChatClient = class {
404
+ constructor(http) {
405
+ this.http = http;
406
+ }
407
+ http;
408
+ /**
409
+ * Sends a user message to an agent task and streams the assistant turn.
410
+ * Creates the task row when `taskId` does not exist yet (client-generated
411
+ * UUIDs are supported, mirroring the bundled UI).
412
+ *
413
+ * `message` may be a plain string (wrapped into a UIMessage) or a full AI
414
+ * SDK `UIMessage`.
415
+ */
416
+ async streamMessage(agentId, taskId, message, options = {}) {
417
+ const uiMessage = typeof message === "string" ? {
418
+ id: generateMessageId(),
419
+ role: "user",
420
+ parts: [{ type: "text", text: message }]
421
+ } : message;
422
+ const response = await this.http.request(
423
+ "POST",
424
+ `/api/agents/${agentId}/tasks/${taskId}/stream-message`,
425
+ {
426
+ raw: true,
427
+ signal: options.signal,
428
+ headers: { Accept: "text/event-stream" },
429
+ body: {
430
+ messages: [uiMessage],
431
+ ...options.chatMode ? { chatMode: options.chatMode } : {}
432
+ }
433
+ }
434
+ );
435
+ return new AgentChatStream(response, { signal: options.signal });
436
+ }
437
+ /**
438
+ * Reattaches to an in-flight agent turn after a disconnect (the resumable
439
+ * stream replays from the beginning of the turn). Returns `null` when
440
+ * there is no active stream to resume (server responds 204).
441
+ */
442
+ async resumeStream(agentId, taskId, options = {}) {
443
+ const response = await this.http.request(
444
+ "GET",
445
+ `/api/agents/${agentId}/tasks/${taskId}/stream-message`,
446
+ {
447
+ raw: true,
448
+ signal: options.signal,
449
+ headers: { Accept: "text/event-stream" }
450
+ }
451
+ );
452
+ if (response.status === 204) {
453
+ return null;
454
+ }
455
+ return new AgentChatStream(response, { signal: options.signal });
456
+ }
457
+ /**
458
+ * Requests cancellation of the active agent turn for a task. Idempotent:
459
+ * succeeds even when no stream is active. Optionally persists the partial
460
+ * assistant message received so far.
461
+ */
462
+ async stop(agentId, taskId, options = {}) {
463
+ return this.http.post(
464
+ `/api/agents/${agentId}/tasks/${taskId}/stream-message/stop`,
465
+ {
466
+ body: options.assistantMessage ? { assistantMessage: options.assistantMessage } : {}
467
+ }
468
+ );
469
+ }
470
+ };
471
+ function generateMessageId() {
472
+ const cryptoObj = globalThis.crypto;
473
+ if (cryptoObj?.randomUUID) {
474
+ return cryptoObj.randomUUID();
475
+ }
476
+ return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
477
+ }
478
+
479
+ // src/resources/actions.ts
480
+ var ActionsResource = class {
481
+ constructor(http, executions) {
482
+ this.http = http;
483
+ this.executions = executions;
484
+ }
485
+ http;
486
+ executions;
487
+ /**
488
+ * Run a single catalogue action as a one-step ad-hoc Execution (`source: SDK`).
489
+ *
490
+ * The platform runs the action synchronously and returns the execution id
491
+ * plus status/output/executionPath. You can also poll
492
+ * {@link ExecutionsResource.get} / {@link ExecutionsResource.waitUntilFinished}
493
+ * with the returned id.
494
+ *
495
+ * Discover `appId` / `actionId` / `inputConfig` via `workflowApps.list()`.
496
+ */
497
+ run(options) {
498
+ return this.http.post("/api/actions/run", {
499
+ body: options
500
+ });
501
+ }
502
+ /**
503
+ * Convenience: run the action and wait until the Execution reaches a
504
+ * terminal status (or pauses in `NEEDS_INPUT`). Prefer {@link run} when the
505
+ * synchronous response is enough.
506
+ */
507
+ async runAndWait(options, waitOptions) {
508
+ const { id } = await this.run(options);
509
+ return this.executions.waitUntilFinished(id, {
510
+ ...waitOptions,
511
+ expansion: [
512
+ "executionPath",
513
+ "source",
514
+ ...waitOptions?.expansion ?? []
515
+ ]
516
+ });
517
+ }
518
+ /**
519
+ * Thin discovery helper: find one action definition from `workflowApps.list()`.
520
+ * Prefer calling `workflowApps.list()` once and caching when looking up many
521
+ * actions.
522
+ */
523
+ async get(appId, actionId) {
524
+ const apps = await this.http.get("/api/workflow-apps");
525
+ const app = apps.find((a) => a.id === appId);
526
+ return app?.actions?.find((action) => action.id === actionId);
527
+ }
528
+ };
529
+
530
+ // src/define/index.ts
531
+ var DEFINE_KINDS = {
532
+ connection: "connection",
533
+ aiConnection: "aiConnection",
534
+ action: "action",
535
+ workflow: "workflow",
536
+ agent: "agent"
537
+ };
538
+ function defineConnection(config = {}) {
539
+ return { kind: DEFINE_KINDS.connection, ...config };
540
+ }
541
+ function defineAiConnection(config = {}) {
542
+ return { kind: DEFINE_KINDS.aiConnection, ...config };
543
+ }
544
+ function defineAction(config) {
545
+ if (!config.id?.trim()) {
546
+ throw new Error("defineAction requires id (catalogue action id)");
547
+ }
548
+ return { kind: DEFINE_KINDS.action, ...config };
549
+ }
550
+ function defineWorkflow(config) {
551
+ if (!config.slug?.trim()) {
552
+ throw new Error("defineWorkflow requires slug");
553
+ }
554
+ if (!config.steps?.length) {
555
+ throw new Error("defineWorkflow requires at least one step");
556
+ }
557
+ return { kind: DEFINE_KINDS.workflow, ...config };
558
+ }
559
+ function defineAgent(config) {
560
+ if (!config.slug?.trim()) {
561
+ throw new Error("defineAgent requires slug");
562
+ }
563
+ if (!config.model?.trim()) {
564
+ throw new Error("defineAgent requires model");
565
+ }
566
+ if (config.instructions == null) {
567
+ throw new Error("defineAgent requires instructions");
568
+ }
569
+ return { kind: DEFINE_KINDS.agent, ...config };
570
+ }
571
+ function resolveActionAppId(action) {
572
+ if (action.appId?.trim()) {
573
+ return action.appId.trim();
574
+ }
575
+ const id = action.id.trim();
576
+ const actionMarker = "_action_";
577
+ const markerIndex = id.indexOf(actionMarker);
578
+ if (markerIndex > 0) {
579
+ return id.slice(0, markerIndex);
580
+ }
581
+ const dot = id.indexOf(".");
582
+ if (dot > 0) {
583
+ return id.slice(0, dot);
584
+ }
585
+ throw new Error(
586
+ `Cannot infer appId for action "${id}". Pass appId explicitly.`
587
+ );
588
+ }
589
+ function resolveActionId(action) {
590
+ const id = action.id.trim();
591
+ const appId = action.appId?.trim();
592
+ if (appId && id.startsWith(`${appId}.`)) {
593
+ return id.slice(appId.length + 1);
594
+ }
595
+ const marker = "_action_";
596
+ if (id.includes(marker)) {
597
+ return id;
598
+ }
599
+ const dot = id.indexOf(".");
600
+ if (dot > 0) {
601
+ return id.slice(dot + 1);
602
+ }
603
+ return id;
604
+ }
605
+
606
+ // src/resources/common.ts
607
+ function serializeListQuery(options = {}) {
608
+ return {
609
+ expansion: serializeCsv(options.expansion),
610
+ filterBy: serializeFilterBy(options.filterBy),
611
+ includeType: serializeCsv(options.includeType)
612
+ };
613
+ }
614
+ function serializeCsv(value) {
615
+ if (value === void 0) {
616
+ return void 0;
617
+ }
618
+ return Array.isArray(value) ? value.join(",") : value;
619
+ }
620
+ function serializeFilterBy(value) {
621
+ if (value === void 0) {
622
+ return void 0;
623
+ }
624
+ if (typeof value === "string") {
625
+ return value;
626
+ }
627
+ return Object.entries(value).map(([key, filterValue]) => `${key}:${filterValue}`).join(",");
628
+ }
629
+
630
+ // src/resources/agents.ts
631
+ function unwrapTool(tool) {
632
+ if ("use" in tool && tool.use) {
633
+ return tool.use;
634
+ }
635
+ return tool;
636
+ }
637
+ function agentDefinitionToUpsert(def, options) {
638
+ const tools = [];
639
+ const workflowIds = [...options?.workflowIds ?? []];
640
+ for (const tool of def.tools ?? []) {
641
+ const unwrapped = unwrapTool(tool);
642
+ if (unwrapped.kind === DEFINE_KINDS.workflow) {
643
+ continue;
644
+ }
645
+ if (unwrapped.kind === DEFINE_KINDS.action) {
646
+ tools.push({
647
+ appId: resolveActionAppId(unwrapped),
648
+ actionId: resolveActionId(unwrapped),
649
+ connectionId: unwrapped.connection?.id,
650
+ name: unwrapped.name,
651
+ description: unwrapped.description,
652
+ input: unwrapped.input
653
+ });
654
+ }
655
+ }
656
+ return {
657
+ slug: def.slug,
658
+ name: def.name,
659
+ description: def.description,
660
+ instructions: def.instructions,
661
+ model: def.model,
662
+ aiConnectionId: def.aiConnection?.id,
663
+ connectionIds: def.connections?.map((c) => c.id).filter((id) => Boolean(id)),
664
+ workflowIds: workflowIds.length ? workflowIds : void 0,
665
+ tools,
666
+ managedByCode: true
667
+ };
668
+ }
669
+ var AgentsResource = class {
670
+ constructor(http) {
671
+ this.http = http;
672
+ }
673
+ http;
674
+ /** Lists agents visible to the caller in the current workspace. */
675
+ list(options) {
676
+ return this.http.get("/api/agents", {
677
+ query: serializeListQuery(options)
678
+ });
679
+ }
680
+ /** Returns a single agent. */
681
+ get(agentId, options) {
682
+ return this.http.get(`/api/agents/${agentId}`, {
683
+ query: serializeListQuery(options)
684
+ });
685
+ }
686
+ /**
687
+ * Upsert an agent by project-scoped slug.
688
+ * Prefer the CLI (`npx @lunnoa/client agents deploy`) for happy-path deploy.
689
+ */
690
+ upsertBySlug(projectId, body, options) {
691
+ const payload = "kind" in body && body.kind === "agent" ? agentDefinitionToUpsert(body, options) : body;
692
+ return this.http.post(`/api/projects/${projectId}/agents/upsert`, {
693
+ body: payload
694
+ });
695
+ }
696
+ };
697
+
698
+ // src/auth/types.ts
699
+ function isLoginRequires2FA(result) {
700
+ return "requires2FA" in result && result.requires2FA === true;
701
+ }
702
+ function mapWireTokens(wire) {
703
+ return {
704
+ accessToken: wire.access_token,
705
+ ...wire.refresh_token !== void 0 ? { refreshToken: wire.refresh_token } : {}
706
+ };
707
+ }
708
+ function mapLoginWire(wire) {
709
+ if ("requires2FA" in wire && wire.requires2FA) {
710
+ return {
711
+ requires2FA: true,
712
+ sessionToken: wire.sessionToken
713
+ };
714
+ }
715
+ const tokens = wire;
716
+ return {
717
+ accessToken: tokens.access_token,
718
+ refreshToken: tokens.refresh_token
719
+ };
720
+ }
721
+
722
+ // src/resources/auth.ts
723
+ var AuthResource = class {
724
+ constructor(http, tokenStore) {
725
+ this.http = http;
726
+ this.tokenStore = tokenStore;
727
+ this.sso = new SsoAuthResource(http);
728
+ }
729
+ http;
730
+ tokenStore;
731
+ sso;
732
+ /**
733
+ * `POST /api/auth/login`
734
+ *
735
+ * On success without 2FA, tokens are written to the token store (when set).
736
+ * When 2FA is required, returns `{ requires2FA, sessionToken }` and does not
737
+ * store tokens until {@link verify2faLogin} succeeds.
738
+ */
739
+ async login(input) {
740
+ const wire = await this.http.post("/api/auth/login", {
741
+ body: { email: input.email, password: input.password },
742
+ skipAuth: true
743
+ });
744
+ const result = mapLoginWire(wire);
745
+ if (!isLoginRequires2FA(result)) {
746
+ await this.persistTokens(result);
747
+ }
748
+ return result;
749
+ }
750
+ /**
751
+ * `POST /api/auth/2fa/verify-login`
752
+ *
753
+ * Completes password login after TOTP or backup-code verification.
754
+ */
755
+ async verify2faLogin(input) {
756
+ const wire = await this.http.post(
757
+ "/api/auth/2fa/verify-login",
758
+ {
759
+ body: {
760
+ sessionToken: input.sessionToken,
761
+ token: input.token
762
+ },
763
+ skipAuth: true
764
+ }
765
+ );
766
+ const result = mapLoginWire(wire);
767
+ if (isLoginRequires2FA(result)) {
768
+ throw new Error(
769
+ "Unexpected 2FA challenge after verify2faLogin; expected access and refresh tokens."
770
+ );
771
+ }
772
+ await this.persistTokens(result);
773
+ return result;
774
+ }
775
+ /**
776
+ * `POST /api/auth/login-with-token`
777
+ *
778
+ * Exchanges a short-lived hidden JWT (SSO callback or email verification)
779
+ * for access and refresh tokens.
780
+ */
781
+ async loginWithToken(input) {
782
+ const wire = await this.http.post("/api/auth/login-with-token", {
783
+ body: { token: input.token },
784
+ skipAuth: true
785
+ });
786
+ const tokens = {
787
+ accessToken: wire.access_token,
788
+ refreshToken: wire.refresh_token
789
+ };
790
+ await this.persistTokens(tokens);
791
+ return tokens;
792
+ }
793
+ /**
794
+ * `POST /api/auth/refresh-token`
795
+ *
796
+ * Returns a new access token. The refresh token itself is not rotated.
797
+ * When a token store is configured and `refreshToken` is omitted, the
798
+ * stored refresh token is used.
799
+ */
800
+ async refresh(input) {
801
+ const refreshToken = input?.refreshToken ?? await this.tokenStore?.getRefreshToken() ?? null;
802
+ if (!refreshToken) {
803
+ throw new Error(
804
+ "auth.refresh requires a refreshToken argument or a token store that holds one."
805
+ );
806
+ }
807
+ const wire = await this.http.post(
808
+ "/api/auth/refresh-token",
809
+ {
810
+ body: { refreshToken },
811
+ skipAuth: true
812
+ }
813
+ );
814
+ const mapped = mapWireTokens(wire);
815
+ if (this.tokenStore) {
816
+ await this.tokenStore.setTokens({
817
+ accessToken: mapped.accessToken,
818
+ // Keep existing refresh token; refresh responses omit it.
819
+ refreshToken: mapped.refreshToken
820
+ });
821
+ }
822
+ return { accessToken: mapped.accessToken };
823
+ }
824
+ /**
825
+ * `GET /api/users/me` — current user profile for the active JWT.
826
+ */
827
+ me() {
828
+ return this.http.get("/api/users/me");
829
+ }
830
+ /**
831
+ * Clears tokens from the token store.
832
+ * There is no server-side logout endpoint; access tokens remain valid until expiry
833
+ * (or until the user's `tokensValidFrom` watermark invalidates them).
834
+ */
835
+ async logout() {
836
+ await this.tokenStore?.clear();
837
+ }
838
+ /** True when the token store has an access token (sync stores only wait on Promise). */
839
+ async hasSession() {
840
+ const token = await this.tokenStore?.getAccessToken();
841
+ return Boolean(token);
842
+ }
843
+ async persistTokens(tokens) {
844
+ if (!this.tokenStore) {
845
+ return;
846
+ }
847
+ await this.tokenStore.setTokens({
848
+ accessToken: tokens.accessToken,
849
+ refreshToken: tokens.refreshToken
850
+ });
851
+ }
852
+ };
853
+ var SsoAuthResource = class {
854
+ constructor(http) {
855
+ this.http = http;
856
+ }
857
+ http;
858
+ /** `GET /api/auth/sso/providers` (public). */
859
+ listProviders() {
860
+ return this.http.get("/api/auth/sso/providers", {
861
+ skipAuth: true
862
+ });
863
+ }
864
+ /**
865
+ * Absolute URL for `GET /api/auth/sso/:providerId/login`.
866
+ * Navigate the browser here to start the OIDC redirect.
867
+ */
868
+ getLoginUrl(providerId) {
869
+ if (!providerId) {
870
+ throw new Error("sso.getLoginUrl requires a providerId.");
871
+ }
872
+ return this.http.buildUrl(
873
+ `/api/auth/sso/${encodeURIComponent(providerId)}/login`
874
+ );
875
+ }
876
+ };
877
+
878
+ // src/resources/connections.ts
879
+ var ConnectionsResource = class {
880
+ constructor(http) {
881
+ this.http = http;
882
+ }
883
+ http;
884
+ /** Lists connections in the workspace. */
885
+ list(options) {
886
+ return this.http.get("/api/connections", {
887
+ query: serializeListQuery(options)
888
+ });
889
+ }
890
+ /** Returns a single connection (secrets are never included). */
891
+ get(connectionId, options) {
892
+ return this.http.get(`/api/connections/${connectionId}`, {
893
+ query: serializeListQuery(options)
894
+ });
895
+ }
896
+ };
897
+
898
+ // src/resources/discovery.ts
899
+ var DiscoveryResource = class {
900
+ constructor(http) {
901
+ this.http = http;
902
+ }
903
+ http;
904
+ /**
905
+ * Returns the deployment's feature flags (storage, vector database, email,
906
+ * SSO, edition gating), letting a custom UI adapt to what is enabled.
907
+ */
908
+ enabledFeatures() {
909
+ return this.http.get("/api/discovery/enabled-features");
910
+ }
911
+ };
912
+
913
+ // src/core/pagination.ts
914
+ async function* iteratePages(fetchPage) {
915
+ let page = 1;
916
+ while (true) {
917
+ const { data, pagination } = await fetchPage(page);
918
+ for (const item of data) {
919
+ yield item;
920
+ }
921
+ if (page >= pagination.totalPages || data.length === 0) {
922
+ return;
923
+ }
924
+ page += 1;
925
+ }
926
+ }
927
+ async function* iterateOffset(fetchBatch) {
928
+ let offset = 0;
929
+ while (true) {
930
+ const { items, pagination } = await fetchBatch(offset);
931
+ for (const item of items) {
932
+ yield item;
933
+ }
934
+ offset += items.length;
935
+ const exhausted = items.length === 0 || (pagination.hasMore !== void 0 ? !pagination.hasMore : offset >= pagination.total);
936
+ if (exhausted) {
937
+ return;
938
+ }
939
+ }
940
+ }
941
+
942
+ // src/resources/entities.ts
943
+ var EntitiesResource = class {
944
+ constructor(http) {
945
+ this.http = http;
946
+ }
947
+ http;
948
+ /** Returns one page of entities matching the filter. */
949
+ list(options = {}) {
950
+ return this.http.get("/api/objects", {
951
+ query: this.serializeListOptions(options)
952
+ });
953
+ }
954
+ /**
955
+ * Async-iterates every entity matching the filter, fetching pages lazily.
956
+ *
957
+ * ```ts
958
+ * for await (const invoice of client.entities.iterate({ objectTypeSlug: 'invoice' })) { ... }
959
+ * ```
960
+ */
961
+ iterate(options = {}) {
962
+ return iteratePages(async (page) => {
963
+ const result = await this.list({ ...options, page });
964
+ return { data: result.data ?? [], pagination: result.pagination };
965
+ });
966
+ }
967
+ /** Returns a single entity. Request `expansion: ['attributes']` for field values. */
968
+ get(entityId, options = {}) {
969
+ return this.http.get(`/api/objects/${entityId}`, {
970
+ query: { expansion: serializeCsv(options.expansion) }
971
+ });
972
+ }
973
+ /** Creates an entity. `attributes` are keyed by field ID. */
974
+ create(data, options = {}) {
975
+ return this.http.post("/api/objects", {
976
+ body: data,
977
+ query: { expansion: serializeCsv(options.expansion) }
978
+ });
979
+ }
980
+ /** Partially updates an entity; supplied attributes are merged. */
981
+ update(entityId, data, options = {}) {
982
+ return this.http.patch(`/api/objects/${entityId}`, {
983
+ body: data,
984
+ query: { expansion: serializeCsv(options.expansion) }
985
+ });
986
+ }
987
+ /** Soft-deletes an entity. */
988
+ delete(entityId) {
989
+ return this.http.delete(`/api/objects/${entityId}`);
990
+ }
991
+ /** Moves an entity to a new state (validated against the type's state machine). */
992
+ changeState(entityId, data) {
993
+ return this.http.post(
994
+ `/api/objects/${entityId}/state`,
995
+ { body: data }
996
+ );
997
+ }
998
+ /** Returns the entity's state changes, newest first. */
999
+ getStateHistory(entityId, options = {}) {
1000
+ return this.http.get(
1001
+ `/api/objects/${entityId}/state/history`,
1002
+ { query: { limit: options.limit } }
1003
+ );
1004
+ }
1005
+ /** Returns the current state and the transitions allowed from it. */
1006
+ getStateTransitions(entityId) {
1007
+ return this.http.get(
1008
+ `/api/objects/${entityId}/state/transitions`
1009
+ );
1010
+ }
1011
+ /** Imports pre-parsed rows into an entity type (SuperAdmin only). */
1012
+ import(data) {
1013
+ return this.http.post("/api/objects/import", {
1014
+ body: data
1015
+ });
1016
+ }
1017
+ /** Exports entities of a type as flat JSON rows. */
1018
+ export(data) {
1019
+ return this.http.post(
1020
+ "/api/objects/export",
1021
+ { body: data }
1022
+ );
1023
+ }
1024
+ /** Returns the column headers and a sample row for importing (SuperAdmin only). */
1025
+ getImportTemplate(entityTypeId) {
1026
+ return this.http.get(
1027
+ `/api/objects/import/template/${entityTypeId}`
1028
+ );
1029
+ }
1030
+ serializeListOptions(options) {
1031
+ return {
1032
+ objectTypeId: options.objectTypeId,
1033
+ objectTypeSlug: options.objectTypeSlug,
1034
+ state: options.state,
1035
+ states: options.states?.join(","),
1036
+ ownerId: options.ownerId,
1037
+ search: options.search,
1038
+ sortBy: options.sortBy,
1039
+ sortOrder: options.sortOrder,
1040
+ page: options.page,
1041
+ pageSize: options.pageSize,
1042
+ expansion: serializeCsv(options.expansion)
1043
+ };
1044
+ }
1045
+ };
1046
+
1047
+ // src/resources/entity-types.ts
1048
+ var EntityTypesResource = class {
1049
+ constructor(http) {
1050
+ this.http = http;
1051
+ }
1052
+ http;
1053
+ /**
1054
+ * Lists entity types in the workspace. Request
1055
+ * `expansion: ['attributeSchema', 'stateSchema']` for the field and state
1056
+ * machine definitions the codegen CLI builds types from.
1057
+ */
1058
+ list(options = {}) {
1059
+ return this.http.get("/api/object-types", {
1060
+ query: {
1061
+ includeDisabled: options.includeDisabled,
1062
+ expansion: serializeCsv(options.expansion)
1063
+ }
1064
+ });
1065
+ }
1066
+ /** Returns a single entity type by ID. */
1067
+ get(id, options = {}) {
1068
+ return this.http.get(`/api/object-types/${id}`, {
1069
+ query: { expansion: serializeCsv(options.expansion) }
1070
+ });
1071
+ }
1072
+ /** Returns a single entity type by its URL-safe slug. */
1073
+ getBySlug(slug, options = {}) {
1074
+ return this.http.get(`/api/object-types/slug/${slug}`, {
1075
+ query: { expansion: serializeCsv(options.expansion) }
1076
+ });
1077
+ }
1078
+ };
1079
+
1080
+ // src/resources/approvals.ts
1081
+ var ApprovalsResource = class {
1082
+ constructor(http) {
1083
+ this.http = http;
1084
+ }
1085
+ http;
1086
+ /**
1087
+ * @param options.status `pending` (default) for actionable items, or `done` for history.
1088
+ */
1089
+ async inbox(options) {
1090
+ return this.http.get("/api/approvals/inbox", {
1091
+ query: { status: options?.status ?? "pending" }
1092
+ });
1093
+ }
1094
+ async decide(approvalId, body) {
1095
+ return this.http.post(`/api/approvals/${approvalId}/decide`, { body });
1096
+ }
1097
+ };
1098
+
1099
+ // src/streaming/execution-progress.ts
1100
+ var ExecutionProgressStream = class {
1101
+ constructor(response, options) {
1102
+ this.response = response;
1103
+ this.options = options;
1104
+ }
1105
+ response;
1106
+ options;
1107
+ consumed = false;
1108
+ [Symbol.asyncIterator]() {
1109
+ return this.events();
1110
+ }
1111
+ async *events() {
1112
+ if (this.consumed) {
1113
+ throw new Error("This ExecutionProgressStream has already been consumed.");
1114
+ }
1115
+ this.consumed = true;
1116
+ const body = this.response.body;
1117
+ if (!body) {
1118
+ return;
1119
+ }
1120
+ for await (const frame of parseSseEvents(body)) {
1121
+ if (this.options?.signal?.aborted) {
1122
+ return;
1123
+ }
1124
+ if (frame.data === null) {
1125
+ continue;
1126
+ }
1127
+ let parsed;
1128
+ try {
1129
+ parsed = JSON.parse(frame.data);
1130
+ } catch {
1131
+ continue;
1132
+ }
1133
+ if (!parsed || typeof parsed !== "object") {
1134
+ continue;
1135
+ }
1136
+ yield {
1137
+ event: frame.event,
1138
+ data: parsed
1139
+ };
1140
+ if (frame.event === "execution.finished" || frame.event === "execution.error") {
1141
+ return;
1142
+ }
1143
+ }
1144
+ }
1145
+ };
1146
+ async function openExecutionStream(http, executionId, options) {
1147
+ const response = await http.request("GET", `/api/executions/${executionId}/stream`, {
1148
+ raw: true,
1149
+ signal: options?.signal
1150
+ });
1151
+ if (!response.ok) {
1152
+ const body = await response.text().catch(() => void 0);
1153
+ let parsed = body;
1154
+ try {
1155
+ parsed = body ? JSON.parse(body) : void 0;
1156
+ } catch {
1157
+ }
1158
+ throw new LunnoaApiError({
1159
+ status: response.status,
1160
+ body: parsed,
1161
+ method: "GET",
1162
+ path: `/api/executions/${executionId}/stream`
1163
+ });
1164
+ }
1165
+ return new ExecutionProgressStream(response, options);
1166
+ }
1167
+
1168
+ // src/resources/executions.ts
1169
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
1170
+ "SUCCESS",
1171
+ "FAILED",
1172
+ "CANCELLED"
1173
+ ]);
1174
+ var ExecutionTimeoutError = class extends Error {
1175
+ constructor(executionId, timeoutMs) {
1176
+ super(
1177
+ `Execution ${executionId} did not finish within ${timeoutMs}ms. It is still running server-side; keep polling executions.get() or increase timeoutMs.`
1178
+ );
1179
+ this.executionId = executionId;
1180
+ this.timeoutMs = timeoutMs;
1181
+ this.name = "ExecutionTimeoutError";
1182
+ }
1183
+ executionId;
1184
+ timeoutMs;
1185
+ };
1186
+ var ExecutionsResource = class {
1187
+ constructor(http, workflowApps) {
1188
+ this.http = http;
1189
+ this.workflowApps = workflowApps;
1190
+ }
1191
+ http;
1192
+ workflowApps;
1193
+ catalogCache = null;
1194
+ /**
1195
+ * Starts a new execution of a workflow. Only the execution ID is returned;
1196
+ * use {@link waitUntilFinished} or `get` to track progress.
1197
+ */
1198
+ execute(workflowId, inputData) {
1199
+ return this.http.post(
1200
+ `/api/executions/workflows/${workflowId}/execute`,
1201
+ { body: { inputData: inputData ?? {} } }
1202
+ );
1203
+ }
1204
+ /** Lists executions in the current workspace, most recently started first. */
1205
+ list(options) {
1206
+ return this.http.get("/api/executions", {
1207
+ query: serializeListQuery(options)
1208
+ });
1209
+ }
1210
+ /**
1211
+ * Returns a single execution. Request `expansion: ['status', 'executionPath']`
1212
+ * for the business-readable step list custom UIs render progress from.
1213
+ */
1214
+ get(executionId, options) {
1215
+ return this.http.get(`/api/executions/${executionId}`, {
1216
+ query: serializeListQuery(options)
1217
+ });
1218
+ }
1219
+ /**
1220
+ * Timeline-ready progress: `executionPath` plus catalogue icons/logos.
1221
+ * Use this for a custom status bar / step list (not raw nodes/edges).
1222
+ */
1223
+ async getProgress(executionId) {
1224
+ const execution = await this.get(executionId, {
1225
+ expansion: [
1226
+ "status",
1227
+ "statusMessage",
1228
+ "executionPath",
1229
+ "pendingInput",
1230
+ "source",
1231
+ "name",
1232
+ "startedAt",
1233
+ "stoppedAt",
1234
+ "output"
1235
+ ]
1236
+ });
1237
+ return this.#toProgress(execution);
1238
+ }
1239
+ /**
1240
+ * Opens the Public API SSE stream (`GET /executions/:id/stream`).
1241
+ * Yields `execution.progress`, `loop.progress`, `execution.finished`, etc.
1242
+ * For a simple timeline, prefer {@link watchProgress}.
1243
+ */
1244
+ stream(executionId, options) {
1245
+ return openExecutionStream(this.http, executionId, options);
1246
+ }
1247
+ /**
1248
+ * Live progress snapshots for a custom timeline UI.
1249
+ * Subscribes to SSE `execution.progress` events (and enriches steps with icons).
1250
+ * Completes when the execution reaches a terminal status or the signal aborts.
1251
+ */
1252
+ async *watchProgress(executionId, options) {
1253
+ const stream = await this.stream(executionId, options);
1254
+ for await (const frame of stream) {
1255
+ if (frame.event === "execution.progress") {
1256
+ const payload = frame.data;
1257
+ yield await this.#progressFromPayload(payload);
1258
+ } else if (frame.event === "execution.finished") {
1259
+ yield await this.getProgress(executionId);
1260
+ return;
1261
+ } else if (frame.event === "execution.error") {
1262
+ throw new Error(
1263
+ typeof frame.data.message === "string" ? frame.data.message : `Execution stream error for ${executionId}`
1264
+ );
1265
+ }
1266
+ }
1267
+ }
1268
+ /**
1269
+ * Polls an execution with exponential backoff until it reaches a terminal
1270
+ * status (`SUCCESS`, `FAILED`, `CANCELLED`) — or pauses in `NEEDS_INPUT`
1271
+ * (unless `stopOnNeedsInput: false`). Resolves with the final execution
1272
+ * including `status`, `statusMessage`, and `output`.
1273
+ */
1274
+ async waitUntilFinished(executionId, options = {}) {
1275
+ const {
1276
+ timeoutMs = 10 * 60 * 1e3,
1277
+ initialDelayMs = 500,
1278
+ maxDelayMs = 5e3,
1279
+ backoffFactor = 1.5,
1280
+ stopOnNeedsInput = true,
1281
+ expansion = [],
1282
+ signal,
1283
+ sleep = defaultSleep
1284
+ } = options;
1285
+ const pollExpansion = Array.from(
1286
+ /* @__PURE__ */ new Set(["status", "statusMessage", "output", ...expansion])
1287
+ );
1288
+ const startedAt = Date.now();
1289
+ let waitedMs = 0;
1290
+ let delay = initialDelayMs;
1291
+ while (true) {
1292
+ signal?.throwIfAborted();
1293
+ const execution = await this.get(executionId, {
1294
+ expansion: pollExpansion
1295
+ });
1296
+ const status = execution.status;
1297
+ if (status && TERMINAL_STATUSES.has(status)) {
1298
+ return execution;
1299
+ }
1300
+ if (stopOnNeedsInput && status === "NEEDS_INPUT") {
1301
+ return execution;
1302
+ }
1303
+ const elapsed = Math.max(Date.now() - startedAt, waitedMs);
1304
+ if (elapsed + delay > timeoutMs) {
1305
+ throw new ExecutionTimeoutError(executionId, timeoutMs);
1306
+ }
1307
+ await sleep(delay);
1308
+ waitedMs += delay;
1309
+ delay = Math.min(delay * backoffFactor, maxDelayMs);
1310
+ }
1311
+ }
1312
+ /**
1313
+ * Resumes an execution paused in `NEEDS_INPUT` by submitting values for the
1314
+ * waiting node. Identify the waiting node and its field schema via
1315
+ * `expansion: ['pendingInput', 'executionPath']` (prefer `pendingInput` for
1316
+ * the form contract; `executionPath` steps with status `NEEDS_INPUT` also
1317
+ * carry the `nodeId`). Required fields from the schema must be present.
1318
+ * The server acknowledges immediately and resumes asynchronously — follow
1319
+ * up with {@link waitUntilFinished}.
1320
+ */
1321
+ async submitInput(executionId, nodeId, values) {
1322
+ await this.http.post(
1323
+ `/api/webhooks/executions/${executionId}/nodes/${nodeId}/input`,
1324
+ { body: values }
1325
+ );
1326
+ }
1327
+ /**
1328
+ * Submit an approve/reject decision for a Request Approval node.
1329
+ * Uses the authenticated Approvals API (not the public input webhook).
1330
+ * Partial approvals keep the execution paused; terminal decisions resume it.
1331
+ */
1332
+ async submitApproval(executionId, nodeId, body) {
1333
+ return this.http.post(
1334
+ `/api/executions/${executionId}/nodes/${nodeId}/approvals`,
1335
+ { body }
1336
+ );
1337
+ }
1338
+ async #toProgress(execution) {
1339
+ const path = execution.executionPath ?? [];
1340
+ const steps = await this.#enrichSteps(path);
1341
+ return {
1342
+ executionId: execution.id,
1343
+ status: execution.status,
1344
+ statusMessage: execution.statusMessage ?? null,
1345
+ name: execution.name ?? null,
1346
+ source: execution.source ?? null,
1347
+ startedAt: execution.startedAt,
1348
+ stoppedAt: execution.stoppedAt ?? null,
1349
+ steps,
1350
+ pendingInput: execution.pendingInput,
1351
+ output: execution.output,
1352
+ activeStepIndex: findActiveStepIndex(steps)
1353
+ };
1354
+ }
1355
+ async #progressFromPayload(payload) {
1356
+ const steps = await this.#enrichSteps(payload.executionPath ?? []);
1357
+ return {
1358
+ executionId: payload.executionId,
1359
+ status: payload.status ?? void 0,
1360
+ statusMessage: payload.statusMessage,
1361
+ name: payload.name,
1362
+ source: payload.source,
1363
+ startedAt: payload.startedAt,
1364
+ stoppedAt: payload.stoppedAt,
1365
+ steps,
1366
+ pendingInput: payload.pendingInput,
1367
+ output: payload.output,
1368
+ activeStepIndex: findActiveStepIndex(steps)
1369
+ };
1370
+ }
1371
+ async #enrichSteps(path) {
1372
+ const catalog = await this.#loadCatalog();
1373
+ return path.map((step) => {
1374
+ const app = catalog?.find((a) => a.id === step.appId);
1375
+ const action = step.actionId && Array.isArray(app?.actions) ? app.actions.find(
1376
+ (a) => a && typeof a === "object" && a.id === step.actionId
1377
+ ) : void 0;
1378
+ const trigger = step.triggerId && Array.isArray(app?.triggers) ? app.triggers.find(
1379
+ (t) => t && typeof t === "object" && t.id === step.triggerId
1380
+ ) : void 0;
1381
+ const iconUrl = typeof action?.iconUrl === "string" && action.iconUrl || typeof trigger?.iconUrl === "string" && trigger.iconUrl || null;
1382
+ const durationMs = computeDurationMs(step.startTime, step.endTime);
1383
+ return {
1384
+ ...step,
1385
+ appLogoUrl: app?.logoUrl ?? null,
1386
+ iconUrl,
1387
+ durationMs
1388
+ };
1389
+ });
1390
+ }
1391
+ async #loadCatalog() {
1392
+ if (!this.workflowApps) {
1393
+ return null;
1394
+ }
1395
+ if (!this.catalogCache) {
1396
+ try {
1397
+ this.catalogCache = await this.workflowApps.list();
1398
+ } catch {
1399
+ return null;
1400
+ }
1401
+ }
1402
+ return this.catalogCache;
1403
+ }
1404
+ };
1405
+ function findActiveStepIndex(steps) {
1406
+ const idx = steps.findIndex(
1407
+ (s) => s.status === "RUNNING" || s.status === "NEEDS_INPUT" || s.status === "RETRYING" || s.status === "WAITING" || s.status === "SCHEDULED"
1408
+ );
1409
+ return idx;
1410
+ }
1411
+ function computeDurationMs(start, end) {
1412
+ if (!start || !end) return null;
1413
+ const a = Date.parse(start);
1414
+ const b = Date.parse(end);
1415
+ if (Number.isNaN(a) || Number.isNaN(b) || b < a) return null;
1416
+ return b - a;
1417
+ }
1418
+ function defaultSleep(ms) {
1419
+ return new Promise((resolve) => setTimeout(resolve, ms));
1420
+ }
1421
+
1422
+ // src/resources/knowledge.ts
1423
+ var KnowledgeResource = class {
1424
+ constructor(http) {
1425
+ this.http = http;
1426
+ }
1427
+ http;
1428
+ /** Lists knowledge notebooks in the workspace. */
1429
+ list(options = {}) {
1430
+ return this.http.get("/api/knowledge", {
1431
+ query: { expansion: serializeCsv(options.expansion) }
1432
+ });
1433
+ }
1434
+ /** Returns a single knowledge notebook. */
1435
+ get(knowledgeId, options = {}) {
1436
+ return this.http.get(`/api/knowledge/${knowledgeId}`, {
1437
+ query: { expansion: serializeCsv(options.expansion) }
1438
+ });
1439
+ }
1440
+ /** Creates a knowledge notebook. Subject to the deployment's storage/vector infrastructure. */
1441
+ create(data) {
1442
+ return this.http.post("/api/knowledge", { body: data });
1443
+ }
1444
+ /** Updates a knowledge notebook. */
1445
+ update(knowledgeId, data) {
1446
+ return this.http.patch(`/api/knowledge/${knowledgeId}`, {
1447
+ body: data
1448
+ });
1449
+ }
1450
+ /** Deletes a knowledge notebook. */
1451
+ delete(knowledgeId) {
1452
+ return this.http.delete(`/api/knowledge/${knowledgeId}`);
1453
+ }
1454
+ /** Saves raw text into the notebook (chunked, embedded, and indexed server-side). */
1455
+ saveUploadedText(knowledgeId, data) {
1456
+ return this.http.post(
1457
+ `/api/knowledge/${knowledgeId}/saveUploadedText`,
1458
+ { body: data }
1459
+ );
1460
+ }
1461
+ /** Lists the documents stored in a notebook. */
1462
+ listDocuments(knowledgeId) {
1463
+ return this.http.get(
1464
+ `/api/knowledge/${knowledgeId}/documents`
1465
+ );
1466
+ }
1467
+ /** Returns one document group (an uploaded document and its chunks). */
1468
+ getDocumentGroup(knowledgeId, groupId) {
1469
+ return this.http.get(
1470
+ `/api/knowledge/${knowledgeId}/documents/${groupId}`
1471
+ );
1472
+ }
1473
+ /** Deletes one document group from the notebook. */
1474
+ deleteDocumentGroup(knowledgeId, groupId) {
1475
+ return this.http.delete(
1476
+ `/api/knowledge/${knowledgeId}/documents/${groupId}`
1477
+ );
1478
+ }
1479
+ };
1480
+
1481
+ // src/resources/projects.ts
1482
+ var ProjectsResource = class {
1483
+ constructor(http) {
1484
+ this.http = http;
1485
+ }
1486
+ http;
1487
+ /** Lists projects in the workspace, most recently updated first. */
1488
+ list(options) {
1489
+ return this.http.get("/api/projects", {
1490
+ query: serializeListQuery(options)
1491
+ });
1492
+ }
1493
+ /** Returns a single project. */
1494
+ get(projectId, options) {
1495
+ return this.http.get(`/api/projects/${projectId}`, {
1496
+ query: serializeListQuery(options)
1497
+ });
1498
+ }
1499
+ /** Creates a project. */
1500
+ create(data) {
1501
+ return this.http.post("/api/projects", { body: data });
1502
+ }
1503
+ };
1504
+
1505
+ // src/resources/queue-items.ts
1506
+ var QueueItemsResource = class {
1507
+ constructor(http) {
1508
+ this.http = http;
1509
+ }
1510
+ http;
1511
+ /** Returns one page of items in a queue (offset-based pagination). */
1512
+ list(queueId, options = {}) {
1513
+ return this.http.get(`/api/queues/${queueId}/items`, {
1514
+ query: {
1515
+ limit: options.limit,
1516
+ offset: options.offset,
1517
+ expansion: serializeCsv(options.expansion),
1518
+ filterBy: serializeFilterBy(options.filterBy)
1519
+ }
1520
+ });
1521
+ }
1522
+ /** Async-iterates every item in a queue, fetching batches lazily. */
1523
+ iterate(queueId, options = {}) {
1524
+ return iterateOffset(async (offset) => {
1525
+ const result = await this.list(queueId, { ...options, offset });
1526
+ return {
1527
+ items: result.items ?? [],
1528
+ pagination: result.pagination
1529
+ };
1530
+ });
1531
+ }
1532
+ /** Returns a single queue item. */
1533
+ get(queueId, itemId) {
1534
+ return this.http.get(
1535
+ `/api/queues/${queueId}/items/${itemId}`
1536
+ );
1537
+ }
1538
+ /** Adds an item to a queue. */
1539
+ create(queueId, data) {
1540
+ return this.http.post(`/api/queues/${queueId}/items`, {
1541
+ body: data
1542
+ });
1543
+ }
1544
+ /** Adds many items to a queue in one call. */
1545
+ bulkCreate(queueId, data) {
1546
+ return this.http.post(
1547
+ `/api/queues/${queueId}/items/bulk`,
1548
+ { body: data }
1549
+ );
1550
+ }
1551
+ /** Updates a queue item. */
1552
+ update(queueId, itemId, data) {
1553
+ return this.http.patch(
1554
+ `/api/queues/${queueId}/items/${itemId}`,
1555
+ { body: data }
1556
+ );
1557
+ }
1558
+ /** Deletes a queue item. */
1559
+ delete(queueId, itemId) {
1560
+ return this.http.delete(
1561
+ `/api/queues/${queueId}/items/${itemId}`
1562
+ );
1563
+ }
1564
+ /** Returns items that are ready to be processed. */
1565
+ listReady(queueId, options = {}) {
1566
+ return this.http.get(`/api/queues/${queueId}/items/ready`, {
1567
+ query: { limit: options.limit }
1568
+ });
1569
+ }
1570
+ /** Returns items eligible for retry. */
1571
+ listRetryable(queueId) {
1572
+ return this.http.get(`/api/queues/${queueId}/items/retry`);
1573
+ }
1574
+ /** Returns items with the given status. */
1575
+ listByStatus(queueId, status) {
1576
+ return this.http.get(
1577
+ `/api/queues/${queueId}/items/status/${status}`
1578
+ );
1579
+ }
1580
+ /** Looks an item up by the external ID your system supplied. */
1581
+ getByExternalId(queueId, externalId) {
1582
+ return this.http.get(
1583
+ `/api/queues/${queueId}/items/external/${externalId}`
1584
+ );
1585
+ }
1586
+ /** Searches items in the queue. */
1587
+ search(queueId, query) {
1588
+ return this.http.get(`/api/queues/${queueId}/items/search`, {
1589
+ query: { q: query }
1590
+ });
1591
+ }
1592
+ /** Returns per-status and per-priority counts for the queue. */
1593
+ stats(queueId) {
1594
+ return this.http.get(`/api/queues/${queueId}/items/stats`);
1595
+ }
1596
+ /** Returns the recorded processing errors of an item. */
1597
+ listErrors(queueId, itemId) {
1598
+ return this.http.get(
1599
+ `/api/queues/${queueId}/items/${itemId}/errors`
1600
+ );
1601
+ }
1602
+ /** Updates the processing status of an item. */
1603
+ updateStatus(queueId, itemId, status) {
1604
+ return this.http.patch(
1605
+ `/api/queues/${queueId}/items/${itemId}/status`,
1606
+ { body: { status } }
1607
+ );
1608
+ }
1609
+ /** Links an item to the workflow execution that processed it. */
1610
+ linkExecution(queueId, itemId, executionId) {
1611
+ return this.http.patch(
1612
+ `/api/queues/${queueId}/items/${itemId}/link-execution`,
1613
+ { body: { executionId } }
1614
+ );
1615
+ }
1616
+ /** Queues the item for another processing attempt. */
1617
+ retry(queueId, itemId) {
1618
+ return this.http.post(
1619
+ `/api/queues/${queueId}/items/${itemId}/retry`
1620
+ );
1621
+ }
1622
+ /** Records a processing error on the item. */
1623
+ addError(queueId, itemId, error) {
1624
+ return this.http.post(
1625
+ `/api/queues/${queueId}/items/${itemId}/error`,
1626
+ { body: error }
1627
+ );
1628
+ }
1629
+ };
1630
+
1631
+ // src/resources/queues.ts
1632
+ var QueuesResource = class {
1633
+ constructor(http) {
1634
+ this.http = http;
1635
+ }
1636
+ http;
1637
+ /** Lists queues in the workspace, ordered by priority then creation date. */
1638
+ list(options) {
1639
+ return this.http.get("/api/queues", {
1640
+ query: serializeListQuery(options)
1641
+ });
1642
+ }
1643
+ /** Returns a single queue. */
1644
+ get(queueId, options) {
1645
+ return this.http.get(`/api/queues/${queueId}`, {
1646
+ query: serializeListQuery(options)
1647
+ });
1648
+ }
1649
+ /** Creates a queue. */
1650
+ create(data) {
1651
+ return this.http.post("/api/queues", { body: data });
1652
+ }
1653
+ /** Updates a queue. */
1654
+ update(queueId, data) {
1655
+ return this.http.patch(`/api/queues/${queueId}`, { body: data });
1656
+ }
1657
+ /** Deletes a queue. */
1658
+ delete(queueId) {
1659
+ return this.http.delete(`/api/queues/${queueId}`);
1660
+ }
1661
+ /** Returns aggregate statistics for a queue. */
1662
+ stats(queueId) {
1663
+ return this.http.get(`/api/queues/${queueId}/stats`);
1664
+ }
1665
+ /** Activates a queue. */
1666
+ activate(queueId) {
1667
+ return this.http.patch(`/api/queues/${queueId}/activate`);
1668
+ }
1669
+ /** Deactivates a queue. */
1670
+ deactivate(queueId) {
1671
+ return this.http.patch(`/api/queues/${queueId}/deactivate`);
1672
+ }
1673
+ };
1674
+
1675
+ // src/resources/runs.ts
1676
+ function isWorkflowDefinition(value) {
1677
+ return typeof value === "object" && value !== null && value.kind === "workflow";
1678
+ }
1679
+ function isActionDefinition(value) {
1680
+ return typeof value === "object" && value !== null && value.kind === "action";
1681
+ }
1682
+ function stepFromDefinition(step) {
1683
+ if ("appId" in step && "actionId" in step && !("use" in step) && !("kind" in step)) {
1684
+ return step;
1685
+ }
1686
+ if (isActionDefinition(step)) {
1687
+ return {
1688
+ appId: resolveActionAppId(step),
1689
+ actionId: resolveActionId(step),
1690
+ ...step.connection?.id ? { connectionId: step.connection.id } : {},
1691
+ ...step.input ? { input: step.input } : {},
1692
+ ...step.name ? { name: step.name } : {}
1693
+ };
1694
+ }
1695
+ const use = step.use;
1696
+ const input = {
1697
+ ...use.input ?? {},
1698
+ ...step.input ?? {}
1699
+ };
1700
+ return {
1701
+ appId: resolveActionAppId(use),
1702
+ actionId: resolveActionId(use),
1703
+ ...use.connection?.id ? { connectionId: use.connection.id } : {},
1704
+ ...Object.keys(input).length ? { input } : {},
1705
+ ...step.name ?? use.name ? { name: step.name ?? use.name } : {}
1706
+ };
1707
+ }
1708
+ function normalizeStartRunInput(input) {
1709
+ if (isWorkflowDefinition(input)) {
1710
+ return {
1711
+ steps: input.steps.map(stepFromDefinition),
1712
+ name: input.name ?? input.slug
1713
+ };
1714
+ }
1715
+ if (Array.isArray(input)) {
1716
+ return { steps: input.map((s) => stepFromDefinition(s)) };
1717
+ }
1718
+ return input;
1719
+ }
1720
+ var RunsResource = class {
1721
+ constructor(http, executions) {
1722
+ this.http = http;
1723
+ this.executions = executions;
1724
+ }
1725
+ http;
1726
+ executions;
1727
+ /**
1728
+ * Start a linear multi-step ad-hoc Execution.
1729
+ *
1730
+ * Accepts a `defineWorkflow` result, a step array, or an explicit
1731
+ * `{ steps, projectId?, name? }` payload.
1732
+ */
1733
+ start(input) {
1734
+ const options = normalizeStartRunInput(input);
1735
+ if (!options.steps?.length) {
1736
+ throw new Error("runs.start requires at least one step");
1737
+ }
1738
+ return this.http.post("/api/runs", {
1739
+ body: options
1740
+ });
1741
+ }
1742
+ /** Convenience: start and wait until terminal / NEEDS_INPUT. */
1743
+ async startAndWait(input, waitOptions) {
1744
+ const { id } = await this.start(input);
1745
+ return this.executions.waitUntilFinished(id, {
1746
+ ...waitOptions,
1747
+ expansion: [
1748
+ "executionPath",
1749
+ "source",
1750
+ ...waitOptions?.expansion ?? []
1751
+ ]
1752
+ });
1753
+ }
1754
+ };
1755
+
1756
+ // src/resources/tasks.ts
1757
+ var TasksResource = class {
1758
+ constructor(http) {
1759
+ this.http = http;
1760
+ }
1761
+ http;
1762
+ /** Lists tasks (conversation threads) visible to the caller, most recently updated first. */
1763
+ list(options) {
1764
+ return this.http.get("/api/tasks", {
1765
+ query: serializeListQuery(options)
1766
+ });
1767
+ }
1768
+ /** Returns a single task. Request `expansion: ['messages']` for the conversation history. */
1769
+ get(taskId, options) {
1770
+ return this.http.get(`/api/tasks/${taskId}`, {
1771
+ query: serializeListQuery(options)
1772
+ });
1773
+ }
1774
+ /** Creates a task (conversation thread) for an agent. */
1775
+ create(agentId, data, options) {
1776
+ return this.http.post(`/api/agents/${agentId}/tasks`, {
1777
+ body: data,
1778
+ query: serializeListQuery(options)
1779
+ });
1780
+ }
1781
+ /** Updates the task name or description. */
1782
+ update(taskId, data, options) {
1783
+ return this.http.patch(`/api/tasks/${taskId}`, {
1784
+ body: data,
1785
+ query: serializeListQuery(options)
1786
+ });
1787
+ }
1788
+ /** Permanently deletes a task and its messages. */
1789
+ delete(taskId) {
1790
+ return this.http.delete(`/api/tasks/${taskId}`);
1791
+ }
1792
+ /**
1793
+ * Sends a message to an agent task and waits for the full agent turn to
1794
+ * finish (non-streaming). Returns either a plain string or the assistant
1795
+ * messages in AI SDK UIMessage format. For token-by-token output use
1796
+ * `client.agentChat.streamMessage` instead.
1797
+ */
1798
+ message(agentId, taskId, message, options = {}) {
1799
+ const uiMessage = typeof message === "string" ? {
1800
+ id: generateMessageId2(),
1801
+ role: "user",
1802
+ parts: [{ type: "text", text: message }]
1803
+ } : message;
1804
+ return this.http.post(
1805
+ `/api/agents/${agentId}/tasks/${taskId}/message`,
1806
+ {
1807
+ body: {
1808
+ messages: [uiMessage],
1809
+ ...options.chatMode ? { chatMode: options.chatMode } : {}
1810
+ }
1811
+ }
1812
+ );
1813
+ }
1814
+ };
1815
+ function generateMessageId2() {
1816
+ const cryptoObj = globalThis.crypto;
1817
+ if (cryptoObj?.randomUUID) {
1818
+ return cryptoObj.randomUUID();
1819
+ }
1820
+ return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
1821
+ }
1822
+
1823
+ // src/resources/variables.ts
1824
+ var VariablesResource = class {
1825
+ constructor(http) {
1826
+ this.http = http;
1827
+ }
1828
+ http;
1829
+ /** Lists variables in the workspace. */
1830
+ list(options) {
1831
+ return this.http.get("/api/variables", {
1832
+ query: serializeListQuery(options)
1833
+ });
1834
+ }
1835
+ /** Returns a single variable. */
1836
+ get(variableId, options) {
1837
+ return this.http.get(`/api/variables/${variableId}`, {
1838
+ query: serializeListQuery(options)
1839
+ });
1840
+ }
1841
+ /** Creates a variable. */
1842
+ create(data) {
1843
+ return this.http.post("/api/variables", { body: data });
1844
+ }
1845
+ /** Updates a variable. */
1846
+ update(variableId, data) {
1847
+ return this.http.patch(`/api/variables/${variableId}`, {
1848
+ body: data
1849
+ });
1850
+ }
1851
+ /** Deletes a variable. */
1852
+ delete(variableId) {
1853
+ return this.http.delete(`/api/variables/${variableId}`);
1854
+ }
1855
+ };
1856
+
1857
+ // src/resources/workflow-apps.ts
1858
+ var WorkflowAppsResource = class {
1859
+ constructor(http) {
1860
+ this.http = http;
1861
+ }
1862
+ http;
1863
+ /** Lists all workflow apps installed on the deployment. */
1864
+ list() {
1865
+ return this.http.get("/api/workflow-apps");
1866
+ }
1867
+ /**
1868
+ * Creates a connection for an app where the connection type is
1869
+ * non-interactive (API key, basic auth). OAuth connections require the
1870
+ * browser flow inside the bundled UI and cannot be created here.
1871
+ */
1872
+ connect(appId, connectionId, value) {
1873
+ return this.http.post(
1874
+ `/api/workflow-apps/${appId}/connections/${connectionId}/connect`,
1875
+ { body: value }
1876
+ );
1877
+ }
1878
+ };
1879
+
1880
+ // src/resources/workflows.ts
1881
+ function stepToUpsertInput(step) {
1882
+ const use = step.use;
1883
+ return {
1884
+ appId: resolveActionAppId(use),
1885
+ actionId: resolveActionId(use),
1886
+ connectionId: use.connection?.id,
1887
+ input: {
1888
+ ...use.input ?? {},
1889
+ ...step.input ?? {}
1890
+ },
1891
+ name: step.name ?? use.name,
1892
+ description: use.description
1893
+ };
1894
+ }
1895
+ function workflowDefinitionToUpsert(def) {
1896
+ return {
1897
+ slug: def.slug,
1898
+ name: def.name,
1899
+ description: def.description,
1900
+ isActive: def.isActive,
1901
+ managedByCode: true,
1902
+ steps: def.steps.map(stepToUpsertInput)
1903
+ };
1904
+ }
1905
+ var WorkflowsResource = class {
1906
+ constructor(http, executions) {
1907
+ this.http = http;
1908
+ this.executions = executions;
1909
+ }
1910
+ http;
1911
+ executions;
1912
+ /** Lists workflows in the current workspace. */
1913
+ list(options) {
1914
+ return this.http.get("/api/workflows", {
1915
+ query: serializeListQuery(options)
1916
+ });
1917
+ }
1918
+ /**
1919
+ * Returns a single workflow. Request `expansion: ['triggerNode']` to read
1920
+ * the manual trigger's custom input field definitions.
1921
+ */
1922
+ get(workflowId, options) {
1923
+ return this.http.get(`/api/workflows/${workflowId}`, {
1924
+ query: serializeListQuery(options)
1925
+ });
1926
+ }
1927
+ /**
1928
+ * Upsert a workflow by project-scoped slug from a linear step list.
1929
+ * Prefer the CLI (`npx @lunnoa/client workflows deploy`) for happy-path deploy.
1930
+ */
1931
+ upsertBySlug(projectId, body) {
1932
+ const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body) : body;
1933
+ return this.http.post(
1934
+ `/api/projects/${projectId}/workflows/upsert`,
1935
+ { body: payload }
1936
+ );
1937
+ }
1938
+ /**
1939
+ * Starts a new execution of the workflow with the given trigger inputs.
1940
+ * Returns immediately with the execution ID.
1941
+ */
1942
+ execute(workflowId, inputs) {
1943
+ return this.executions.execute(workflowId, inputs);
1944
+ }
1945
+ /**
1946
+ * Convenience: execute the workflow and wait (poll with backoff) until it
1947
+ * reaches a terminal status or pauses in `NEEDS_INPUT`.
1948
+ */
1949
+ async executeAndWait(workflowId, inputs, waitOptions) {
1950
+ const { id } = await this.executions.execute(workflowId, inputs);
1951
+ return this.executions.waitUntilFinished(id, waitOptions);
1952
+ }
1953
+ /**
1954
+ * Triggers a workflow through its unauthenticated custom webhook endpoint
1955
+ * (`POST /api/webhooks/workflows/:workflowId`). Only works for workflows
1956
+ * with a webhook trigger. Returns the synchronous trigger output when the
1957
+ * workflow responds synchronously.
1958
+ */
1959
+ triggerWebhook(workflowId, payload) {
1960
+ return this.http.post(
1961
+ `/api/webhooks/workflows/${workflowId}`,
1962
+ { body: payload ?? {} }
1963
+ );
1964
+ }
1965
+ };
1966
+
1967
+ // src/client.ts
1968
+ var LunnoaClient = class {
1969
+ /** The underlying HTTP layer, exposed for advanced/escape-hatch requests. */
1970
+ http;
1971
+ /** Login, refresh, SSO discovery, and session helpers (Pattern B). */
1972
+ auth;
1973
+ agents;
1974
+ tasks;
1975
+ /** Hand-written SSE streaming client for agent chat (AI SDK UIMessage chunks). */
1976
+ agentChat;
1977
+ workflows;
1978
+ executions;
1979
+ /** Run catalogue actions as one-step ad-hoc Executions (`source: SDK`). */
1980
+ actions;
1981
+ /**
1982
+ * Multi-step ad-hoc Executions (`source: SDK`) via `POST /api/runs`.
1983
+ * Prefer `defineWorkflow` + `runs.start` for local linear chains.
1984
+ */
1985
+ runs;
1986
+ /** Authenticated Approvals inbox (workspace API). */
1987
+ approvals;
1988
+ entities;
1989
+ entityTypes;
1990
+ knowledge;
1991
+ queues;
1992
+ queueItems;
1993
+ variables;
1994
+ connections;
1995
+ projects;
1996
+ workflowApps;
1997
+ discovery;
1998
+ constructor(options) {
1999
+ this.http = new HttpClient(options);
2000
+ this.auth = new AuthResource(this.http, this.http.tokenStore);
2001
+ this.agents = new AgentsResource(this.http);
2002
+ this.tasks = new TasksResource(this.http);
2003
+ this.agentChat = new AgentChatClient(this.http);
2004
+ this.workflowApps = new WorkflowAppsResource(this.http);
2005
+ this.executions = new ExecutionsResource(this.http, this.workflowApps);
2006
+ this.actions = new ActionsResource(this.http, this.executions);
2007
+ this.runs = new RunsResource(this.http, this.executions);
2008
+ this.approvals = new ApprovalsResource(this.http);
2009
+ this.workflows = new WorkflowsResource(this.http, this.executions);
2010
+ this.entities = new EntitiesResource(this.http);
2011
+ this.entityTypes = new EntityTypesResource(this.http);
2012
+ this.knowledge = new KnowledgeResource(this.http);
2013
+ this.queues = new QueuesResource(this.http);
2014
+ this.queueItems = new QueueItemsResource(this.http);
2015
+ this.variables = new VariablesResource(this.http);
2016
+ this.connections = new ConnectionsResource(this.http);
2017
+ this.projects = new ProjectsResource(this.http);
2018
+ this.discovery = new DiscoveryResource(this.http);
2019
+ }
2020
+ };
2021
+
2022
+ // src/auth/token-store.ts
2023
+ function createMemoryTokenStore(initial) {
2024
+ let accessToken = initial?.accessToken ?? null;
2025
+ let refreshToken = initial?.refreshToken ?? null;
2026
+ return {
2027
+ getAccessToken: () => accessToken,
2028
+ getRefreshToken: () => refreshToken,
2029
+ setTokens: (tokens) => {
2030
+ accessToken = tokens.accessToken;
2031
+ if (tokens.refreshToken !== void 0) {
2032
+ refreshToken = tokens.refreshToken;
2033
+ }
2034
+ },
2035
+ clear: () => {
2036
+ accessToken = null;
2037
+ refreshToken = null;
2038
+ }
2039
+ };
2040
+ }
2041
+ function createLocalStorageTokenStore(options = {}) {
2042
+ const accessTokenKey = options.accessTokenKey ?? "accessToken";
2043
+ const refreshTokenKey = options.refreshTokenKey ?? "refreshToken";
2044
+ const storage = options.storage ?? (typeof globalThis !== "undefined" ? globalThis.localStorage : void 0);
2045
+ if (!storage) {
2046
+ throw new Error(
2047
+ "createLocalStorageTokenStore requires localStorage (or a custom storage option). On the server, use createMemoryTokenStore or pass accessToken / apiKey instead."
2048
+ );
2049
+ }
2050
+ return {
2051
+ getAccessToken: () => storage.getItem(accessTokenKey),
2052
+ getRefreshToken: () => storage.getItem(refreshTokenKey),
2053
+ setTokens: (tokens) => {
2054
+ storage.setItem(accessTokenKey, tokens.accessToken);
2055
+ if (tokens.refreshToken !== void 0) {
2056
+ storage.setItem(refreshTokenKey, tokens.refreshToken);
2057
+ }
2058
+ },
2059
+ clear: () => {
2060
+ storage.removeItem(accessTokenKey);
2061
+ storage.removeItem(refreshTokenKey);
2062
+ }
2063
+ };
2064
+ }
2065
+
2066
+ // src/auth/create-client.ts
2067
+ function createBrowserClient(options) {
2068
+ const tokenStore = options.tokenStore ?? createLocalStorageTokenStore();
2069
+ return new LunnoaClient({
2070
+ baseUrl: options.baseUrl,
2071
+ tokenStore,
2072
+ autoRefresh: options.autoRefresh ?? true,
2073
+ fetch: options.fetch,
2074
+ headers: options.headers
2075
+ });
2076
+ }
2077
+ function createServerClient(options) {
2078
+ if (!options.apiKey && !options.accessToken && !options.tokenStore) {
2079
+ throw new Error(
2080
+ "createServerClient requires apiKey, accessToken, or tokenStore."
2081
+ );
2082
+ }
2083
+ return new LunnoaClient({
2084
+ baseUrl: options.baseUrl,
2085
+ apiKey: options.apiKey,
2086
+ accessToken: options.accessToken,
2087
+ tokenStore: options.tokenStore,
2088
+ autoRefresh: options.autoRefresh,
2089
+ fetch: options.fetch,
2090
+ headers: options.headers
2091
+ });
2092
+ }
2093
+
2094
+ exports.API_KEY_PREFIX = API_KEY_PREFIX;
2095
+ exports.ActionsResource = ActionsResource;
2096
+ exports.AgentChatClient = AgentChatClient;
2097
+ exports.AgentChatStream = AgentChatStream;
2098
+ exports.AgentsResource = AgentsResource;
2099
+ exports.ApprovalsResource = ApprovalsResource;
2100
+ exports.AuthResource = AuthResource;
2101
+ exports.ConnectionsResource = ConnectionsResource;
2102
+ exports.DEFINE_KINDS = DEFINE_KINDS;
2103
+ exports.DiscoveryResource = DiscoveryResource;
2104
+ exports.EntitiesResource = EntitiesResource;
2105
+ exports.EntityTypesResource = EntityTypesResource;
2106
+ exports.ExecutionProgressStream = ExecutionProgressStream;
2107
+ exports.ExecutionTimeoutError = ExecutionTimeoutError;
2108
+ exports.ExecutionsResource = ExecutionsResource;
2109
+ exports.HttpClient = HttpClient;
2110
+ exports.KnowledgeResource = KnowledgeResource;
2111
+ exports.LunnoaApiError = LunnoaApiError;
2112
+ exports.LunnoaClient = LunnoaClient;
2113
+ exports.ProjectsResource = ProjectsResource;
2114
+ exports.QueueItemsResource = QueueItemsResource;
2115
+ exports.QueuesResource = QueuesResource;
2116
+ exports.RunsResource = RunsResource;
2117
+ exports.SsoAuthResource = SsoAuthResource;
2118
+ exports.TasksResource = TasksResource;
2119
+ exports.VariablesResource = VariablesResource;
2120
+ exports.WorkflowAppsResource = WorkflowAppsResource;
2121
+ exports.WorkflowsResource = WorkflowsResource;
2122
+ exports.assertApiKeyAllowedInEnvironment = assertApiKeyAllowedInEnvironment;
2123
+ exports.createBrowserClient = createBrowserClient;
2124
+ exports.createLocalStorageTokenStore = createLocalStorageTokenStore;
2125
+ exports.createMemoryTokenStore = createMemoryTokenStore;
2126
+ exports.createServerClient = createServerClient;
2127
+ exports.defineAction = defineAction;
2128
+ exports.defineAgent = defineAgent;
2129
+ exports.defineAiConnection = defineAiConnection;
2130
+ exports.defineConnection = defineConnection;
2131
+ exports.defineWorkflow = defineWorkflow;
2132
+ exports.isBrowserEnvironment = isBrowserEnvironment;
2133
+ exports.isLoginRequires2FA = isLoginRequires2FA;
2134
+ exports.iterateOffset = iterateOffset;
2135
+ exports.iteratePages = iteratePages;
2136
+ exports.parseSseEvents = parseSseEvents;
2137
+ exports.parseSseStream = parseSseStream;
2138
+ exports.resolveActionAppId = resolveActionAppId;
2139
+ exports.resolveActionId = resolveActionId;
2140
+ //# sourceMappingURL=index.cjs.map
2141
+ //# sourceMappingURL=index.cjs.map