@tangle-network/hub-sdk 1.0.3 → 1.0.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/README.md +25 -0
- package/dist/bridge-D8_lAKaW.js +81 -0
- package/dist/bridge-D8_lAKaW.js.map +1 -0
- package/dist/client-BH3gTLjN.d.ts +2571 -0
- package/dist/client-BH3gTLjN.d.ts.map +1 -0
- package/dist/client-CsdLLECZ.js +1574 -0
- package/dist/client-CsdLLECZ.js.map +1 -0
- package/dist/index.d.ts +3 -2568
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1568
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +119 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +482 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/stdio.d.ts +1 -0
- package/dist/mcp/stdio.js +16 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/package.json +14 -6
|
@@ -0,0 +1,1574 @@
|
|
|
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
|
+
/** Current latched compliance state and the BAA acceptance behind it. */
|
|
100
|
+
async getCompliance(appId) {
|
|
101
|
+
return this.request(`/v1/apps/${encodedId(appId)}/compliance`, { method: "GET" });
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Latch HIPAA compliance for this app. The request MUST carry the exact
|
|
105
|
+
* `agreementVersion` the deployment currently offers — the acceptance is
|
|
106
|
+
* recorded before the claim exists, and the claim can never be cleared:
|
|
107
|
+
* `hipaa: false` on a latched app is a 409, on an unlatched app a no-op.
|
|
108
|
+
*/
|
|
109
|
+
async setCompliance(appId, input) {
|
|
110
|
+
return this.request(`/v1/apps/${encodedId(appId)}/compliance`, jsonInit("PUT", input));
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/knowledge-management.ts
|
|
115
|
+
var HubKnowledgeManagementClient = class {
|
|
116
|
+
constructor(request) {
|
|
117
|
+
this.request = request;
|
|
118
|
+
}
|
|
119
|
+
materialize(input) {
|
|
120
|
+
return this.request("/v1/knowledge/attachments/materialize", jsonInit("POST", input));
|
|
121
|
+
}
|
|
122
|
+
authorization() {
|
|
123
|
+
return this.request("/v1/knowledge/authorization", { method: "GET" });
|
|
124
|
+
}
|
|
125
|
+
disconnectAuthorization() {
|
|
126
|
+
return this.request("/v1/knowledge/authorization", { method: "DELETE" });
|
|
127
|
+
}
|
|
128
|
+
capabilities() {
|
|
129
|
+
return this.request("/v1/knowledge/capabilities", { method: "GET" });
|
|
130
|
+
}
|
|
131
|
+
list() {
|
|
132
|
+
return this.request("/v1/knowledge/sources", { method: "GET" });
|
|
133
|
+
}
|
|
134
|
+
get(sourceId) {
|
|
135
|
+
return this.request(`/v1/knowledge/sources/${encodeURIComponent(sourceId)}`, { method: "GET" });
|
|
136
|
+
}
|
|
137
|
+
create(input) {
|
|
138
|
+
return this.request("/v1/knowledge/sources", jsonInit("POST", input));
|
|
139
|
+
}
|
|
140
|
+
refresh(sourceId, input) {
|
|
141
|
+
return this.request(`/v1/knowledge/sources/${encodeURIComponent(sourceId)}/refresh`, jsonInit("POST", input));
|
|
142
|
+
}
|
|
143
|
+
attach(sourceId, input) {
|
|
144
|
+
return this.request(`/v1/knowledge/sources/${encodeURIComponent(sourceId)}/attachments`, jsonInit("POST", input));
|
|
145
|
+
}
|
|
146
|
+
attachments(sandboxId) {
|
|
147
|
+
return this.request(`/v1/knowledge/attachments${sandboxId ? `?sandboxId=${encodeURIComponent(sandboxId)}` : ""}`, { method: "GET" });
|
|
148
|
+
}
|
|
149
|
+
detach(attachmentId) {
|
|
150
|
+
return this.request(`/v1/knowledge/attachments/${encodeURIComponent(attachmentId)}`, { method: "DELETE" });
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/knowledge.ts
|
|
155
|
+
/** Runtime-only retrieval. The authenticated sandbox credential determines the
|
|
156
|
+
* scope on the server; neither a caller nor an agent can supply another scope. */
|
|
157
|
+
var HubKnowledgeClient = class {
|
|
158
|
+
management;
|
|
159
|
+
constructor(request) {
|
|
160
|
+
this.request = request;
|
|
161
|
+
this.management = new HubKnowledgeManagementClient(request);
|
|
162
|
+
}
|
|
163
|
+
bindings() {
|
|
164
|
+
return this.request("/v1/knowledge/runtime/bindings", { method: "GET" });
|
|
165
|
+
}
|
|
166
|
+
query(input, options) {
|
|
167
|
+
return this.request("/v1/knowledge/runtime/query", {
|
|
168
|
+
...jsonInit("POST", {
|
|
169
|
+
query: input.query,
|
|
170
|
+
...input.expectedReferences !== void 0 ? { expectedReferences: input.expectedReferences } : {},
|
|
171
|
+
...input.limit !== void 0 ? { limit: input.limit } : {}
|
|
172
|
+
}),
|
|
173
|
+
signal: options?.signal
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/lines.ts
|
|
179
|
+
/** The `on.schedule` mapping a Line schedule helper writes. */
|
|
180
|
+
function scheduleYaml(cron, timezone, until) {
|
|
181
|
+
return ` schedule: { ${[
|
|
182
|
+
`cron: ${JSON.stringify(cron)}`,
|
|
183
|
+
`timezone: ${JSON.stringify(timezone)}`,
|
|
184
|
+
...until ? [`until: ${JSON.stringify(until)}`] : []
|
|
185
|
+
].join(", ")} }`;
|
|
186
|
+
}
|
|
187
|
+
/** Line calls share HubClient authentication and envelope handling. */
|
|
188
|
+
var HubLinesClient = class {
|
|
189
|
+
members;
|
|
190
|
+
memberApplications;
|
|
191
|
+
checkins;
|
|
192
|
+
constructor(request, createWorkflow, requestApplicationDispatchCurrent) {
|
|
193
|
+
this.request = request;
|
|
194
|
+
this.createWorkflow = createWorkflow;
|
|
195
|
+
this.requestApplicationDispatchCurrent = requestApplicationDispatchCurrent;
|
|
196
|
+
this.members = { list: async (lineId) => (await this.request(`/v1/hub/lines/${encodeURIComponent(lineId)}/members`, { method: "GET" })).members };
|
|
197
|
+
const applicationPath = (lineId, memberId) => `/v1/hub/lines/${encodeURIComponent(lineId)}/members/${encodeURIComponent(memberId)}/application`;
|
|
198
|
+
this.memberApplications = {
|
|
199
|
+
get: async (lineId, memberId) => await this.request(applicationPath(lineId, memberId), { method: "GET" }),
|
|
200
|
+
select: async (lineId, memberId, input) => (await this.request(applicationPath(lineId, memberId), jsonInit("PUT", input))).selection,
|
|
201
|
+
clear: async (lineId, memberId, input) => {
|
|
202
|
+
await this.request(applicationPath(lineId, memberId), jsonInit("DELETE", input));
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
this.checkins = { schedule: (cron, options) => {
|
|
206
|
+
const name = options.name ?? `line-checkin-${crypto.randomUUID().slice(0, 8)}`;
|
|
207
|
+
const yaml = [
|
|
208
|
+
`name: ${JSON.stringify(name)}`,
|
|
209
|
+
"on:",
|
|
210
|
+
scheduleYaml(cron, options.timezone, options.until),
|
|
211
|
+
"do:",
|
|
212
|
+
" - agent.run:",
|
|
213
|
+
" line:",
|
|
214
|
+
` id: ${JSON.stringify(options.lineId)}`,
|
|
215
|
+
` member: ${JSON.stringify(options.member)}`,
|
|
216
|
+
` prompt: ${JSON.stringify(options.prompt)}`
|
|
217
|
+
].join("\n");
|
|
218
|
+
return this.createWorkflow(yaml);
|
|
219
|
+
} };
|
|
220
|
+
}
|
|
221
|
+
async scheduleSend(cron, options) {
|
|
222
|
+
const name = options.name ?? `line-send-${crypto.randomUUID().slice(0, 8)}`;
|
|
223
|
+
const yaml = [
|
|
224
|
+
`name: ${JSON.stringify(name)}`,
|
|
225
|
+
"on:",
|
|
226
|
+
scheduleYaml(cron, options.timezone, options.until),
|
|
227
|
+
"do:",
|
|
228
|
+
" - line.send:",
|
|
229
|
+
` lineId: ${JSON.stringify(options.lineId)}`,
|
|
230
|
+
` memberId: ${JSON.stringify(options.memberId)}`,
|
|
231
|
+
` text: ${JSON.stringify(options.text)}`,
|
|
232
|
+
...options.notBefore ? [` notBefore: ${JSON.stringify(options.notBefore)}`] : []
|
|
233
|
+
].join("\n");
|
|
234
|
+
return this.createWorkflow(yaml);
|
|
235
|
+
}
|
|
236
|
+
async context() {
|
|
237
|
+
return this.request("/v1/hub/lines/context", { method: "GET" });
|
|
238
|
+
}
|
|
239
|
+
async send(lineId, input) {
|
|
240
|
+
return this.request(`/v1/hub/lines/${encodeURIComponent(lineId)}/send`, jsonInit("POST", input));
|
|
241
|
+
}
|
|
242
|
+
/** Read an owner send's receipt, including its coded answer. Queued is not delivered. */
|
|
243
|
+
async sentMessage(lineId, messageId) {
|
|
244
|
+
return this.request(`/v1/hub/lines/${encodeURIComponent(lineId)}/outbox/${encodeURIComponent(messageId)}`, { method: "GET" });
|
|
245
|
+
}
|
|
246
|
+
async remind(lineId, input) {
|
|
247
|
+
return this.request(`/v1/hub/lines/${encodeURIComponent(lineId)}/remind`, jsonInit("POST", input));
|
|
248
|
+
}
|
|
249
|
+
async applicationDispatchCurrent(lineId, messageId, input, callbackBearer) {
|
|
250
|
+
if (!this.requestApplicationDispatchCurrent) throw new Error("Use HubClient to check application dispatch authority");
|
|
251
|
+
return (await this.requestApplicationDispatchCurrent(`/v1/hub/lines/${encodeURIComponent(lineId)}/messages/${encodeURIComponent(messageId)}/application-dispatch/current`, jsonInit("POST", input), callbackBearer)).current;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/managed-numbers.ts
|
|
256
|
+
/** Administrative number operations through Hub's authenticated transport.
|
|
257
|
+
* A mutation is one attempt; an uncertain create reuses its quote and reference. */
|
|
258
|
+
var HubManagedNumbersClient = class {
|
|
259
|
+
constructor(request) {
|
|
260
|
+
this.request = request;
|
|
261
|
+
}
|
|
262
|
+
readiness() {
|
|
263
|
+
return this.request("/v1/hub/numbers/readiness", { method: "GET" });
|
|
264
|
+
}
|
|
265
|
+
/** One page of orders. Scope by `clientReference` to ask about one binding,
|
|
266
|
+
* and follow `nextCursor` for the rest; a page is never ownership evidence. */
|
|
267
|
+
list(query = {}) {
|
|
268
|
+
const params = new URLSearchParams();
|
|
269
|
+
if (query.clientReference !== void 0) params.set("clientReference", query.clientReference);
|
|
270
|
+
if (query.cursor !== void 0) params.set("cursor", query.cursor);
|
|
271
|
+
if (query.limit !== void 0) params.set("limit", String(query.limit));
|
|
272
|
+
const search = params.toString();
|
|
273
|
+
return this.request(`/v1/hub/numbers${search ? `?${search}` : ""}`, { method: "GET" });
|
|
274
|
+
}
|
|
275
|
+
get(id) {
|
|
276
|
+
return this.request(this.path(id), { method: "GET" });
|
|
277
|
+
}
|
|
278
|
+
quote(input) {
|
|
279
|
+
return this.request("/v1/hub/numbers/quote", this.post(input));
|
|
280
|
+
}
|
|
281
|
+
/** `clientReference` and `transport`, when given, must match the signed
|
|
282
|
+
* quote or Hub refuses the purchase with a conflict. */
|
|
283
|
+
create(input) {
|
|
284
|
+
return this.request("/v1/hub/numbers", this.post(input));
|
|
285
|
+
}
|
|
286
|
+
/** Fences references so no order can take them again. Call it as the last
|
|
287
|
+
* step before retiring the resource they name: a purchase that races the
|
|
288
|
+
* retirement is then refused (`409 number_reference_fenced`) instead of
|
|
289
|
+
* binding a line to a resource that is gone. Refused with
|
|
290
|
+
* `409 number_reference_held` while a live order holds one of them; cancel
|
|
291
|
+
* it first. A cancelled order no longer holds its reference, so the fence
|
|
292
|
+
* succeeds as soon as `cancel` returns, whether or not its release is still
|
|
293
|
+
* pending. Repeating a fence is harmless. */
|
|
294
|
+
fence(input) {
|
|
295
|
+
return this.request("/v1/hub/numbers/fences", this.post(input));
|
|
296
|
+
}
|
|
297
|
+
advance(id) {
|
|
298
|
+
return this.request(`${this.path(id)}/advance`, this.post({}));
|
|
299
|
+
}
|
|
300
|
+
cancel(id) {
|
|
301
|
+
return this.request(`${this.path(id)}/cancel`, this.post({}));
|
|
302
|
+
}
|
|
303
|
+
path(id) {
|
|
304
|
+
if (!/^num_[a-f0-9]{36}$/.test(id)) throw new Error("Invalid managed number order id");
|
|
305
|
+
return `/v1/hub/numbers/${encodeURIComponent(id)}`;
|
|
306
|
+
}
|
|
307
|
+
post(body) {
|
|
308
|
+
return jsonInit("POST", body);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
//#endregion
|
|
312
|
+
//#region src/redaction.ts
|
|
313
|
+
const SECRET_KEY_PATTERN = /(api[_-]?key|authorization|capability[_-]?token|token|secret)/i;
|
|
314
|
+
const SECRET_VALUE_PATTERN = /\b(?:sk-tan[-_]|hubcap_|hct_|lrc_|gh[pousr]_)[A-Za-z0-9_.=-]+\b/g;
|
|
315
|
+
function redactHubValue(value) {
|
|
316
|
+
if (typeof value === "string") return value.replace(SECRET_VALUE_PATTERN, "[REDACTED]");
|
|
317
|
+
if (Array.isArray(value)) return value.map((item) => redactHubValue(item));
|
|
318
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, SECRET_KEY_PATTERN.test(key) ? "[REDACTED]" : redactHubValue(entryValue)]));
|
|
319
|
+
return value;
|
|
320
|
+
}
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/transport.ts
|
|
323
|
+
var HubSdkError = class extends Error {
|
|
324
|
+
code;
|
|
325
|
+
details;
|
|
326
|
+
status;
|
|
327
|
+
constructor(error, options = {}) {
|
|
328
|
+
super(String(redactHubValue(error.message)));
|
|
329
|
+
this.name = "HubSdkError";
|
|
330
|
+
this.code = error.code;
|
|
331
|
+
this.details = redactHubValue(error.details);
|
|
332
|
+
this.status = options.status;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
function bodySnippet(body, max = 200) {
|
|
336
|
+
const trimmed = body.trim();
|
|
337
|
+
return trimmed.length > max ? `${trimmed.slice(0, max)}…` : trimmed;
|
|
338
|
+
}
|
|
339
|
+
function httpError(path, status, statusText, body, contentType, omitResponseDetails = false) {
|
|
340
|
+
const suffix = !omitResponseDetails && statusText ? ` ${statusText}` : "";
|
|
341
|
+
return new HubSdkError({
|
|
342
|
+
code: `HUB_HTTP_${status}`,
|
|
343
|
+
message: `Hub request ${path} failed: HTTP ${status}${suffix}`,
|
|
344
|
+
details: {
|
|
345
|
+
status,
|
|
346
|
+
path,
|
|
347
|
+
...omitResponseDetails ? {} : {
|
|
348
|
+
contentType: contentType || null,
|
|
349
|
+
bodySnippet: bodySnippet(body)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}, { status });
|
|
353
|
+
}
|
|
354
|
+
function isHubErrorEnvelope(value) {
|
|
355
|
+
if (typeof value !== "object" || value === null) return false;
|
|
356
|
+
const candidate = value;
|
|
357
|
+
if (candidate.success !== false) return false;
|
|
358
|
+
const error = candidate.error;
|
|
359
|
+
return typeof error === "object" && error !== null && typeof error.code === "string";
|
|
360
|
+
}
|
|
361
|
+
function isHubSuccessEnvelope(value) {
|
|
362
|
+
return typeof value === "object" && value !== null && value.success === true;
|
|
363
|
+
}
|
|
364
|
+
function isRecord(value) {
|
|
365
|
+
return typeof value === "object" && value !== null;
|
|
366
|
+
}
|
|
367
|
+
var HubTransport = class {
|
|
368
|
+
baseUrl;
|
|
369
|
+
fetch;
|
|
370
|
+
authHeaders;
|
|
371
|
+
#apiKey;
|
|
372
|
+
constructor(options) {
|
|
373
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
374
|
+
this.#apiKey = options.apiKey;
|
|
375
|
+
this.authHeaders = options.authHeaders;
|
|
376
|
+
this.fetch = options.fetch ?? fetch.bind(globalThis);
|
|
377
|
+
}
|
|
378
|
+
/** The API key this client authenticates with, when it has one. */
|
|
379
|
+
get apiKey() {
|
|
380
|
+
return this.#apiKey;
|
|
381
|
+
}
|
|
382
|
+
async request(path, init, options = {}) {
|
|
383
|
+
const callerAuth = options.auth === "caller";
|
|
384
|
+
const response = await this.fetch(`${this.baseUrl}${path}`, {
|
|
385
|
+
...init,
|
|
386
|
+
headers: await this.buildHeaders(init.headers, callerAuth)
|
|
387
|
+
});
|
|
388
|
+
const status = response.status;
|
|
389
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
390
|
+
const bodyText = await response.text();
|
|
391
|
+
if (!contentType.includes("application/json")) throw httpError(path, status, response.statusText, bodyText, contentType, callerAuth);
|
|
392
|
+
let parsed;
|
|
393
|
+
try {
|
|
394
|
+
parsed = JSON.parse(bodyText);
|
|
395
|
+
} catch {
|
|
396
|
+
throw httpError(path, status, response.statusText, bodyText, contentType, callerAuth);
|
|
397
|
+
}
|
|
398
|
+
if (isHubErrorEnvelope(parsed)) {
|
|
399
|
+
if (callerAuth) throw new HubSdkError({
|
|
400
|
+
code: parsed.error.code,
|
|
401
|
+
message: `Hub request ${path} failed: HTTP ${status}`
|
|
402
|
+
}, { status });
|
|
403
|
+
throw new HubSdkError(parsed.error, { status });
|
|
404
|
+
}
|
|
405
|
+
if (isHubSuccessEnvelope(parsed)) {
|
|
406
|
+
if (callerAuth && !response.ok) throw httpError(path, status, response.statusText, bodyText, contentType, callerAuth);
|
|
407
|
+
if (options.validateData && !options.validateData(parsed.data)) throw new HubSdkError({
|
|
408
|
+
code: `HUB_HTTP_${status}`,
|
|
409
|
+
message: `Hub request ${path} returned invalid data`,
|
|
410
|
+
details: {
|
|
411
|
+
status,
|
|
412
|
+
path
|
|
413
|
+
}
|
|
414
|
+
}, { status });
|
|
415
|
+
return parsed.data;
|
|
416
|
+
}
|
|
417
|
+
throw httpError(path, status, response.statusText, bodyText, contentType, callerAuth);
|
|
418
|
+
}
|
|
419
|
+
async buildHeaders(headers, callerAuth = false) {
|
|
420
|
+
const mergedHeaders = new Headers({ Accept: "application/json" });
|
|
421
|
+
if (!callerAuth && this.apiKey) mergedHeaders.set("Authorization", `Bearer ${this.apiKey}`);
|
|
422
|
+
if (!callerAuth && this.authHeaders) for (const [key, value] of new Headers(await this.authHeaders())) mergedHeaders.set(key, value);
|
|
423
|
+
for (const [key, value] of new Headers(headers)) mergedHeaders.set(key, value);
|
|
424
|
+
return mergedHeaders;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
/** Decode a failed non-envelope response using the same redacted SDK errors. */
|
|
428
|
+
async function throwHubResponseError(path, response, status, contentType) {
|
|
429
|
+
const bodyText = await response.text().catch(() => "");
|
|
430
|
+
if (contentType.includes("application/json")) {
|
|
431
|
+
let parsed;
|
|
432
|
+
try {
|
|
433
|
+
parsed = JSON.parse(bodyText);
|
|
434
|
+
} catch {
|
|
435
|
+
parsed = void 0;
|
|
436
|
+
}
|
|
437
|
+
if (isHubErrorEnvelope(parsed)) throw new HubSdkError(parsed.error, { status });
|
|
438
|
+
}
|
|
439
|
+
throw httpError(path, status, response.statusText, bodyText, contentType);
|
|
440
|
+
}
|
|
441
|
+
/** Whether an error is the hub's "this credential is not accepted" answer. */
|
|
442
|
+
function isHubUnauthenticated(error) {
|
|
443
|
+
return error instanceof HubSdkError && (error.code === "HUB_UNAUTHENTICATED" || error.status === 401);
|
|
444
|
+
}
|
|
445
|
+
//#endregion
|
|
446
|
+
//#region src/environment.ts
|
|
447
|
+
const HUB_URL_ENV_VAR = "TANGLE_HUB_URL";
|
|
448
|
+
const HUB_API_KEY_ENV_VAR = "TANGLE_API_KEY";
|
|
449
|
+
const HUB_CAPABILITY_TOKEN_ENV_VAR = "TANGLE_HUB_CAPABILITY_TOKEN";
|
|
450
|
+
function resolveHubBaseUrl(env = readProcessEnv()) {
|
|
451
|
+
const raw = env[HUB_URL_ENV_VAR];
|
|
452
|
+
if (raw === void 0 || raw.trim() === "") throw new HubSdkError({
|
|
453
|
+
code: "HUB_CONFIG_MISSING",
|
|
454
|
+
message: `${HUB_URL_ENV_VAR} is required`,
|
|
455
|
+
details: { envVar: HUB_URL_ENV_VAR }
|
|
456
|
+
});
|
|
457
|
+
const trimmed = raw.trim();
|
|
458
|
+
try {
|
|
459
|
+
new URL(trimmed);
|
|
460
|
+
} catch {
|
|
461
|
+
throw new HubSdkError({
|
|
462
|
+
code: "HUB_CONFIG_INVALID",
|
|
463
|
+
message: `${HUB_URL_ENV_VAR} is not a valid URL`,
|
|
464
|
+
details: { envVar: HUB_URL_ENV_VAR }
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
return trimmed.replace(/\/+$/, "").replace(/\/v1\/hub$/, "");
|
|
468
|
+
}
|
|
469
|
+
function resolveHubAuth(env = readProcessEnv()) {
|
|
470
|
+
const present = [nonEmpty(env[HUB_API_KEY_ENV_VAR]), nonEmpty(env[HUB_CAPABILITY_TOKEN_ENV_VAR])].filter((value) => value !== void 0);
|
|
471
|
+
if (present.length === 0) throw new HubSdkError({
|
|
472
|
+
code: "HUB_CONFIG_MISSING",
|
|
473
|
+
message: `One of ${HUB_API_KEY_ENV_VAR} or ${HUB_CAPABILITY_TOKEN_ENV_VAR} is required`,
|
|
474
|
+
details: { envVars: [HUB_API_KEY_ENV_VAR, HUB_CAPABILITY_TOKEN_ENV_VAR] }
|
|
475
|
+
});
|
|
476
|
+
if (present.length > 1) throw new HubSdkError({
|
|
477
|
+
code: "HUB_CONFIG_INVALID",
|
|
478
|
+
message: `Set exactly one of ${HUB_API_KEY_ENV_VAR} or ${HUB_CAPABILITY_TOKEN_ENV_VAR}, not both`,
|
|
479
|
+
details: { envVars: [HUB_API_KEY_ENV_VAR, HUB_CAPABILITY_TOKEN_ENV_VAR] }
|
|
480
|
+
});
|
|
481
|
+
return present[0];
|
|
482
|
+
}
|
|
483
|
+
function nonEmpty(value) {
|
|
484
|
+
if (value === void 0) return void 0;
|
|
485
|
+
const trimmed = value.trim();
|
|
486
|
+
return trimmed.length === 0 ? void 0 : trimmed;
|
|
487
|
+
}
|
|
488
|
+
function readProcessEnv() {
|
|
489
|
+
return globalThis.process?.env ?? {};
|
|
490
|
+
}
|
|
491
|
+
//#endregion
|
|
492
|
+
//#region src/authorization.ts
|
|
493
|
+
var HubTokensClient = class {
|
|
494
|
+
constructor(request) {
|
|
495
|
+
this.request = request;
|
|
496
|
+
}
|
|
497
|
+
async mint(options) {
|
|
498
|
+
return this.request("/v1/hub/tokens", jsonInit("POST", options));
|
|
499
|
+
}
|
|
500
|
+
async list(options = {}) {
|
|
501
|
+
const searchParams = new URLSearchParams();
|
|
502
|
+
if (options.includeExpired) searchParams.set("includeExpired", "true");
|
|
503
|
+
const query = searchParams.toString();
|
|
504
|
+
return { tokens: (await this.request(`/v1/hub/tokens${query ? `?${query}` : ""}`, { method: "GET" })).tokens.map((token) => ({
|
|
505
|
+
id: token.id,
|
|
506
|
+
providerId: token.providerId,
|
|
507
|
+
connectionId: token.connectionId,
|
|
508
|
+
connectionDisplay: token.connectionDisplay,
|
|
509
|
+
actionPath: token.actionPath,
|
|
510
|
+
expiresAt: token.expiresAt,
|
|
511
|
+
status: token.status,
|
|
512
|
+
principalKind: token.principalKind,
|
|
513
|
+
createdAt: token.createdAt
|
|
514
|
+
})) };
|
|
515
|
+
}
|
|
516
|
+
async revoke(tokenId) {
|
|
517
|
+
const response = await this.request(`/v1/hub/tokens/${encodeURIComponent(tokenId)}`, { method: "DELETE" });
|
|
518
|
+
return {
|
|
519
|
+
tokenId: response.tokenId,
|
|
520
|
+
status: response.status
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
var HubPermissionsClient = class {
|
|
525
|
+
constructor(request) {
|
|
526
|
+
this.request = request;
|
|
527
|
+
}
|
|
528
|
+
async list(connectionId) {
|
|
529
|
+
return this.request(`/v1/hub/policies?connectionId=${encodeURIComponent(connectionId)}`, { method: "GET" });
|
|
530
|
+
}
|
|
531
|
+
async set(input) {
|
|
532
|
+
return this.request("/v1/hub/policies", jsonInit("PUT", input));
|
|
533
|
+
}
|
|
534
|
+
/** Reset an action to its default by deleting any stored override. */
|
|
535
|
+
async delete(input) {
|
|
536
|
+
return this.request("/v1/hub/policies", jsonInit("DELETE", input));
|
|
537
|
+
}
|
|
538
|
+
/** Bulk-allow every write action of a connection's provider. Reads are
|
|
539
|
+
* already allowed for sandbox agents; destructive actions stay `ask`.
|
|
540
|
+
* Idempotent: actions with an existing policy are left untouched. */
|
|
541
|
+
async allowWrites(connectionId) {
|
|
542
|
+
return this.request("/v1/hub/policies/allow-writes", jsonInit("POST", { connectionId }));
|
|
543
|
+
}
|
|
544
|
+
/** Inverse of `allowWrites`: delete only the rows it created for this
|
|
545
|
+
* connection. Manual per-action decisions are left intact. */
|
|
546
|
+
async revertWrites(connectionId) {
|
|
547
|
+
return this.request("/v1/hub/policies/revert-writes", jsonInit("POST", { connectionId }));
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
var HubApprovalsClient = class {
|
|
551
|
+
constructor(request) {
|
|
552
|
+
this.request = request;
|
|
553
|
+
}
|
|
554
|
+
async list() {
|
|
555
|
+
return this.request("/v1/hub/approvals", { method: "GET" });
|
|
556
|
+
}
|
|
557
|
+
async approve(approvalId) {
|
|
558
|
+
return this.request(`/v1/hub/approvals/${encodeURIComponent(approvalId)}/approve`, { method: "POST" });
|
|
559
|
+
}
|
|
560
|
+
async deny(approvalId) {
|
|
561
|
+
return this.request(`/v1/hub/approvals/${encodeURIComponent(approvalId)}/deny`, { method: "POST" });
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
//#endregion
|
|
565
|
+
//#region src/tools.ts
|
|
566
|
+
var HubToolsClient = class {
|
|
567
|
+
constructor(request) {
|
|
568
|
+
this.request = request;
|
|
569
|
+
}
|
|
570
|
+
async search(query, options = {}) {
|
|
571
|
+
return this.request("/v1/hub/tools/search", jsonInit("POST", {
|
|
572
|
+
query,
|
|
573
|
+
provider: options.provider,
|
|
574
|
+
limit: options.limit
|
|
575
|
+
}));
|
|
576
|
+
}
|
|
577
|
+
async sources() {
|
|
578
|
+
return this.request("/v1/hub/tools/sources", { method: "GET" });
|
|
579
|
+
}
|
|
580
|
+
async describe(path) {
|
|
581
|
+
return this.request("/v1/hub/tools/describe", jsonInit("POST", { path }));
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Run one hub tool. `raw` is the tool input: the SDK forwards it verbatim as
|
|
585
|
+
* the request body's `input`, and the server validates it against the tool's
|
|
586
|
+
* own schema, so the client never gives it a shape.
|
|
587
|
+
*/
|
|
588
|
+
async invoke(path, raw, options = {}) {
|
|
589
|
+
const execInit = this.buildExecInit(path, raw, options);
|
|
590
|
+
try {
|
|
591
|
+
return await this.request("/v1/hub/exec", execInit);
|
|
592
|
+
} catch (error) {
|
|
593
|
+
if (!(error instanceof HubSdkError) || !requiresCapabilityToken(error)) throw error;
|
|
594
|
+
const approvalId = getApprovalId(error);
|
|
595
|
+
if (approvalId && options.approve !== true) throw error;
|
|
596
|
+
if (options.approve) {
|
|
597
|
+
if (!approvalId) throw error;
|
|
598
|
+
const approved = await this.request(`/v1/hub/approvals/${encodeURIComponent(approvalId)}/approve`, { method: "POST" });
|
|
599
|
+
validateApprovedExecution(approved, path, options.connectionId);
|
|
600
|
+
if (!approved.capabilityToken?.token) throw new HubSdkError({
|
|
601
|
+
code: "HUB_CONFIG_MISSING",
|
|
602
|
+
message: "Hub approval did not return a capability token",
|
|
603
|
+
details: { approvalId }
|
|
604
|
+
});
|
|
605
|
+
return this.request("/v1/hub/exec", {
|
|
606
|
+
...execInit,
|
|
607
|
+
headers: {
|
|
608
|
+
"Content-Type": "application/json",
|
|
609
|
+
Authorization: `Bearer ${approved.capabilityToken.token}`
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const minted = await this.request("/v1/hub/tokens", jsonInit("POST", {
|
|
615
|
+
actionPath: path,
|
|
616
|
+
connectionId: options.connectionId,
|
|
617
|
+
provider: options.connectionId ? void 0 : path.split(".")[0]
|
|
618
|
+
}));
|
|
619
|
+
return this.request("/v1/hub/exec", {
|
|
620
|
+
...execInit,
|
|
621
|
+
headers: {
|
|
622
|
+
"Content-Type": "application/json",
|
|
623
|
+
Authorization: `Bearer ${minted.token}`
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
buildExecInit(path, raw, options) {
|
|
628
|
+
return jsonInit("POST", {
|
|
629
|
+
path,
|
|
630
|
+
input: raw,
|
|
631
|
+
connectionId: options.connectionId,
|
|
632
|
+
idempotencyKey: options.idempotencyKey
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
function requiresCapabilityToken(error) {
|
|
637
|
+
return error.code === "HUB_APPROVAL_REQUIRED";
|
|
638
|
+
}
|
|
639
|
+
function getApprovalId(error) {
|
|
640
|
+
const id = error.details?.approval?.id;
|
|
641
|
+
return typeof id === "string" ? id : void 0;
|
|
642
|
+
}
|
|
643
|
+
function validateApprovedExecution(approved, path, connectionId) {
|
|
644
|
+
if (approved.approval.actionPath !== path) throw new HubSdkError({
|
|
645
|
+
code: "HUB_POLICY_DENIED",
|
|
646
|
+
message: "Hub approval action does not match requested execution",
|
|
647
|
+
details: {
|
|
648
|
+
approvalId: approved.approval.id,
|
|
649
|
+
expectedActionPath: path,
|
|
650
|
+
approvalActionPath: approved.approval.actionPath
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
if (connectionId !== void 0 && approved.approval.connectionId !== connectionId) throw new HubSdkError({
|
|
654
|
+
code: "HUB_POLICY_DENIED",
|
|
655
|
+
message: "Hub approval connection does not match requested execution",
|
|
656
|
+
details: {
|
|
657
|
+
approvalId: approved.approval.id,
|
|
658
|
+
expectedConnectionId: connectionId,
|
|
659
|
+
approvalConnectionId: approved.approval.connectionId
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
//#endregion
|
|
664
|
+
//#region src/inbound-webhooks.ts
|
|
665
|
+
var HubInboundWebhooksClient = class {
|
|
666
|
+
constructor(request) {
|
|
667
|
+
this.request = request;
|
|
668
|
+
}
|
|
669
|
+
async create(connectionId, options = {}) {
|
|
670
|
+
return this.request(this.path(connectionId), jsonInit("POST", options));
|
|
671
|
+
}
|
|
672
|
+
async get(connectionId) {
|
|
673
|
+
return this.request(this.path(connectionId), { method: "GET" });
|
|
674
|
+
}
|
|
675
|
+
/** Add one owned Linq WhatsApp number without rotating the webhook secret. */
|
|
676
|
+
async ensurePhoneNumber(connectionId, phoneNumberId) {
|
|
677
|
+
if (!phoneNumberId || phoneNumberId.length > 128 || !/^[\x21-\x7e]+$/.test(phoneNumberId)) throw new Error("A bounded phone-number id is required");
|
|
678
|
+
return this.request(`${this.path(connectionId)}/phone-numbers/ensure`, jsonInit("POST", { phoneNumberId }));
|
|
679
|
+
}
|
|
680
|
+
/** Remove event subscriptions first. This does not silently rotate a live URL. */
|
|
681
|
+
async delete(connectionId) {
|
|
682
|
+
return this.request(this.path(connectionId), { method: "DELETE" });
|
|
683
|
+
}
|
|
684
|
+
path(connectionId) {
|
|
685
|
+
if (!connectionId || connectionId.length > 256) throw new Error("A bounded connection id is required");
|
|
686
|
+
return `/v1/hub/connections/${encodeURIComponent(connectionId)}/inbound-webhook`;
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
//#endregion
|
|
690
|
+
//#region src/connections.ts
|
|
691
|
+
const GITHUB_EXACT_CONNECT_SCOPES = [
|
|
692
|
+
"read:user",
|
|
693
|
+
"repo",
|
|
694
|
+
"read:org"
|
|
695
|
+
];
|
|
696
|
+
function sameOAuthScopeSet(left, right) {
|
|
697
|
+
if (!Array.isArray(left)) return false;
|
|
698
|
+
const leftSet = new Set(left);
|
|
699
|
+
const rightSet = new Set(right);
|
|
700
|
+
return leftSet.size === left.length && rightSet.size === right.length && leftSet.size === rightSet.size && [...leftSet].every((scope) => rightSet.has(scope));
|
|
701
|
+
}
|
|
702
|
+
var HubConnectionsClient = class {
|
|
703
|
+
inboundWebhook;
|
|
704
|
+
constructor(request) {
|
|
705
|
+
this.request = request;
|
|
706
|
+
this.inboundWebhook = new HubInboundWebhooksClient(request);
|
|
707
|
+
}
|
|
708
|
+
async list() {
|
|
709
|
+
return this.request("/v1/hub/connections", { method: "GET" });
|
|
710
|
+
}
|
|
711
|
+
/** Connector catalog: every provider the hub can expose, each flagged with
|
|
712
|
+
* `configured` (whether its OAuth app credentials are wired). Callers render
|
|
713
|
+
* this alongside `list()` to offer Connect for not-yet-connected providers. */
|
|
714
|
+
async providers() {
|
|
715
|
+
return this.request("/v1/hub/providers", { method: "GET" });
|
|
716
|
+
}
|
|
717
|
+
async start(provider, options = {}) {
|
|
718
|
+
if (options.requestedScopes !== void 0 && (provider !== "github" || !sameOAuthScopeSet(options.requestedScopes, GITHUB_EXACT_CONNECT_SCOPES))) throw new HubSdkError({
|
|
719
|
+
code: "HUB_INVALID_INPUT",
|
|
720
|
+
message: "GitHub requestedScopes must be read:user, repo, and read:org"
|
|
721
|
+
});
|
|
722
|
+
const started = await this.request(`/v1/hub/connections/${encodeURIComponent(provider)}/start`, jsonInit("POST", {
|
|
723
|
+
returnUrl: options.returnUrl,
|
|
724
|
+
cli: options.cli,
|
|
725
|
+
connectionParameters: options.connectionParameters,
|
|
726
|
+
requestedScopes: options.requestedScopes
|
|
727
|
+
}));
|
|
728
|
+
if (options.requestedScopes !== void 0 && (started.provider !== provider || !sameOAuthScopeSet(started.scopes, options.requestedScopes))) throw new HubSdkError({
|
|
729
|
+
code: "HUB_CONFIG_MISSING",
|
|
730
|
+
message: "Hub did not preserve the requested OAuth scopes"
|
|
731
|
+
});
|
|
732
|
+
return started;
|
|
733
|
+
}
|
|
734
|
+
async startWhatsappEmbeddedSignup(options = {}) {
|
|
735
|
+
return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/start", jsonInit("POST", { returnUrl: options.returnUrl }));
|
|
736
|
+
}
|
|
737
|
+
async completeWhatsappEmbeddedSignup(input) {
|
|
738
|
+
return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/complete", jsonInit("POST", input));
|
|
739
|
+
}
|
|
740
|
+
/** Connect a non-OAuth (api-key) connector by submitting the user's key.
|
|
741
|
+
* The server validates the key with a live probe before persisting and
|
|
742
|
+
* returns the created (or reconnected) connection — there is no redirect,
|
|
743
|
+
* unlike `start`. Cloudbeds requires propertyId metadata and PriceLabs
|
|
744
|
+
* requires listing ID/PMS pairs; other providers have no metadata. */
|
|
745
|
+
async connectApiKey(provider, apiKey, metadata) {
|
|
746
|
+
return this.request(`/v1/hub/connections/${encodeURIComponent(provider)}/connect-key`, jsonInit("POST", {
|
|
747
|
+
apiKey,
|
|
748
|
+
...metadata === void 0 ? {} : { metadata }
|
|
749
|
+
}));
|
|
750
|
+
}
|
|
751
|
+
async revoke(connectionId) {
|
|
752
|
+
return this.request(`/v1/hub/connections/${encodeURIComponent(connectionId)}`, { method: "DELETE" });
|
|
753
|
+
}
|
|
754
|
+
async health(connectionId) {
|
|
755
|
+
return this.request(`/v1/hub/connections/${encodeURIComponent(connectionId)}/health`, { method: "POST" });
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
//#endregion
|
|
759
|
+
//#region src/channels.ts
|
|
760
|
+
var HubChannelsClient = class {
|
|
761
|
+
constructor(request) {
|
|
762
|
+
this.request = request;
|
|
763
|
+
}
|
|
764
|
+
async list() {
|
|
765
|
+
return this.request("/v1/hub/channels", { method: "GET" });
|
|
766
|
+
}
|
|
767
|
+
async createEmail(input = {}) {
|
|
768
|
+
return this.request("/v1/hub/channels/email", jsonInit("POST", input));
|
|
769
|
+
}
|
|
770
|
+
async delete(channelId) {
|
|
771
|
+
return this.request(`/v1/hub/channels/${encodeURIComponent(channelId)}`, { method: "DELETE" });
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
var HubEventSubscriptionsClient = class {
|
|
775
|
+
constructor(request) {
|
|
776
|
+
this.request = request;
|
|
777
|
+
}
|
|
778
|
+
async list() {
|
|
779
|
+
return this.request("/v1/hub/event-subscriptions", { method: "GET" });
|
|
780
|
+
}
|
|
781
|
+
async create(input) {
|
|
782
|
+
return this.request("/v1/hub/event-subscriptions", jsonInit("POST", input));
|
|
783
|
+
}
|
|
784
|
+
async delete(subscriptionId) {
|
|
785
|
+
return this.request(`/v1/hub/event-subscriptions/${encodeURIComponent(subscriptionId)}`, { method: "DELETE" });
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
//#endregion
|
|
789
|
+
//#region src/github-app.ts
|
|
790
|
+
var HubGithubAppClient = class {
|
|
791
|
+
constructor(request) {
|
|
792
|
+
this.request = request;
|
|
793
|
+
}
|
|
794
|
+
async mintInstallationToken(input) {
|
|
795
|
+
return this.request("/v1/hub/github-app/mint-installation-token", jsonInit("POST", input));
|
|
796
|
+
}
|
|
797
|
+
/** Every installation the caller may act through — their own and any org's
|
|
798
|
+
* they were verified against. The management surface's read. */
|
|
799
|
+
async listInstallations() {
|
|
800
|
+
return this.request("/v1/hub/github-app/installations", { method: "GET" });
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Find installations `listInstallations` cannot show: the hub only registers
|
|
804
|
+
* what the setup redirect witnessed, so an org install approved out of band —
|
|
805
|
+
* or created by another member — is invisible until this asks GitHub for it.
|
|
806
|
+
* Links whatever the caller can prove access to; never uninstalls anything.
|
|
807
|
+
*/
|
|
808
|
+
async syncInstallations() {
|
|
809
|
+
return this.request("/v1/hub/github-app/installations/sync", { method: "POST" });
|
|
810
|
+
}
|
|
811
|
+
/** Give up THIS caller's access. Not an uninstall — the installation is
|
|
812
|
+
* account-level and other members may still be using it. */
|
|
813
|
+
async disconnectInstallation(installationId) {
|
|
814
|
+
return this.request(`/v1/hub/github-app/installations/${encodeURIComponent(String(installationId))}/access`, { method: "DELETE" });
|
|
815
|
+
}
|
|
816
|
+
async getInstallation(installationId) {
|
|
817
|
+
return this.request(`/v1/hub/github-app/installations/${encodeURIComponent(String(installationId))}`, { method: "GET" });
|
|
818
|
+
}
|
|
819
|
+
async listRepos(installationId) {
|
|
820
|
+
return this.request(`/v1/hub/github-app/installations/${encodeURIComponent(String(installationId))}/repos`, { method: "GET" });
|
|
821
|
+
}
|
|
822
|
+
async isRepoInstalled(input) {
|
|
823
|
+
const params = new URLSearchParams({
|
|
824
|
+
owner: input.owner,
|
|
825
|
+
repo: input.repo
|
|
826
|
+
});
|
|
827
|
+
return this.request(`/v1/hub/github-app/is-repo-installed?${params.toString()}`, { method: "GET" });
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
//#endregion
|
|
831
|
+
//#region src/audit.ts
|
|
832
|
+
var HubAuditClient = class {
|
|
833
|
+
constructor(request) {
|
|
834
|
+
this.request = request;
|
|
835
|
+
}
|
|
836
|
+
async list(options = {}) {
|
|
837
|
+
const searchParams = new URLSearchParams();
|
|
838
|
+
if (options.provider !== void 0) searchParams.set("provider", options.provider);
|
|
839
|
+
if (options.action !== void 0) searchParams.set("action", options.action);
|
|
840
|
+
if (options.status !== void 0) searchParams.set("status", options.status);
|
|
841
|
+
if (options.since !== void 0) searchParams.set("since", options.since);
|
|
842
|
+
if (options.until !== void 0) searchParams.set("until", options.until);
|
|
843
|
+
if (options.cursor !== void 0) searchParams.set("cursor", options.cursor);
|
|
844
|
+
if (options.limit !== void 0) searchParams.set("limit", String(options.limit));
|
|
845
|
+
const query = searchParams.toString();
|
|
846
|
+
return this.request(`/v1/hub/audit${query ? `?${query}` : ""}`, { method: "GET" });
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
//#endregion
|
|
850
|
+
//#region src/workflow-run-events.ts
|
|
851
|
+
/** Read a wire field as a string, or "" when the server sent anything else.
|
|
852
|
+
* `raw` is that field straight off the parsed payload, before any check. */
|
|
853
|
+
function optString(raw) {
|
|
854
|
+
return typeof raw === "string" ? raw : "";
|
|
855
|
+
}
|
|
856
|
+
/** An agent iteration's terminal status (`iteration.ended`) — only ever
|
|
857
|
+
* succeeded/failed; an iteration is never guard-skipped. */
|
|
858
|
+
function isIterationStatus(value) {
|
|
859
|
+
return value === "succeeded" || value === "failed";
|
|
860
|
+
}
|
|
861
|
+
/** An action's terminal status on `action.finished`. Beyond succeeded/failed a
|
|
862
|
+
* guarded action can end `skipped` (its `if` resolved false, so it never ran) —
|
|
863
|
+
* accepted here so a skipped event is surfaced, not dropped as malformed. */
|
|
864
|
+
function isActionFinishedStatus(value) {
|
|
865
|
+
return value === "succeeded" || value === "failed" || value === "skipped";
|
|
866
|
+
}
|
|
867
|
+
/** Terminal status of a whole run's `run.done` event. A run can end `cancelled`
|
|
868
|
+
* (a user cancel) — a value neither the action nor iteration status checkers
|
|
869
|
+
* accept — so `run.done` uses this rather than them, which would drop a
|
|
870
|
+
* cancelled terminal event and hang the stream. */
|
|
871
|
+
function isRunDoneStatus(value) {
|
|
872
|
+
return value === "succeeded" || value === "failed" || value === "cancelled";
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Map one raw SSE event (its `event:` name + parsed JSON `data:`) to a typed
|
|
876
|
+
* run-stream event, validating the fields each variant declares rather than
|
|
877
|
+
* blindly casting the wire payload. A frame that doesn't match its event's
|
|
878
|
+
* shape (or an unknown event name) returns null so the consumer skips it — a
|
|
879
|
+
* malformed `token` can never reach a consumer as `{ delta: undefined }`, and
|
|
880
|
+
* new server event kinds don't break older clients.
|
|
881
|
+
*/
|
|
882
|
+
function toRunStreamEvent(eventType, raw) {
|
|
883
|
+
if (eventType === "ping") return { type: "ping" };
|
|
884
|
+
if (!isRecord(raw)) return null;
|
|
885
|
+
switch (eventType) {
|
|
886
|
+
case "snapshot": return typeof raw.id === "string" && typeof raw.workflowId === "string" && typeof raw.status === "string" && Array.isArray(raw.actionResults) ? {
|
|
887
|
+
type: "snapshot",
|
|
888
|
+
run: raw
|
|
889
|
+
} : null;
|
|
890
|
+
case "token": return typeof raw.delta === "string" && typeof raw.actionIndex === "number" ? {
|
|
891
|
+
type: "token",
|
|
892
|
+
actionIndex: raw.actionIndex,
|
|
893
|
+
delta: raw.delta,
|
|
894
|
+
at: optString(raw.at)
|
|
895
|
+
} : null;
|
|
896
|
+
case "run.done": return isRunDoneStatus(raw.status) ? {
|
|
897
|
+
type: "run.done",
|
|
898
|
+
status: raw.status,
|
|
899
|
+
error: typeof raw.error === "string" ? raw.error : null,
|
|
900
|
+
...typeof raw.at === "string" ? { at: raw.at } : {}
|
|
901
|
+
} : null;
|
|
902
|
+
case "run.waiting": return typeof raw.decisionId === "string" && raw.decisionId.length > 0 ? {
|
|
903
|
+
type: "run.waiting",
|
|
904
|
+
decisionId: raw.decisionId,
|
|
905
|
+
...typeof raw.at === "string" ? { at: raw.at } : {}
|
|
906
|
+
} : null;
|
|
907
|
+
case "action.started": return typeof raw.index === "number" && typeof raw.kind === "string" && typeof raw.at === "string" ? {
|
|
908
|
+
type: "action.started",
|
|
909
|
+
index: raw.index,
|
|
910
|
+
kind: raw.kind,
|
|
911
|
+
at: raw.at
|
|
912
|
+
} : null;
|
|
913
|
+
case "action.finished": return typeof raw.index === "number" && typeof raw.kind === "string" && isActionFinishedStatus(raw.status) && typeof raw.at === "string" ? {
|
|
914
|
+
type: "action.finished",
|
|
915
|
+
index: raw.index,
|
|
916
|
+
kind: raw.kind,
|
|
917
|
+
status: raw.status,
|
|
918
|
+
at: raw.at,
|
|
919
|
+
...typeof raw.costUsd === "number" ? { costUsd: raw.costUsd } : {},
|
|
920
|
+
...typeof raw.error === "string" ? { error: raw.error } : {}
|
|
921
|
+
} : null;
|
|
922
|
+
case "iteration.started": return typeof raw.actionIndex === "number" && typeof raw.iterationIndex === "number" && typeof raw.at === "string" ? {
|
|
923
|
+
type: "iteration.started",
|
|
924
|
+
actionIndex: raw.actionIndex,
|
|
925
|
+
iterationIndex: raw.iterationIndex,
|
|
926
|
+
at: raw.at,
|
|
927
|
+
...typeof raw.model === "string" ? { model: raw.model } : {}
|
|
928
|
+
} : null;
|
|
929
|
+
case "iteration.ended": return typeof raw.actionIndex === "number" && typeof raw.iterationIndex === "number" && isIterationStatus(raw.status) && typeof raw.at === "string" ? {
|
|
930
|
+
type: "iteration.ended",
|
|
931
|
+
actionIndex: raw.actionIndex,
|
|
932
|
+
iterationIndex: raw.iterationIndex,
|
|
933
|
+
status: raw.status,
|
|
934
|
+
at: raw.at,
|
|
935
|
+
...typeof raw.outputPreview === "string" ? { outputPreview: raw.outputPreview } : {},
|
|
936
|
+
...typeof raw.inputTokens === "number" ? { inputTokens: raw.inputTokens } : {},
|
|
937
|
+
...typeof raw.outputTokens === "number" ? { outputTokens: raw.outputTokens } : {},
|
|
938
|
+
...typeof raw.costUsd === "number" ? { costUsd: raw.costUsd } : {}
|
|
939
|
+
} : null;
|
|
940
|
+
default: return null;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
//#endregion
|
|
944
|
+
//#region src/workflows-kv.ts
|
|
945
|
+
/** The workflow's durable keyspace, shared by all its runs. */
|
|
946
|
+
var WorkflowKvResource = class {
|
|
947
|
+
constructor(request) {
|
|
948
|
+
this.request = request;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* The workflow's whole keyspace as metadata (key, version, updatedAt — never
|
|
952
|
+
* values), key-ordered. Bounded by the server's per-workflow key cap.
|
|
953
|
+
*/
|
|
954
|
+
async listKv(id) {
|
|
955
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv`, { method: "GET" });
|
|
956
|
+
}
|
|
957
|
+
/** One KV entry (value + version); `HubSdkError(NOT_FOUND)` when absent. */
|
|
958
|
+
async getKv(id, key) {
|
|
959
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, { method: "GET" });
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Upsert a key; returns the entry with its NEW version (increments on every
|
|
963
|
+
* write). Throws `HubSdkError(KV_VALUE_TOO_LARGE)` past the 64KiB value cap
|
|
964
|
+
* or `KV_KEY_LIMIT` past the per-workflow key cap. `raw` is the caller's
|
|
965
|
+
* value, stored and read back verbatim, so the client never gives it a shape.
|
|
966
|
+
*/
|
|
967
|
+
async putKv(id, key, raw) {
|
|
968
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, jsonInit("PUT", { value: raw }));
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Compare-and-swap: the write lands only when the stored version still
|
|
972
|
+
* equals `expectedVersion`; otherwise `HubSdkError(KV_VERSION_CONFLICT)`
|
|
973
|
+
* (409) — re-read and retry. Creation is {@link putKv}'s job. `raw` is the
|
|
974
|
+
* caller's value, stored and read back verbatim, so the client never gives it
|
|
975
|
+
* a shape.
|
|
976
|
+
*/
|
|
977
|
+
async casKv(id, key, expectedVersion, raw) {
|
|
978
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}/cas`, jsonInit("POST", {
|
|
979
|
+
expectedVersion,
|
|
980
|
+
value: raw
|
|
981
|
+
}));
|
|
982
|
+
}
|
|
983
|
+
/** Delete a key; idempotent — `deleted` reports whether a row was removed. */
|
|
984
|
+
async deleteKv(id, key) {
|
|
985
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, { method: "DELETE" });
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
//#endregion
|
|
989
|
+
//#region src/workflows.ts
|
|
990
|
+
/** True for an aborted/timed-out fetch (or any abort). fetch rejects with the
|
|
991
|
+
* signal's reason — a DOMException named `AbortError` (caller) or `TimeoutError`
|
|
992
|
+
* (AbortSignal.timeout). */
|
|
993
|
+
function isAbortError(cause) {
|
|
994
|
+
return cause instanceof Error && (cause.name === "AbortError" || cause.name === "TimeoutError");
|
|
995
|
+
}
|
|
996
|
+
/** A signal that aborts when the caller's signal aborts OR `timeoutMs` elapses.
|
|
997
|
+
* Returns undefined when neither bound applies (no per-request abort needed). */
|
|
998
|
+
function combineAbortSignals(caller, timeoutMs) {
|
|
999
|
+
const signals = [];
|
|
1000
|
+
if (caller) signals.push(caller);
|
|
1001
|
+
if (timeoutMs !== null) signals.push(AbortSignal.timeout(timeoutMs));
|
|
1002
|
+
if (signals.length === 0) return void 0;
|
|
1003
|
+
return signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
1004
|
+
}
|
|
1005
|
+
/** Resolve after `ms`, or reject if `signal` aborts first. Clears its timer on
|
|
1006
|
+
* abort so a cancelled wait leaves nothing pending. */
|
|
1007
|
+
function delay(ms, signal) {
|
|
1008
|
+
return new Promise((resolve, reject) => {
|
|
1009
|
+
if (signal?.aborted) {
|
|
1010
|
+
reject(signal.reason ?? /* @__PURE__ */ new Error("The operation was aborted"));
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
const onAbort = () => {
|
|
1014
|
+
clearTimeout(timer);
|
|
1015
|
+
reject(signal?.reason ?? /* @__PURE__ */ new Error("The operation was aborted"));
|
|
1016
|
+
};
|
|
1017
|
+
const timer = setTimeout(() => {
|
|
1018
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1019
|
+
resolve();
|
|
1020
|
+
}, ms);
|
|
1021
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
/** Wait before reopening a run event stream that a restart ended. */
|
|
1025
|
+
const STREAM_REOPEN_DELAY_MS = 1e3;
|
|
1026
|
+
/** Reopens in a row that deliver nothing before watchRun gives up. */
|
|
1027
|
+
const MAX_IDLE_STREAM_REOPENS = 30;
|
|
1028
|
+
function abortableDelay(ms, signal) {
|
|
1029
|
+
return new Promise((resolve, reject) => {
|
|
1030
|
+
if (signal?.aborted) return reject(signal.reason);
|
|
1031
|
+
const timer = setTimeout(() => {
|
|
1032
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1033
|
+
resolve();
|
|
1034
|
+
}, ms);
|
|
1035
|
+
const onAbort = () => {
|
|
1036
|
+
clearTimeout(timer);
|
|
1037
|
+
reject(signal?.reason);
|
|
1038
|
+
};
|
|
1039
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
var HubWorkflowsClient = class extends WorkflowKvResource {
|
|
1043
|
+
constructor(request, stream, download) {
|
|
1044
|
+
super(request);
|
|
1045
|
+
this.stream = stream;
|
|
1046
|
+
this.download = download;
|
|
1047
|
+
}
|
|
1048
|
+
async list() {
|
|
1049
|
+
return this.request("/v1/workflows", { method: "GET" });
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Owner-wide fleet overview: one row per workflow with its run tallies over
|
|
1053
|
+
* a trailing window (default 7d), sorted failing-first. Answers "how are ALL
|
|
1054
|
+
* my workflows doing" in one call — the per-workflow insights endpoint is
|
|
1055
|
+
* the drill-down, not a way to build this row set client-side.
|
|
1056
|
+
*/
|
|
1057
|
+
async fleetInsights(opts = {}) {
|
|
1058
|
+
const query = opts.window ? `?window=${encodeURIComponent(opts.window)}` : "";
|
|
1059
|
+
return this.request(`/v1/workflows/insights/fleet${query}`, { method: "GET" });
|
|
1060
|
+
}
|
|
1061
|
+
async get(id) {
|
|
1062
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}`, { method: "GET" });
|
|
1063
|
+
}
|
|
1064
|
+
async create(yaml) {
|
|
1065
|
+
return this.request("/v1/workflows", jsonInit("POST", { yaml }));
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Replace the workflow's definition from YAML (recompiled server-side,
|
|
1069
|
+
* triggers reconciled). Every landed update appends a revision;
|
|
1070
|
+
* `opts.note` rides onto that revision row (e.g. "why this change"), visible
|
|
1071
|
+
* in {@link listRevisions}. Throws `HubSdkError`: `WORKFLOW_INVALID` when the
|
|
1072
|
+
* YAML doesn't compile or its connections aren't met, `CONFLICT` on a
|
|
1073
|
+
* concurrent modification, `NOT_FOUND` for an unknown/foreign id.
|
|
1074
|
+
*/
|
|
1075
|
+
async update(id, yaml, opts = {}) {
|
|
1076
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}`, jsonInit("PUT", {
|
|
1077
|
+
yaml,
|
|
1078
|
+
...opts.note !== void 0 ? { note: opts.note } : {}
|
|
1079
|
+
}));
|
|
1080
|
+
}
|
|
1081
|
+
async delete(id) {
|
|
1082
|
+
await this.request(`/v1/workflows/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Enable or disable a workflow without editing its YAML. Works even when the
|
|
1086
|
+
* workflow's connection was revoked (a disabled workflow can't be recompiled
|
|
1087
|
+
* via `update`, but it can still be paused/resumed here).
|
|
1088
|
+
*/
|
|
1089
|
+
async setEnabled(id, enabled) {
|
|
1090
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}`, jsonInit("PATCH", { enabled }));
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* One page of run history, newest first. Pass the previous page's
|
|
1094
|
+
* `nextCursor` to fetch the next; a null `nextCursor` means the last page.
|
|
1095
|
+
*/
|
|
1096
|
+
async listRuns(id, opts = {}) {
|
|
1097
|
+
const params = new URLSearchParams();
|
|
1098
|
+
if (opts.limit !== void 0) params.set("limit", String(opts.limit));
|
|
1099
|
+
if (opts.cursor !== void 0) params.set("cursor", opts.cursor);
|
|
1100
|
+
const query = params.toString();
|
|
1101
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs${query ? `?${query}` : ""}`, { method: "GET" });
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Enqueue an immediate ("Run now") run and return its `runId`. The run goes
|
|
1105
|
+
* through the identical execution path a trigger delivery uses.
|
|
1106
|
+
*
|
|
1107
|
+
* `inputs` supplies the trigger fields the workflow reads (its
|
|
1108
|
+
* {@link HubWorkflow.manualRunInputs}), as a flat `{ path: value }` map — e.g.
|
|
1109
|
+
* `{ "pull_request.number": "123" }`. Omit it for a one-click run of a
|
|
1110
|
+
* workflow that reads no trigger fields. Throws `HubSdkError`:
|
|
1111
|
+
* `MISSING_RUN_INPUTS` when a required field is absent (`details.missing`
|
|
1112
|
+
* names them), `NOT_FOUND` for an unknown/foreign id. A paused workflow
|
|
1113
|
+
* still runs once; pausing only stops its triggers. Pass `opts.signal` to
|
|
1114
|
+
* abort a slow request.
|
|
1115
|
+
*/
|
|
1116
|
+
async run(id, inputs, opts = {}) {
|
|
1117
|
+
const hasInputs = inputs !== void 0 && Object.keys(inputs).length > 0;
|
|
1118
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/run`, {
|
|
1119
|
+
...jsonInit("POST", hasInputs ? { inputs } : {}),
|
|
1120
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* A single run's full detail: per-action `output` + `agentRun` execution
|
|
1125
|
+
* detail (size-capped server-side) and the trigger context — the deep "why
|
|
1126
|
+
* did this run do what it did" view. `NOT_FOUND` when the run is unknown or
|
|
1127
|
+
* does not belong to both the caller and this workflow.
|
|
1128
|
+
*/
|
|
1129
|
+
async getRun(id, runId, opts = {}) {
|
|
1130
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}`, {
|
|
1131
|
+
method: "GET",
|
|
1132
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Stop a queued or in-flight run. A `queued` run is cancelled synchronously
|
|
1137
|
+
* and comes back `{ status: "cancelled" }` (already terminal); a `running` run
|
|
1138
|
+
* is signalled to abort and comes back `{ status: "cancelling", signalled }` —
|
|
1139
|
+
* it settles `cancelled` a moment later as its current action tears down, which
|
|
1140
|
+
* a {@link watchRun}/{@link getRun} observes. Throws `HubSdkError`:
|
|
1141
|
+
* `RUN_NOT_CANCELLABLE` when the run already finished, `NOT_FOUND` for an
|
|
1142
|
+
* unknown/foreign run id. Pass `opts.signal` to abort a slow request.
|
|
1143
|
+
*/
|
|
1144
|
+
async cancel(id, runId, opts = {}) {
|
|
1145
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/cancel`, {
|
|
1146
|
+
method: "POST",
|
|
1147
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Re-run a terminal failed/cancelled run: enqueues a FRESH run of the same
|
|
1152
|
+
* workflow with the SAME trigger context the original ran against, linked
|
|
1153
|
+
* back to it via `retriedFromRunId`. The original row is never mutated —
|
|
1154
|
+
* execution is at-most-once, so a retry is always a new run, never a
|
|
1155
|
+
* requeue. The response carries the NEW run id (and the run it retried);
|
|
1156
|
+
* follow it with {@link getRun}/{@link watchRun}/{@link waitForRun} like any
|
|
1157
|
+
* other run. Throws `HubSdkError`: `RUN_NOT_RETRYABLE` when the run is not
|
|
1158
|
+
* terminal-failed/cancelled (queued/running/waiting, or succeeded),
|
|
1159
|
+
* `NOT_FOUND` for an unknown/foreign run id; a workflow paused since the
|
|
1160
|
+
* failure still retries. Pass `opts.signal` to abort a slow request.
|
|
1161
|
+
*/
|
|
1162
|
+
async retryRun(id, runId, opts = {}) {
|
|
1163
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/retry`, {
|
|
1164
|
+
method: "POST",
|
|
1165
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Stream a run's live progress as an async iterable of typed events. The
|
|
1170
|
+
* first event is always a `snapshot` of the current persisted state; then
|
|
1171
|
+
* `action.*` / `iteration.*` / `token` ticks arrive as the run executes;
|
|
1172
|
+
* `ping` is a keepalive; a terminal `run.done` ends the iteration. If the run
|
|
1173
|
+
* is already finished when the stream opens, it yields the snapshot then
|
|
1174
|
+
* `run.done` and completes.
|
|
1175
|
+
*
|
|
1176
|
+
* Live ticks require the worker executing the run to share the API process;
|
|
1177
|
+
* across instances only `snapshot` + `run.done` (from the server's terminal
|
|
1178
|
+
* poll) arrive — the persisted record, read via {@link getRun}, stays the
|
|
1179
|
+
* source of truth. Pass `signal` to cancel; iterating to `run.done` (or
|
|
1180
|
+
* breaking early) releases the connection.
|
|
1181
|
+
*
|
|
1182
|
+
* A stream that ends before `run.done` or `run.waiting` (the Platform is
|
|
1183
|
+
* restarting or handing over) is reopened after a second, as is one refused
|
|
1184
|
+
* with `SERVICE_DRAINING`; the reopened stream starts with a fresh
|
|
1185
|
+
* `snapshot`. After 30 reopens in a row that deliver nothing it throws
|
|
1186
|
+
* `HubSdkError(WORKFLOW_STREAM_INTERRUPTED)`.
|
|
1187
|
+
*/
|
|
1188
|
+
async *watchRun(id, runId, opts = {}) {
|
|
1189
|
+
const openStream = this.stream;
|
|
1190
|
+
if (!openStream) throw new HubSdkError({
|
|
1191
|
+
code: "HUB_CONFIG_INVALID",
|
|
1192
|
+
message: "watchRun needs a streaming transport; use HubClient (which wires it up) rather than constructing HubWorkflowsClient directly"
|
|
1193
|
+
});
|
|
1194
|
+
const path = `/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/events`;
|
|
1195
|
+
let idleReopens = 0;
|
|
1196
|
+
for (;;) {
|
|
1197
|
+
let delivered = false;
|
|
1198
|
+
const connection = this.watchRunOnce(openStream, path, opts);
|
|
1199
|
+
try {
|
|
1200
|
+
for (;;) {
|
|
1201
|
+
const next = await connection.next();
|
|
1202
|
+
if (next.done) {
|
|
1203
|
+
if (next.value === "ended") return;
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
delivered = true;
|
|
1207
|
+
yield next.value;
|
|
1208
|
+
}
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
if (!(error instanceof HubSdkError) || error.code !== "SERVICE_DRAINING" || opts.signal?.aborted) throw error;
|
|
1211
|
+
} finally {
|
|
1212
|
+
await connection.return("interrupted");
|
|
1213
|
+
}
|
|
1214
|
+
idleReopens = delivered ? 0 : idleReopens + 1;
|
|
1215
|
+
if (idleReopens > MAX_IDLE_STREAM_REOPENS) throw new HubSdkError({
|
|
1216
|
+
code: "WORKFLOW_STREAM_INTERRUPTED",
|
|
1217
|
+
message: `the run event stream ended ${MAX_IDLE_STREAM_REOPENS} times without delivering an event`
|
|
1218
|
+
});
|
|
1219
|
+
await abortableDelay(STREAM_REOPEN_DELAY_MS, opts.signal);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
/** One connection's events, then how it ended. */
|
|
1223
|
+
async *watchRunOnce(openStream, path, opts) {
|
|
1224
|
+
const reader = (await openStream(path, {
|
|
1225
|
+
method: "GET",
|
|
1226
|
+
headers: { Accept: "text/event-stream" },
|
|
1227
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
1228
|
+
})).getReader();
|
|
1229
|
+
const decoder = new TextDecoder();
|
|
1230
|
+
const parser = new SSEChunkParser({ transform: (raw) => {
|
|
1231
|
+
try {
|
|
1232
|
+
return JSON.parse(raw);
|
|
1233
|
+
} catch {
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
} });
|
|
1237
|
+
try {
|
|
1238
|
+
for (;;) {
|
|
1239
|
+
const { done, value } = await reader.read();
|
|
1240
|
+
const text = done ? decoder.decode() : decoder.decode(value, { stream: true });
|
|
1241
|
+
const parsedEvents = done ? [...parser.push(text), ...parser.flush()] : parser.push(text);
|
|
1242
|
+
for (const parsed of parsedEvents) {
|
|
1243
|
+
const event = toRunStreamEvent(parsed.eventType, parsed.data);
|
|
1244
|
+
if (!event) continue;
|
|
1245
|
+
yield event;
|
|
1246
|
+
if (event.type === "run.done" || event.type === "run.waiting") return "ended";
|
|
1247
|
+
}
|
|
1248
|
+
if (done) return "interrupted";
|
|
1249
|
+
}
|
|
1250
|
+
} finally {
|
|
1251
|
+
await reader.cancel().catch(() => {});
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Poll {@link getRun} until the run reaches a terminal state (`succeeded` or
|
|
1256
|
+
* `failed`) and return its detail. The scripting primitive behind "run and
|
|
1257
|
+
* print the result": pairs with {@link run} for a one-call trigger-and-wait.
|
|
1258
|
+
*
|
|
1259
|
+
* Returns as soon as the run leaves the active set (`queued` / `running`) —
|
|
1260
|
+
* i.e. on `succeeded`, `failed`, or any future non-active status — so a new
|
|
1261
|
+
* terminal state can never hang the wait. Polls every `pollIntervalMs`
|
|
1262
|
+
* (default 2000). With `timeoutMs` set, throws
|
|
1263
|
+
* `HubSdkError(WORKFLOW_RUN_TIMEOUT)` — carrying the last observed status on
|
|
1264
|
+
* `details.status` — only once the deadline has actually passed; the last
|
|
1265
|
+
* wait is clamped to the remaining time so a `timeoutMs` shorter than the
|
|
1266
|
+
* poll interval still waits the full requested window rather than giving up a
|
|
1267
|
+
* poll early. Pass `signal` to cancel the wait between polls.
|
|
1268
|
+
*/
|
|
1269
|
+
async waitForRun(id, runId, opts = {}) {
|
|
1270
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 2e3;
|
|
1271
|
+
if (!Number.isFinite(pollIntervalMs) || pollIntervalMs <= 0) throw new HubSdkError({
|
|
1272
|
+
code: "HUB_CONFIG_INVALID",
|
|
1273
|
+
message: `waitForRun pollIntervalMs must be a positive number (got ${opts.pollIntervalMs})`
|
|
1274
|
+
});
|
|
1275
|
+
if (opts.timeoutMs !== void 0 && (!Number.isFinite(opts.timeoutMs) || opts.timeoutMs <= 0)) throw new HubSdkError({
|
|
1276
|
+
code: "HUB_CONFIG_INVALID",
|
|
1277
|
+
message: `waitForRun timeoutMs must be a positive number (got ${opts.timeoutMs})`
|
|
1278
|
+
});
|
|
1279
|
+
const deadline = opts.timeoutMs !== void 0 ? Date.now() + opts.timeoutMs : null;
|
|
1280
|
+
const timeoutError = (status) => new HubSdkError({
|
|
1281
|
+
code: "WORKFLOW_RUN_TIMEOUT",
|
|
1282
|
+
message: `Workflow run ${runId} did not reach a terminal state within ${opts.timeoutMs}ms${status ? ` (last status: ${status})` : ""}`,
|
|
1283
|
+
details: {
|
|
1284
|
+
runId,
|
|
1285
|
+
status,
|
|
1286
|
+
timeoutMs: opts.timeoutMs
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
let lastStatus;
|
|
1290
|
+
for (;;) {
|
|
1291
|
+
if (opts.signal?.aborted) throw opts.signal.reason ?? /* @__PURE__ */ new Error("The operation was aborted");
|
|
1292
|
+
const remainingMs = deadline !== null ? deadline - Date.now() : null;
|
|
1293
|
+
if (remainingMs !== null && remainingMs <= 0) throw timeoutError(lastStatus);
|
|
1294
|
+
const pollSignal = combineAbortSignals(opts.signal, remainingMs);
|
|
1295
|
+
let run;
|
|
1296
|
+
try {
|
|
1297
|
+
run = await this.getRun(id, runId, pollSignal ? { signal: pollSignal } : {});
|
|
1298
|
+
} catch (err) {
|
|
1299
|
+
if (opts.signal?.aborted) throw err;
|
|
1300
|
+
if (deadline !== null && isAbortError(err)) throw timeoutError(lastStatus);
|
|
1301
|
+
throw err;
|
|
1302
|
+
}
|
|
1303
|
+
lastStatus = run.status;
|
|
1304
|
+
if (run.status !== "queued" && run.status !== "running") return run;
|
|
1305
|
+
const delayRemainingMs = deadline !== null ? deadline - Date.now() : null;
|
|
1306
|
+
if (delayRemainingMs !== null && delayRemainingMs <= 0) throw timeoutError(lastStatus);
|
|
1307
|
+
await delay(delayRemainingMs !== null ? Math.min(pollIntervalMs, delayRemainingMs) : pollIntervalMs, opts.signal);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* The workflow's definition history, newest rev first — metadata only (rev,
|
|
1312
|
+
* attribution, note); fetch the YAML per-rev with {@link getRevision}. Empty
|
|
1313
|
+
* for a pre-versioning workflow until its next update.
|
|
1314
|
+
*/
|
|
1315
|
+
async listRevisions(id) {
|
|
1316
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/revisions`, { method: "GET" });
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* One revision WITH its YAML (and the actions compiled alongside it), for
|
|
1320
|
+
* diff/inspect views. Throws `HubSdkError(REVISION_NOT_FOUND)` for an
|
|
1321
|
+
* unknown rev, `NOT_FOUND` for an unknown/foreign workflow.
|
|
1322
|
+
*/
|
|
1323
|
+
async getRevision(id, rev) {
|
|
1324
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/revisions/${encodeURIComponent(String(rev))}`, { method: "GET" });
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Restore a prior rev's YAML as a NEW head revision (history is append-only)
|
|
1328
|
+
* through the same compile path as {@link update}, so the restored definition
|
|
1329
|
+
* is re-validated against today's connections/models. Throws `HubSdkError`:
|
|
1330
|
+
* `REVISION_NOT_FOUND` for an unknown rev, `WORKFLOW_INVALID` when the old
|
|
1331
|
+
* YAML no longer compiles, `CONFLICT` on a concurrent modification.
|
|
1332
|
+
*/
|
|
1333
|
+
async rollback(id, rev) {
|
|
1334
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/rollback`, jsonInit("POST", { rev }));
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Deterministic health stats over the workflow's last N terminal runs
|
|
1338
|
+
* (default 20, max 100): per-node visits/failures/cost/duration, failure
|
|
1339
|
+
* clusters by normalized error signature, and distillation CANDIDATES
|
|
1340
|
+
* (agent.run nodes with near-static outputs — labeled with their
|
|
1341
|
+
* measurements, never as verdicts). No model in this path.
|
|
1342
|
+
*/
|
|
1343
|
+
async health(id, opts = {}) {
|
|
1344
|
+
const query = opts.runs !== void 0 ? `?runs=${opts.runs}` : "";
|
|
1345
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/health${query}`, { method: "GET" });
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* One agent round's execution timeline, paged. `action` is the action's
|
|
1349
|
+
* `${steps[N]}` position and `iteration` the round's position in that
|
|
1350
|
+
* action's `iterations` — both exactly as the run detail serializes them.
|
|
1351
|
+
* Pass the previous page's `nextCursor` as `after` for the next page; a null
|
|
1352
|
+
* `nextCursor` means the round is fully read.
|
|
1353
|
+
*/
|
|
1354
|
+
async listRunSteps(id, runId, input) {
|
|
1355
|
+
const params = new URLSearchParams({
|
|
1356
|
+
action: String(input.action),
|
|
1357
|
+
iteration: String(input.iteration)
|
|
1358
|
+
});
|
|
1359
|
+
if (input.after !== void 0) params.set("after", String(input.after));
|
|
1360
|
+
if (input.limit !== void 0) params.set("limit", String(input.limit));
|
|
1361
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/steps?${params.toString()}`, { method: "GET" });
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Store one artifact for a run (raw bytes) and return the opaque ref — the
|
|
1365
|
+
* value a script.run node returns as its step output. `contentType` defaults
|
|
1366
|
+
* to `application/octet-stream`; `nodeId` attributes the write to a graph
|
|
1367
|
+
* node (owner credential only — a run-scoped token binds its own node).
|
|
1368
|
+
* Throws `HubSdkError(ARTIFACT_TOO_LARGE)` past the server cap.
|
|
1369
|
+
*/
|
|
1370
|
+
async uploadArtifact(id, runId, input) {
|
|
1371
|
+
const headers = {
|
|
1372
|
+
"Content-Type": input.contentType ?? "application/octet-stream",
|
|
1373
|
+
"x-wf-artifact-name": input.name
|
|
1374
|
+
};
|
|
1375
|
+
if (input.nodeId !== void 0) headers["x-wf-artifact-node"] = input.nodeId;
|
|
1376
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/artifacts`, {
|
|
1377
|
+
method: "POST",
|
|
1378
|
+
body: input.bytes,
|
|
1379
|
+
headers
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
/** Metadata for every artifact of the run (never the bytes), oldest first. */
|
|
1383
|
+
async listArtifacts(id, runId) {
|
|
1384
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/artifacts`, { method: "GET" });
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* The artifact's raw bytes with their stored content type — the route every
|
|
1388
|
+
* `downloadUrl` attached to an `$artifact` ref in a run detail points at.
|
|
1389
|
+
*/
|
|
1390
|
+
async downloadArtifact(id, runId, artifactId) {
|
|
1391
|
+
const downloadBytes = this.download;
|
|
1392
|
+
if (!downloadBytes) throw new HubSdkError({
|
|
1393
|
+
code: "HUB_CONFIG_INVALID",
|
|
1394
|
+
message: "downloadArtifact needs a byte transport; use HubClient (which wires it up) rather than constructing HubWorkflowsClient directly"
|
|
1395
|
+
});
|
|
1396
|
+
return downloadBytes(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(artifactId)}`, { method: "GET" });
|
|
1397
|
+
}
|
|
1398
|
+
/** The curated starter gallery runnable on THIS deployment: metadata,
|
|
1399
|
+
* parameters, referenced providers, and a concrete `previewYaml` per card. */
|
|
1400
|
+
async listTemplates() {
|
|
1401
|
+
return this.request("/v1/workflows/templates", { method: "GET" });
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* One-click create from a gallery template: substitutes `parameters` into
|
|
1405
|
+
* the template server-side, then runs the SAME create path as
|
|
1406
|
+
* {@link create}. A `{ created: false }` result is NOT an error — an
|
|
1407
|
+
* actionable continuation listing the connections to satisfy before
|
|
1408
|
+
* retrying. Invalid parameters or a compile failure throw `HubSdkError`
|
|
1409
|
+
* (400); an unknown template id throws `NOT_FOUND`.
|
|
1410
|
+
*/
|
|
1411
|
+
async instantiateTemplate(templateId, parameters) {
|
|
1412
|
+
return this.request(`/v1/workflows/templates/${encodeURIComponent(templateId)}/instantiate`, jsonInit("POST", parameters ? { parameters } : {}));
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* The caller's open decisions across all workflows — the always-available
|
|
1416
|
+
* "pending approvals" surface. Each entry carries its workflow's name (null
|
|
1417
|
+
* when the workflow was deleted while the run was parked).
|
|
1418
|
+
*/
|
|
1419
|
+
async listPendingDecisions() {
|
|
1420
|
+
return this.request("/v1/workflows/decisions/pending", { method: "GET" });
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* The pending decision a run is parked on. Throws `HubSdkError(NOT_FOUND)`
|
|
1424
|
+
* when the run is unknown/foreign or holds no pending decision.
|
|
1425
|
+
*/
|
|
1426
|
+
async getRunDecision(id, runId) {
|
|
1427
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/decision`, { method: "GET" });
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Answer the decision a run is parked on and resume it from the next action.
|
|
1431
|
+
* `decisionId` is REQUIRED: a run can resolve one decision and re-park on the
|
|
1432
|
+
* next, so an answer addressed only by run could silently land on a question
|
|
1433
|
+
* the caller never saw — a mismatch throws `HubSdkError(DECISION_SUPERSEDED)`.
|
|
1434
|
+
* Other typed failures: `INVALID_CHOICE` (400), `DECISION_ALREADY_RESOLVED` /
|
|
1435
|
+
* `DECISION_EXPIRED` (409), `DECISION_NOT_FOUND` (404), `RUN_NOT_WAITING`
|
|
1436
|
+
* (409 — answered, timed out, or cancelled first).
|
|
1437
|
+
*/
|
|
1438
|
+
async resolveRunDecision(id, runId, input) {
|
|
1439
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/decision/resolve`, jsonInit("POST", input));
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Dry-run a YAML definition against the platform compiler without saving it.
|
|
1443
|
+
*
|
|
1444
|
+
* `opts.structural` selects the CI mode: the platform skips its owner-scoped
|
|
1445
|
+
* connection/skill/profile resolution, so the verdict depends only on the
|
|
1446
|
+
* definition and the deployment's capabilities — identical no matter which
|
|
1447
|
+
* providers the calling account has connected. Use it in pipelines that
|
|
1448
|
+
* assert "this YAML is well-formed"; leave it off in an editor, where naming
|
|
1449
|
+
* the unconnected providers is the point.
|
|
1450
|
+
*/
|
|
1451
|
+
async validate(yaml, opts = {}) {
|
|
1452
|
+
return this.request("/v1/workflows/validate", jsonInit("POST", {
|
|
1453
|
+
yaml,
|
|
1454
|
+
...opts.structural ? { structural: true } : {}
|
|
1455
|
+
}));
|
|
1456
|
+
}
|
|
1457
|
+
async schema() {
|
|
1458
|
+
return this.request("/v1/workflows/schema", { method: "GET" });
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
//#endregion
|
|
1462
|
+
//#region src/client.ts
|
|
1463
|
+
function isDispatchCurrentData(value) {
|
|
1464
|
+
return isRecord(value) && typeof value.current === "boolean";
|
|
1465
|
+
}
|
|
1466
|
+
var HubClient = class HubClient extends HubTransport {
|
|
1467
|
+
knowledge;
|
|
1468
|
+
allowances;
|
|
1469
|
+
apps;
|
|
1470
|
+
connections;
|
|
1471
|
+
channels;
|
|
1472
|
+
numbers;
|
|
1473
|
+
eventSubscriptions;
|
|
1474
|
+
permissions;
|
|
1475
|
+
tokens;
|
|
1476
|
+
tools;
|
|
1477
|
+
approvals;
|
|
1478
|
+
audit;
|
|
1479
|
+
githubApp;
|
|
1480
|
+
workflows;
|
|
1481
|
+
lines;
|
|
1482
|
+
constructor(options) {
|
|
1483
|
+
super(options);
|
|
1484
|
+
this.knowledge = new HubKnowledgeClient((path, init) => this.request(path, init));
|
|
1485
|
+
this.allowances = new HubAllowancesClient((path, init) => this.request(path, init));
|
|
1486
|
+
this.apps = new HubAppsClient((path, init) => this.request(path, init));
|
|
1487
|
+
this.numbers = new HubManagedNumbersClient((path, init) => this.request(path, init));
|
|
1488
|
+
this.connections = new HubConnectionsClient((path, init) => this.request(path, init));
|
|
1489
|
+
this.channels = new HubChannelsClient((path, init) => this.request(path, init));
|
|
1490
|
+
this.eventSubscriptions = new HubEventSubscriptionsClient((path, init) => this.request(path, init));
|
|
1491
|
+
this.permissions = new HubPermissionsClient((path, init) => this.request(path, init));
|
|
1492
|
+
this.tokens = new HubTokensClient((path, init) => this.request(path, init));
|
|
1493
|
+
this.tools = new HubToolsClient((path, init) => this.request(path, init));
|
|
1494
|
+
this.approvals = new HubApprovalsClient((path, init) => this.request(path, init));
|
|
1495
|
+
this.audit = new HubAuditClient((path, init) => this.request(path, init));
|
|
1496
|
+
this.githubApp = new HubGithubAppClient((path, init) => this.request(path, init));
|
|
1497
|
+
this.workflows = new HubWorkflowsClient((path, init) => this.request(path, init), (path, init) => this.stream(path, init), (path, init) => this.requestBytes(path, init));
|
|
1498
|
+
this.lines = new HubLinesClient((path, init) => this.request(path, init), (yaml) => this.workflows.create(yaml), (path, init, callbackBearer) => {
|
|
1499
|
+
if (typeof callbackBearer !== "string" || !/^[\x21-\x7e]{16,512}$/.test(callbackBearer)) throw new HubSdkError({
|
|
1500
|
+
code: "HUB_INVALID_INPUT",
|
|
1501
|
+
message: "Application callback bearer is invalid"
|
|
1502
|
+
});
|
|
1503
|
+
const headers = new Headers(init.headers);
|
|
1504
|
+
headers.set("Authorization", `Bearer ${callbackBearer}`);
|
|
1505
|
+
return this.request(path, {
|
|
1506
|
+
...init,
|
|
1507
|
+
headers,
|
|
1508
|
+
credentials: "omit",
|
|
1509
|
+
redirect: "manual"
|
|
1510
|
+
}, {
|
|
1511
|
+
auth: "caller",
|
|
1512
|
+
validateData: isDispatchCurrentData
|
|
1513
|
+
});
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
static fromEnv(options = {}) {
|
|
1517
|
+
const env = options.env ?? readProcessEnv();
|
|
1518
|
+
return new HubClient({
|
|
1519
|
+
baseUrl: resolveHubBaseUrl(env),
|
|
1520
|
+
apiKey: options.apiKey ?? (options.authHeaders ? void 0 : resolveHubAuth(env)),
|
|
1521
|
+
authHeaders: options.authHeaders,
|
|
1522
|
+
fetch: options.fetch
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
async status() {
|
|
1526
|
+
return this.request("/v1/hub/status", { method: "GET" });
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Open a streaming (Server-Sent Events) response and return its raw body.
|
|
1530
|
+
* Shares auth-header building + `fetch` with {@link request}, but does NOT
|
|
1531
|
+
* buffer or JSON-parse the body — the caller consumes the stream.
|
|
1532
|
+
*
|
|
1533
|
+
* A successful stream comes back as `text/event-stream`. Anything else is an
|
|
1534
|
+
* error envelope (404/429/401 JSON) or a transport failure (gateway/HTML
|
|
1535
|
+
* page); it is surfaced the same way {@link request} surfaces errors — a
|
|
1536
|
+
* typed `HubSdkError` from a `{success:false}` envelope, otherwise an
|
|
1537
|
+
* `HUB_HTTP_<status>` transport error — rather than handed back as bogus SSE.
|
|
1538
|
+
*/
|
|
1539
|
+
async stream(path, init) {
|
|
1540
|
+
const response = await this.fetch(`${this.baseUrl}${path}`, {
|
|
1541
|
+
...init,
|
|
1542
|
+
headers: await this.buildHeaders(init.headers)
|
|
1543
|
+
});
|
|
1544
|
+
const status = response.status;
|
|
1545
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1546
|
+
if (!response.ok || !contentType.includes("text/event-stream")) await throwHubResponseError(path, response, status, contentType);
|
|
1547
|
+
if (!response.body) throw httpError(path, status, response.statusText, "event-stream response had no body", contentType);
|
|
1548
|
+
return response.body;
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Fetch raw (non-envelope) bytes — the artifact download route, whose 200 is
|
|
1552
|
+
* the artifact's own content type, not JSON. Error responses on such routes
|
|
1553
|
+
* are still the standard envelope, so a failure surfaces exactly like
|
|
1554
|
+
* {@link request}: a typed `HubSdkError` from a `{success:false}` body,
|
|
1555
|
+
* otherwise an `HUB_HTTP_<status>` transport error.
|
|
1556
|
+
*/
|
|
1557
|
+
async requestBytes(path, init) {
|
|
1558
|
+
const response = await this.fetch(`${this.baseUrl}${path}`, {
|
|
1559
|
+
...init,
|
|
1560
|
+
headers: await this.buildHeaders(init.headers)
|
|
1561
|
+
});
|
|
1562
|
+
const status = response.status;
|
|
1563
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1564
|
+
if (!response.ok) await throwHubResponseError(path, response, status, contentType);
|
|
1565
|
+
return {
|
|
1566
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
1567
|
+
contentType
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
//#endregion
|
|
1572
|
+
export { HubLinesClient as C, HubAllowancesClient as D, HubAppsClient as E, jsonInit as O, HubManagedNumbersClient as S, HubKnowledgeManagementClient as T, resolveHubBaseUrl as _, HubChannelsClient as a, isHubUnauthenticated as b, HubInboundWebhooksClient as c, HubPermissionsClient as d, HubTokensClient as f, resolveHubAuth as g, HUB_URL_ENV_VAR as h, HubGithubAppClient as i, HubToolsClient as l, HUB_CAPABILITY_TOKEN_ENV_VAR as m, HubWorkflowsClient as n, HubEventSubscriptionsClient as o, HUB_API_KEY_ENV_VAR as p, HubAuditClient as r, HubConnectionsClient as s, HubClient as t, HubApprovalsClient as u, HubSdkError as v, HubKnowledgeClient as w, redactHubValue as x, HubTransport as y };
|
|
1573
|
+
|
|
1574
|
+
//# sourceMappingURL=client-CsdLLECZ.js.map
|