@tangle-network/hub-sdk 0.29.0-develop.20260819122040.176c418 → 0.29.5

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 CHANGED
@@ -1,7 +1,330 @@
1
- import { SSEChunkParser } from "@tangle-network/sdk-telemetry";
1
+ import { SSEChunkParser } from "@tangle-network/agent-core/sse";
2
+ //#region src/json-init.ts
3
+ /** The init of a request whose body is JSON. Every client builds its bodies
4
+ * here, so the content type cannot drift from the encoding. */
5
+ function jsonInit(method, body) {
6
+ return {
7
+ method,
8
+ body: JSON.stringify(body),
9
+ headers: { "Content-Type": "application/json" }
10
+ };
11
+ }
12
+ //#endregion
13
+ //#region src/allowances.ts
14
+ var HubAllowancesClient = class {
15
+ constructor(request) {
16
+ this.request = request;
17
+ }
18
+ async plan(meter) {
19
+ return (await this.request(this.path(meter), { method: "GET" })).plan;
20
+ }
21
+ async setPlan(meter, plan) {
22
+ return (await this.request(this.path(meter), this.body("PUT", plan))).plan;
23
+ }
24
+ /** Admit one turn or refuse it. Retrying the same `turnId` never counts twice. */
25
+ async admit(meter, input) {
26
+ return (await this.request(`${this.path(meter)}/turns`, this.body("POST", input))).allowance;
27
+ }
28
+ /** What the member's next turn would get, without counting anything. */
29
+ async status(meter, subject) {
30
+ const query = new URLSearchParams();
31
+ if (subject.role) query.set("role", subject.role);
32
+ if (subject.payer) query.set("payer", subject.payer);
33
+ if (subject.payerRole) query.set("payerRole", subject.payerRole);
34
+ const suffix = query.size ? `?${query}` : "";
35
+ return (await this.request(`${this.memberPath(meter, subject.member)}${suffix}`, { method: "GET" })).allowance;
36
+ }
37
+ /** Move a member to the paid allowance until `expiresAt` (the paid period's end). */
38
+ async grant(meter, member, input) {
39
+ return (await this.request(`${this.memberPath(meter, member)}/grant`, this.body("PUT", {
40
+ tier: "paid",
41
+ ...input
42
+ }))).grant;
43
+ }
44
+ /** Synchronize a paid grant by durable product revision. Null expiry writes
45
+ * a retained revocation. A delayed older update is refused, never reactivated.
46
+ * There is no fallback to the unversioned grant endpoint on older servers. */
47
+ async syncGrant(meter, member, input) {
48
+ const { grant } = await this.request(`${this.memberPath(meter, member)}/grant/sync`, this.body("PUT", input));
49
+ if (grant.member !== member || grant.source !== input.source || grant.revision !== input.revision || grant.reference !== input.reference || grant.expiresAt !== (input.expiresAt === null ? null : new Date(input.expiresAt).toISOString())) throw new Error("The Hub returned a mismatched grant synchronization receipt");
50
+ return grant;
51
+ }
52
+ async revoke(meter, member) {
53
+ await this.request(`${this.memberPath(meter, member)}/grant`, { method: "DELETE" });
54
+ }
55
+ /** Turns per member and per line on one UTC day (default today). */
56
+ async usage(meter, day) {
57
+ const suffix = day ? `?day=${encodeURIComponent(day)}` : "";
58
+ return (await this.request(`${this.path(meter)}/usage${suffix}`, { method: "GET" })).usage;
59
+ }
60
+ path(meter) {
61
+ return `/v1/hub/allowances/${encodeURIComponent(meter)}`;
62
+ }
63
+ memberPath(meter, member) {
64
+ return `${this.path(meter)}/members/${encodeURIComponent(member)}`;
65
+ }
66
+ body(method, body) {
67
+ return jsonInit(method, body);
68
+ }
69
+ };
70
+ //#endregion
71
+ //#region src/apps.ts
72
+ function encodedId(id) {
73
+ if (typeof id !== "string" || !id || id.length > 256 || id === "." || id === "..") throw new Error("A bounded app or grant id is required");
74
+ return encodeURIComponent(id);
75
+ }
76
+ var HubAppsClient = class {
77
+ grants;
78
+ constructor(request) {
79
+ this.request = request;
80
+ this.grants = {
81
+ list: () => this.request("/v1/me/app-grants", { method: "GET" }),
82
+ revoke: async (grantId) => this.request(`/v1/me/app-grants/${encodedId(grantId)}/revoke`, { method: "POST" })
83
+ };
84
+ }
85
+ list() {
86
+ return this.request("/v1/apps", { method: "GET" });
87
+ }
88
+ /** The secret is returned only by creation; keep it in your server vault. */
89
+ create(input) {
90
+ return this.request("/v1/apps", jsonInit("POST", input));
91
+ }
92
+ /** Immutable association of two existing records with the same owner. */
93
+ async linkOAuthClient(appId, clientId) {
94
+ return this.request(`/v1/apps/${encodedId(appId)}/oidc-client`, jsonInit("POST", { clientId }));
95
+ }
96
+ async revoke(appId) {
97
+ return this.request(`/v1/apps/${encodedId(appId)}/revoke`, { method: "POST" });
98
+ }
99
+ };
100
+ //#endregion
101
+ //#region src/inbound-webhooks.ts
102
+ var HubInboundWebhooksClient = class {
103
+ constructor(request) {
104
+ this.request = request;
105
+ }
106
+ async create(connectionId, options = {}) {
107
+ return this.request(this.path(connectionId), jsonInit("POST", options));
108
+ }
109
+ async get(connectionId) {
110
+ return this.request(this.path(connectionId), { method: "GET" });
111
+ }
112
+ /** Add one owned Linq WhatsApp number without rotating the webhook secret. */
113
+ async ensurePhoneNumber(connectionId, phoneNumberId) {
114
+ if (!phoneNumberId || phoneNumberId.length > 128 || !/^[\x21-\x7e]+$/.test(phoneNumberId)) throw new Error("A bounded phone-number id is required");
115
+ return this.request(`${this.path(connectionId)}/phone-numbers/ensure`, jsonInit("POST", { phoneNumberId }));
116
+ }
117
+ /** Remove event subscriptions first. This does not silently rotate a live URL. */
118
+ async delete(connectionId) {
119
+ return this.request(this.path(connectionId), { method: "DELETE" });
120
+ }
121
+ path(connectionId) {
122
+ if (!connectionId || connectionId.length > 256) throw new Error("A bounded connection id is required");
123
+ return `/v1/hub/connections/${encodeURIComponent(connectionId)}/inbound-webhook`;
124
+ }
125
+ };
126
+ //#endregion
127
+ //#region src/knowledge-management.ts
128
+ var HubKnowledgeManagementClient = class {
129
+ constructor(request) {
130
+ this.request = request;
131
+ }
132
+ materialize(input) {
133
+ return this.request("/v1/knowledge/attachments/materialize", jsonInit("POST", input));
134
+ }
135
+ authorization() {
136
+ return this.request("/v1/knowledge/authorization", { method: "GET" });
137
+ }
138
+ disconnectAuthorization() {
139
+ return this.request("/v1/knowledge/authorization", { method: "DELETE" });
140
+ }
141
+ capabilities() {
142
+ return this.request("/v1/knowledge/capabilities", { method: "GET" });
143
+ }
144
+ list() {
145
+ return this.request("/v1/knowledge/sources", { method: "GET" });
146
+ }
147
+ get(sourceId) {
148
+ return this.request(`/v1/knowledge/sources/${encodeURIComponent(sourceId)}`, { method: "GET" });
149
+ }
150
+ create(input) {
151
+ return this.request("/v1/knowledge/sources", jsonInit("POST", input));
152
+ }
153
+ refresh(sourceId, input) {
154
+ return this.request(`/v1/knowledge/sources/${encodeURIComponent(sourceId)}/refresh`, jsonInit("POST", input));
155
+ }
156
+ attach(sourceId, input) {
157
+ return this.request(`/v1/knowledge/sources/${encodeURIComponent(sourceId)}/attachments`, jsonInit("POST", input));
158
+ }
159
+ attachments(sandboxId) {
160
+ return this.request(`/v1/knowledge/attachments${sandboxId ? `?sandboxId=${encodeURIComponent(sandboxId)}` : ""}`, { method: "GET" });
161
+ }
162
+ detach(attachmentId) {
163
+ return this.request(`/v1/knowledge/attachments/${encodeURIComponent(attachmentId)}`, { method: "DELETE" });
164
+ }
165
+ };
166
+ //#endregion
167
+ //#region src/knowledge.ts
168
+ /** Runtime-only retrieval. The authenticated sandbox credential determines the
169
+ * scope on the server; neither a caller nor an agent can supply another scope. */
170
+ var HubKnowledgeClient = class {
171
+ management;
172
+ constructor(request) {
173
+ this.request = request;
174
+ this.management = new HubKnowledgeManagementClient(request);
175
+ }
176
+ bindings() {
177
+ return this.request("/v1/knowledge/runtime/bindings", { method: "GET" });
178
+ }
179
+ query(input, options) {
180
+ return this.request("/v1/knowledge/runtime/query", {
181
+ ...jsonInit("POST", {
182
+ query: input.query,
183
+ ...input.expectedReferences !== void 0 ? { expectedReferences: input.expectedReferences } : {},
184
+ ...input.limit !== void 0 ? { limit: input.limit } : {}
185
+ }),
186
+ signal: options?.signal
187
+ });
188
+ }
189
+ };
190
+ //#endregion
191
+ //#region src/lines.ts
192
+ /** The `on.schedule` mapping a Line schedule helper writes. */
193
+ function scheduleYaml(cron, timezone, until) {
194
+ return ` schedule: { ${[
195
+ `cron: ${JSON.stringify(cron)}`,
196
+ `timezone: ${JSON.stringify(timezone)}`,
197
+ ...until ? [`until: ${JSON.stringify(until)}`] : []
198
+ ].join(", ")} }`;
199
+ }
200
+ /** Line calls share HubClient authentication and envelope handling. */
201
+ var HubLinesClient = class {
202
+ members;
203
+ memberApplications;
204
+ checkins;
205
+ constructor(request, createWorkflow, requestApplicationDispatchCurrent) {
206
+ this.request = request;
207
+ this.createWorkflow = createWorkflow;
208
+ this.requestApplicationDispatchCurrent = requestApplicationDispatchCurrent;
209
+ this.members = { list: async (lineId) => (await this.request(`/v1/hub/lines/${encodeURIComponent(lineId)}/members`, { method: "GET" })).members };
210
+ const applicationPath = (lineId, memberId) => `/v1/hub/lines/${encodeURIComponent(lineId)}/members/${encodeURIComponent(memberId)}/application`;
211
+ this.memberApplications = {
212
+ get: async (lineId, memberId) => await this.request(applicationPath(lineId, memberId), { method: "GET" }),
213
+ select: async (lineId, memberId, input) => (await this.request(applicationPath(lineId, memberId), jsonInit("PUT", input))).selection,
214
+ clear: async (lineId, memberId, input) => {
215
+ await this.request(applicationPath(lineId, memberId), jsonInit("DELETE", input));
216
+ }
217
+ };
218
+ this.checkins = { schedule: (cron, options) => {
219
+ const name = options.name ?? `line-checkin-${crypto.randomUUID().slice(0, 8)}`;
220
+ const yaml = [
221
+ `name: ${JSON.stringify(name)}`,
222
+ "on:",
223
+ scheduleYaml(cron, options.timezone, options.until),
224
+ "do:",
225
+ " - agent.run:",
226
+ " line:",
227
+ ` id: ${JSON.stringify(options.lineId)}`,
228
+ ` member: ${JSON.stringify(options.member)}`,
229
+ ` prompt: ${JSON.stringify(options.prompt)}`
230
+ ].join("\n");
231
+ return this.createWorkflow(yaml);
232
+ } };
233
+ }
234
+ async scheduleSend(cron, options) {
235
+ const name = options.name ?? `line-send-${crypto.randomUUID().slice(0, 8)}`;
236
+ const yaml = [
237
+ `name: ${JSON.stringify(name)}`,
238
+ "on:",
239
+ scheduleYaml(cron, options.timezone, options.until),
240
+ "do:",
241
+ " - line.send:",
242
+ ` lineId: ${JSON.stringify(options.lineId)}`,
243
+ ` memberId: ${JSON.stringify(options.memberId)}`,
244
+ ` text: ${JSON.stringify(options.text)}`,
245
+ ...options.notBefore ? [` notBefore: ${JSON.stringify(options.notBefore)}`] : []
246
+ ].join("\n");
247
+ return this.createWorkflow(yaml);
248
+ }
249
+ async context() {
250
+ return this.request("/v1/hub/lines/context", { method: "GET" });
251
+ }
252
+ async send(lineId, input) {
253
+ return this.request(`/v1/hub/lines/${encodeURIComponent(lineId)}/send`, jsonInit("POST", input));
254
+ }
255
+ /** Read an owner send's receipt, including its coded answer. Queued is not delivered. */
256
+ async sentMessage(lineId, messageId) {
257
+ return this.request(`/v1/hub/lines/${encodeURIComponent(lineId)}/outbox/${encodeURIComponent(messageId)}`, { method: "GET" });
258
+ }
259
+ async remind(lineId, input) {
260
+ return this.request(`/v1/hub/lines/${encodeURIComponent(lineId)}/remind`, jsonInit("POST", input));
261
+ }
262
+ async applicationDispatchCurrent(lineId, messageId, input, callbackBearer) {
263
+ if (!this.requestApplicationDispatchCurrent) throw new Error("Use HubClient to check application dispatch authority");
264
+ return (await this.requestApplicationDispatchCurrent(`/v1/hub/lines/${encodeURIComponent(lineId)}/messages/${encodeURIComponent(messageId)}/application-dispatch/current`, jsonInit("POST", input), callbackBearer)).current;
265
+ }
266
+ };
267
+ //#endregion
268
+ //#region src/managed-numbers.ts
269
+ /** Administrative number operations through Hub's authenticated transport.
270
+ * A mutation is one attempt; an uncertain create reuses its quote and reference. */
271
+ var HubManagedNumbersClient = class {
272
+ constructor(request) {
273
+ this.request = request;
274
+ }
275
+ readiness() {
276
+ return this.request("/v1/hub/numbers/readiness", { method: "GET" });
277
+ }
278
+ /** One page of orders. Scope by `clientReference` to ask about one binding,
279
+ * and follow `nextCursor` for the rest; a page is never ownership evidence. */
280
+ list(query = {}) {
281
+ const params = new URLSearchParams();
282
+ if (query.clientReference !== void 0) params.set("clientReference", query.clientReference);
283
+ if (query.cursor !== void 0) params.set("cursor", query.cursor);
284
+ if (query.limit !== void 0) params.set("limit", String(query.limit));
285
+ const search = params.toString();
286
+ return this.request(`/v1/hub/numbers${search ? `?${search}` : ""}`, { method: "GET" });
287
+ }
288
+ get(id) {
289
+ return this.request(this.path(id), { method: "GET" });
290
+ }
291
+ quote(input) {
292
+ return this.request("/v1/hub/numbers/quote", this.post(input));
293
+ }
294
+ /** `clientReference` and `transport`, when given, must match the signed
295
+ * quote or Hub refuses the purchase with a conflict. */
296
+ create(input) {
297
+ return this.request("/v1/hub/numbers", this.post(input));
298
+ }
299
+ /** Fences references so no order can take them again. Call it as the last
300
+ * step before retiring the resource they name: a purchase that races the
301
+ * retirement is then refused (`409 number_reference_fenced`) instead of
302
+ * binding a line to a resource that is gone. Refused with
303
+ * `409 number_reference_held` while a live order holds one of them; cancel
304
+ * it first. A cancelled order no longer holds its reference, so the fence
305
+ * succeeds as soon as `cancel` returns, whether or not its release is still
306
+ * pending. Repeating a fence is harmless. */
307
+ fence(input) {
308
+ return this.request("/v1/hub/numbers/fences", this.post(input));
309
+ }
310
+ advance(id) {
311
+ return this.request(`${this.path(id)}/advance`, this.post({}));
312
+ }
313
+ cancel(id) {
314
+ return this.request(`${this.path(id)}/cancel`, this.post({}));
315
+ }
316
+ path(id) {
317
+ if (!/^num_[a-f0-9]{36}$/.test(id)) throw new Error("Invalid managed number order id");
318
+ return `/v1/hub/numbers/${encodeURIComponent(id)}`;
319
+ }
320
+ post(body) {
321
+ return jsonInit("POST", body);
322
+ }
323
+ };
324
+ //#endregion
2
325
  //#region src/redaction.ts
