@tangle-network/hub-sdk 0.28.0-develop.20260819054947.83fb85f → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,266 @@
1
1
  import { SSEChunkParser } from "@tangle-network/sdk-telemetry";
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/lines.ts
128
+ /** The `on.schedule` mapping a Line schedule helper writes. */
129
+ function scheduleYaml(cron, timezone, until) {
130
+ return ` schedule: { ${[
131
+ `cron: ${JSON.stringify(cron)}`,
132
+ `timezone: ${JSON.stringify(timezone)}`,
133
+ ...until ? [`until: ${JSON.stringify(until)}`] : []
134
+ ].join(", ")} }`;
135
+ }
136
+ /** Line calls share HubClient authentication and envelope handling. */
137
+ var HubLinesClient = class {
138
+ members;
139
+ memberApplications;
140
+ checkins;
141
+ constructor(request, createWorkflow, requestApplicationDispatchCurrent) {
142
+ this.request = request;
143
+ this.createWorkflow = createWorkflow;
144
+ this.requestApplicationDispatchCurrent = requestApplicationDispatchCurrent;
145
+ this.members = { list: async (lineId) => (await this.request(`/v1/hub/lines/${encodeURIComponent(lineId)}/members`, { method: "GET" })).members };
146
+ const applicationPath = (lineId, memberId) => `/v1/hub/lines/${encodeURIComponent(lineId)}/members/${encodeURIComponent(memberId)}/application`;
147
+ this.memberApplications = {
148
+ get: async (lineId, memberId) => await this.request(applicationPath(lineId, memberId), { method: "GET" }),
149
+ select: async (lineId, memberId, input) => (await this.request(applicationPath(lineId, memberId), jsonInit("PUT", input))).selection,
150
+ clear: async (lineId, memberId, input) => {
151
+ await this.request(applicationPath(lineId, memberId), jsonInit("DELETE", input));
152
+ }
153
+ };
154
+ this.checkins = { schedule: (cron, options) => {
155
+ const name = options.name ?? `line-checkin-${crypto.randomUUID().slice(0, 8)}`;
156
+ const yaml = [
157
+ `name: ${JSON.stringify(name)}`,
158
+ "on:",
159
+ scheduleYaml(cron, options.timezone, options.until),
160
+ "do:",
161
+ " - agent.run:",
162
+ " line:",
163
+ ` id: ${JSON.stringify(options.lineId)}`,
164
+ ` member: ${JSON.stringify(options.member)}`,
165
+ ` prompt: ${JSON.stringify(options.prompt)}`
166
+ ].join("\n");
167
+ return this.createWorkflow(yaml);
168
+ } };
169
+ }
170
+ async scheduleSend(cron, options) {
171
+ const name = options.name ?? `line-send-${crypto.randomUUID().slice(0, 8)}`;
172
+ const yaml = [
173
+ `name: ${JSON.stringify(name)}`,
174
+ "on:",
175
+ scheduleYaml(cron, options.timezone, options.until),
176
+ "do:",
177
+ " - line.send:",
178
+ ` lineId: ${JSON.stringify(options.lineId)}`,
179
+ ` memberId: ${JSON.stringify(options.memberId)}`,
180
+ ` text: ${JSON.stringify(options.text)}`,
181
+ ...options.notBefore ? [` notBefore: ${JSON.stringify(options.notBefore)}`] : []
182
+ ].join("\n");
183
+ return this.createWorkflow(yaml);
184
+ }
185
+ async context() {
186
+ return this.request("/v1/hub/lines/context", { method: "GET" });
187
+ }
188
+ async send(lineId, input) {
189
+ return this.request(`/v1/hub/lines/${encodeURIComponent(lineId)}/send`, jsonInit("POST", input));
190
+ }
191
+ /** Read an owner send's receipt, including its coded answer. Queued is not delivered. */
192
+ async sentMessage(lineId, messageId) {
193
+ return this.request(`/v1/hub/lines/${encodeURIComponent(lineId)}/outbox/${encodeURIComponent(messageId)}`, { method: "GET" });
194
+ }
195
+ async remind(lineId, input) {
196
+ return this.request(`/v1/hub/lines/${encodeURIComponent(lineId)}/remind`, jsonInit("POST", input));
197
+ }
198
+ async applicationDispatchCurrent(lineId, messageId, input, callbackBearer) {
199
+ if (!this.requestApplicationDispatchCurrent) throw new Error("Use HubClient to check application dispatch authority");
200
+ return (await this.requestApplicationDispatchCurrent(`/v1/hub/lines/${encodeURIComponent(lineId)}/messages/${encodeURIComponent(messageId)}/application-dispatch/current`, jsonInit("POST", input), callbackBearer)).current;
201
+ }
202
+ };
203
+ //#endregion
204
+ //#region src/managed-numbers.ts
205
+ /** Administrative number operations through Hub's authenticated transport.
206
+ * A mutation is one attempt; an uncertain create reuses its quote and reference. */
207
+ var HubManagedNumbersClient = class {
208
+ constructor(request) {
209
+ this.request = request;
210
+ }
211
+ readiness() {
212
+ return this.request("/v1/hub/numbers/readiness", { method: "GET" });
213
+ }
214
+ /** One page of orders. Scope by `clientReference` to ask about one binding,
215
+ * and follow `nextCursor` for the rest; a page is never ownership evidence. */
216
+ list(query = {}) {
217
+ const params = new URLSearchParams();
218
+ if (query.clientReference !== void 0) params.set("clientReference", query.clientReference);
219
+ if (query.cursor !== void 0) params.set("cursor", query.cursor);
220
+ if (query.limit !== void 0) params.set("limit", String(query.limit));
221
+ const search = params.toString();
222
+ return this.request(`/v1/hub/numbers${search ? `?${search}` : ""}`, { method: "GET" });
223
+ }
224
+ get(id) {
225
+ return this.request(this.path(id), { method: "GET" });
226
+ }
227
+ quote(input) {
228
+ return this.request("/v1/hub/numbers/quote", this.post(input));
229
+ }
230
+ /** `clientReference` and `transport`, when given, must match the signed
231
+ * quote or Hub refuses the purchase with a conflict. */
232
+ create(input) {
233
+ return this.request("/v1/hub/numbers", this.post(input));
234
+ }
235
+ /** Fences references so no order can take them again. Call it as the last
236
+ * step before retiring the resource they name: a purchase that races the
237
+ * retirement is then refused (`409 number_reference_fenced`) instead of
238
+ * binding a line to a resource that is gone. Refused with
239
+ * `409 number_reference_held` while a live order holds one of them; cancel
240
+ * it first. A cancelled order no longer holds its reference, so the fence
241
+ * succeeds as soon as `cancel` returns, whether or not its release is still
242
+ * pending. Repeating a fence is harmless. */
243
+ fence(input) {
244
+ return this.request("/v1/hub/numbers/fences", this.post(input));
245
+ }
246
+ advance(id) {
247
+ return this.request(`${this.path(id)}/advance`, this.post({}));
248
+ }
249
+ cancel(id) {
250
+ return this.request(`${this.path(id)}/cancel`, this.post({}));
251
+ }
252
+ path(id) {
253
+ if (!/^num_[a-f0-9]{36}$/.test(id)) throw new Error("Invalid managed number order id");
254
+ return `/v1/hub/numbers/${encodeURIComponent(id)}`;
255
+ }
256
+ post(body) {
257
+ return jsonInit("POST", body);
258
+ }
259
+ };
260
+ //#endregion
2
261
  //#region src/redaction.ts
