@collective-intelligence/external-apps-client-sdk 0.0.0-stage → 0.1.1

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.js ADDED
@@ -0,0 +1,725 @@
1
+ import { a as parseMountPath, c as isClientOperationId, i as createHttpOperationTransport, n as createResourceClient, o as parseOriginUrl, r as createEntityProtocol, s as clientOperationIds, t as createEntityHttpTransport } from "./resources-BYm1KWAZ.js";
2
+ const base64Url = (bytes) => {
3
+ let binary = "";
4
+ for (const byte of bytes) binary += String.fromCodePoint(byte);
5
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
6
+ };
7
+ const createOauthRandomValue = (crypto = globalThis.crypto) => base64Url(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)));
8
+ const createPkcePair = async (crypto = globalThis.crypto) => {
9
+ const verifier = createOauthRandomValue(crypto);
10
+ const challengeBytes = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)));
11
+ return {
12
+ challenge: base64Url(challengeBytes),
13
+ method: "S256",
14
+ verifier
15
+ };
16
+ };
17
+ var FederatedAuthError = class extends Error {
18
+ code;
19
+ constructor(code, message) {
20
+ super(message);
21
+ this.name = "FederatedAuthError";
22
+ this.code = code;
23
+ }
24
+ };
25
+ const transactionLifetimeMs = 6e5;
26
+ const maxReturnTargetLength = 2048;
27
+ const asUrl = (value, field) => {
28
+ try {
29
+ const url = new URL(value);
30
+ if (url.username !== "" || url.password !== "") throw new Error("credentials");
31
+ return url;
32
+ } catch {
33
+ throw new FederatedAuthError("configuration_error", `${field} must be an absolute URL without credentials`);
34
+ }
35
+ };
36
+ const tenantOrigin = (value) => {
37
+ const url = asUrl(value, "tenantUrl");
38
+ if (url.pathname !== "/" || url.search !== "" || url.hash !== "") throw new FederatedAuthError("configuration_error", "tenantUrl must contain only the tenant origin");
39
+ return url.origin;
40
+ };
41
+ const normalizedReturnTarget = (value, applicationOrigin) => {
42
+ if (value.length > maxReturnTargetLength) throw new FederatedAuthError("invalid_return_target", "The return target is too long");
43
+ const target = new URL(value, applicationOrigin);
44
+ if (target.origin !== applicationOrigin) throw new FederatedAuthError("invalid_return_target", "The return target must share the callback origin");
45
+ return target.href;
46
+ };
47
+ const isRecord = (value) => value !== null && Object(value) === value && !Array.isArray(value);
48
+ const parseTransaction = (serialized) => {
49
+ const value = JSON.parse(serialized);
50
+ if (!isRecord(value) || typeof value.expiresAt !== "number" || typeof value.returnTo !== "string" || typeof value.state !== "string" || typeof value.verifier !== "string") throw new FederatedAuthError("invalid_state", "Invalid login transaction");
51
+ return {
52
+ expiresAt: value.expiresAt,
53
+ returnTo: value.returnTo,
54
+ state: value.state,
55
+ verifier: value.verifier
56
+ };
57
+ };
58
+ const parseToken = (value, now) => {
59
+ const accessToken = value.access_token;
60
+ const expiresIn = value.expires_in;
61
+ if (typeof accessToken !== "string" || accessToken.length === 0 || typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0 || value.token_type !== "Bearer") throw new FederatedAuthError("token_exchange_failed", "The authorization server returned an invalid token");
62
+ return {
63
+ accessToken,
64
+ expiresAt: now + expiresIn * 1e3
65
+ };
66
+ };
67
+ const createFederatedAuth = (options) => {
68
+ const tenantUrl = tenantOrigin(options.tenantUrl);
69
+ const redirectUri = asUrl(options.redirectUri, "redirectUri");
70
+ const crypto = options.crypto ?? globalThis.crypto;
71
+ const fetch = options.fetch ?? globalThis.fetch;
72
+ const now = options.now ?? Date.now;
73
+ const storage = options.sessionStorage ?? globalThis.sessionStorage;
74
+ const navigate = options.navigate ?? ((url) => {
75
+ const { location } = globalThis;
76
+ if (!location || !("assign" in location) || typeof location.assign !== "function") throw new FederatedAuthError("configuration_error", "Provide navigate when browser navigation is unavailable");
77
+ location.assign(url);
78
+ });
79
+ const storageKey = `ci:federated-auth:${redirectUri.origin}:${options.clientId}`;
80
+ const listeners = /* @__PURE__ */ new Set();
81
+ let state = { status: "anonymous" };
82
+ let token;
83
+ let completion;
84
+ let rememberedReturnTo;
85
+ if (options.clientId.length === 0 || options.scopes.length === 0) throw new FederatedAuthError("configuration_error", "clientId and at least one scope are required");
86
+ if (crypto === void 0 || storage === void 0 || fetch === void 0) throw new FederatedAuthError("configuration_error", "Browser crypto, fetch, and sessionStorage are required");
87
+ const transition = (next) => {
88
+ state = next;
89
+ for (const listener of listeners) listener(next);
90
+ };
91
+ const fail = (error) => {
92
+ token = void 0;
93
+ transition({
94
+ error,
95
+ status: "error"
96
+ });
97
+ throw error;
98
+ };
99
+ const beginLogin = async ({ returnTo }) => {
100
+ const normalizedReturnTo = normalizedReturnTarget(returnTo, redirectUri.origin);
101
+ rememberedReturnTo = normalizedReturnTo;
102
+ const pkce = await createPkcePair(crypto);
103
+ const transaction = {
104
+ expiresAt: now() + transactionLifetimeMs,
105
+ returnTo: normalizedReturnTo,
106
+ state: createOauthRandomValue(crypto),
107
+ verifier: pkce.verifier
108
+ };
109
+ storage.setItem(storageKey, JSON.stringify(transaction));
110
+ const authorizationUrl = new URL("/oauth/authorize", tenantUrl);
111
+ authorizationUrl.searchParams.set("client_id", options.clientId);
112
+ authorizationUrl.searchParams.set("redirect_uri", redirectUri.href);
113
+ authorizationUrl.searchParams.set("response_type", "code");
114
+ authorizationUrl.searchParams.set("scope", options.scopes.join(" "));
115
+ authorizationUrl.searchParams.set("state", transaction.state);
116
+ authorizationUrl.searchParams.set("code_challenge", pkce.challenge);
117
+ authorizationUrl.searchParams.set("code_challenge_method", pkce.method);
118
+ token = void 0;
119
+ completion = void 0;
120
+ transition({ status: "redirecting" });
121
+ navigate(authorizationUrl.href);
122
+ return authorizationUrl.href;
123
+ };
124
+ const complete = async (callbackUrl) => {
125
+ let callback;
126
+ try {
127
+ callback = asUrl(callbackUrl, "callbackUrl");
128
+ } catch {
129
+ storage.removeItem(storageKey);
130
+ return fail(new FederatedAuthError("invalid_callback", "The callback URL is invalid"));
131
+ }
132
+ if (callback.origin !== redirectUri.origin || callback.pathname !== redirectUri.pathname) {
133
+ storage.removeItem(storageKey);
134
+ return fail(new FederatedAuthError("invalid_callback", "The callback URL does not match redirectUri"));
135
+ }
136
+ const serialized = storage.getItem(storageKey);
137
+ storage.removeItem(storageKey);
138
+ if (serialized === null) return fail(new FederatedAuthError("invalid_state", "No login transaction exists"));
139
+ let transaction;
140
+ try {
141
+ transaction = parseTransaction(serialized);
142
+ } catch (error) {
143
+ return fail(error instanceof FederatedAuthError ? error : new FederatedAuthError("invalid_state", "Invalid login transaction"));
144
+ }
145
+ if (transaction.expiresAt <= now()) return fail(new FederatedAuthError("transaction_expired", "The login transaction expired"));
146
+ if (callback.searchParams.get("state") !== transaction.state) return fail(new FederatedAuthError("invalid_state", "OAuth state mismatch"));
147
+ const authorizationError = callback.searchParams.get("error");
148
+ const code = callback.searchParams.get("code");
149
+ if (authorizationError !== null || code === null || code.length === 0) return fail(new FederatedAuthError("authorization_failed", authorizationError ?? "The callback did not include a code"));
150
+ const body = new URLSearchParams({
151
+ client_id: options.clientId,
152
+ code,
153
+ code_verifier: transaction.verifier,
154
+ grant_type: "authorization_code",
155
+ redirect_uri: redirectUri.href
156
+ });
157
+ const response = await fetch(new URL("/oauth/token", tenantUrl), {
158
+ body,
159
+ headers: { "content-type": "application/x-www-form-urlencoded" },
160
+ method: "POST"
161
+ });
162
+ if (!response.ok) return fail(new FederatedAuthError("token_exchange_failed", "The authorization code exchange failed"));
163
+ try {
164
+ const tokenResponse = await response.json();
165
+ if (!(tokenResponse instanceof Object)) throw new FederatedAuthError("token_exchange_failed", "The authorization server returned an invalid token");
166
+ const payload = {
167
+ access_token: Object.entries(tokenResponse).find(([key]) => key === "access_token")?.[1],
168
+ expires_in: Object.entries(tokenResponse).find(([key]) => key === "expires_in")?.[1],
169
+ token_type: Object.entries(tokenResponse).find(([key]) => key === "token_type")?.[1]
170
+ };
171
+ token = parseToken(payload, now());
172
+ } catch (error) {
173
+ return fail(error instanceof FederatedAuthError ? error : new FederatedAuthError("token_exchange_failed", "The authorization server returned an invalid token"));
174
+ }
175
+ transition({
176
+ expiresAt: token.expiresAt,
177
+ status: "authenticated"
178
+ });
179
+ rememberedReturnTo = transaction.returnTo;
180
+ return { returnTo: transaction.returnTo };
181
+ };
182
+ const reauthorize = async (input) => {
183
+ const returnTo = input?.returnTo ?? rememberedReturnTo;
184
+ if (returnTo === void 0) throw new FederatedAuthError("reauthorization_required", "A same-origin return target is required to restart CI login");
185
+ return await beginLogin({ returnTo });
186
+ };
187
+ return {
188
+ beginLogin,
189
+ completeLogin: async (callbackUrl) => {
190
+ if (completion !== void 0) return await completion;
191
+ const current = complete(callbackUrl);
192
+ completion = current;
193
+ const retire = async () => {
194
+ try {
195
+ await current;
196
+ } catch {} finally {
197
+ if (completion === current) completion = void 0;
198
+ }
199
+ };
200
+ retire();
201
+ return await current;
202
+ },
203
+ getAccessToken: async () => {
204
+ if (token !== void 0 && token.expiresAt <= now()) {
205
+ token = void 0;
206
+ transition({ status: "expired" });
207
+ await reauthorize();
208
+ throw new FederatedAuthError("reauthorization_required", "A new CI login is required");
209
+ }
210
+ if (token === void 0) throw new FederatedAuthError("reauthorization_required", "A new CI login is required");
211
+ return token.accessToken;
212
+ },
213
+ getState: () => state,
214
+ logout: async () => {
215
+ const accessToken = token?.accessToken;
216
+ token = void 0;
217
+ completion = void 0;
218
+ rememberedReturnTo = void 0;
219
+ storage.removeItem(storageKey);
220
+ transition({ status: "anonymous" });
221
+ if (accessToken !== void 0) try {
222
+ await fetch(new URL("/oauth/logout", tenantUrl), {
223
+ headers: { authorization: `Bearer ${accessToken}` },
224
+ method: "POST"
225
+ });
226
+ } catch {}
227
+ },
228
+ reauthorize,
229
+ subscribe: (listener) => {
230
+ listeners.add(listener);
231
+ return () => {
232
+ listeners.delete(listener);
233
+ };
234
+ }
235
+ };
236
+ };
237
+ const parseExpiresAt = (value) => {
238
+ if (typeof value === "string") {
239
+ const parsed = Date.parse(value);
240
+ return Number.isFinite(parsed) ? parsed : null;
241
+ }
242
+ if (typeof value === "number" && Number.isFinite(value)) return value;
243
+ return null;
244
+ };
245
+ const asRecord$2 = (value) => {
246
+ if (typeof value !== "object" || value === null) return null;
247
+ return value;
248
+ };
249
+ const readString$1 = (record, key) => {
250
+ const candidate = record[key];
251
+ return typeof candidate === "string" ? candidate : null;
252
+ };
253
+ const parseIdentity = (value) => {
254
+ const payload = asRecord$2(value);
255
+ if (payload === null) return null;
256
+ const nested = asRecord$2(payload.identity) ?? payload;
257
+ const id = readString$1(nested, "id");
258
+ const organizationId = readString$1(nested, "organizationId") ?? readString$1(payload, "organizationId");
259
+ if (id === null || organizationId === null) return null;
260
+ return {
261
+ email: readString$1(nested, "email"),
262
+ id,
263
+ name: readString$1(nested, "name"),
264
+ organizationId
265
+ };
266
+ };
267
+ /** Browser session client for a sealed-cookie BFF. The access token never leaves the server. */
268
+ const createServerSessionAuth = (options) => {
269
+ const { basePath } = options;
270
+ if (!/^\/[\w/-]+$/u.test(basePath) || basePath.endsWith("/")) throw new FederatedAuthError("configuration_error", "basePath must be an absolute path without a trailing slash");
271
+ const fetch = options.fetch ?? globalThis.fetch;
272
+ const origin = options.origin ?? "";
273
+ const now = options.now ?? Date.now;
274
+ const navigate = options.navigate ?? ((url) => {
275
+ const { location } = globalThis;
276
+ if (!location || !("assign" in location) || typeof location.assign !== "function") throw new FederatedAuthError("configuration_error", "Provide navigate when browser navigation is unavailable");
277
+ location.assign(url);
278
+ });
279
+ const listeners = /* @__PURE__ */ new Set();
280
+ let state = { status: "anonymous" };
281
+ let rememberedReturnTo;
282
+ let completion;
283
+ const transition = (next) => {
284
+ state = next;
285
+ for (const listener of listeners) listener(next);
286
+ };
287
+ const refresh = async () => {
288
+ const response = await fetch(`${origin}${basePath}/session`, {
289
+ credentials: "same-origin",
290
+ headers: { accept: "application/json" }
291
+ });
292
+ if (response.status === 401) {
293
+ transition({ status: "anonymous" });
294
+ return state;
295
+ }
296
+ if (!response.ok) {
297
+ const error = new FederatedAuthError("authorization_failed", "The application session could not be read");
298
+ transition({
299
+ error,
300
+ status: "error"
301
+ });
302
+ return state;
303
+ }
304
+ const payload = await response.json();
305
+ const record = asRecord$2(payload);
306
+ const expiresAt = record === null ? null : parseExpiresAt(record.expiresAt);
307
+ const identity = parseIdentity(payload);
308
+ if (expiresAt === null || identity === null) {
309
+ const error = new FederatedAuthError("authorization_failed", "The application session is invalid");
310
+ transition({
311
+ error,
312
+ status: "error"
313
+ });
314
+ return state;
315
+ }
316
+ if (expiresAt <= now()) {
317
+ transition({ status: "expired" });
318
+ return state;
319
+ }
320
+ transition({
321
+ expiresAt,
322
+ identity,
323
+ status: "authenticated"
324
+ });
325
+ return state;
326
+ };
327
+ const beginLogin = ({ returnTo }) => {
328
+ rememberedReturnTo = returnTo;
329
+ const url = `${origin}${basePath}/login?returnTo=${encodeURIComponent(returnTo)}`;
330
+ transition({ status: "redirecting" });
331
+ navigate(url);
332
+ return Promise.resolve(url);
333
+ };
334
+ const complete = async () => {
335
+ await refresh();
336
+ if (state.status === "error") throw state.error;
337
+ return { returnTo: rememberedReturnTo ?? "/" };
338
+ };
339
+ const reauthorize = async (input) => {
340
+ const returnTo = input?.returnTo ?? rememberedReturnTo;
341
+ if (returnTo === void 0) throw new FederatedAuthError("reauthorization_required", "A same-origin return target is required to restart CI login");
342
+ return await beginLogin({ returnTo });
343
+ };
344
+ return {
345
+ beginLogin,
346
+ completeLogin: async () => {
347
+ if (completion !== void 0) return await completion;
348
+ const current = complete();
349
+ completion = current;
350
+ const retire = async () => {
351
+ try {
352
+ await current;
353
+ } catch {} finally {
354
+ if (completion === current) completion = void 0;
355
+ }
356
+ };
357
+ retire();
358
+ return await current;
359
+ },
360
+ getAccessToken: () => Promise.reject(new FederatedAuthError("reauthorization_required", "The CI access token stays on the application server")),
361
+ getState: () => state,
362
+ logout: async () => {
363
+ rememberedReturnTo = void 0;
364
+ completion = void 0;
365
+ try {
366
+ await fetch(`${origin}${basePath}/logout`, {
367
+ credentials: "same-origin",
368
+ method: "POST"
369
+ });
370
+ } catch {}
371
+ transition({ status: "anonymous" });
372
+ },
373
+ reauthorize,
374
+ subscribe: (listener) => {
375
+ listeners.add(listener);
376
+ return () => {
377
+ listeners.delete(listener);
378
+ };
379
+ }
380
+ };
381
+ };
382
+ /**
383
+ * Ten minutes: a skill run's five-minute AI deadline plus queueing and
384
+ * delivery slack. Long runs return a `runId` immediately; follow is how
385
+ * callers wait for their outcome.
386
+ */
387
+ const DEFAULT_FOLLOW_RUN_DEADLINE_MS = 6e5;
388
+ const DEFAULT_IDLE_MS = 50;
389
+ const MAX_BACKOFF_IDLE_MS = 1e3;
390
+ const terminalEventKinds = /* @__PURE__ */ new Set([
391
+ "cancelled",
392
+ "completed",
393
+ "failed"
394
+ ]);
395
+ const terminalSnapshotStatuses = /* @__PURE__ */ new Set([
396
+ "cancelled",
397
+ "failed",
398
+ "succeeded"
399
+ ]);
400
+ const asRecord$1 = (value) => {
401
+ if (typeof value !== "object" || value === null) return null;
402
+ return value;
403
+ };
404
+ const readString = (record, key) => {
405
+ const candidate = record[key];
406
+ return typeof candidate === "string" ? candidate : null;
407
+ };
408
+ const snapshotStatus = (value) => {
409
+ const record = asRecord$1(value);
410
+ if (record === null) return null;
411
+ const nested = asRecord$1(record.result);
412
+ return readString(record, "status") ?? (nested === null ? null : readString(nested, "status"));
413
+ };
414
+ const eventKind = (event) => {
415
+ const record = asRecord$1(event);
416
+ return record === null ? null : readString(record, "kind");
417
+ };
418
+ const idle = async (ms, sleep, signal) => {
419
+ if (ms <= 0 || signal?.aborted === true) return;
420
+ if (sleep !== void 0) {
421
+ await sleep(ms);
422
+ return;
423
+ }
424
+ const cleanup = new AbortController();
425
+ try {
426
+ await new Promise((resolve) => {
427
+ const timeout = setTimeout(resolve, ms);
428
+ signal?.addEventListener("abort", () => {
429
+ clearTimeout(timeout);
430
+ resolve();
431
+ }, {
432
+ once: true,
433
+ signal: cleanup.signal
434
+ });
435
+ });
436
+ } finally {
437
+ cleanup.abort();
438
+ }
439
+ };
440
+ const isTerminalEvent = (event) => {
441
+ const kind = eventKind(event);
442
+ return kind !== null && terminalEventKinds.has(kind);
443
+ };
444
+ /**
445
+ * Reads the pages after `page` of a run that has already finished and
446
+ * returns the last non-empty one. Stops at the first terminal event.
447
+ */
448
+ const readRemainingPages = async (runs, options, page) => {
449
+ let last = page;
450
+ while (last.nextCursor !== null && !last.events.some(isTerminalEvent)) {
451
+ options.signal?.throwIfAborted();
452
+ const next = await runs.stream({
453
+ afterCursor: last.nextCursor,
454
+ runId: options.runId
455
+ });
456
+ if (!next.ok) throw new Error(next.error.message);
457
+ if (next.value.events.length === 0) return last;
458
+ last = next.value;
459
+ }
460
+ return last;
461
+ };
462
+ /**
463
+ * Follows dedicated run events until a terminal event or snapshot status.
464
+ * Resolves with the last event page read.
465
+ */
466
+ const followRun = async (runs, options) => {
467
+ const deadlineMs = options.deadlineMs ?? 6e5;
468
+ const idleMs = options.idleMs ?? DEFAULT_IDLE_MS;
469
+ const maxIdleMs = Math.max(idleMs, MAX_BACKOFF_IDLE_MS);
470
+ const now = options.now ?? Date.now;
471
+ const deadline = now() + deadlineMs;
472
+ let afterCursor;
473
+ let emptyPolls = 0;
474
+ while (now() < deadline) {
475
+ options.signal?.throwIfAborted();
476
+ const snapshot = await runs.get({ runId: options.runId });
477
+ if (!snapshot.ok) throw new Error(snapshot.error.message);
478
+ const page = await runs.stream(afterCursor === void 0 ? { runId: options.runId } : {
479
+ afterCursor,
480
+ runId: options.runId
481
+ });
482
+ if (!page.ok) throw new Error(page.error.message);
483
+ if (page.value.events.some(isTerminalEvent)) return page.value;
484
+ const status = snapshotStatus(snapshot.value);
485
+ if (status !== null && terminalSnapshotStatuses.has(status)) return await readRemainingPages(runs, options, page.value);
486
+ if (page.value.nextCursor !== null) afterCursor = page.value.nextCursor;
487
+ emptyPolls = page.value.events.length === 0 ? emptyPolls + 1 : 0;
488
+ await idle(Math.min(idleMs * 2 ** emptyPolls, maxIdleMs), options.sleep, options.signal);
489
+ }
490
+ throw new Error("Run did not finish");
491
+ };
492
+ const clientSdkProtocolVersion = "v1";
493
+ const executionOptions = (organizationId, options) => options?.signal === void 0 ? { organizationId } : {
494
+ organizationId,
495
+ signal: options.signal
496
+ };
497
+ const scopedOperation = (transport, organizationId, operationId) => async (input, options) => await transport.execute(operationId, input, executionOptions(organizationId, options));
498
+ const rootOperation = (transport, operationId) => async (input, options) => {
499
+ const { organizationId, ...wireInput } = input;
500
+ return await transport.execute(operationId, wireInput, executionOptions(organizationId, options));
501
+ };
502
+ const domains = (transport, organizationId) => {
503
+ const operation = (operationId) => organizationId === void 0 ? rootOperation(transport, operationId) : scopedOperation(transport, organizationId, operationId);
504
+ return {
505
+ agents: {
506
+ invoke: operation("agents_invoke"),
507
+ list: operation("agents_list")
508
+ },
509
+ attachments: {
510
+ downloadUrl: operation("attachments_download_url"),
511
+ upload: operation("attachments_upload")
512
+ },
513
+ context: { get: operation("context_get") },
514
+ knowledge: {
515
+ autocomplete: operation("knowledge_autocomplete"),
516
+ createEntity: operation("knowledge_create_entity"),
517
+ createRelation: operation("knowledge_create_relation"),
518
+ deleteEntity: operation("knowledge_delete_entity"),
519
+ deleteRelation: operation("knowledge_delete_relation"),
520
+ facets: operation("knowledge_facets"),
521
+ getEntity: operation("knowledge_get_entity"),
522
+ getEntitySchemaSnapshot: operation("knowledge_get_entity_schema_snapshot"),
523
+ getSchemaQueryContext: operation("knowledge_get_schema_query_context"),
524
+ query: operation("knowledge_query"),
525
+ searchEntities: operation("knowledge_search_entities"),
526
+ traverse: operation("knowledge_traverse"),
527
+ updateEntity: operation("knowledge_update_entity"),
528
+ updateRelation: operation("knowledge_update_relation"),
529
+ upsertEntity: operation("knowledge_upsert_entity")
530
+ },
531
+ runs: {
532
+ cancel: operation("runs_cancel"),
533
+ follow: (async (options) => {
534
+ const orgId = organizationId ?? options.organizationId;
535
+ if (orgId === void 0 || orgId.length === 0) throw new TypeError("organizationId is required");
536
+ return await followRun({
537
+ get: (input) => transport.execute("runs_get", input, executionOptions(orgId, options)),
538
+ stream: (input) => transport.execute("runs_stream", input, executionOptions(orgId, options))
539
+ }, options);
540
+ }),
541
+ get: operation("runs_get"),
542
+ stream: operation("runs_stream")
543
+ },
544
+ skills: {
545
+ invoke: operation("skills_invoke"),
546
+ list: operation("skills_list")
547
+ },
548
+ workflows: {
549
+ invoke: operation("workflows_invoke"),
550
+ list: operation("workflows_list")
551
+ }
552
+ };
553
+ };
554
+ const createClient = (options) => {
555
+ const publicDomains = domains(options.transport);
556
+ const forOrganization = (organizationId) => {
557
+ if (organizationId.length === 0) throw new TypeError("organizationId is required");
558
+ return {
559
+ ...domains(options.transport, organizationId),
560
+ organizationId
561
+ };
562
+ };
563
+ return {
564
+ ...publicDomains,
565
+ context: {
566
+ ...publicDomains.context,
567
+ forOrganization
568
+ },
569
+ execute: options.transport.execute,
570
+ forOrganization,
571
+ protocolVersion: "v1"
572
+ };
573
+ };
574
+ const createProxyTransport = (options) => {
575
+ const serverUrl = parseOriginUrl(options.serverUrl, "serverUrl");
576
+ const path = parseMountPath(options.path);
577
+ const { authentication } = options;
578
+ return createHttpOperationTransport({
579
+ ...authentication.mode === "federated" ? { authorization: authentication.auth.getAccessToken } : { credentials: authentication.credentials ?? "same-origin" },
580
+ basePath: path,
581
+ fetch: options.fetch ?? globalThis.fetch,
582
+ origin: serverUrl
583
+ });
584
+ };
585
+ const mapAuthState = (state) => {
586
+ switch (state.status) {
587
+ case "authenticated":
588
+ if (state.identity === void 0) return {
589
+ message: "The application session is invalid",
590
+ status: "unavailable"
591
+ };
592
+ return {
593
+ expiresAt: new Date(state.expiresAt).toISOString(),
594
+ identity: state.identity,
595
+ status: "authenticated"
596
+ };
597
+ case "expired": return { status: "expired" };
598
+ case "error": return {
599
+ message: state.error.message,
600
+ status: "unavailable"
601
+ };
602
+ default: return { status: "anonymous" };
603
+ }
604
+ };
605
+ /** Browser runtime for a sealed-cookie BFF: session identity, proxy client, and run follow. */
606
+ const createDelegatedApplicationRuntime = (options) => {
607
+ const fetch = options.fetch ?? globalThis.fetch;
608
+ const authBasePath = options.authBasePath ?? "/api/auth";
609
+ const proxyPath = options.proxyPath ?? "/api";
610
+ const sessionAuth = createServerSessionAuth({
611
+ basePath: authBasePath,
612
+ fetch,
613
+ ...options.navigate && { navigate: options.navigate },
614
+ origin: options.origin
615
+ });
616
+ const client = createClient({ transport: createProxyTransport({
617
+ authentication: {
618
+ credentials: "same-origin",
619
+ mode: "server_delegated"
620
+ },
621
+ fetch,
622
+ path: proxyPath,
623
+ serverUrl: options.origin
624
+ }) });
625
+ let state = { status: "checking" };
626
+ let generation = 0;
627
+ const listeners = /* @__PURE__ */ new Set();
628
+ const publish = (next) => {
629
+ state = next;
630
+ for (const notify of listeners) notify();
631
+ };
632
+ const subscribe = (notify) => {
633
+ listeners.add(notify);
634
+ return () => {
635
+ listeners.delete(notify);
636
+ };
637
+ };
638
+ const refresh = async () => {
639
+ generation += 1;
640
+ const current = generation;
641
+ publish({ status: "checking" });
642
+ try {
643
+ await sessionAuth.completeLogin(`${options.origin}${authBasePath}/callback`);
644
+ if (current !== generation) return;
645
+ publish(mapAuthState(sessionAuth.getState()));
646
+ } catch (error) {
647
+ if (current === generation) publish({
648
+ message: error instanceof Error ? error.message : "CI session verification is unavailable",
649
+ status: "unavailable"
650
+ });
651
+ }
652
+ };
653
+ const login = async (returnTo) => {
654
+ generation += 1;
655
+ publish({ status: "anonymous" });
656
+ await sessionAuth.beginLogin({ returnTo });
657
+ };
658
+ const initialize = async (url) => {
659
+ await refresh();
660
+ if (state.status === "authenticated") return;
661
+ if (state.status === "anonymous" || state.status === "expired") await login(url);
662
+ };
663
+ const logout = async () => {
664
+ generation += 1;
665
+ await sessionAuth.logout();
666
+ publish({ status: "anonymous" });
667
+ };
668
+ const followRun = async (runId) => {
669
+ if (state.status !== "authenticated") throw new Error("A CI session is required to follow a run");
670
+ return await client.forOrganization(state.identity.organizationId).runs.follow({ runId });
671
+ };
672
+ const auth = sessionAuth;
673
+ return {
674
+ applicationId: options.applicationId,
675
+ auth,
676
+ client,
677
+ followRun,
678
+ getSnapshot: () => state,
679
+ initialize,
680
+ login,
681
+ logout,
682
+ refresh,
683
+ sessionAuth,
684
+ subscribe
685
+ };
686
+ };
687
+ const asRecord = (value) => {
688
+ if (typeof value !== "object" || value === null) return null;
689
+ return value;
690
+ };
691
+ const readRunId = (value) => {
692
+ const payload = asRecord(value);
693
+ const runId = payload === null ? null : payload.runId;
694
+ return typeof runId === "string" ? runId : null;
695
+ };
696
+ /** Posts a Fixed workflow BFF route and returns the admitted run id. */
697
+ const createFixedWorkflowClient = (options) => {
698
+ const fetch = options.fetch ?? globalThis.fetch;
699
+ return { invoke: async (input) => {
700
+ const response = await fetch(options.url(input), {
701
+ body: JSON.stringify(options.body === void 0 ? input : options.body(input)),
702
+ credentials: "same-origin",
703
+ headers: {
704
+ "content-type": "application/json",
705
+ "x-idempotency-key": crypto.randomUUID()
706
+ },
707
+ method: "POST"
708
+ });
709
+ const payload = await response.json();
710
+ const runId = readRunId(payload);
711
+ if (!response.ok || runId === null) throw new Error("Fixed workflow did not return a run id");
712
+ return { runId };
713
+ } };
714
+ };
715
+ const nextStreamCursor = (page) => page.nextCursor;
716
+ const createDirectTransport = (options) => {
717
+ const tenantUrl = parseOriginUrl(options.tenantUrl, "tenantUrl");
718
+ return createHttpOperationTransport({
719
+ authorization: options.auth.getAccessToken,
720
+ basePath: "/application/v1",
721
+ fetch: options.fetch ?? globalThis.fetch,
722
+ origin: tenantUrl
723
+ });
724
+ };
725
+ export { DEFAULT_FOLLOW_RUN_DEADLINE_MS, FederatedAuthError, clientOperationIds, clientSdkProtocolVersion, createClient, createDelegatedApplicationRuntime, createDirectTransport, createEntityHttpTransport, createEntityProtocol, createFederatedAuth, createFixedWorkflowClient, createProxyTransport, createResourceClient, createServerSessionAuth, followRun, isClientOperationId, nextStreamCursor };