3
326
  const SECRET_KEY_PATTERN = /(api[_-]?key|authorization|capability[_-]?token|token|secret)/i;
4
- const SECRET_VALUE_PATTERN = /\b(?:sk-tan[-_]|hubcap_|hct_|gh[pousr]_)[A-Za-z0-9_=-]+\b/g;
327
+ const SECRET_VALUE_PATTERN = /\b(?:sk-tan[-_]|hubcap_|hct_|lrc_|gh[pousr]_)[A-Za-z0-9_.=-]+\b/g;
5
328
  function redactHubValue(value) {
6
329
  if (typeof value === "string") return value.replace(SECRET_VALUE_PATTERN, "[REDACTED]");
7
330
  if (Array.isArray(value)) return value.map((item) => redactHubValue(item));
@@ -26,16 +349,18 @@ function bodySnippet(body, max = 200) {
26
349
  const trimmed = body.trim();
27
350
  return trimmed.length > max ? `${trimmed.slice(0, max)}…` : trimmed;
28
351
  }
29
- function httpError(path, status, statusText, body, contentType) {
30
- const suffix = statusText ? ` ${statusText}` : "";
352
+ function httpError(path, status, statusText, body, contentType, omitResponseDetails = false) {
353
+ const suffix = !omitResponseDetails && statusText ? ` ${statusText}` : "";
31
354
  return new HubSdkError({
32
355
  code: `HUB_HTTP_${status}`,
33
356
  message: `Hub request ${path} failed: HTTP ${status}${suffix}`,
34
357
  details: {
35
358
  status,
36
359
  path,
37
- contentType: contentType || null,
38
- bodySnippet: bodySnippet(body)
360
+ ...omitResponseDetails ? {} : {
361
+ contentType: contentType || null,
362
+ bodySnippet: bodySnippet(body)
363
+ }
39
364
  }
40
365
  }, { status });
41
366
  }
@@ -52,6 +377,9 @@ function isHubSuccessEnvelope(value) {
52
377
  function isRecord$1(value) {
53
378
  return typeof value === "object" && value !== null;
54
379
  }
380
+ function isDispatchCurrentData(value) {
381
+ return isRecord$1(value) && typeof value.current === "boolean";
382
+ }
55
383
  /** Read a wire field as a string, or "" when the server sent anything else.
56
384
  * `raw` is that field straight off the parsed payload, before any check. */
57
385
  function optString(raw) {
@@ -222,13 +550,69 @@ function nonEmpty(value) {
222
550
  function readProcessEnv() {
223
551
  return globalThis.process?.env ?? {};
224
552
  }
225
- var HubClient = class HubClient {
553
+ var HubTransport = class {
226
554
  baseUrl;
227
555
  fetch;
228
556
  apiKey;
229
557
  authHeaders;
558
+ constructor(options) {
559
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
560
+ this.apiKey = options.apiKey;
561
+ this.authHeaders = options.authHeaders;
562
+ this.fetch = options.fetch ?? fetch.bind(globalThis);
563
+ }
564
+ async request(path, init, options = {}) {
565
+ const callerAuth = options.auth === "caller";
566
+ const response = await this.fetch(`${this.baseUrl}${path}`, {
567
+ ...init,
568
+ headers: await this.buildHeaders(init.headers, callerAuth)
569
+ });
570
+ const status = response.status;
571
+ const contentType = response.headers.get("content-type") ?? "";
572
+ const bodyText = await response.text();
573
+ if (!contentType.includes("application/json")) throw httpError(path, status, response.statusText, bodyText, contentType, callerAuth);
574
+ let parsed;
575
+ try {
576
+ parsed = JSON.parse(bodyText);
577
+ } catch {
578
+ throw httpError(path, status, response.statusText, bodyText, contentType, callerAuth);
579
+ }
580
+ if (isHubErrorEnvelope(parsed)) {
581
+ if (callerAuth) throw new HubSdkError({
582
+ code: parsed.error.code,
583
+ message: `Hub request ${path} failed: HTTP ${status}`
584
+ }, { status });
585
+ throw new HubSdkError(parsed.error, { status });
586
+ }
587
+ if (isHubSuccessEnvelope(parsed)) {
588
+ if (callerAuth && !response.ok) throw httpError(path, status, response.statusText, bodyText, contentType, callerAuth);
589
+ if (options.validateData && !options.validateData(parsed.data)) throw new HubSdkError({
590
+ code: `HUB_HTTP_${status}`,
591
+ message: `Hub request ${path} returned invalid data`,
592
+ details: {
593
+ status,
594
+ path
595
+ }
596
+ }, { status });
597
+ return parsed.data;
598
+ }
599
+ throw httpError(path, status, response.statusText, bodyText, contentType, callerAuth);
600
+ }
601
+ async buildHeaders(headers, callerAuth = false) {
602
+ const mergedHeaders = new Headers({ Accept: "application/json" });
603
+ if (!callerAuth && this.apiKey) mergedHeaders.set("Authorization", `Bearer ${this.apiKey}`);
604
+ if (!callerAuth && this.authHeaders) for (const [key, value] of new Headers(await this.authHeaders())) mergedHeaders.set(key, value);
605
+ for (const [key, value] of new Headers(headers)) mergedHeaders.set(key, value);
606
+ return mergedHeaders;
607
+ }
608
+ };
609
+ var HubClient = class HubClient extends HubTransport {
610
+ knowledge;
611
+ allowances;
612
+ apps;
230
613
  connections;
231
614
  channels;
615
+ numbers;
232
616
  eventSubscriptions;
233
617
  permissions;
234
618
  tokens;
@@ -237,11 +621,13 @@ var HubClient = class HubClient {
237
621
  audit;
238
622
  githubApp;
239
623
  workflows;
624
+ lines;
240
625
  constructor(options) {
241
- this.baseUrl = options.baseUrl.replace(/\/+$/, "");
242
- this.apiKey = options.apiKey;
243
- this.authHeaders = options.authHeaders;
244
- this.fetch = options.fetch ?? fetch.bind(globalThis);
626
+ super(options);
627
+ this.knowledge = new HubKnowledgeClient((path, init) => this.request(path, init));
628
+ this.allowances = new HubAllowancesClient((path, init) => this.request(path, init));
629
+ this.apps = new HubAppsClient((path, init) => this.request(path, init));
630
+ this.numbers = new HubManagedNumbersClient((path, init) => this.request(path, init));
245
631
  this.connections = new HubConnectionsClient((path, init) => this.request(path, init));
246
632
  this.channels = new HubChannelsClient((path, init) => this.request(path, init));
247
633
  this.eventSubscriptions = new HubEventSubscriptionsClient((path, init) => this.request(path, init));
@@ -252,6 +638,23 @@ var HubClient = class HubClient {
252
638
  this.audit = new HubAuditClient((path, init) => this.request(path, init));
253
639
  this.githubApp = new HubGithubAppClient((path, init) => this.request(path, init));
254
640
  this.workflows = new HubWorkflowsClient((path, init) => this.request(path, init), (path, init) => this.stream(path, init), (path, init) => this.requestBytes(path, init));
641
+ this.lines = new HubLinesClient((path, init) => this.request(path, init), (yaml) => this.workflows.create(yaml), (path, init, callbackBearer) => {
642
+ if (typeof callbackBearer !== "string" || !/^[\x21-\x7e]{16,512}$/.test(callbackBearer)) throw new HubSdkError({
643
+ code: "HUB_INVALID_INPUT",
644
+ message: "Application callback bearer is invalid"
645
+ });
646
+ const headers = new Headers(init.headers);
647
+ headers.set("Authorization", `Bearer ${callbackBearer}`);
648
+ return this.request(path, {
649
+ ...init,
650
+ headers,
651
+ credentials: "omit",
652
+ redirect: "manual"
653
+ }, {
654
+ auth: "caller",
655
+ validateData: isDispatchCurrentData
656
+ });
657
+ });
255
658
  }
256
659
  static fromEnv(options = {}) {
257
660
  const env = options.env ?? readProcessEnv();
@@ -265,25 +668,6 @@ var HubClient = class HubClient {
265
668
  async status() {
266
669
  return this.request("/v1/hub/status", { method: "GET" });
267
670
  }
268
- async request(path, init) {
269
- const response = await this.fetch(`${this.baseUrl}${path}`, {
270
- ...init,
271
- headers: await this.buildHeaders(init.headers)
272
- });
273
- const status = response.status;
274
- const contentType = response.headers.get("content-type") ?? "";
275
- const bodyText = await response.text();
276
- if (!contentType.includes("application/json")) throw httpError(path, status, response.statusText, bodyText, contentType);
277
- let parsed;
278
- try {
279
- parsed = JSON.parse(bodyText);
280
- } catch {
281
- throw httpError(path, status, response.statusText, bodyText, contentType);
282
- }
283
- if (isHubErrorEnvelope(parsed)) throw new HubSdkError(parsed.error, { status });
284
- if (isHubSuccessEnvelope(parsed)) return parsed.data;
285
- throw httpError(path, status, response.statusText, bodyText, contentType);
286
- }
287
671
  /**
288
672
  * Open a streaming (Server-Sent Events) response and return its raw body.
289
673
  * Shares auth-header building + `fetch` with {@link request}, but does NOT
@@ -350,25 +734,13 @@ var HubClient = class HubClient {
350
734
  contentType
351
735
  };
352
736
  }
353
- async buildHeaders(headers) {
354
- const mergedHeaders = new Headers(headers);
355
- if (!mergedHeaders.has("Accept")) mergedHeaders.set("Accept", "application/json");
356
- if (this.apiKey) mergedHeaders.set("Authorization", `Bearer ${this.apiKey}`);
357
- if (this.authHeaders) for (const [key, value] of new Headers(await this.authHeaders())) mergedHeaders.set(key, value);
358
- for (const [key, value] of new Headers(headers)) mergedHeaders.set(key, value);
359
- return mergedHeaders;
360
- }
361
737
  };
362
738
  var HubTokensClient = class {
363
739
  constructor(request) {
364
740
  this.request = request;
365
741
  }
366
742
  async mint(options) {
367
- return this.request("/v1/hub/tokens", {
368
- method: "POST",
369
- body: JSON.stringify(options),
370
- headers: { "Content-Type": "application/json" }
371
- });
743
+ return this.request("/v1/hub/tokens", jsonInit("POST", options));
372
744
  }
373
745
  async list(options = {}) {
374
746
  const searchParams = new URLSearchParams();
@@ -402,38 +774,22 @@ var HubPermissionsClient = class {
402
774
  return this.request(`/v1/hub/policies?connectionId=${encodeURIComponent(connectionId)}`, { method: "GET" });
403
775
  }
404
776
  async set(input) {
405
- return this.request("/v1/hub/policies", {
406
- method: "PUT",
407
- body: JSON.stringify(input),
408
- headers: { "Content-Type": "application/json" }
409
- });
777
+ return this.request("/v1/hub/policies", jsonInit("PUT", input));
410
778
  }
411
779
  /** Reset an action to its default by deleting any stored override. */
412
780
  async delete(input) {
413
- return this.request("/v1/hub/policies", {
414
- method: "DELETE",
415
- body: JSON.stringify(input),
416
- headers: { "Content-Type": "application/json" }
417
- });
781
+ return this.request("/v1/hub/policies", jsonInit("DELETE", input));
418
782
  }
419
783
  /** Bulk-allow every write action of a connection's provider. Reads are
420
784
  * already allowed for sandbox agents; destructive actions stay `ask`.
421
785
  * Idempotent: actions with an existing policy are left untouched. */
422
786
  async allowWrites(connectionId) {
423
- return this.request("/v1/hub/policies/allow-writes", {
424
- method: "POST",
425
- body: JSON.stringify({ connectionId }),
426
- headers: { "Content-Type": "application/json" }
427
- });
787
+ return this.request("/v1/hub/policies/allow-writes", jsonInit("POST", { connectionId }));
428
788
  }
429
789
  /** Inverse of `allowWrites`: delete only the rows it created for this
430
790
  * connection. Manual per-action decisions are left intact. */
431
791
  async revertWrites(connectionId) {
432
- return this.request("/v1/hub/policies/revert-writes", {
433
- method: "POST",
434
- body: JSON.stringify({ connectionId }),
435
- headers: { "Content-Type": "application/json" }
436
- });
792
+ return this.request("/v1/hub/policies/revert-writes", jsonInit("POST", { connectionId }));
437
793
  }
438
794
  };
439
795
  var HubToolsClient = class {
@@ -441,25 +797,17 @@ var HubToolsClient = class {
441
797
  this.request = request;
442
798
  }
443
799
  async search(query, options = {}) {
444
- return this.request("/v1/hub/tools/search", {
445
- method: "POST",
446
- body: JSON.stringify({
447
- query,
448
- provider: options.provider,
449
- limit: options.limit
450
- }),
451
- headers: { "Content-Type": "application/json" }
452
- });
800
+ return this.request("/v1/hub/tools/search", jsonInit("POST", {
801
+ query,
802
+ provider: options.provider,
803
+ limit: options.limit
804
+ }));
453
805
  }
454
806
  async sources() {
455
807
  return this.request("/v1/hub/tools/sources", { method: "GET" });
456
808
  }
457
809
  async describe(path) {
458
- return this.request("/v1/hub/tools/describe", {
459
- method: "POST",
460
- body: JSON.stringify({ path }),
461
- headers: { "Content-Type": "application/json" }
462
- });
810
+ return this.request("/v1/hub/tools/describe", jsonInit("POST", { path }));
463
811
  }
464
812
  /**
465
813
  * Run one hub tool. `raw` is the tool input: the SDK forwards it verbatim as
@@ -472,8 +820,9 @@ var HubToolsClient = class {
472
820
  return await this.request("/v1/hub/exec", execInit);
473
821
  } catch (error) {
474
822
  if (!(error instanceof HubSdkError) || !requiresCapabilityToken(error)) throw error;
823
+ const approvalId = getApprovalId(error);
824
+ if (approvalId && options.approve !== true) throw error;
475
825
  if (options.approve) {
476
- const approvalId = getApprovalId(error);
477
826
  if (!approvalId) throw error;
478
827
  const approved = await this.request(`/v1/hub/approvals/${encodeURIComponent(approvalId)}/approve`, { method: "POST" });
479
828
  validateApprovedExecution(approved, path, options.connectionId);
@@ -491,15 +840,11 @@ var HubToolsClient = class {
491
840
  });
492
841
  }
493
842
  }
494
- const minted = await this.request("/v1/hub/tokens", {
495
- method: "POST",
496
- body: JSON.stringify({
497
- actionPath: path,
498
- connectionId: options.connectionId,
499
- provider: options.connectionId ? void 0 : path.split(".")[0]
500
- }),
501
- headers: { "Content-Type": "application/json" }
502
- });
843
+ const minted = await this.request("/v1/hub/tokens", jsonInit("POST", {
844
+ actionPath: path,
845
+ connectionId: options.connectionId,
846
+ provider: options.connectionId ? void 0 : path.split(".")[0]
847
+ }));
503
848
  return this.request("/v1/hub/exec", {
504
849
  ...execInit,
505
850
  headers: {
@@ -509,16 +854,12 @@ var HubToolsClient = class {
509
854
  });
510
855
  }
511
856
  buildExecInit(path, raw, options) {
512
- return {
513
- method: "POST",
514
- body: JSON.stringify({
515
- path,
516
- input: raw,
517
- connectionId: options.connectionId,
518
- idempotencyKey: options.idempotencyKey
519
- }),
520
- headers: { "Content-Type": "application/json" }
521
- };
857
+ return jsonInit("POST", {
858
+ path,
859
+ input: raw,
860
+ connectionId: options.connectionId,
861
+ idempotencyKey: options.idempotencyKey
862
+ });
522
863
  }
523
864
  };
524
865
  function requiresCapabilityToken(error) {
@@ -548,6 +889,17 @@ function validateApprovedExecution(approved, path, connectionId) {
548
889
  }
549
890
  });
550
891
  }
892
+ const GITHUB_EXACT_CONNECT_SCOPES = [
893
+ "read:user",
894
+ "repo",
895
+ "read:org"
896
+ ];
897
+ function sameOAuthScopeSet(left, right) {
898
+ if (!Array.isArray(left)) return false;
899
+ const leftSet = new Set(left);
900
+ const rightSet = new Set(right);
901
+ return leftSet.size === left.length && rightSet.size === right.length && leftSet.size === rightSet.size && [...leftSet].every((scope) => rightSet.has(scope));
902
+ }
551
903
  var HubChannelsClient = class {
552
904
  constructor(request) {
553
905
  this.request = request;
@@ -556,11 +908,7 @@ var HubChannelsClient = class {
556
908
  return this.request("/v1/hub/channels", { method: "GET" });
557
909
  }
558
910
  async createEmail(input = {}) {
559
- return this.request("/v1/hub/channels/email", {
560
- method: "POST",
561
- body: JSON.stringify(input),
562
- headers: { "Content-Type": "application/json" }
563
- });
911
+ return this.request("/v1/hub/channels/email", jsonInit("POST", input));
564
912
  }
565
913
  async delete(channelId) {
566
914
  return this.request(`/v1/hub/channels/${encodeURIComponent(channelId)}`, { method: "DELETE" });
@@ -574,19 +922,17 @@ var HubEventSubscriptionsClient = class {
574
922
  return this.request("/v1/hub/event-subscriptions", { method: "GET" });
575
923
  }
576
924
  async create(input) {
577
- return this.request("/v1/hub/event-subscriptions", {
578
- method: "POST",
579
- body: JSON.stringify(input),
580
- headers: { "Content-Type": "application/json" }
581
- });
925
+ return this.request("/v1/hub/event-subscriptions", jsonInit("POST", input));
582
926
  }
583
927
  async delete(subscriptionId) {
584
928
  return this.request(`/v1/hub/event-subscriptions/${encodeURIComponent(subscriptionId)}`, { method: "DELETE" });
585
929
  }
586
930
  };
587
931
  var HubConnectionsClient = class {
932
+ inboundWebhook;
588
933
  constructor(request) {
589
934
  this.request = request;
935
+ this.inboundWebhook = new HubInboundWebhooksClient(request);
590
936
  }
591
937
  async list() {
592
938
  return this.request("/v1/hub/connections", { method: "GET" });
@@ -598,40 +944,38 @@ var HubConnectionsClient = class {
598
944
  return this.request("/v1/hub/providers", { method: "GET" });
599
945
  }
600
946
  async start(provider, options = {}) {
601
- return this.request(`/v1/hub/connections/${encodeURIComponent(provider)}/start`, {
602
- method: "POST",
603
- body: JSON.stringify({
604
- returnUrl: options.returnUrl,
605
- cli: options.cli,
606
- connectionParameters: options.connectionParameters
607
- }),
608
- headers: { "Content-Type": "application/json" }
947
+ if (options.requestedScopes !== void 0 && (provider !== "github" || !sameOAuthScopeSet(options.requestedScopes, GITHUB_EXACT_CONNECT_SCOPES))) throw new HubSdkError({
948
+ code: "HUB_INVALID_INPUT",
949
+ message: "GitHub requestedScopes must be read:user, repo, and read:org"
950
+ });
951
+ const started = await this.request(`/v1/hub/connections/${encodeURIComponent(provider)}/start`, jsonInit("POST", {
952
+ returnUrl: options.returnUrl,
953
+ cli: options.cli,
954
+ connectionParameters: options.connectionParameters,
955
+ requestedScopes: options.requestedScopes
956
+ }));
957
+ if (options.requestedScopes !== void 0 && (started.provider !== provider || !sameOAuthScopeSet(started.scopes, options.requestedScopes))) throw new HubSdkError({
958
+ code: "HUB_CONFIG_MISSING",
959
+ message: "Hub did not preserve the requested OAuth scopes"
609
960
  });
961
+ return started;
610
962
  }
611
963
  async startWhatsappEmbeddedSignup(options = {}) {
612
- return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/start", {
613
- method: "POST",
614
- body: JSON.stringify({ returnUrl: options.returnUrl }),
615
- headers: { "Content-Type": "application/json" }
616
- });
964
+ return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/start", jsonInit("POST", { returnUrl: options.returnUrl }));
617
965
  }
618
966
  async completeWhatsappEmbeddedSignup(input) {
619
- return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/complete", {
620
- method: "POST",
621
- body: JSON.stringify(input),
622
- headers: { "Content-Type": "application/json" }
623
- });
967
+ return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/complete", jsonInit("POST", input));
624
968
  }
625
969
  /** Connect a non-OAuth (api-key) connector by submitting the user's key.
626
970
  * The server validates the key with a live probe before persisting and
627
971
  * returns the created (or reconnected) connection — there is no redirect,
628
- * unlike `start`. */
629
- async connectApiKey(provider, apiKey) {
630
- return this.request(`/v1/hub/connections/${encodeURIComponent(provider)}/connect-key`, {
631
- method: "POST",
632
- body: JSON.stringify({ apiKey }),
633
- headers: { "Content-Type": "application/json" }
634
- });
972
+ * unlike `start`. Cloudbeds requires propertyId metadata and PriceLabs
973
+ * requires listing ID/PMS pairs; other providers have no metadata. */
974
+ async connectApiKey(provider, apiKey, metadata) {
975
+ return this.request(`/v1/hub/connections/${encodeURIComponent(provider)}/connect-key`, jsonInit("POST", {
976
+ apiKey,
977
+ ...metadata === void 0 ? {} : { metadata }
978
+ }));
635
979
  }
636
980
  async revoke(connectionId) {
637
981
  return this.request(`/v1/hub/connections/${encodeURIComponent(connectionId)}`, { method: "DELETE" });
@@ -659,11 +1003,7 @@ var HubGithubAppClient = class {
659
1003
  this.request = request;
660
1004
  }
661
1005
  async mintInstallationToken(input) {
662
- return this.request("/v1/hub/github-app/mint-installation-token", {
663
- method: "POST",
664
- body: JSON.stringify(input),
665
- headers: { "Content-Type": "application/json" }
666
- });
1006
+ return this.request("/v1/hub/github-app/mint-installation-token", jsonInit("POST", input));
667
1007
  }
668
1008
  /** Every installation the caller may act through — their own and any org's
669
1009
  * they were verified against. The management surface's read. */
@@ -738,11 +1078,7 @@ var HubWorkflowsClient = class {
738
1078
  return this.request(`/v1/workflows/${encodeURIComponent(id)}`, { method: "GET" });
739
1079
  }
740
1080
  async create(yaml) {
741
- return this.request("/v1/workflows", {
742
- method: "POST",
743
- body: JSON.stringify({ yaml }),
744
- headers: { "Content-Type": "application/json" }
745
- });
1081
+ return this.request("/v1/workflows", jsonInit("POST", { yaml }));
746
1082
  }
747
1083
  /**
748
1084
  * Replace the workflow's definition from YAML (recompiled server-side,
@@ -753,14 +1089,10 @@ var HubWorkflowsClient = class {
753
1089
  * concurrent modification, `NOT_FOUND` for an unknown/foreign id.
754
1090
  */
755
1091
  async update(id, yaml, opts = {}) {
756
- return this.request(`/v1/workflows/${encodeURIComponent(id)}`, {
757
- method: "PUT",
758
- body: JSON.stringify({
759
- yaml,
760
- ...opts.note !== void 0 ? { note: opts.note } : {}
761
- }),
762
- headers: { "Content-Type": "application/json" }
763
- });
1092
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}`, jsonInit("PUT", {
1093
+ yaml,
1094
+ ...opts.note !== void 0 ? { note: opts.note } : {}
1095
+ }));
764
1096
  }
765
1097
  async delete(id) {
766
1098
  await this.request(`/v1/workflows/${encodeURIComponent(id)}`, { method: "DELETE" });
@@ -771,11 +1103,7 @@ var HubWorkflowsClient = class {
771
1103
  * via `update`, but it can still be paused/resumed here).
772
1104
  */
773
1105
  async setEnabled(id, enabled) {
774
- return this.request(`/v1/workflows/${encodeURIComponent(id)}`, {
775
- method: "PATCH",
776
- body: JSON.stringify({ enabled }),
777
- headers: { "Content-Type": "application/json" }
778
- });
1106
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}`, jsonInit("PATCH", { enabled }));
779
1107
  }
780
1108
  /**
781
1109
  * One page of run history, newest first. Pass the previous page's
@@ -803,9 +1131,7 @@ var HubWorkflowsClient = class {
803
1131
  async run(id, inputs, opts = {}) {
804
1132
  const hasInputs = inputs !== void 0 && Object.keys(inputs).length > 0;
805
1133
  return this.request(`/v1/workflows/${encodeURIComponent(id)}/run`, {
806
- method: "POST",
807
- body: JSON.stringify(hasInputs ? { inputs } : {}),
808
- headers: { "Content-Type": "application/json" },
1134
+ ...jsonInit("POST", hasInputs ? { inputs } : {}),
809
1135
  ...opts.signal ? { signal: opts.signal } : {}
810
1136
  });
811
1137
  }
@@ -984,11 +1310,7 @@ var HubWorkflowsClient = class {
984
1310
  * YAML no longer compiles, `CONFLICT` on a concurrent modification.
985
1311
  */
986
1312
  async rollback(id, rev) {
987
- return this.request(`/v1/workflows/${encodeURIComponent(id)}/rollback`, {
988
- method: "POST",
989
- body: JSON.stringify({ rev }),
990
- headers: { "Content-Type": "application/json" }
991
- });
1313
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}/rollback`, jsonInit("POST", { rev }));
992
1314
  }
993
1315
  /**
994
1316
  * Deterministic health stats over the workflow's last N terminal runs
@@ -1009,11 +1331,7 @@ var HubWorkflowsClient = class {
1009
1331
  * until an owner approves.
1010
1332
  */
1011
1333
  async propose(id, input) {
1012
- return this.request(`/v1/workflows/${encodeURIComponent(id)}/proposals`, {
1013
- method: "POST",
1014
- body: JSON.stringify(input),
1015
- headers: { "Content-Type": "application/json" }
1016
- });
1334
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}/proposals`, jsonInit("POST", input));
1017
1335
  }
1018
1336
  /**
1019
1337
  * Pending (proposed) and declined (rejected) revisions, newest first, with
@@ -1040,11 +1358,7 @@ var HubWorkflowsClient = class {
1040
1358
  * pending proposal.
1041
1359
  */
1042
1360
  async rejectProposal(id, rev, opts = {}) {
1043
- return this.request(`/v1/workflows/${encodeURIComponent(id)}/proposals/${encodeURIComponent(String(rev))}/reject`, {
1044
- method: "POST",
1045
- body: JSON.stringify(opts.reason !== void 0 ? { reason: opts.reason } : {}),
1046
- headers: { "Content-Type": "application/json" }
1047
- });
1361
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}/proposals/${encodeURIComponent(String(rev))}/reject`, jsonInit("POST", opts.reason !== void 0 ? { reason: opts.reason } : {}));
1048
1362
  }
1049
1363
  /**
1050
1364
  * Mint a fresh webhook hook token, retiring the current one immediately.
@@ -1124,11 +1438,7 @@ var HubWorkflowsClient = class {
1124
1438
  * value, stored and read back verbatim, so the client never gives it a shape.
1125
1439
  */
1126
1440
  async putKv(id, key, raw) {
1127
- return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, {
1128
- method: "PUT",
1129
- body: JSON.stringify({ value: raw }),
1130
- headers: { "Content-Type": "application/json" }
1131
- });
1441
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, jsonInit("PUT", { value: raw }));
1132
1442
  }
1133
1443
  /**
1134
1444
  * Compare-and-swap: the write lands only when the stored version still
@@ -1138,14 +1448,10 @@ var HubWorkflowsClient = class {
1138
1448
  * a shape.
1139
1449
  */
1140
1450
  async casKv(id, key, expectedVersion, raw) {
1141
- return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}/cas`, {
1142
- method: "POST",
1143
- body: JSON.stringify({
1144
- expectedVersion,
1145
- value: raw
1146
- }),
1147
- headers: { "Content-Type": "application/json" }
1148
- });
1451
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}/cas`, jsonInit("POST", {
1452
+ expectedVersion,
1453
+ value: raw
1454
+ }));
1149
1455
  }
1150
1456
  /** Delete a key; idempotent — `deleted` reports whether a row was removed. */
1151
1457
  async deleteKv(id, key) {
@@ -1165,11 +1471,7 @@ var HubWorkflowsClient = class {
1165
1471
  * (400); an unknown template id throws `NOT_FOUND`.
1166
1472
  */
1167
1473
  async instantiateTemplate(templateId, parameters) {
1168
- return this.request(`/v1/workflows/templates/${encodeURIComponent(templateId)}/instantiate`, {
1169
- method: "POST",
1170
- body: JSON.stringify(parameters ? { parameters } : {}),
1171
- headers: { "Content-Type": "application/json" }
1172
- });
1474
+ return this.request(`/v1/workflows/templates/${encodeURIComponent(templateId)}/instantiate`, jsonInit("POST", parameters ? { parameters } : {}));
1173
1475
  }
1174
1476
  /**
1175
1477
  * The caller's open decisions across all workflows — the always-available
@@ -1196,11 +1498,7 @@ var HubWorkflowsClient = class {
1196
1498
  * (409 — answered, timed out, or cancelled first).
1197
1499
  */
1198
1500
  async resolveRunDecision(id, runId, input) {
1199
- return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/decision/resolve`, {
1200
- method: "POST",
1201
- body: JSON.stringify(input),
1202
- headers: { "Content-Type": "application/json" }
1203
- });
1501
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/decision/resolve`, jsonInit("POST", input));
1204
1502
  }
1205
1503
  /**
1206
1504
  * Dry-run a YAML definition against the platform compiler without saving it.
@@ -1213,17 +1511,93 @@ var HubWorkflowsClient = class {
1213
1511
  * the unconnected providers is the point.
1214
1512
  */
1215
1513
  async validate(yaml, opts = {}) {
1216
- return this.request("/v1/workflows/validate", {
1514
+ return this.request("/v1/workflows/validate", jsonInit("POST", {
1515
+ yaml,
1516
+ ...opts.structural ? { structural: true } : {}
1517
+ }));
1518
+ }
1519
+ async schema() {
1520
+ return this.request("/v1/workflows/schema", { method: "GET" });
1521
+ }
1522
+ };
1523
+ //#endregion
1524
+ //#region src/broker.ts
1525
+ /** Server-only client. App secrets never become a user's general API key.
1526
+ * Every invocation mints and consumes one short-lived, exact-grant token. */
1527
+ var HubAppBrokerClient = class {
1528
+ baseUrl;
1529
+ fetcher;
1530
+ constructor(options) {
1531
+ this.options = options;
1532
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
1533
+ this.fetcher = options.fetch ?? fetch.bind(globalThis);
1534
+ }
1535
+ async token(path, body) {
1536
+ const response = await this.fetcher(`${this.baseUrl}${path}`, {
1217
1537
  method: "POST",
1538
+ redirect: "error",
1539
+ headers: { "Content-Type": "application/json" },
1218
1540
  body: JSON.stringify({
1219
- yaml,
1220
- ...opts.structural ? { structural: true } : {}
1221
- }),
1222
- headers: { "Content-Type": "application/json" }
1541
+ ...body,
1542
+ client_id: this.options.clientId,
1543
+ client_secret: this.options.clientSecret
1544
+ })
1223
1545
  });
1546
+ const result = await response.json().catch(() => null);
1547
+ if (!response.ok || !result || typeof result !== "object" || Array.isArray(result)) throw new HubSdkError({
1548
+ code: "HUB_CONFIG_MISSING",
1549
+ message: "Application authorization is unavailable"
1550
+ }, { status: response.status });
1551
+ return result;
1552
+ }
1553
+ readToken(body) {
1554
+ if (typeof body.access_token !== "string" || !body.access_token.startsWith("sk-tan-broker-") || body.token_type !== "Bearer" || typeof body.expires_in !== "number" || !Number.isSafeInteger(body.expires_in) || body.expires_in <= 0 || typeof body.scope !== "string") throw new HubSdkError({
1555
+ code: "HUB_CONFIG_MISSING",
1556
+ message: "Invalid application authorization response"
1557
+ });
1558
+ return {
1559
+ accessToken: body.access_token,
1560
+ expiresIn: body.expires_in,
1561
+ scopes: body.scope.split(" ").filter(Boolean)
1562
+ };
1224
1563
  }
1225
- async schema() {
1226
- return this.request("/v1/workflows/schema", { method: "GET" });
1564
+ async exchange(input) {
1565
+ const body = await this.token("/v1/apps/oauth/token", {
1566
+ grant_type: "authorization_code",
1567
+ code: input.code,
1568
+ redirect_uri: input.redirectUri,
1569
+ connection_id: input.connectionId,
1570
+ ttl_seconds: 60
1571
+ });
1572
+ const token = this.readToken(body);
1573
+ if (typeof body.grant_id !== "string" || !body.grant_id || body.connection_id !== input.connectionId) throw new HubSdkError({
1574
+ code: "HUB_CONFIG_MISSING",
1575
+ message: "Application connection did not match consent"
1576
+ });
1577
+ return {
1578
+ ...token,
1579
+ grantId: body.grant_id,
1580
+ connectionId: input.connectionId
1581
+ };
1582
+ }
1583
+ async mint(grantId) {
1584
+ if (!/^[A-Za-z0-9_-]{1,256}$/.test(grantId)) throw new Error("Invalid application grant");
1585
+ return this.readToken(await this.token(`/v1/apps/grants/${encodeURIComponent(grantId)}/mint-broker-token`, {
1586
+ grant_id: grantId,
1587
+ ttl_seconds: 60
1588
+ }));
1589
+ }
1590
+ async invoke(grantId, connectionId, actionPath, input) {
1591
+ const token = await this.mint(grantId);
1592
+ if (!token.scopes.includes(actionPath)) throw new HubSdkError({
1593
+ code: "HUB_POLICY_DENIED",
1594
+ message: "This action was not granted to the application"
1595
+ });
1596
+ return new HubClient({
1597
+ baseUrl: this.baseUrl,
1598
+ apiKey: token.accessToken,
1599
+ fetch: this.fetcher
1600
+ }).tools.invoke(actionPath, input, { connectionId });
1227
1601
  }
1228
1602
  };
1229
1603
  //#endregion
@@ -1484,6 +1858,77 @@ function requestFailure(code, status, error, headers) {
1484
1858
  };
1485
1859
  }
1486
1860
  //#endregion
1487
- export { HUB_API_KEY_ENV_VAR, HUB_CAPABILITY_TOKEN_ENV_VAR, HUB_URL_ENV_VAR, HubApprovalsClient, HubAuditClient, HubChannelsClient, HubClient, HubConnectionsClient, HubEventDeliveryError, HubEventSubscriptionsClient, HubGithubAppClient, HubPermissionsClient, HubSdkError, HubTokensClient, HubToolsClient, HubWorkflowsClient, authenticateHubEventRequest, deriveHubEventCallbackSecret, parseHubEventDelivery, redactHubValue, resolveHubAuth, resolveHubBaseUrl, verifyHubEventSignature };
1861
+ //#region src/knowledge-approval.ts
1862
+ /** Called only after the owner reviews this exact pending effect and clicks
1863
+ * Approve. Re-read first: a stale tab cannot approve a substituted operation. */
1864
+ async function approveKnowledgeSource(input) {
1865
+ const { source, port, approvals } = input;
1866
+ if (source.status !== "awaiting-approval" || !source.approval || !source.refreshRequestId || source.approval.preview.sourceId !== source.id || source.approval.preview.requestId !== source.refreshRequestId) throw new Error("This source has no reviewable pending action. Reload knowledge.");
1867
+ const current = await port.get(source.id);
1868
+ if (current.id !== source.id || current.status !== "awaiting-approval" || current.refreshRequestId !== source.refreshRequestId || JSON.stringify(current.approval) !== JSON.stringify(source.approval)) throw new Error("The requested action changed. Reload knowledge and review it again.");
1869
+ let approvalToken;
1870
+ try {
1871
+ const decision = await approvals.approve(source.approval.id);
1872
+ if (decision.approval.id !== source.approval.id || decision.approval.actionPath !== source.approval.path || decision.approval.connectionId !== source.approval.preview.connectionId || decision.approval.providerId !== source.approval.preview.provider || decision.approval.status !== "approved" || !decision.capabilityToken?.token) throw new Error("The approval did not return a matching one-time capability. Reload knowledge.");
1873
+ approvalToken = decision.capabilityToken.token;
1874
+ return await port.refresh(source.id, {
1875
+ requestId: source.refreshRequestId,
1876
+ approvalToken
1877
+ });
1878
+ } finally {
1879
+ approvalToken = void 0;
1880
+ }
1881
+ }
1882
+ //#endregion
1883
+ //#region src/line-grants.ts
1884
+ function path(binding, suffix = "") {
1885
+ if (!binding.workspaceId || !binding.workspaceAgentId) throw new Error("A workspace and agent are required for a line grant");
1886
+ return `/v1/hub/line-grants/current${suffix}?${new URLSearchParams({
1887
+ workspaceId: binding.workspaceId,
1888
+ workspaceAgentId: binding.workspaceAgentId
1889
+ })}`;
1890
+ }
1891
+ /** Every call uses HubTransport's authHeaders, so one-use app broker tokens can be minted per request. */
1892
+ var HubLineGrantsClient = class extends HubTransport {
1893
+ current(binding) {
1894
+ return this.request(path(binding), { method: "GET" });
1895
+ }
1896
+ async line(binding) {
1897
+ return (await this.request(path(binding, "/line"), { method: "GET" })).line;
1898
+ }
1899
+ async attach(binding, input) {
1900
+ return (await this.request(path(binding, "/attachment"), jsonInit("PUT", input))).attachment;
1901
+ }
1902
+ detach(binding) {
1903
+ return this.request(path(binding, "/attachment"), { method: "DELETE" });
1904
+ }
1905
+ async threads(binding) {
1906
+ return (await this.request(path(binding, "/threads"), { method: "GET" })).threads;
1907
+ }
1908
+ async messages(binding, threadId) {
1909
+ if (!threadId || threadId.length > 256) throw new Error("A bounded line thread id is required");
1910
+ return (await this.request(path(binding, `/threads/${encodeURIComponent(threadId)}/messages`), { method: "GET" })).messages;
1911
+ }
1912
+ memberApplication(binding) {
1913
+ return this.request(path(binding, "/member-application"), { method: "GET" });
1914
+ }
1915
+ /**
1916
+ * Route the owner's texts to this app's callback; the incumbent attachment
1917
+ * stays. `replaceSelectionId` moves them from another app on the owner's
1918
+ * explicit choice.
1919
+ */
1920
+ async selectApplication(binding, input) {
1921
+ return (await this.request(path(binding, "/member-application"), jsonInit("PUT", input))).selection;
1922
+ }
1923
+ /** Return the owner's texts to the incumbent attachment. */
1924
+ async clearApplication(binding, input) {
1925
+ await this.request(path(binding, "/member-application"), jsonInit("DELETE", input));
1926
+ }
1927
+ revoke(binding) {
1928
+ return this.request(path(binding), { method: "DELETE" });
1929
+ }
1930
+ };
1931
+ //#endregion
1932
+ export { HUB_API_KEY_ENV_VAR, HUB_CAPABILITY_TOKEN_ENV_VAR, HUB_URL_ENV_VAR, HubAllowancesClient, HubAppBrokerClient, HubApprovalsClient, HubAppsClient, HubAuditClient, HubChannelsClient, HubClient, HubConnectionsClient, HubEventDeliveryError, HubEventSubscriptionsClient, HubGithubAppClient, HubInboundWebhooksClient, HubKnowledgeClient, HubKnowledgeManagementClient, HubLineGrantsClient, HubLinesClient, HubManagedNumbersClient, HubPermissionsClient, HubSdkError, HubTokensClient, HubToolsClient, HubWorkflowsClient, approveKnowledgeSource, authenticateHubEventRequest, deriveHubEventCallbackSecret, parseHubEventDelivery, redactHubValue, resolveHubAuth, resolveHubBaseUrl, verifyHubEventSignature };
1488
1933
 
1489
1934
  //# sourceMappingURL=index.js.map