3
262
  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;
263
+ const SECRET_VALUE_PATTERN = /\b(?:sk-tan[-_]|hubcap_|hct_|lrc_|gh[pousr]_)[A-Za-z0-9_.=-]+\b/g;
5
264
  function redactHubValue(value) {
6
265
  if (typeof value === "string") return value.replace(SECRET_VALUE_PATTERN, "[REDACTED]");
7
266
  if (Array.isArray(value)) return value.map((item) => redactHubValue(item));
@@ -26,16 +285,18 @@ function bodySnippet(body, max = 200) {
26
285
  const trimmed = body.trim();
27
286
  return trimmed.length > max ? `${trimmed.slice(0, max)}…` : trimmed;
28
287
  }
29
- function httpError(path, status, statusText, body, contentType) {
30
- const suffix = statusText ? ` ${statusText}` : "";
288
+ function httpError(path, status, statusText, body, contentType, omitResponseDetails = false) {
289
+ const suffix = !omitResponseDetails && statusText ? ` ${statusText}` : "";
31
290
  return new HubSdkError({
32
291
  code: `HUB_HTTP_${status}`,
33
292
  message: `Hub request ${path} failed: HTTP ${status}${suffix}`,
34
293
  details: {
35
294
  status,
36
295
  path,
37
- contentType: contentType || null,
38
- bodySnippet: bodySnippet(body)
296
+ ...omitResponseDetails ? {} : {
297
+ contentType: contentType || null,
298
+ bodySnippet: bodySnippet(body)
299
+ }
39
300
  }
40
301
  }, { status });
41
302
  }
@@ -52,8 +313,13 @@ function isHubSuccessEnvelope(value) {
52
313
  function isRecord$1(value) {
53
314
  return typeof value === "object" && value !== null;
54
315
  }
55
- function optString(value) {
56
- return typeof value === "string" ? value : "";
316
+ function isDispatchCurrentData(value) {
317
+ return isRecord$1(value) && typeof value.current === "boolean";
318
+ }
319
+ /** Read a wire field as a string, or "" when the server sent anything else.
320
+ * `raw` is that field straight off the parsed payload, before any check. */
321
+ function optString(raw) {
322
+ return typeof raw === "string" ? raw : "";
57
323
  }
58
324
  /** An agent iteration's terminal status (`iteration.ended`) — only ever
59
325
  * succeeded/failed; an iteration is never guard-skipped. */
@@ -81,63 +347,63 @@ function isRunDoneStatus(value) {
81
347
  * malformed `token` can never reach a consumer as `{ delta: undefined }`, and
82
348
  * new server event kinds don't break older clients.
83
349
  */
84
- function toRunStreamEvent(eventType, data) {
350
+ function toRunStreamEvent(eventType, raw) {
85
351
  if (eventType === "ping") return { type: "ping" };
86
- if (!isRecord$1(data)) return null;
352
+ if (!isRecord$1(raw)) return null;
87
353
  switch (eventType) {
88
- case "snapshot": return typeof data.id === "string" && typeof data.workflowId === "string" && typeof data.status === "string" && Array.isArray(data.actionResults) ? {
354
+ case "snapshot": return typeof raw.id === "string" && typeof raw.workflowId === "string" && typeof raw.status === "string" && Array.isArray(raw.actionResults) ? {
89
355
  type: "snapshot",
90
- run: data
356
+ run: raw
91
357
  } : null;
92
- case "token": return typeof data.delta === "string" && typeof data.actionIndex === "number" ? {
358
+ case "token": return typeof raw.delta === "string" && typeof raw.actionIndex === "number" ? {
93
359
  type: "token",
94
- actionIndex: data.actionIndex,
95
- delta: data.delta,
96
- at: optString(data.at)
360
+ actionIndex: raw.actionIndex,
361
+ delta: raw.delta,
362
+ at: optString(raw.at)
97
363
  } : null;
98
- case "run.done": return isRunDoneStatus(data.status) ? {
364
+ case "run.done": return isRunDoneStatus(raw.status) ? {
99
365
  type: "run.done",
100
- status: data.status,
101
- error: typeof data.error === "string" ? data.error : null,
102
- ...typeof data.at === "string" ? { at: data.at } : {}
366
+ status: raw.status,
367
+ error: typeof raw.error === "string" ? raw.error : null,
368
+ ...typeof raw.at === "string" ? { at: raw.at } : {}
103
369
  } : null;
104
- case "run.waiting": return typeof data.decisionId === "string" && data.decisionId.length > 0 ? {
370
+ case "run.waiting": return typeof raw.decisionId === "string" && raw.decisionId.length > 0 ? {
105
371
  type: "run.waiting",
106
- decisionId: data.decisionId,
107
- ...typeof data.at === "string" ? { at: data.at } : {}
372
+ decisionId: raw.decisionId,
373
+ ...typeof raw.at === "string" ? { at: raw.at } : {}
108
374
  } : null;
109
- case "action.started": return typeof data.index === "number" && typeof data.kind === "string" && typeof data.at === "string" ? {
375
+ case "action.started": return typeof raw.index === "number" && typeof raw.kind === "string" && typeof raw.at === "string" ? {
110
376
  type: "action.started",
111
- index: data.index,
112
- kind: data.kind,
113
- at: data.at
377
+ index: raw.index,
378
+ kind: raw.kind,
379
+ at: raw.at
114
380
  } : null;
115
- case "action.finished": return typeof data.index === "number" && typeof data.kind === "string" && isActionFinishedStatus(data.status) && typeof data.at === "string" ? {
381
+ case "action.finished": return typeof raw.index === "number" && typeof raw.kind === "string" && isActionFinishedStatus(raw.status) && typeof raw.at === "string" ? {
116
382
  type: "action.finished",
117
- index: data.index,
118
- kind: data.kind,
119
- status: data.status,
120
- at: data.at,
121
- ...typeof data.costUsd === "number" ? { costUsd: data.costUsd } : {},
122
- ...typeof data.error === "string" ? { error: data.error } : {}
383
+ index: raw.index,
384
+ kind: raw.kind,
385
+ status: raw.status,
386
+ at: raw.at,
387
+ ...typeof raw.costUsd === "number" ? { costUsd: raw.costUsd } : {},
388
+ ...typeof raw.error === "string" ? { error: raw.error } : {}
123
389
  } : null;
124
- case "iteration.started": return typeof data.actionIndex === "number" && typeof data.iterationIndex === "number" && typeof data.at === "string" ? {
390
+ case "iteration.started": return typeof raw.actionIndex === "number" && typeof raw.iterationIndex === "number" && typeof raw.at === "string" ? {
125
391
  type: "iteration.started",
126
- actionIndex: data.actionIndex,
127
- iterationIndex: data.iterationIndex,
128
- at: data.at,
129
- ...typeof data.model === "string" ? { model: data.model } : {}
392
+ actionIndex: raw.actionIndex,
393
+ iterationIndex: raw.iterationIndex,
394
+ at: raw.at,
395
+ ...typeof raw.model === "string" ? { model: raw.model } : {}
130
396
  } : null;
131
- case "iteration.ended": return typeof data.actionIndex === "number" && typeof data.iterationIndex === "number" && isIterationStatus(data.status) && typeof data.at === "string" ? {
397
+ case "iteration.ended": return typeof raw.actionIndex === "number" && typeof raw.iterationIndex === "number" && isIterationStatus(raw.status) && typeof raw.at === "string" ? {
132
398
  type: "iteration.ended",
133
- actionIndex: data.actionIndex,
134
- iterationIndex: data.iterationIndex,
135
- status: data.status,
136
- at: data.at,
137
- ...typeof data.outputPreview === "string" ? { outputPreview: data.outputPreview } : {},
138
- ...typeof data.inputTokens === "number" ? { inputTokens: data.inputTokens } : {},
139
- ...typeof data.outputTokens === "number" ? { outputTokens: data.outputTokens } : {},
140
- ...typeof data.costUsd === "number" ? { costUsd: data.costUsd } : {}
399
+ actionIndex: raw.actionIndex,
400
+ iterationIndex: raw.iterationIndex,
401
+ status: raw.status,
402
+ at: raw.at,
403
+ ...typeof raw.outputPreview === "string" ? { outputPreview: raw.outputPreview } : {},
404
+ ...typeof raw.inputTokens === "number" ? { inputTokens: raw.inputTokens } : {},
405
+ ...typeof raw.outputTokens === "number" ? { outputTokens: raw.outputTokens } : {},
406
+ ...typeof raw.costUsd === "number" ? { costUsd: raw.costUsd } : {}
141
407
  } : null;
142
408
  default: return null;
143
409
  }
@@ -145,8 +411,8 @@ function toRunStreamEvent(eventType, data) {
145
411
  /** True for an aborted/timed-out fetch (or any abort). fetch rejects with the
146
412
  * signal's reason — a DOMException named `AbortError` (caller) or `TimeoutError`
147
413
  * (AbortSignal.timeout). */
148
- function isAbortError(err) {
149
- return err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
414
+ function isAbortError(cause) {
415
+ return cause instanceof Error && (cause.name === "AbortError" || cause.name === "TimeoutError");
150
416
  }
151
417
  /** A signal that aborts when the caller's signal aborts OR `timeoutMs` elapses.
152
418
  * Returns undefined when neither bound applies (no per-request abort needed). */
@@ -220,13 +486,68 @@ function nonEmpty(value) {
220
486
  function readProcessEnv() {
221
487
  return globalThis.process?.env ?? {};
222
488
  }
223
- var HubClient = class HubClient {
489
+ var HubTransport = class {
224
490
  baseUrl;
225
491
  fetch;
226
492
  apiKey;
227
493
  authHeaders;
494
+ constructor(options) {
495
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
496
+ this.apiKey = options.apiKey;
497
+ this.authHeaders = options.authHeaders;
498
+ this.fetch = options.fetch ?? fetch.bind(globalThis);
499
+ }
500
+ async request(path, init, options = {}) {
501
+ const callerAuth = options.auth === "caller";
502
+ const response = await this.fetch(`${this.baseUrl}${path}`, {
503
+ ...init,
504
+ headers: await this.buildHeaders(init.headers, callerAuth)
505
+ });
506
+ const status = response.status;
507
+ const contentType = response.headers.get("content-type") ?? "";
508
+ const bodyText = await response.text();
509
+ if (!contentType.includes("application/json")) throw httpError(path, status, response.statusText, bodyText, contentType, callerAuth);
510
+ let parsed;
511
+ try {
512
+ parsed = JSON.parse(bodyText);
513
+ } catch {
514
+ throw httpError(path, status, response.statusText, bodyText, contentType, callerAuth);
515
+ }
516
+ if (isHubErrorEnvelope(parsed)) {
517
+ if (callerAuth) throw new HubSdkError({
518
+ code: parsed.error.code,
519
+ message: `Hub request ${path} failed: HTTP ${status}`
520
+ }, { status });
521
+ throw new HubSdkError(parsed.error, { status });
522
+ }
523
+ if (isHubSuccessEnvelope(parsed)) {
524
+ if (callerAuth && !response.ok) throw httpError(path, status, response.statusText, bodyText, contentType, callerAuth);
525
+ if (options.validateData && !options.validateData(parsed.data)) throw new HubSdkError({
526
+ code: `HUB_HTTP_${status}`,
527
+ message: `Hub request ${path} returned invalid data`,
528
+ details: {
529
+ status,
530
+ path
531
+ }
532
+ }, { status });
533
+ return parsed.data;
534
+ }
535
+ throw httpError(path, status, response.statusText, bodyText, contentType, callerAuth);
536
+ }
537
+ async buildHeaders(headers, callerAuth = false) {
538
+ const mergedHeaders = new Headers({ Accept: "application/json" });
539
+ if (!callerAuth && this.apiKey) mergedHeaders.set("Authorization", `Bearer ${this.apiKey}`);
540
+ if (!callerAuth && this.authHeaders) for (const [key, value] of new Headers(await this.authHeaders())) mergedHeaders.set(key, value);
541
+ for (const [key, value] of new Headers(headers)) mergedHeaders.set(key, value);
542
+ return mergedHeaders;
543
+ }
544
+ };
545
+ var HubClient = class HubClient extends HubTransport {
546
+ allowances;
547
+ apps;
228
548
  connections;
229
549
  channels;
550
+ numbers;
230
551
  eventSubscriptions;
231
552
  permissions;
232
553
  tokens;
@@ -235,11 +556,12 @@ var HubClient = class HubClient {
235
556
  audit;
236
557
  githubApp;
237
558
  workflows;
559
+ lines;
238
560
  constructor(options) {
239
- this.baseUrl = options.baseUrl.replace(/\/+$/, "");
240
- this.apiKey = options.apiKey;
241
- this.authHeaders = options.authHeaders;
242
- this.fetch = options.fetch ?? fetch.bind(globalThis);
561
+ super(options);
562
+ this.allowances = new HubAllowancesClient((path, init) => this.request(path, init));
563
+ this.apps = new HubAppsClient((path, init) => this.request(path, init));
564
+ this.numbers = new HubManagedNumbersClient((path, init) => this.request(path, init));
243
565
  this.connections = new HubConnectionsClient((path, init) => this.request(path, init));
244
566
  this.channels = new HubChannelsClient((path, init) => this.request(path, init));
245
567
  this.eventSubscriptions = new HubEventSubscriptionsClient((path, init) => this.request(path, init));
@@ -250,6 +572,23 @@ var HubClient = class HubClient {
250
572
  this.audit = new HubAuditClient((path, init) => this.request(path, init));
251
573
  this.githubApp = new HubGithubAppClient((path, init) => this.request(path, init));
252
574
  this.workflows = new HubWorkflowsClient((path, init) => this.request(path, init), (path, init) => this.stream(path, init), (path, init) => this.requestBytes(path, init));
575
+ this.lines = new HubLinesClient((path, init) => this.request(path, init), (yaml) => this.workflows.create(yaml), (path, init, callbackBearer) => {
576
+ if (typeof callbackBearer !== "string" || !/^[\x21-\x7e]{16,512}$/.test(callbackBearer)) throw new HubSdkError({
577
+ code: "HUB_INVALID_INPUT",
578
+ message: "Application callback bearer is invalid"
579
+ });
580
+ const headers = new Headers(init.headers);
581
+ headers.set("Authorization", `Bearer ${callbackBearer}`);
582
+ return this.request(path, {
583
+ ...init,
584
+ headers,
585
+ credentials: "omit",
586
+ redirect: "manual"
587
+ }, {
588
+ auth: "caller",
589
+ validateData: isDispatchCurrentData
590
+ });
591
+ });
253
592
  }
254
593
  static fromEnv(options = {}) {
255
594
  const env = options.env ?? readProcessEnv();
@@ -263,25 +602,6 @@ var HubClient = class HubClient {
263
602
  async status() {
264
603
  return this.request("/v1/hub/status", { method: "GET" });
265
604
  }
266
- async request(path, init) {
267
- const response = await this.fetch(`${this.baseUrl}${path}`, {
268
- ...init,
269
- headers: await this.buildHeaders(init.headers)
270
- });
271
- const status = response.status;
272
- const contentType = response.headers.get("content-type") ?? "";
273
- const bodyText = await response.text();
274
- if (!contentType.includes("application/json")) throw httpError(path, status, response.statusText, bodyText, contentType);
275
- let parsed;
276
- try {
277
- parsed = JSON.parse(bodyText);
278
- } catch {
279
- throw httpError(path, status, response.statusText, bodyText, contentType);
280
- }
281
- if (isHubErrorEnvelope(parsed)) throw new HubSdkError(parsed.error, { status });
282
- if (isHubSuccessEnvelope(parsed)) return parsed.data;
283
- throw httpError(path, status, response.statusText, bodyText, contentType);
284
- }
285
605
  /**
286
606
  * Open a streaming (Server-Sent Events) response and return its raw body.
287
607
  * Shares auth-header building + `fetch` with {@link request}, but does NOT
@@ -348,25 +668,13 @@ var HubClient = class HubClient {
348
668
  contentType
349
669
  };
350
670
  }
351
- async buildHeaders(headers) {
352
- const mergedHeaders = new Headers(headers);
353
- if (!mergedHeaders.has("Accept")) mergedHeaders.set("Accept", "application/json");
354
- if (this.apiKey) mergedHeaders.set("Authorization", `Bearer ${this.apiKey}`);
355
- if (this.authHeaders) for (const [key, value] of new Headers(await this.authHeaders())) mergedHeaders.set(key, value);
356
- for (const [key, value] of new Headers(headers)) mergedHeaders.set(key, value);
357
- return mergedHeaders;
358
- }
359
671
  };
360
672
  var HubTokensClient = class {
361
673
  constructor(request) {
362
674
  this.request = request;
363
675
  }
364
676
  async mint(options) {
365
- return this.request("/v1/hub/tokens", {
366
- method: "POST",
367
- body: JSON.stringify(options),
368
- headers: { "Content-Type": "application/json" }
369
- });
677
+ return this.request("/v1/hub/tokens", jsonInit("POST", options));
370
678
  }
371
679
  async list(options = {}) {
372
680
  const searchParams = new URLSearchParams();
@@ -400,38 +708,22 @@ var HubPermissionsClient = class {
400
708
  return this.request(`/v1/hub/policies?connectionId=${encodeURIComponent(connectionId)}`, { method: "GET" });
401
709
  }
402
710
  async set(input) {
403
- return this.request("/v1/hub/policies", {
404
- method: "PUT",
405
- body: JSON.stringify(input),
406
- headers: { "Content-Type": "application/json" }
407
- });
711
+ return this.request("/v1/hub/policies", jsonInit("PUT", input));
408
712
  }
409
713
  /** Reset an action to its default by deleting any stored override. */
410
714
  async delete(input) {
411
- return this.request("/v1/hub/policies", {
412
- method: "DELETE",
413
- body: JSON.stringify(input),
414
- headers: { "Content-Type": "application/json" }
415
- });
715
+ return this.request("/v1/hub/policies", jsonInit("DELETE", input));
416
716
  }
417
717
  /** Bulk-allow every write action of a connection's provider. Reads are
418
718
  * already allowed for sandbox agents; destructive actions stay `ask`.
419
719
  * Idempotent: actions with an existing policy are left untouched. */
420
720
  async allowWrites(connectionId) {
421
- return this.request("/v1/hub/policies/allow-writes", {
422
- method: "POST",
423
- body: JSON.stringify({ connectionId }),
424
- headers: { "Content-Type": "application/json" }
425
- });
721
+ return this.request("/v1/hub/policies/allow-writes", jsonInit("POST", { connectionId }));
426
722
  }
427
723
  /** Inverse of `allowWrites`: delete only the rows it created for this
428
724
  * connection. Manual per-action decisions are left intact. */
429
725
  async revertWrites(connectionId) {
430
- return this.request("/v1/hub/policies/revert-writes", {
431
- method: "POST",
432
- body: JSON.stringify({ connectionId }),
433
- headers: { "Content-Type": "application/json" }
434
- });
726
+ return this.request("/v1/hub/policies/revert-writes", jsonInit("POST", { connectionId }));
435
727
  }
436
728
  };
437
729
  var HubToolsClient = class {
@@ -439,34 +731,32 @@ var HubToolsClient = class {
439
731
  this.request = request;
440
732
  }
441
733
  async search(query, options = {}) {
442
- return this.request("/v1/hub/tools/search", {
443
- method: "POST",
444
- body: JSON.stringify({
445
- query,
446
- provider: options.provider,
447
- limit: options.limit
448
- }),
449
- headers: { "Content-Type": "application/json" }
450
- });
734
+ return this.request("/v1/hub/tools/search", jsonInit("POST", {
735
+ query,
736
+ provider: options.provider,
737
+ limit: options.limit
738
+ }));
451
739
  }
452
740
  async sources() {
453
741
  return this.request("/v1/hub/tools/sources", { method: "GET" });
454
742
  }
455
743
  async describe(path) {
456
- return this.request("/v1/hub/tools/describe", {
457
- method: "POST",
458
- body: JSON.stringify({ path }),
459
- headers: { "Content-Type": "application/json" }
460
- });
744
+ return this.request("/v1/hub/tools/describe", jsonInit("POST", { path }));
461
745
  }
462
- async invoke(path, input, options = {}) {
463
- const execInit = this.buildExecInit(path, input, options.connectionId);
746
+ /**
747
+ * Run one hub tool. `raw` is the tool input: the SDK forwards it verbatim as
748
+ * the request body's `input`, and the server validates it against the tool's
749
+ * own schema, so the client never gives it a shape.
750
+ */
751
+ async invoke(path, raw, options = {}) {
752
+ const execInit = this.buildExecInit(path, raw, options);
464
753
  try {
465
754
  return await this.request("/v1/hub/exec", execInit);
466
755
  } catch (error) {
467
756
  if (!(error instanceof HubSdkError) || !requiresCapabilityToken(error)) throw error;
757
+ const approvalId = getApprovalId(error);
758
+ if (approvalId && options.approve !== true) throw error;
468
759
  if (options.approve) {
469
- const approvalId = getApprovalId(error);
470
760
  if (!approvalId) throw error;
471
761
  const approved = await this.request(`/v1/hub/approvals/${encodeURIComponent(approvalId)}/approve`, { method: "POST" });
472
762
  validateApprovedExecution(approved, path, options.connectionId);
@@ -484,15 +774,11 @@ var HubToolsClient = class {
484
774
  });
485
775
  }
486
776
  }
487
- const minted = await this.request("/v1/hub/tokens", {
488
- method: "POST",
489
- body: JSON.stringify({
490
- actionPath: path,
491
- connectionId: options.connectionId,
492
- provider: options.connectionId ? void 0 : path.split(".")[0]
493
- }),
494
- headers: { "Content-Type": "application/json" }
495
- });
777
+ const minted = await this.request("/v1/hub/tokens", jsonInit("POST", {
778
+ actionPath: path,
779
+ connectionId: options.connectionId,
780
+ provider: options.connectionId ? void 0 : path.split(".")[0]
781
+ }));
496
782
  return this.request("/v1/hub/exec", {
497
783
  ...execInit,
498
784
  headers: {
@@ -501,16 +787,13 @@ var HubToolsClient = class {
501
787
  }
502
788
  });
503
789
  }
504
- buildExecInit(path, input, connectionId) {
505
- return {
506
- method: "POST",
507
- body: JSON.stringify({
508
- path,
509
- input,
510
- connectionId
511
- }),
512
- headers: { "Content-Type": "application/json" }
513
- };
790
+ buildExecInit(path, raw, options) {
791
+ return jsonInit("POST", {
792
+ path,
793
+ input: raw,
794
+ connectionId: options.connectionId,
795
+ idempotencyKey: options.idempotencyKey
796
+ });
514
797
  }
515
798
  };
516
799
  function requiresCapabilityToken(error) {
@@ -540,6 +823,17 @@ function validateApprovedExecution(approved, path, connectionId) {
540
823
  }
541
824
  });
542
825
  }
826
+ const GITHUB_EXACT_CONNECT_SCOPES = [
827
+ "read:user",
828
+ "repo",
829
+ "read:org"
830
+ ];
831
+ function sameOAuthScopeSet(left, right) {
832
+ if (!Array.isArray(left)) return false;
833
+ const leftSet = new Set(left);
834
+ const rightSet = new Set(right);
835
+ return leftSet.size === left.length && rightSet.size === right.length && leftSet.size === rightSet.size && [...leftSet].every((scope) => rightSet.has(scope));
836
+ }
543
837
  var HubChannelsClient = class {
544
838
  constructor(request) {
545
839
  this.request = request;
@@ -548,11 +842,7 @@ var HubChannelsClient = class {
548
842
  return this.request("/v1/hub/channels", { method: "GET" });
549
843
  }
550
844
  async createEmail(input = {}) {
551
- return this.request("/v1/hub/channels/email", {
552
- method: "POST",
553
- body: JSON.stringify(input),
554
- headers: { "Content-Type": "application/json" }
555
- });
845
+ return this.request("/v1/hub/channels/email", jsonInit("POST", input));
556
846
  }
557
847
  async delete(channelId) {
558
848
  return this.request(`/v1/hub/channels/${encodeURIComponent(channelId)}`, { method: "DELETE" });
@@ -566,19 +856,17 @@ var HubEventSubscriptionsClient = class {
566
856
  return this.request("/v1/hub/event-subscriptions", { method: "GET" });
567
857
  }
568
858
  async create(input) {
569
- return this.request("/v1/hub/event-subscriptions", {
570
- method: "POST",
571
- body: JSON.stringify(input),
572
- headers: { "Content-Type": "application/json" }
573
- });
859
+ return this.request("/v1/hub/event-subscriptions", jsonInit("POST", input));
574
860
  }
575
861
  async delete(subscriptionId) {
576
862
  return this.request(`/v1/hub/event-subscriptions/${encodeURIComponent(subscriptionId)}`, { method: "DELETE" });
577
863
  }
578
864
  };
579
865
  var HubConnectionsClient = class {
866
+ inboundWebhook;
580
867
  constructor(request) {
581
868
  this.request = request;
869
+ this.inboundWebhook = new HubInboundWebhooksClient(request);
582
870
  }
583
871
  async list() {
584
872
  return this.request("/v1/hub/connections", { method: "GET" });
@@ -590,40 +878,38 @@ var HubConnectionsClient = class {
590
878
  return this.request("/v1/hub/providers", { method: "GET" });
591
879
  }
592
880
  async start(provider, options = {}) {
593
- return this.request(`/v1/hub/connections/${encodeURIComponent(provider)}/start`, {
594
- method: "POST",
595
- body: JSON.stringify({
596
- returnUrl: options.returnUrl,
597
- cli: options.cli,
598
- connectionParameters: options.connectionParameters
599
- }),
600
- headers: { "Content-Type": "application/json" }
881
+ if (options.requestedScopes !== void 0 && (provider !== "github" || !sameOAuthScopeSet(options.requestedScopes, GITHUB_EXACT_CONNECT_SCOPES))) throw new HubSdkError({
882
+ code: "HUB_INVALID_INPUT",
883
+ message: "GitHub requestedScopes must be read:user, repo, and read:org"
884
+ });
885
+ const started = await this.request(`/v1/hub/connections/${encodeURIComponent(provider)}/start`, jsonInit("POST", {
886
+ returnUrl: options.returnUrl,
887
+ cli: options.cli,
888
+ connectionParameters: options.connectionParameters,
889
+ requestedScopes: options.requestedScopes
890
+ }));
891
+ if (options.requestedScopes !== void 0 && (started.provider !== provider || !sameOAuthScopeSet(started.scopes, options.requestedScopes))) throw new HubSdkError({
892
+ code: "HUB_CONFIG_MISSING",
893
+ message: "Hub did not preserve the requested OAuth scopes"
601
894
  });
895
+ return started;
602
896
  }
603
897
  async startWhatsappEmbeddedSignup(options = {}) {
604
- return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/start", {
605
- method: "POST",
606
- body: JSON.stringify({ returnUrl: options.returnUrl }),
607
- headers: { "Content-Type": "application/json" }
608
- });
898
+ return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/start", jsonInit("POST", { returnUrl: options.returnUrl }));
609
899
  }
610
900
  async completeWhatsappEmbeddedSignup(input) {
611
- return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/complete", {
612
- method: "POST",
613
- body: JSON.stringify(input),
614
- headers: { "Content-Type": "application/json" }
615
- });
901
+ return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/complete", jsonInit("POST", input));
616
902
  }
617
903
  /** Connect a non-OAuth (api-key) connector by submitting the user's key.
618
904
  * The server validates the key with a live probe before persisting and
619
905
  * returns the created (or reconnected) connection — there is no redirect,
620
- * unlike `start`. */
621
- async connectApiKey(provider, apiKey) {
622
- return this.request(`/v1/hub/connections/${encodeURIComponent(provider)}/connect-key`, {
623
- method: "POST",
624
- body: JSON.stringify({ apiKey }),
625
- headers: { "Content-Type": "application/json" }
626
- });
906
+ * unlike `start`. Cloudbeds requires propertyId metadata and PriceLabs
907
+ * requires listing ID/PMS pairs; other providers have no metadata. */
908
+ async connectApiKey(provider, apiKey, metadata) {
909
+ return this.request(`/v1/hub/connections/${encodeURIComponent(provider)}/connect-key`, jsonInit("POST", {
910
+ apiKey,
911
+ ...metadata === void 0 ? {} : { metadata }
912
+ }));
627
913
  }
628
914
  async revoke(connectionId) {
629
915
  return this.request(`/v1/hub/connections/${encodeURIComponent(connectionId)}`, { method: "DELETE" });
@@ -651,11 +937,7 @@ var HubGithubAppClient = class {
651
937
  this.request = request;
652
938
  }
653
939
  async mintInstallationToken(input) {
654
- return this.request("/v1/hub/github-app/mint-installation-token", {
655
- method: "POST",
656
- body: JSON.stringify(input),
657
- headers: { "Content-Type": "application/json" }
658
- });
940
+ return this.request("/v1/hub/github-app/mint-installation-token", jsonInit("POST", input));
659
941
  }
660
942
  /** Every installation the caller may act through — their own and any org's
661
943
  * they were verified against. The management surface's read. */
@@ -730,11 +1012,7 @@ var HubWorkflowsClient = class {
730
1012
  return this.request(`/v1/workflows/${encodeURIComponent(id)}`, { method: "GET" });
731
1013
  }
732
1014
  async create(yaml) {
733
- return this.request("/v1/workflows", {
734
- method: "POST",
735
- body: JSON.stringify({ yaml }),
736
- headers: { "Content-Type": "application/json" }
737
- });
1015
+ return this.request("/v1/workflows", jsonInit("POST", { yaml }));
738
1016
  }
739
1017
  /**
740
1018
  * Replace the workflow's definition from YAML (recompiled server-side,
@@ -745,14 +1023,10 @@ var HubWorkflowsClient = class {
745
1023
  * concurrent modification, `NOT_FOUND` for an unknown/foreign id.
746
1024
  */
747
1025
  async update(id, yaml, opts = {}) {
748
- return this.request(`/v1/workflows/${encodeURIComponent(id)}`, {
749
- method: "PUT",
750
- body: JSON.stringify({
751
- yaml,
752
- ...opts.note !== void 0 ? { note: opts.note } : {}
753
- }),
754
- headers: { "Content-Type": "application/json" }
755
- });
1026
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}`, jsonInit("PUT", {
1027
+ yaml,
1028
+ ...opts.note !== void 0 ? { note: opts.note } : {}
1029
+ }));
756
1030
  }
757
1031
  async delete(id) {
758
1032
  await this.request(`/v1/workflows/${encodeURIComponent(id)}`, { method: "DELETE" });
@@ -763,11 +1037,7 @@ var HubWorkflowsClient = class {
763
1037
  * via `update`, but it can still be paused/resumed here).
764
1038
  */
765
1039
  async setEnabled(id, enabled) {
766
- return this.request(`/v1/workflows/${encodeURIComponent(id)}`, {
767
- method: "PATCH",
768
- body: JSON.stringify({ enabled }),
769
- headers: { "Content-Type": "application/json" }
770
- });
1040
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}`, jsonInit("PATCH", { enabled }));
771
1041
  }
772
1042
  /**
773
1043
  * One page of run history, newest first. Pass the previous page's
@@ -795,9 +1065,7 @@ var HubWorkflowsClient = class {
795
1065
  async run(id, inputs, opts = {}) {
796
1066
  const hasInputs = inputs !== void 0 && Object.keys(inputs).length > 0;
797
1067
  return this.request(`/v1/workflows/${encodeURIComponent(id)}/run`, {
798
- method: "POST",
799
- body: JSON.stringify(hasInputs ? { inputs } : {}),
800
- headers: { "Content-Type": "application/json" },
1068
+ ...jsonInit("POST", hasInputs ? { inputs } : {}),
801
1069
  ...opts.signal ? { signal: opts.signal } : {}
802
1070
  });
803
1071
  }
@@ -976,11 +1244,7 @@ var HubWorkflowsClient = class {
976
1244
  * YAML no longer compiles, `CONFLICT` on a concurrent modification.
977
1245
  */
978
1246
  async rollback(id, rev) {
979
- return this.request(`/v1/workflows/${encodeURIComponent(id)}/rollback`, {
980
- method: "POST",
981
- body: JSON.stringify({ rev }),
982
- headers: { "Content-Type": "application/json" }
983
- });
1247
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}/rollback`, jsonInit("POST", { rev }));
984
1248
  }
985
1249
  /**
986
1250
  * Deterministic health stats over the workflow's last N terminal runs
@@ -1001,11 +1265,7 @@ var HubWorkflowsClient = class {
1001
1265
  * until an owner approves.
1002
1266
  */
1003
1267
  async propose(id, input) {
1004
- return this.request(`/v1/workflows/${encodeURIComponent(id)}/proposals`, {
1005
- method: "POST",
1006
- body: JSON.stringify(input),
1007
- headers: { "Content-Type": "application/json" }
1008
- });
1268
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}/proposals`, jsonInit("POST", input));
1009
1269
  }
1010
1270
  /**
1011
1271
  * Pending (proposed) and declined (rejected) revisions, newest first, with
@@ -1032,11 +1292,7 @@ var HubWorkflowsClient = class {
1032
1292
  * pending proposal.
1033
1293
  */
1034
1294
  async rejectProposal(id, rev, opts = {}) {
1035
- return this.request(`/v1/workflows/${encodeURIComponent(id)}/proposals/${encodeURIComponent(String(rev))}/reject`, {
1036
- method: "POST",
1037
- body: JSON.stringify(opts.reason !== void 0 ? { reason: opts.reason } : {}),
1038
- headers: { "Content-Type": "application/json" }
1039
- });
1295
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}/proposals/${encodeURIComponent(String(rev))}/reject`, jsonInit("POST", opts.reason !== void 0 ? { reason: opts.reason } : {}));
1040
1296
  }
1041
1297
  /**
1042
1298
  * Mint a fresh webhook hook token, retiring the current one immediately.
@@ -1112,29 +1368,24 @@ var HubWorkflowsClient = class {
1112
1368
  /**
1113
1369
  * Upsert a key; returns the entry with its NEW version (increments on every
1114
1370
  * write). Throws `HubSdkError(KV_VALUE_TOO_LARGE)` past the 64KiB value cap
1115
- * or `KV_KEY_LIMIT` past the per-workflow key cap.
1371
+ * or `KV_KEY_LIMIT` past the per-workflow key cap. `raw` is the caller's
1372
+ * value, stored and read back verbatim, so the client never gives it a shape.
1116
1373
  */
1117
- async putKv(id, key, value) {
1118
- return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, {
1119
- method: "PUT",
1120
- body: JSON.stringify({ value }),
1121
- headers: { "Content-Type": "application/json" }
1122
- });
1374
+ async putKv(id, key, raw) {
1375
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, jsonInit("PUT", { value: raw }));
1123
1376
  }
1124
1377
  /**
1125
1378
  * Compare-and-swap: the write lands only when the stored version still
1126
1379
  * equals `expectedVersion`; otherwise `HubSdkError(KV_VERSION_CONFLICT)`
1127
- * (409) — re-read and retry. Creation is {@link putKv}'s job.
1380
+ * (409) — re-read and retry. Creation is {@link putKv}'s job. `raw` is the
1381
+ * caller's value, stored and read back verbatim, so the client never gives it
1382
+ * a shape.
1128
1383
  */
1129
- async casKv(id, key, expectedVersion, value) {
1130
- return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}/cas`, {
1131
- method: "POST",
1132
- body: JSON.stringify({
1133
- expectedVersion,
1134
- value
1135
- }),
1136
- headers: { "Content-Type": "application/json" }
1137
- });
1384
+ async casKv(id, key, expectedVersion, raw) {
1385
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}/cas`, jsonInit("POST", {
1386
+ expectedVersion,
1387
+ value: raw
1388
+ }));
1138
1389
  }
1139
1390
  /** Delete a key; idempotent — `deleted` reports whether a row was removed. */
1140
1391
  async deleteKv(id, key) {
@@ -1154,11 +1405,7 @@ var HubWorkflowsClient = class {
1154
1405
  * (400); an unknown template id throws `NOT_FOUND`.
1155
1406
  */
1156
1407
  async instantiateTemplate(templateId, parameters) {
1157
- return this.request(`/v1/workflows/templates/${encodeURIComponent(templateId)}/instantiate`, {
1158
- method: "POST",
1159
- body: JSON.stringify(parameters ? { parameters } : {}),
1160
- headers: { "Content-Type": "application/json" }
1161
- });
1408
+ return this.request(`/v1/workflows/templates/${encodeURIComponent(templateId)}/instantiate`, jsonInit("POST", parameters ? { parameters } : {}));
1162
1409
  }
1163
1410
  /**
1164
1411
  * The caller's open decisions across all workflows — the always-available
@@ -1185,11 +1432,7 @@ var HubWorkflowsClient = class {
1185
1432
  * (409 — answered, timed out, or cancelled first).
1186
1433
  */
1187
1434
  async resolveRunDecision(id, runId, input) {
1188
- return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/decision/resolve`, {
1189
- method: "POST",
1190
- body: JSON.stringify(input),
1191
- headers: { "Content-Type": "application/json" }
1192
- });
1435
+ return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/decision/resolve`, jsonInit("POST", input));
1193
1436
  }
1194
1437
  /**
1195
1438
  * Dry-run a YAML definition against the platform compiler without saving it.
@@ -1202,17 +1445,93 @@ var HubWorkflowsClient = class {
1202
1445
  * the unconnected providers is the point.
1203
1446
  */
1204
1447
  async validate(yaml, opts = {}) {
1205
- return this.request("/v1/workflows/validate", {
1448
+ return this.request("/v1/workflows/validate", jsonInit("POST", {
1449
+ yaml,
1450
+ ...opts.structural ? { structural: true } : {}
1451
+ }));
1452
+ }
1453
+ async schema() {
1454
+ return this.request("/v1/workflows/schema", { method: "GET" });
1455
+ }
1456
+ };
1457
+ //#endregion
1458
+ //#region src/broker.ts
1459
+ /** Server-only client. App secrets never become a user's general API key.
1460
+ * Every invocation mints and consumes one short-lived, exact-grant token. */
1461
+ var HubAppBrokerClient = class {
1462
+ baseUrl;
1463
+ fetcher;
1464
+ constructor(options) {
1465
+ this.options = options;
1466
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
1467
+ this.fetcher = options.fetch ?? fetch.bind(globalThis);
1468
+ }
1469
+ async token(path, body) {
1470
+ const response = await this.fetcher(`${this.baseUrl}${path}`, {
1206
1471
  method: "POST",
1472
+ redirect: "error",
1473
+ headers: { "Content-Type": "application/json" },
1207
1474
  body: JSON.stringify({
1208
- yaml,
1209
- ...opts.structural ? { structural: true } : {}
1210
- }),
1211
- headers: { "Content-Type": "application/json" }
1475
+ ...body,
1476
+ client_id: this.options.clientId,
1477
+ client_secret: this.options.clientSecret
1478
+ })
1479
+ });
1480
+ const result = await response.json().catch(() => null);
1481
+ if (!response.ok || !result || typeof result !== "object" || Array.isArray(result)) throw new HubSdkError({
1482
+ code: "HUB_CONFIG_MISSING",
1483
+ message: "Application authorization is unavailable"
1484
+ }, { status: response.status });
1485
+ return result;
1486
+ }
1487
+ readToken(body) {
1488
+ 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({
1489
+ code: "HUB_CONFIG_MISSING",
1490
+ message: "Invalid application authorization response"
1491
+ });
1492
+ return {
1493
+ accessToken: body.access_token,
1494
+ expiresIn: body.expires_in,
1495
+ scopes: body.scope.split(" ").filter(Boolean)
1496
+ };
1497
+ }
1498
+ async exchange(input) {
1499
+ const body = await this.token("/v1/apps/oauth/token", {
1500
+ grant_type: "authorization_code",
1501
+ code: input.code,
1502
+ redirect_uri: input.redirectUri,
1503
+ connection_id: input.connectionId,
1504
+ ttl_seconds: 60
1505
+ });
1506
+ const token = this.readToken(body);
1507
+ if (typeof body.grant_id !== "string" || !body.grant_id || body.connection_id !== input.connectionId) throw new HubSdkError({
1508
+ code: "HUB_CONFIG_MISSING",
1509
+ message: "Application connection did not match consent"
1212
1510
  });
1511
+ return {
1512
+ ...token,
1513
+ grantId: body.grant_id,
1514
+ connectionId: input.connectionId
1515
+ };
1213
1516
  }
1214
- async schema() {
1215
- return this.request("/v1/workflows/schema", { method: "GET" });
1517
+ async mint(grantId) {
1518
+ if (!/^[A-Za-z0-9_-]{1,256}$/.test(grantId)) throw new Error("Invalid application grant");
1519
+ return this.readToken(await this.token(`/v1/apps/grants/${encodeURIComponent(grantId)}/mint-broker-token`, {
1520
+ grant_id: grantId,
1521
+ ttl_seconds: 60
1522
+ }));
1523
+ }
1524
+ async invoke(grantId, connectionId, actionPath, input) {
1525
+ const token = await this.mint(grantId);
1526
+ if (!token.scopes.includes(actionPath)) throw new HubSdkError({
1527
+ code: "HUB_POLICY_DENIED",
1528
+ message: "This action was not granted to the application"
1529
+ });
1530
+ return new HubClient({
1531
+ baseUrl: this.baseUrl,
1532
+ apiKey: token.accessToken,
1533
+ fetch: this.fetcher
1534
+ }).tools.invoke(actionPath, input, { connectionId });
1216
1535
  }
1217
1536
  };
1218
1537
  //#endregion
@@ -1473,6 +1792,55 @@ function requestFailure(code, status, error, headers) {
1473
1792
  };
1474
1793
  }
1475
1794
  //#endregion
1476
- 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 };
1795
+ //#region src/line-grants.ts
1796
+ function path(binding, suffix = "") {
1797
+ if (!binding.workspaceId || !binding.workspaceAgentId) throw new Error("A workspace and agent are required for a line grant");
1798
+ return `/v1/hub/line-grants/current${suffix}?${new URLSearchParams({
1799
+ workspaceId: binding.workspaceId,
1800
+ workspaceAgentId: binding.workspaceAgentId
1801
+ })}`;
1802
+ }
1803
+ /** Every call uses HubTransport's authHeaders, so one-use app broker tokens can be minted per request. */
1804
+ var HubLineGrantsClient = class extends HubTransport {
1805
+ current(binding) {
1806
+ return this.request(path(binding), { method: "GET" });
1807
+ }
1808
+ async line(binding) {
1809
+ return (await this.request(path(binding, "/line"), { method: "GET" })).line;
1810
+ }
1811
+ async attach(binding, input) {
1812
+ return (await this.request(path(binding, "/attachment"), jsonInit("PUT", input))).attachment;
1813
+ }
1814
+ detach(binding) {
1815
+ return this.request(path(binding, "/attachment"), { method: "DELETE" });
1816
+ }
1817
+ async threads(binding) {
1818
+ return (await this.request(path(binding, "/threads"), { method: "GET" })).threads;
1819
+ }
1820
+ async messages(binding, threadId) {
1821
+ if (!threadId || threadId.length > 256) throw new Error("A bounded line thread id is required");
1822
+ return (await this.request(path(binding, `/threads/${encodeURIComponent(threadId)}/messages`), { method: "GET" })).messages;
1823
+ }
1824
+ memberApplication(binding) {
1825
+ return this.request(path(binding, "/member-application"), { method: "GET" });
1826
+ }
1827
+ /**
1828
+ * Route the owner's texts to this app's callback; the incumbent attachment
1829
+ * stays. `replaceSelectionId` moves them from another app on the owner's
1830
+ * explicit choice.
1831
+ */
1832
+ async selectApplication(binding, input) {
1833
+ return (await this.request(path(binding, "/member-application"), jsonInit("PUT", input))).selection;
1834
+ }
1835
+ /** Return the owner's texts to the incumbent attachment. */
1836
+ async clearApplication(binding, input) {
1837
+ await this.request(path(binding, "/member-application"), jsonInit("DELETE", input));
1838
+ }
1839
+ revoke(binding) {
1840
+ return this.request(path(binding), { method: "DELETE" });
1841
+ }
1842
+ };
1843
+ //#endregion
1844
+ 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, HubLineGrantsClient, HubLinesClient, HubManagedNumbersClient, HubPermissionsClient, HubSdkError, HubTokensClient, HubToolsClient, HubWorkflowsClient, authenticateHubEventRequest, deriveHubEventCallbackSecret, parseHubEventDelivery, redactHubValue, resolveHubAuth, resolveHubBaseUrl, verifyHubEventSignature };
1477
1845
 
1478
1846
  //# sourceMappingURL=index.js.map