@tangle-network/hub-sdk 0.32.0-develop.20260826055638.8c2f7a4 → 0.32.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/README.md +194 -8
- package/dist/index.d.ts +1706 -561
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1077 -526
- package/dist/index.js.map +1 -1
- package/package.json +13 -5
package/dist/index.js
CHANGED
|
@@ -1,7 +1,317 @@
|
|
|
1
|
-
import { SSEChunkParser } from "@tangle-network/
|
|
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
|
|
2
312
|
//#region src/redaction.ts
|
|
3
313
|
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_
|
|
314
|
+
const SECRET_VALUE_PATTERN = /\b(?:sk-tan[-_]|hubcap_|hct_|lrc_|gh[pousr]_)[A-Za-z0-9_.=-]+\b/g;
|
|
5
315
|
function redactHubValue(value) {
|
|
6
316
|
if (typeof value === "string") return value.replace(SECRET_VALUE_PATTERN, "[REDACTED]");
|
|
7
317
|
if (Array.isArray(value)) return value.map((item) => redactHubValue(item));
|
|
@@ -9,7 +319,7 @@ function redactHubValue(value) {
|
|
|
9
319
|
return value;
|
|
10
320
|
}
|
|
11
321
|
//#endregion
|
|
12
|
-
//#region src/
|
|
322
|
+
//#region src/transport.ts
|
|
13
323
|
var HubSdkError = class extends Error {
|
|
14
324
|
code;
|
|
15
325
|
details;
|
|
@@ -26,16 +336,18 @@ function bodySnippet(body, max = 200) {
|
|
|
26
336
|
const trimmed = body.trim();
|
|
27
337
|
return trimmed.length > max ? `${trimmed.slice(0, max)}…` : trimmed;
|
|
28
338
|
}
|
|
29
|
-
function httpError(path, status, statusText, body, contentType) {
|
|
30
|
-
const suffix = statusText ? ` ${statusText}` : "";
|
|
339
|
+
function httpError(path, status, statusText, body, contentType, omitResponseDetails = false) {
|
|
340
|
+
const suffix = !omitResponseDetails && statusText ? ` ${statusText}` : "";
|
|
31
341
|
return new HubSdkError({
|
|
32
342
|
code: `HUB_HTTP_${status}`,
|
|
33
343
|
message: `Hub request ${path} failed: HTTP ${status}${suffix}`,
|
|
34
344
|
details: {
|
|
35
345
|
status,
|
|
36
346
|
path,
|
|
37
|
-
|
|
38
|
-
|
|
347
|
+
...omitResponseDetails ? {} : {
|
|
348
|
+
contentType: contentType || null,
|
|
349
|
+
bodySnippet: bodySnippet(body)
|
|
350
|
+
}
|
|
39
351
|
}
|
|
40
352
|
}, { status });
|
|
41
353
|
}
|
|
@@ -52,132 +364,82 @@ function isHubSuccessEnvelope(value) {
|
|
|
52
364
|
function isRecord$1(value) {
|
|
53
365
|
return typeof value === "object" && value !== null;
|
|
54
366
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/** An action's terminal status on `action.finished`. Beyond succeeded/failed a
|
|
66
|
-
* guarded action can end `skipped` (its `if` resolved false, so it never ran) —
|
|
67
|
-
* accepted here so a skipped event is surfaced, not dropped as malformed. */
|
|
68
|
-
function isActionFinishedStatus(value) {
|
|
69
|
-
return value === "succeeded" || value === "failed" || value === "skipped";
|
|
70
|
-
}
|
|
71
|
-
/** Terminal status of a whole run's `run.done` event. A run can end `cancelled`
|
|
72
|
-
* (a user cancel) — a value neither the action nor iteration status checkers
|
|
73
|
-
* accept — so `run.done` uses this rather than them, which would drop a
|
|
74
|
-
* cancelled terminal event and hang the stream. */
|
|
75
|
-
function isRunDoneStatus(value) {
|
|
76
|
-
return value === "succeeded" || value === "failed" || value === "cancelled";
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Map one raw SSE event (its `event:` name + parsed JSON `data:`) to a typed
|
|
80
|
-
* run-stream event, validating the fields each variant declares rather than
|
|
81
|
-
* blindly casting the wire payload. A frame that doesn't match its event's
|
|
82
|
-
* shape (or an unknown event name) returns null so the consumer skips it — a
|
|
83
|
-
* malformed `token` can never reach a consumer as `{ delta: undefined }`, and
|
|
84
|
-
* new server event kinds don't break older clients.
|
|
85
|
-
*/
|
|
86
|
-
function toRunStreamEvent(eventType, raw) {
|
|
87
|
-
if (eventType === "ping") return { type: "ping" };
|
|
88
|
-
if (!isRecord$1(raw)) return null;
|
|
89
|
-
switch (eventType) {
|
|
90
|
-
case "snapshot": return typeof raw.id === "string" && typeof raw.workflowId === "string" && typeof raw.status === "string" && Array.isArray(raw.actionResults) ? {
|
|
91
|
-
type: "snapshot",
|
|
92
|
-
run: raw
|
|
93
|
-
} : null;
|
|
94
|
-
case "token": return typeof raw.delta === "string" && typeof raw.actionIndex === "number" ? {
|
|
95
|
-
type: "token",
|
|
96
|
-
actionIndex: raw.actionIndex,
|
|
97
|
-
delta: raw.delta,
|
|
98
|
-
at: optString(raw.at)
|
|
99
|
-
} : null;
|
|
100
|
-
case "run.done": return isRunDoneStatus(raw.status) ? {
|
|
101
|
-
type: "run.done",
|
|
102
|
-
status: raw.status,
|
|
103
|
-
error: typeof raw.error === "string" ? raw.error : null,
|
|
104
|
-
...typeof raw.at === "string" ? { at: raw.at } : {}
|
|
105
|
-
} : null;
|
|
106
|
-
case "run.waiting": return typeof raw.decisionId === "string" && raw.decisionId.length > 0 ? {
|
|
107
|
-
type: "run.waiting",
|
|
108
|
-
decisionId: raw.decisionId,
|
|
109
|
-
...typeof raw.at === "string" ? { at: raw.at } : {}
|
|
110
|
-
} : null;
|
|
111
|
-
case "action.started": return typeof raw.index === "number" && typeof raw.kind === "string" && typeof raw.at === "string" ? {
|
|
112
|
-
type: "action.started",
|
|
113
|
-
index: raw.index,
|
|
114
|
-
kind: raw.kind,
|
|
115
|
-
at: raw.at
|
|
116
|
-
} : null;
|
|
117
|
-
case "action.finished": return typeof raw.index === "number" && typeof raw.kind === "string" && isActionFinishedStatus(raw.status) && typeof raw.at === "string" ? {
|
|
118
|
-
type: "action.finished",
|
|
119
|
-
index: raw.index,
|
|
120
|
-
kind: raw.kind,
|
|
121
|
-
status: raw.status,
|
|
122
|
-
at: raw.at,
|
|
123
|
-
...typeof raw.costUsd === "number" ? { costUsd: raw.costUsd } : {},
|
|
124
|
-
...typeof raw.error === "string" ? { error: raw.error } : {}
|
|
125
|
-
} : null;
|
|
126
|
-
case "iteration.started": return typeof raw.actionIndex === "number" && typeof raw.iterationIndex === "number" && typeof raw.at === "string" ? {
|
|
127
|
-
type: "iteration.started",
|
|
128
|
-
actionIndex: raw.actionIndex,
|
|
129
|
-
iterationIndex: raw.iterationIndex,
|
|
130
|
-
at: raw.at,
|
|
131
|
-
...typeof raw.model === "string" ? { model: raw.model } : {}
|
|
132
|
-
} : null;
|
|
133
|
-
case "iteration.ended": return typeof raw.actionIndex === "number" && typeof raw.iterationIndex === "number" && isIterationStatus(raw.status) && typeof raw.at === "string" ? {
|
|
134
|
-
type: "iteration.ended",
|
|
135
|
-
actionIndex: raw.actionIndex,
|
|
136
|
-
iterationIndex: raw.iterationIndex,
|
|
137
|
-
status: raw.status,
|
|
138
|
-
at: raw.at,
|
|
139
|
-
...typeof raw.outputPreview === "string" ? { outputPreview: raw.outputPreview } : {},
|
|
140
|
-
...typeof raw.inputTokens === "number" ? { inputTokens: raw.inputTokens } : {},
|
|
141
|
-
...typeof raw.outputTokens === "number" ? { outputTokens: raw.outputTokens } : {},
|
|
142
|
-
...typeof raw.costUsd === "number" ? { costUsd: raw.costUsd } : {}
|
|
143
|
-
} : null;
|
|
144
|
-
default: return null;
|
|
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);
|
|
145
377
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return new Promise((resolve, reject) => {
|
|
166
|
-
if (signal?.aborted) {
|
|
167
|
-
reject(signal.reason ?? /* @__PURE__ */ new Error("The operation was aborted"));
|
|
168
|
-
return;
|
|
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);
|
|
169
397
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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);
|
|
180
440
|
}
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region src/environment.ts
|
|
181
443
|
const HUB_URL_ENV_VAR = "TANGLE_HUB_URL";
|
|
182
444
|
const HUB_API_KEY_ENV_VAR = "TANGLE_API_KEY";
|
|
183
445
|
const HUB_CAPABILITY_TOKEN_ENV_VAR = "TANGLE_HUB_CAPABILITY_TOKEN";
|
|
@@ -204,171 +466,32 @@ function resolveHubAuth(env = readProcessEnv()) {
|
|
|
204
466
|
const present = [nonEmpty(env[HUB_API_KEY_ENV_VAR]), nonEmpty(env[HUB_CAPABILITY_TOKEN_ENV_VAR])].filter((value) => value !== void 0);
|
|
205
467
|
if (present.length === 0) throw new HubSdkError({
|
|
206
468
|
code: "HUB_CONFIG_MISSING",
|
|
207
|
-
message: `One of ${HUB_API_KEY_ENV_VAR} or ${HUB_CAPABILITY_TOKEN_ENV_VAR} is required`,
|
|
208
|
-
details: { envVars: [HUB_API_KEY_ENV_VAR, HUB_CAPABILITY_TOKEN_ENV_VAR] }
|
|
209
|
-
});
|
|
210
|
-
if (present.length > 1) throw new HubSdkError({
|
|
211
|
-
code: "HUB_CONFIG_INVALID",
|
|
212
|
-
message: `Set exactly one of ${HUB_API_KEY_ENV_VAR} or ${HUB_CAPABILITY_TOKEN_ENV_VAR}, not both`,
|
|
213
|
-
details: { envVars: [HUB_API_KEY_ENV_VAR, HUB_CAPABILITY_TOKEN_ENV_VAR] }
|
|
214
|
-
});
|
|
215
|
-
return present[0];
|
|
216
|
-
}
|
|
217
|
-
function nonEmpty(value) {
|
|
218
|
-
if (value === void 0) return void 0;
|
|
219
|
-
const trimmed = value.trim();
|
|
220
|
-
return trimmed.length === 0 ? void 0 : trimmed;
|
|
221
|
-
}
|
|
222
|
-
function readProcessEnv() {
|
|
223
|
-
return globalThis.process?.env ?? {};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
fetch;
|
|
228
|
-
apiKey;
|
|
229
|
-
authHeaders;
|
|
230
|
-
connections;
|
|
231
|
-
channels;
|
|
232
|
-
eventSubscriptions;
|
|
233
|
-
permissions;
|
|
234
|
-
tokens;
|
|
235
|
-
tools;
|
|
236
|
-
approvals;
|
|
237
|
-
audit;
|
|
238
|
-
githubApp;
|
|
239
|
-
workflows;
|
|
240
|
-
constructor(options) {
|
|
241
|
-
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
242
|
-
this.apiKey = options.apiKey;
|
|
243
|
-
this.authHeaders = options.authHeaders;
|
|
244
|
-
this.fetch = options.fetch ?? fetch.bind(globalThis);
|
|
245
|
-
this.connections = new HubConnectionsClient((path, init) => this.request(path, init));
|
|
246
|
-
this.channels = new HubChannelsClient((path, init) => this.request(path, init));
|
|
247
|
-
this.eventSubscriptions = new HubEventSubscriptionsClient((path, init) => this.request(path, init));
|
|
248
|
-
this.permissions = new HubPermissionsClient((path, init) => this.request(path, init));
|
|
249
|
-
this.tokens = new HubTokensClient((path, init) => this.request(path, init));
|
|
250
|
-
this.tools = new HubToolsClient((path, init) => this.request(path, init));
|
|
251
|
-
this.approvals = new HubApprovalsClient((path, init) => this.request(path, init));
|
|
252
|
-
this.audit = new HubAuditClient((path, init) => this.request(path, init));
|
|
253
|
-
this.githubApp = new HubGithubAppClient((path, init) => this.request(path, init));
|
|
254
|
-
this.workflows = new HubWorkflowsClient((path, init) => this.request(path, init), (path, init) => this.stream(path, init), (path, init) => this.requestBytes(path, init));
|
|
255
|
-
}
|
|
256
|
-
static fromEnv(options = {}) {
|
|
257
|
-
const env = options.env ?? readProcessEnv();
|
|
258
|
-
return new HubClient({
|
|
259
|
-
baseUrl: resolveHubBaseUrl(env),
|
|
260
|
-
apiKey: options.apiKey ?? (options.authHeaders ? void 0 : resolveHubAuth(env)),
|
|
261
|
-
authHeaders: options.authHeaders,
|
|
262
|
-
fetch: options.fetch
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
async status() {
|
|
266
|
-
return this.request("/v1/hub/status", { method: "GET" });
|
|
267
|
-
}
|
|
268
|
-
async request(path, init) {
|
|
269
|
-
const response = await this.fetch(`${this.baseUrl}${path}`, {
|
|
270
|
-
...init,
|
|
271
|
-
headers: await this.buildHeaders(init.headers)
|
|
272
|
-
});
|
|
273
|
-
const status = response.status;
|
|
274
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
275
|
-
const bodyText = await response.text();
|
|
276
|
-
if (!contentType.includes("application/json")) throw httpError(path, status, response.statusText, bodyText, contentType);
|
|
277
|
-
let parsed;
|
|
278
|
-
try {
|
|
279
|
-
parsed = JSON.parse(bodyText);
|
|
280
|
-
} catch {
|
|
281
|
-
throw httpError(path, status, response.statusText, bodyText, contentType);
|
|
282
|
-
}
|
|
283
|
-
if (isHubErrorEnvelope(parsed)) throw new HubSdkError(parsed.error, { status });
|
|
284
|
-
if (isHubSuccessEnvelope(parsed)) return parsed.data;
|
|
285
|
-
throw httpError(path, status, response.statusText, bodyText, contentType);
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Open a streaming (Server-Sent Events) response and return its raw body.
|
|
289
|
-
* Shares auth-header building + `fetch` with {@link request}, but does NOT
|
|
290
|
-
* buffer or JSON-parse the body — the caller consumes the stream.
|
|
291
|
-
*
|
|
292
|
-
* A successful stream comes back as `text/event-stream`. Anything else is an
|
|
293
|
-
* error envelope (404/429/401 JSON) or a transport failure (gateway/HTML
|
|
294
|
-
* page); it is surfaced the same way {@link request} surfaces errors — a
|
|
295
|
-
* typed `HubSdkError` from a `{success:false}` envelope, otherwise an
|
|
296
|
-
* `HUB_HTTP_<status>` transport error — rather than handed back as bogus SSE.
|
|
297
|
-
*/
|
|
298
|
-
async stream(path, init) {
|
|
299
|
-
const response = await this.fetch(`${this.baseUrl}${path}`, {
|
|
300
|
-
...init,
|
|
301
|
-
headers: await this.buildHeaders(init.headers)
|
|
302
|
-
});
|
|
303
|
-
const status = response.status;
|
|
304
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
305
|
-
if (!response.ok || !contentType.includes("text/event-stream")) {
|
|
306
|
-
const bodyText = await response.text().catch(() => "");
|
|
307
|
-
if (contentType.includes("application/json")) {
|
|
308
|
-
let parsed;
|
|
309
|
-
try {
|
|
310
|
-
parsed = JSON.parse(bodyText);
|
|
311
|
-
} catch {
|
|
312
|
-
parsed = void 0;
|
|
313
|
-
}
|
|
314
|
-
if (isHubErrorEnvelope(parsed)) throw new HubSdkError(parsed.error, { status });
|
|
315
|
-
}
|
|
316
|
-
throw httpError(path, status, response.statusText, bodyText, contentType);
|
|
317
|
-
}
|
|
318
|
-
if (!response.body) throw httpError(path, status, response.statusText, "event-stream response had no body", contentType);
|
|
319
|
-
return response.body;
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Fetch raw (non-envelope) bytes — the artifact download route, whose 200 is
|
|
323
|
-
* the artifact's own content type, not JSON. Error responses on such routes
|
|
324
|
-
* are still the standard envelope, so a failure surfaces exactly like
|
|
325
|
-
* {@link request}: a typed `HubSdkError` from a `{success:false}` body,
|
|
326
|
-
* otherwise an `HUB_HTTP_<status>` transport error.
|
|
327
|
-
*/
|
|
328
|
-
async requestBytes(path, init) {
|
|
329
|
-
const response = await this.fetch(`${this.baseUrl}${path}`, {
|
|
330
|
-
...init,
|
|
331
|
-
headers: await this.buildHeaders(init.headers)
|
|
332
|
-
});
|
|
333
|
-
const status = response.status;
|
|
334
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
335
|
-
if (!response.ok) {
|
|
336
|
-
const bodyText = await response.text().catch(() => "");
|
|
337
|
-
if (contentType.includes("application/json")) {
|
|
338
|
-
let parsed;
|
|
339
|
-
try {
|
|
340
|
-
parsed = JSON.parse(bodyText);
|
|
341
|
-
} catch {
|
|
342
|
-
parsed = void 0;
|
|
343
|
-
}
|
|
344
|
-
if (isHubErrorEnvelope(parsed)) throw new HubSdkError(parsed.error, { status });
|
|
345
|
-
}
|
|
346
|
-
throw httpError(path, status, response.statusText, bodyText, contentType);
|
|
347
|
-
}
|
|
348
|
-
return {
|
|
349
|
-
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
350
|
-
contentType
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
async buildHeaders(headers) {
|
|
354
|
-
const mergedHeaders = new Headers(headers);
|
|
355
|
-
if (!mergedHeaders.has("Accept")) mergedHeaders.set("Accept", "application/json");
|
|
356
|
-
if (this.apiKey) mergedHeaders.set("Authorization", `Bearer ${this.apiKey}`);
|
|
357
|
-
if (this.authHeaders) for (const [key, value] of new Headers(await this.authHeaders())) mergedHeaders.set(key, value);
|
|
358
|
-
for (const [key, value] of new Headers(headers)) mergedHeaders.set(key, value);
|
|
359
|
-
return mergedHeaders;
|
|
360
|
-
}
|
|
361
|
-
};
|
|
469
|
+
message: `One of ${HUB_API_KEY_ENV_VAR} or ${HUB_CAPABILITY_TOKEN_ENV_VAR} is required`,
|
|
470
|
+
details: { envVars: [HUB_API_KEY_ENV_VAR, HUB_CAPABILITY_TOKEN_ENV_VAR] }
|
|
471
|
+
});
|
|
472
|
+
if (present.length > 1) throw new HubSdkError({
|
|
473
|
+
code: "HUB_CONFIG_INVALID",
|
|
474
|
+
message: `Set exactly one of ${HUB_API_KEY_ENV_VAR} or ${HUB_CAPABILITY_TOKEN_ENV_VAR}, not both`,
|
|
475
|
+
details: { envVars: [HUB_API_KEY_ENV_VAR, HUB_CAPABILITY_TOKEN_ENV_VAR] }
|
|
476
|
+
});
|
|
477
|
+
return present[0];
|
|
478
|
+
}
|
|
479
|
+
function nonEmpty(value) {
|
|
480
|
+
if (value === void 0) return void 0;
|
|
481
|
+
const trimmed = value.trim();
|
|
482
|
+
return trimmed.length === 0 ? void 0 : trimmed;
|
|
483
|
+
}
|
|
484
|
+
function readProcessEnv() {
|
|
485
|
+
return globalThis.process?.env ?? {};
|
|
486
|
+
}
|
|
487
|
+
//#endregion
|
|
488
|
+
//#region src/authorization.ts
|
|
362
489
|
var HubTokensClient = class {
|
|
363
490
|
constructor(request) {
|
|
364
491
|
this.request = request;
|
|
365
492
|
}
|
|
366
493
|
async mint(options) {
|
|
367
|
-
return this.request("/v1/hub/tokens",
|
|
368
|
-
method: "POST",
|
|
369
|
-
body: JSON.stringify(options),
|
|
370
|
-
headers: { "Content-Type": "application/json" }
|
|
371
|
-
});
|
|
494
|
+
return this.request("/v1/hub/tokens", jsonInit("POST", options));
|
|
372
495
|
}
|
|
373
496
|
async list(options = {}) {
|
|
374
497
|
const searchParams = new URLSearchParams();
|
|
@@ -402,64 +525,56 @@ var HubPermissionsClient = class {
|
|
|
402
525
|
return this.request(`/v1/hub/policies?connectionId=${encodeURIComponent(connectionId)}`, { method: "GET" });
|
|
403
526
|
}
|
|
404
527
|
async set(input) {
|
|
405
|
-
return this.request("/v1/hub/policies",
|
|
406
|
-
method: "PUT",
|
|
407
|
-
body: JSON.stringify(input),
|
|
408
|
-
headers: { "Content-Type": "application/json" }
|
|
409
|
-
});
|
|
528
|
+
return this.request("/v1/hub/policies", jsonInit("PUT", input));
|
|
410
529
|
}
|
|
411
530
|
/** Reset an action to its default by deleting any stored override. */
|
|
412
531
|
async delete(input) {
|
|
413
|
-
return this.request("/v1/hub/policies",
|
|
414
|
-
method: "DELETE",
|
|
415
|
-
body: JSON.stringify(input),
|
|
416
|
-
headers: { "Content-Type": "application/json" }
|
|
417
|
-
});
|
|
532
|
+
return this.request("/v1/hub/policies", jsonInit("DELETE", input));
|
|
418
533
|
}
|
|
419
534
|
/** Bulk-allow every write action of a connection's provider. Reads are
|
|
420
535
|
* already allowed for sandbox agents; destructive actions stay `ask`.
|
|
421
536
|
* Idempotent: actions with an existing policy are left untouched. */
|
|
422
537
|
async allowWrites(connectionId) {
|
|
423
|
-
return this.request("/v1/hub/policies/allow-writes", {
|
|
424
|
-
method: "POST",
|
|
425
|
-
body: JSON.stringify({ connectionId }),
|
|
426
|
-
headers: { "Content-Type": "application/json" }
|
|
427
|
-
});
|
|
538
|
+
return this.request("/v1/hub/policies/allow-writes", jsonInit("POST", { connectionId }));
|
|
428
539
|
}
|
|
429
540
|
/** Inverse of `allowWrites`: delete only the rows it created for this
|
|
430
541
|
* connection. Manual per-action decisions are left intact. */
|
|
431
542
|
async revertWrites(connectionId) {
|
|
432
|
-
return this.request("/v1/hub/policies/revert-writes", {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
543
|
+
return this.request("/v1/hub/policies/revert-writes", jsonInit("POST", { connectionId }));
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
var HubApprovalsClient = class {
|
|
547
|
+
constructor(request) {
|
|
548
|
+
this.request = request;
|
|
549
|
+
}
|
|
550
|
+
async list() {
|
|
551
|
+
return this.request("/v1/hub/approvals", { method: "GET" });
|
|
552
|
+
}
|
|
553
|
+
async approve(approvalId) {
|
|
554
|
+
return this.request(`/v1/hub/approvals/${encodeURIComponent(approvalId)}/approve`, { method: "POST" });
|
|
555
|
+
}
|
|
556
|
+
async deny(approvalId) {
|
|
557
|
+
return this.request(`/v1/hub/approvals/${encodeURIComponent(approvalId)}/deny`, { method: "POST" });
|
|
437
558
|
}
|
|
438
559
|
};
|
|
560
|
+
//#endregion
|
|
561
|
+
//#region src/tools.ts
|
|
439
562
|
var HubToolsClient = class {
|
|
440
563
|
constructor(request) {
|
|
441
564
|
this.request = request;
|
|
442
565
|
}
|
|
443
566
|
async search(query, options = {}) {
|
|
444
|
-
return this.request("/v1/hub/tools/search", {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
limit: options.limit
|
|
450
|
-
}),
|
|
451
|
-
headers: { "Content-Type": "application/json" }
|
|
452
|
-
});
|
|
567
|
+
return this.request("/v1/hub/tools/search", jsonInit("POST", {
|
|
568
|
+
query,
|
|
569
|
+
provider: options.provider,
|
|
570
|
+
limit: options.limit
|
|
571
|
+
}));
|
|
453
572
|
}
|
|
454
573
|
async sources() {
|
|
455
574
|
return this.request("/v1/hub/tools/sources", { method: "GET" });
|
|
456
575
|
}
|
|
457
576
|
async describe(path) {
|
|
458
|
-
return this.request("/v1/hub/tools/describe", {
|
|
459
|
-
method: "POST",
|
|
460
|
-
body: JSON.stringify({ path }),
|
|
461
|
-
headers: { "Content-Type": "application/json" }
|
|
462
|
-
});
|
|
577
|
+
return this.request("/v1/hub/tools/describe", jsonInit("POST", { path }));
|
|
463
578
|
}
|
|
464
579
|
/**
|
|
465
580
|
* Run one hub tool. `raw` is the tool input: the SDK forwards it verbatim as
|
|
@@ -472,8 +587,9 @@ var HubToolsClient = class {
|
|
|
472
587
|
return await this.request("/v1/hub/exec", execInit);
|
|
473
588
|
} catch (error) {
|
|
474
589
|
if (!(error instanceof HubSdkError) || !requiresCapabilityToken(error)) throw error;
|
|
590
|
+
const approvalId = getApprovalId(error);
|
|
591
|
+
if (approvalId && options.approve !== true) throw error;
|
|
475
592
|
if (options.approve) {
|
|
476
|
-
const approvalId = getApprovalId(error);
|
|
477
593
|
if (!approvalId) throw error;
|
|
478
594
|
const approved = await this.request(`/v1/hub/approvals/${encodeURIComponent(approvalId)}/approve`, { method: "POST" });
|
|
479
595
|
validateApprovedExecution(approved, path, options.connectionId);
|
|
@@ -491,15 +607,11 @@ var HubToolsClient = class {
|
|
|
491
607
|
});
|
|
492
608
|
}
|
|
493
609
|
}
|
|
494
|
-
const minted = await this.request("/v1/hub/tokens", {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
provider: options.connectionId ? void 0 : path.split(".")[0]
|
|
500
|
-
}),
|
|
501
|
-
headers: { "Content-Type": "application/json" }
|
|
502
|
-
});
|
|
610
|
+
const minted = await this.request("/v1/hub/tokens", jsonInit("POST", {
|
|
611
|
+
actionPath: path,
|
|
612
|
+
connectionId: options.connectionId,
|
|
613
|
+
provider: options.connectionId ? void 0 : path.split(".")[0]
|
|
614
|
+
}));
|
|
503
615
|
return this.request("/v1/hub/exec", {
|
|
504
616
|
...execInit,
|
|
505
617
|
headers: {
|
|
@@ -509,16 +621,12 @@ var HubToolsClient = class {
|
|
|
509
621
|
});
|
|
510
622
|
}
|
|
511
623
|
buildExecInit(path, raw, options) {
|
|
512
|
-
return {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
idempotencyKey: options.idempotencyKey
|
|
519
|
-
}),
|
|
520
|
-
headers: { "Content-Type": "application/json" }
|
|
521
|
-
};
|
|
624
|
+
return jsonInit("POST", {
|
|
625
|
+
path,
|
|
626
|
+
input: raw,
|
|
627
|
+
connectionId: options.connectionId,
|
|
628
|
+
idempotencyKey: options.idempotencyKey
|
|
629
|
+
});
|
|
522
630
|
}
|
|
523
631
|
};
|
|
524
632
|
function requiresCapabilityToken(error) {
|
|
@@ -548,45 +656,50 @@ function validateApprovedExecution(approved, path, connectionId) {
|
|
|
548
656
|
}
|
|
549
657
|
});
|
|
550
658
|
}
|
|
551
|
-
|
|
659
|
+
//#endregion
|
|
660
|
+
//#region src/inbound-webhooks.ts
|
|
661
|
+
var HubInboundWebhooksClient = class {
|
|
552
662
|
constructor(request) {
|
|
553
663
|
this.request = request;
|
|
554
664
|
}
|
|
555
|
-
async
|
|
556
|
-
return this.request(
|
|
557
|
-
}
|
|
558
|
-
async createEmail(input = {}) {
|
|
559
|
-
return this.request("/v1/hub/channels/email", {
|
|
560
|
-
method: "POST",
|
|
561
|
-
body: JSON.stringify(input),
|
|
562
|
-
headers: { "Content-Type": "application/json" }
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
async delete(channelId) {
|
|
566
|
-
return this.request(`/v1/hub/channels/${encodeURIComponent(channelId)}`, { method: "DELETE" });
|
|
665
|
+
async create(connectionId, options = {}) {
|
|
666
|
+
return this.request(this.path(connectionId), jsonInit("POST", options));
|
|
567
667
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
constructor(request) {
|
|
571
|
-
this.request = request;
|
|
668
|
+
async get(connectionId) {
|
|
669
|
+
return this.request(this.path(connectionId), { method: "GET" });
|
|
572
670
|
}
|
|
573
|
-
|
|
574
|
-
|
|
671
|
+
/** Add one owned Linq WhatsApp number without rotating the webhook secret. */
|
|
672
|
+
async ensurePhoneNumber(connectionId, phoneNumberId) {
|
|
673
|
+
if (!phoneNumberId || phoneNumberId.length > 128 || !/^[\x21-\x7e]+$/.test(phoneNumberId)) throw new Error("A bounded phone-number id is required");
|
|
674
|
+
return this.request(`${this.path(connectionId)}/phone-numbers/ensure`, jsonInit("POST", { phoneNumberId }));
|
|
575
675
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
body: JSON.stringify(input),
|
|
580
|
-
headers: { "Content-Type": "application/json" }
|
|
581
|
-
});
|
|
676
|
+
/** Remove event subscriptions first. This does not silently rotate a live URL. */
|
|
677
|
+
async delete(connectionId) {
|
|
678
|
+
return this.request(this.path(connectionId), { method: "DELETE" });
|
|
582
679
|
}
|
|
583
|
-
|
|
584
|
-
|
|
680
|
+
path(connectionId) {
|
|
681
|
+
if (!connectionId || connectionId.length > 256) throw new Error("A bounded connection id is required");
|
|
682
|
+
return `/v1/hub/connections/${encodeURIComponent(connectionId)}/inbound-webhook`;
|
|
585
683
|
}
|
|
586
684
|
};
|
|
685
|
+
//#endregion
|
|
686
|
+
//#region src/connections.ts
|
|
687
|
+
const GITHUB_EXACT_CONNECT_SCOPES = [
|
|
688
|
+
"read:user",
|
|
689
|
+
"repo",
|
|
690
|
+
"read:org"
|
|
691
|
+
];
|
|
692
|
+
function sameOAuthScopeSet(left, right) {
|
|
693
|
+
if (!Array.isArray(left)) return false;
|
|
694
|
+
const leftSet = new Set(left);
|
|
695
|
+
const rightSet = new Set(right);
|
|
696
|
+
return leftSet.size === left.length && rightSet.size === right.length && leftSet.size === rightSet.size && [...leftSet].every((scope) => rightSet.has(scope));
|
|
697
|
+
}
|
|
587
698
|
var HubConnectionsClient = class {
|
|
699
|
+
inboundWebhook;
|
|
588
700
|
constructor(request) {
|
|
589
701
|
this.request = request;
|
|
702
|
+
this.inboundWebhook = new HubInboundWebhooksClient(request);
|
|
590
703
|
}
|
|
591
704
|
async list() {
|
|
592
705
|
return this.request("/v1/hub/connections", { method: "GET" });
|
|
@@ -598,40 +711,38 @@ var HubConnectionsClient = class {
|
|
|
598
711
|
return this.request("/v1/hub/providers", { method: "GET" });
|
|
599
712
|
}
|
|
600
713
|
async start(provider, options = {}) {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
returnUrl: options.returnUrl,
|
|
605
|
-
cli: options.cli,
|
|
606
|
-
connectionParameters: options.connectionParameters
|
|
607
|
-
}),
|
|
608
|
-
headers: { "Content-Type": "application/json" }
|
|
714
|
+
if (options.requestedScopes !== void 0 && (provider !== "github" || !sameOAuthScopeSet(options.requestedScopes, GITHUB_EXACT_CONNECT_SCOPES))) throw new HubSdkError({
|
|
715
|
+
code: "HUB_INVALID_INPUT",
|
|
716
|
+
message: "GitHub requestedScopes must be read:user, repo, and read:org"
|
|
609
717
|
});
|
|
718
|
+
const started = await this.request(`/v1/hub/connections/${encodeURIComponent(provider)}/start`, jsonInit("POST", {
|
|
719
|
+
returnUrl: options.returnUrl,
|
|
720
|
+
cli: options.cli,
|
|
721
|
+
connectionParameters: options.connectionParameters,
|
|
722
|
+
requestedScopes: options.requestedScopes
|
|
723
|
+
}));
|
|
724
|
+
if (options.requestedScopes !== void 0 && (started.provider !== provider || !sameOAuthScopeSet(started.scopes, options.requestedScopes))) throw new HubSdkError({
|
|
725
|
+
code: "HUB_CONFIG_MISSING",
|
|
726
|
+
message: "Hub did not preserve the requested OAuth scopes"
|
|
727
|
+
});
|
|
728
|
+
return started;
|
|
610
729
|
}
|
|
611
730
|
async startWhatsappEmbeddedSignup(options = {}) {
|
|
612
|
-
return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/start", {
|
|
613
|
-
method: "POST",
|
|
614
|
-
body: JSON.stringify({ returnUrl: options.returnUrl }),
|
|
615
|
-
headers: { "Content-Type": "application/json" }
|
|
616
|
-
});
|
|
731
|
+
return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/start", jsonInit("POST", { returnUrl: options.returnUrl }));
|
|
617
732
|
}
|
|
618
733
|
async completeWhatsappEmbeddedSignup(input) {
|
|
619
|
-
return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/complete",
|
|
620
|
-
method: "POST",
|
|
621
|
-
body: JSON.stringify(input),
|
|
622
|
-
headers: { "Content-Type": "application/json" }
|
|
623
|
-
});
|
|
734
|
+
return this.request("/v1/hub/connections/whatsapp-business/embedded-signup/complete", jsonInit("POST", input));
|
|
624
735
|
}
|
|
625
736
|
/** Connect a non-OAuth (api-key) connector by submitting the user's key.
|
|
626
737
|
* The server validates the key with a live probe before persisting and
|
|
627
738
|
* returns the created (or reconnected) connection — there is no redirect,
|
|
628
|
-
* unlike `start`.
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
});
|
|
739
|
+
* unlike `start`. Cloudbeds requires propertyId metadata and PriceLabs
|
|
740
|
+
* requires listing ID/PMS pairs; other providers have no metadata. */
|
|
741
|
+
async connectApiKey(provider, apiKey, metadata) {
|
|
742
|
+
return this.request(`/v1/hub/connections/${encodeURIComponent(provider)}/connect-key`, jsonInit("POST", {
|
|
743
|
+
apiKey,
|
|
744
|
+
...metadata === void 0 ? {} : { metadata }
|
|
745
|
+
}));
|
|
635
746
|
}
|
|
636
747
|
async revoke(connectionId) {
|
|
637
748
|
return this.request(`/v1/hub/connections/${encodeURIComponent(connectionId)}`, { method: "DELETE" });
|
|
@@ -640,30 +751,44 @@ var HubConnectionsClient = class {
|
|
|
640
751
|
return this.request(`/v1/hub/connections/${encodeURIComponent(connectionId)}/health`, { method: "POST" });
|
|
641
752
|
}
|
|
642
753
|
};
|
|
643
|
-
|
|
754
|
+
//#endregion
|
|
755
|
+
//#region src/channels.ts
|
|
756
|
+
var HubChannelsClient = class {
|
|
644
757
|
constructor(request) {
|
|
645
758
|
this.request = request;
|
|
646
759
|
}
|
|
647
760
|
async list() {
|
|
648
|
-
return this.request("/v1/hub/
|
|
761
|
+
return this.request("/v1/hub/channels", { method: "GET" });
|
|
649
762
|
}
|
|
650
|
-
async
|
|
651
|
-
return this.request(
|
|
763
|
+
async createEmail(input = {}) {
|
|
764
|
+
return this.request("/v1/hub/channels/email", jsonInit("POST", input));
|
|
652
765
|
}
|
|
653
|
-
async
|
|
654
|
-
return this.request(`/v1/hub/
|
|
766
|
+
async delete(channelId) {
|
|
767
|
+
return this.request(`/v1/hub/channels/${encodeURIComponent(channelId)}`, { method: "DELETE" });
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
var HubEventSubscriptionsClient = class {
|
|
771
|
+
constructor(request) {
|
|
772
|
+
this.request = request;
|
|
773
|
+
}
|
|
774
|
+
async list() {
|
|
775
|
+
return this.request("/v1/hub/event-subscriptions", { method: "GET" });
|
|
776
|
+
}
|
|
777
|
+
async create(input) {
|
|
778
|
+
return this.request("/v1/hub/event-subscriptions", jsonInit("POST", input));
|
|
779
|
+
}
|
|
780
|
+
async delete(subscriptionId) {
|
|
781
|
+
return this.request(`/v1/hub/event-subscriptions/${encodeURIComponent(subscriptionId)}`, { method: "DELETE" });
|
|
655
782
|
}
|
|
656
783
|
};
|
|
784
|
+
//#endregion
|
|
785
|
+
//#region src/github-app.ts
|
|
657
786
|
var HubGithubAppClient = class {
|
|
658
787
|
constructor(request) {
|
|
659
788
|
this.request = request;
|
|
660
789
|
}
|
|
661
790
|
async mintInstallationToken(input) {
|
|
662
|
-
return this.request("/v1/hub/github-app/mint-installation-token",
|
|
663
|
-
method: "POST",
|
|
664
|
-
body: JSON.stringify(input),
|
|
665
|
-
headers: { "Content-Type": "application/json" }
|
|
666
|
-
});
|
|
791
|
+
return this.request("/v1/hub/github-app/mint-installation-token", jsonInit("POST", input));
|
|
667
792
|
}
|
|
668
793
|
/** Every installation the caller may act through — their own and any org's
|
|
669
794
|
* they were verified against. The management surface's read. */
|
|
@@ -698,26 +823,221 @@ var HubGithubAppClient = class {
|
|
|
698
823
|
return this.request(`/v1/hub/github-app/is-repo-installed?${params.toString()}`, { method: "GET" });
|
|
699
824
|
}
|
|
700
825
|
};
|
|
826
|
+
//#endregion
|
|
827
|
+
//#region src/audit.ts
|
|
701
828
|
var HubAuditClient = class {
|
|
702
829
|
constructor(request) {
|
|
703
830
|
this.request = request;
|
|
704
831
|
}
|
|
705
|
-
async list(options = {}) {
|
|
706
|
-
const searchParams = new URLSearchParams();
|
|
707
|
-
if (options.provider !== void 0) searchParams.set("provider", options.provider);
|
|
708
|
-
if (options.action !== void 0) searchParams.set("action", options.action);
|
|
709
|
-
if (options.status !== void 0) searchParams.set("status", options.status);
|
|
710
|
-
if (options.since !== void 0) searchParams.set("since", options.since);
|
|
711
|
-
if (options.until !== void 0) searchParams.set("until", options.until);
|
|
712
|
-
if (options.cursor !== void 0) searchParams.set("cursor", options.cursor);
|
|
713
|
-
if (options.limit !== void 0) searchParams.set("limit", String(options.limit));
|
|
714
|
-
const query = searchParams.toString();
|
|
715
|
-
return this.request(`/v1/hub/audit${query ? `?${query}` : ""}`, { method: "GET" });
|
|
832
|
+
async list(options = {}) {
|
|
833
|
+
const searchParams = new URLSearchParams();
|
|
834
|
+
if (options.provider !== void 0) searchParams.set("provider", options.provider);
|
|
835
|
+
if (options.action !== void 0) searchParams.set("action", options.action);
|
|
836
|
+
if (options.status !== void 0) searchParams.set("status", options.status);
|
|
837
|
+
if (options.since !== void 0) searchParams.set("since", options.since);
|
|
838
|
+
if (options.until !== void 0) searchParams.set("until", options.until);
|
|
839
|
+
if (options.cursor !== void 0) searchParams.set("cursor", options.cursor);
|
|
840
|
+
if (options.limit !== void 0) searchParams.set("limit", String(options.limit));
|
|
841
|
+
const query = searchParams.toString();
|
|
842
|
+
return this.request(`/v1/hub/audit${query ? `?${query}` : ""}`, { method: "GET" });
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
//#endregion
|
|
846
|
+
//#region src/workflow-run-events.ts
|
|
847
|
+
/** Read a wire field as a string, or "" when the server sent anything else.
|
|
848
|
+
* `raw` is that field straight off the parsed payload, before any check. */
|
|
849
|
+
function optString(raw) {
|
|
850
|
+
return typeof raw === "string" ? raw : "";
|
|
851
|
+
}
|
|
852
|
+
/** An agent iteration's terminal status (`iteration.ended`) — only ever
|
|
853
|
+
* succeeded/failed; an iteration is never guard-skipped. */
|
|
854
|
+
function isIterationStatus(value) {
|
|
855
|
+
return value === "succeeded" || value === "failed";
|
|
856
|
+
}
|
|
857
|
+
/** An action's terminal status on `action.finished`. Beyond succeeded/failed a
|
|
858
|
+
* guarded action can end `skipped` (its `if` resolved false, so it never ran) —
|
|
859
|
+
* accepted here so a skipped event is surfaced, not dropped as malformed. */
|
|
860
|
+
function isActionFinishedStatus(value) {
|
|
861
|
+
return value === "succeeded" || value === "failed" || value === "skipped";
|
|
862
|
+
}
|
|
863
|
+
/** Terminal status of a whole run's `run.done` event. A run can end `cancelled`
|
|
864
|
+
* (a user cancel) — a value neither the action nor iteration status checkers
|
|
865
|
+
* accept — so `run.done` uses this rather than them, which would drop a
|
|
866
|
+
* cancelled terminal event and hang the stream. */
|
|
867
|
+
function isRunDoneStatus(value) {
|
|
868
|
+
return value === "succeeded" || value === "failed" || value === "cancelled";
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Map one raw SSE event (its `event:` name + parsed JSON `data:`) to a typed
|
|
872
|
+
* run-stream event, validating the fields each variant declares rather than
|
|
873
|
+
* blindly casting the wire payload. A frame that doesn't match its event's
|
|
874
|
+
* shape (or an unknown event name) returns null so the consumer skips it — a
|
|
875
|
+
* malformed `token` can never reach a consumer as `{ delta: undefined }`, and
|
|
876
|
+
* new server event kinds don't break older clients.
|
|
877
|
+
*/
|
|
878
|
+
function toRunStreamEvent(eventType, raw) {
|
|
879
|
+
if (eventType === "ping") return { type: "ping" };
|
|
880
|
+
if (!isRecord$1(raw)) return null;
|
|
881
|
+
switch (eventType) {
|
|
882
|
+
case "snapshot": return typeof raw.id === "string" && typeof raw.workflowId === "string" && typeof raw.status === "string" && Array.isArray(raw.actionResults) ? {
|
|
883
|
+
type: "snapshot",
|
|
884
|
+
run: raw
|
|
885
|
+
} : null;
|
|
886
|
+
case "token": return typeof raw.delta === "string" && typeof raw.actionIndex === "number" ? {
|
|
887
|
+
type: "token",
|
|
888
|
+
actionIndex: raw.actionIndex,
|
|
889
|
+
delta: raw.delta,
|
|
890
|
+
at: optString(raw.at)
|
|
891
|
+
} : null;
|
|
892
|
+
case "run.done": return isRunDoneStatus(raw.status) ? {
|
|
893
|
+
type: "run.done",
|
|
894
|
+
status: raw.status,
|
|
895
|
+
error: typeof raw.error === "string" ? raw.error : null,
|
|
896
|
+
...typeof raw.at === "string" ? { at: raw.at } : {}
|
|
897
|
+
} : null;
|
|
898
|
+
case "run.waiting": return typeof raw.decisionId === "string" && raw.decisionId.length > 0 ? {
|
|
899
|
+
type: "run.waiting",
|
|
900
|
+
decisionId: raw.decisionId,
|
|
901
|
+
...typeof raw.at === "string" ? { at: raw.at } : {}
|
|
902
|
+
} : null;
|
|
903
|
+
case "action.started": return typeof raw.index === "number" && typeof raw.kind === "string" && typeof raw.at === "string" ? {
|
|
904
|
+
type: "action.started",
|
|
905
|
+
index: raw.index,
|
|
906
|
+
kind: raw.kind,
|
|
907
|
+
at: raw.at
|
|
908
|
+
} : null;
|
|
909
|
+
case "action.finished": return typeof raw.index === "number" && typeof raw.kind === "string" && isActionFinishedStatus(raw.status) && typeof raw.at === "string" ? {
|
|
910
|
+
type: "action.finished",
|
|
911
|
+
index: raw.index,
|
|
912
|
+
kind: raw.kind,
|
|
913
|
+
status: raw.status,
|
|
914
|
+
at: raw.at,
|
|
915
|
+
...typeof raw.costUsd === "number" ? { costUsd: raw.costUsd } : {},
|
|
916
|
+
...typeof raw.error === "string" ? { error: raw.error } : {}
|
|
917
|
+
} : null;
|
|
918
|
+
case "iteration.started": return typeof raw.actionIndex === "number" && typeof raw.iterationIndex === "number" && typeof raw.at === "string" ? {
|
|
919
|
+
type: "iteration.started",
|
|
920
|
+
actionIndex: raw.actionIndex,
|
|
921
|
+
iterationIndex: raw.iterationIndex,
|
|
922
|
+
at: raw.at,
|
|
923
|
+
...typeof raw.model === "string" ? { model: raw.model } : {}
|
|
924
|
+
} : null;
|
|
925
|
+
case "iteration.ended": return typeof raw.actionIndex === "number" && typeof raw.iterationIndex === "number" && isIterationStatus(raw.status) && typeof raw.at === "string" ? {
|
|
926
|
+
type: "iteration.ended",
|
|
927
|
+
actionIndex: raw.actionIndex,
|
|
928
|
+
iterationIndex: raw.iterationIndex,
|
|
929
|
+
status: raw.status,
|
|
930
|
+
at: raw.at,
|
|
931
|
+
...typeof raw.outputPreview === "string" ? { outputPreview: raw.outputPreview } : {},
|
|
932
|
+
...typeof raw.inputTokens === "number" ? { inputTokens: raw.inputTokens } : {},
|
|
933
|
+
...typeof raw.outputTokens === "number" ? { outputTokens: raw.outputTokens } : {},
|
|
934
|
+
...typeof raw.costUsd === "number" ? { costUsd: raw.costUsd } : {}
|
|
935
|
+
} : null;
|
|
936
|
+
default: return null;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
//#endregion
|
|
940
|
+
//#region src/workflows-kv.ts
|
|
941
|
+
/** The workflow's durable keyspace, shared by all its runs. */
|
|
942
|
+
var WorkflowKvResource = class {
|
|
943
|
+
constructor(request) {
|
|
944
|
+
this.request = request;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* The workflow's whole keyspace as metadata (key, version, updatedAt — never
|
|
948
|
+
* values), key-ordered. Bounded by the server's per-workflow key cap.
|
|
949
|
+
*/
|
|
950
|
+
async listKv(id) {
|
|
951
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv`, { method: "GET" });
|
|
952
|
+
}
|
|
953
|
+
/** One KV entry (value + version); `HubSdkError(NOT_FOUND)` when absent. */
|
|
954
|
+
async getKv(id, key) {
|
|
955
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, { method: "GET" });
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Upsert a key; returns the entry with its NEW version (increments on every
|
|
959
|
+
* write). Throws `HubSdkError(KV_VALUE_TOO_LARGE)` past the 64KiB value cap
|
|
960
|
+
* or `KV_KEY_LIMIT` past the per-workflow key cap. `raw` is the caller's
|
|
961
|
+
* value, stored and read back verbatim, so the client never gives it a shape.
|
|
962
|
+
*/
|
|
963
|
+
async putKv(id, key, raw) {
|
|
964
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, jsonInit("PUT", { value: raw }));
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Compare-and-swap: the write lands only when the stored version still
|
|
968
|
+
* equals `expectedVersion`; otherwise `HubSdkError(KV_VERSION_CONFLICT)`
|
|
969
|
+
* (409) — re-read and retry. Creation is {@link putKv}'s job. `raw` is the
|
|
970
|
+
* caller's value, stored and read back verbatim, so the client never gives it
|
|
971
|
+
* a shape.
|
|
972
|
+
*/
|
|
973
|
+
async casKv(id, key, expectedVersion, raw) {
|
|
974
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}/cas`, jsonInit("POST", {
|
|
975
|
+
expectedVersion,
|
|
976
|
+
value: raw
|
|
977
|
+
}));
|
|
978
|
+
}
|
|
979
|
+
/** Delete a key; idempotent — `deleted` reports whether a row was removed. */
|
|
980
|
+
async deleteKv(id, key) {
|
|
981
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, { method: "DELETE" });
|
|
716
982
|
}
|
|
717
983
|
};
|
|
718
|
-
|
|
984
|
+
//#endregion
|
|
985
|
+
//#region src/workflows.ts
|
|
986
|
+
/** True for an aborted/timed-out fetch (or any abort). fetch rejects with the
|
|
987
|
+
* signal's reason — a DOMException named `AbortError` (caller) or `TimeoutError`
|
|
988
|
+
* (AbortSignal.timeout). */
|
|
989
|
+
function isAbortError(cause) {
|
|
990
|
+
return cause instanceof Error && (cause.name === "AbortError" || cause.name === "TimeoutError");
|
|
991
|
+
}
|
|
992
|
+
/** A signal that aborts when the caller's signal aborts OR `timeoutMs` elapses.
|
|
993
|
+
* Returns undefined when neither bound applies (no per-request abort needed). */
|
|
994
|
+
function combineAbortSignals(caller, timeoutMs) {
|
|
995
|
+
const signals = [];
|
|
996
|
+
if (caller) signals.push(caller);
|
|
997
|
+
if (timeoutMs !== null) signals.push(AbortSignal.timeout(timeoutMs));
|
|
998
|
+
if (signals.length === 0) return void 0;
|
|
999
|
+
return signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
1000
|
+
}
|
|
1001
|
+
/** Resolve after `ms`, or reject if `signal` aborts first. Clears its timer on
|
|
1002
|
+
* abort so a cancelled wait leaves nothing pending. */
|
|
1003
|
+
function delay(ms, signal) {
|
|
1004
|
+
return new Promise((resolve, reject) => {
|
|
1005
|
+
if (signal?.aborted) {
|
|
1006
|
+
reject(signal.reason ?? /* @__PURE__ */ new Error("The operation was aborted"));
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
const onAbort = () => {
|
|
1010
|
+
clearTimeout(timer);
|
|
1011
|
+
reject(signal?.reason ?? /* @__PURE__ */ new Error("The operation was aborted"));
|
|
1012
|
+
};
|
|
1013
|
+
const timer = setTimeout(() => {
|
|
1014
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1015
|
+
resolve();
|
|
1016
|
+
}, ms);
|
|
1017
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
/** Wait before reopening a run event stream that a restart ended. */
|
|
1021
|
+
const STREAM_REOPEN_DELAY_MS = 1e3;
|
|
1022
|
+
/** Reopens in a row that deliver nothing before watchRun gives up. */
|
|
1023
|
+
const MAX_IDLE_STREAM_REOPENS = 30;
|
|
1024
|
+
function abortableDelay(ms, signal) {
|
|
1025
|
+
return new Promise((resolve, reject) => {
|
|
1026
|
+
if (signal?.aborted) return reject(signal.reason);
|
|
1027
|
+
const timer = setTimeout(() => {
|
|
1028
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1029
|
+
resolve();
|
|
1030
|
+
}, ms);
|
|
1031
|
+
const onAbort = () => {
|
|
1032
|
+
clearTimeout(timer);
|
|
1033
|
+
reject(signal?.reason);
|
|
1034
|
+
};
|
|
1035
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
var HubWorkflowsClient = class extends WorkflowKvResource {
|
|
719
1039
|
constructor(request, stream, download) {
|
|
720
|
-
|
|
1040
|
+
super(request);
|
|
721
1041
|
this.stream = stream;
|
|
722
1042
|
this.download = download;
|
|
723
1043
|
}
|
|
@@ -738,11 +1058,7 @@ var HubWorkflowsClient = class {
|
|
|
738
1058
|
return this.request(`/v1/workflows/${encodeURIComponent(id)}`, { method: "GET" });
|
|
739
1059
|
}
|
|
740
1060
|
async create(yaml) {
|
|
741
|
-
return this.request("/v1/workflows", {
|
|
742
|
-
method: "POST",
|
|
743
|
-
body: JSON.stringify({ yaml }),
|
|
744
|
-
headers: { "Content-Type": "application/json" }
|
|
745
|
-
});
|
|
1061
|
+
return this.request("/v1/workflows", jsonInit("POST", { yaml }));
|
|
746
1062
|
}
|
|
747
1063
|
/**
|
|
748
1064
|
* Replace the workflow's definition from YAML (recompiled server-side,
|
|
@@ -753,14 +1069,10 @@ var HubWorkflowsClient = class {
|
|
|
753
1069
|
* concurrent modification, `NOT_FOUND` for an unknown/foreign id.
|
|
754
1070
|
*/
|
|
755
1071
|
async update(id, yaml, opts = {}) {
|
|
756
|
-
return this.request(`/v1/workflows/${encodeURIComponent(id)}`, {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
...opts.note !== void 0 ? { note: opts.note } : {}
|
|
761
|
-
}),
|
|
762
|
-
headers: { "Content-Type": "application/json" }
|
|
763
|
-
});
|
|
1072
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}`, jsonInit("PUT", {
|
|
1073
|
+
yaml,
|
|
1074
|
+
...opts.note !== void 0 ? { note: opts.note } : {}
|
|
1075
|
+
}));
|
|
764
1076
|
}
|
|
765
1077
|
async delete(id) {
|
|
766
1078
|
await this.request(`/v1/workflows/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
@@ -771,11 +1083,7 @@ var HubWorkflowsClient = class {
|
|
|
771
1083
|
* via `update`, but it can still be paused/resumed here).
|
|
772
1084
|
*/
|
|
773
1085
|
async setEnabled(id, enabled) {
|
|
774
|
-
return this.request(`/v1/workflows/${encodeURIComponent(id)}`, {
|
|
775
|
-
method: "PATCH",
|
|
776
|
-
body: JSON.stringify({ enabled }),
|
|
777
|
-
headers: { "Content-Type": "application/json" }
|
|
778
|
-
});
|
|
1086
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}`, jsonInit("PATCH", { enabled }));
|
|
779
1087
|
}
|
|
780
1088
|
/**
|
|
781
1089
|
* One page of run history, newest first. Pass the previous page's
|
|
@@ -797,15 +1105,14 @@ var HubWorkflowsClient = class {
|
|
|
797
1105
|
* `{ "pull_request.number": "123" }`. Omit it for a one-click run of a
|
|
798
1106
|
* workflow that reads no trigger fields. Throws `HubSdkError`:
|
|
799
1107
|
* `MISSING_RUN_INPUTS` when a required field is absent (`details.missing`
|
|
800
|
-
* names them), `
|
|
801
|
-
*
|
|
1108
|
+
* names them), `NOT_FOUND` for an unknown/foreign id. A paused workflow
|
|
1109
|
+
* still runs once; pausing only stops its triggers. Pass `opts.signal` to
|
|
1110
|
+
* abort a slow request.
|
|
802
1111
|
*/
|
|
803
1112
|
async run(id, inputs, opts = {}) {
|
|
804
1113
|
const hasInputs = inputs !== void 0 && Object.keys(inputs).length > 0;
|
|
805
1114
|
return this.request(`/v1/workflows/${encodeURIComponent(id)}/run`, {
|
|
806
|
-
|
|
807
|
-
body: JSON.stringify(hasInputs ? { inputs } : {}),
|
|
808
|
-
headers: { "Content-Type": "application/json" },
|
|
1115
|
+
...jsonInit("POST", hasInputs ? { inputs } : {}),
|
|
809
1116
|
...opts.signal ? { signal: opts.signal } : {}
|
|
810
1117
|
});
|
|
811
1118
|
}
|
|
@@ -845,8 +1152,8 @@ var HubWorkflowsClient = class {
|
|
|
845
1152
|
* follow it with {@link getRun}/{@link watchRun}/{@link waitForRun} like any
|
|
846
1153
|
* other run. Throws `HubSdkError`: `RUN_NOT_RETRYABLE` when the run is not
|
|
847
1154
|
* terminal-failed/cancelled (queued/running/waiting, or succeeded),
|
|
848
|
-
* `
|
|
849
|
-
*
|
|
1155
|
+
* `NOT_FOUND` for an unknown/foreign run id; a workflow paused since the
|
|
1156
|
+
* failure still retries. Pass `opts.signal` to abort a slow request.
|
|
850
1157
|
*/
|
|
851
1158
|
async retryRun(id, runId, opts = {}) {
|
|
852
1159
|
return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/retry`, {
|
|
@@ -867,6 +1174,12 @@ var HubWorkflowsClient = class {
|
|
|
867
1174
|
* poll) arrive — the persisted record, read via {@link getRun}, stays the
|
|
868
1175
|
* source of truth. Pass `signal` to cancel; iterating to `run.done` (or
|
|
869
1176
|
* breaking early) releases the connection.
|
|
1177
|
+
*
|
|
1178
|
+
* A stream that ends before `run.done` or `run.waiting` (the Platform is
|
|
1179
|
+
* restarting or handing over) is reopened after a second, as is one refused
|
|
1180
|
+
* with `SERVICE_DRAINING`; the reopened stream starts with a fresh
|
|
1181
|
+
* `snapshot`. After 30 reopens in a row that deliver nothing it throws
|
|
1182
|
+
* `HubSdkError(WORKFLOW_STREAM_INTERRUPTED)`.
|
|
870
1183
|
*/
|
|
871
1184
|
async *watchRun(id, runId, opts = {}) {
|
|
872
1185
|
const openStream = this.stream;
|
|
@@ -874,7 +1187,37 @@ var HubWorkflowsClient = class {
|
|
|
874
1187
|
code: "HUB_CONFIG_INVALID",
|
|
875
1188
|
message: "watchRun needs a streaming transport; use HubClient (which wires it up) rather than constructing HubWorkflowsClient directly"
|
|
876
1189
|
});
|
|
877
|
-
const
|
|
1190
|
+
const path = `/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/events`;
|
|
1191
|
+
let idleReopens = 0;
|
|
1192
|
+
for (;;) {
|
|
1193
|
+
let delivered = false;
|
|
1194
|
+
const connection = this.watchRunOnce(openStream, path, opts);
|
|
1195
|
+
try {
|
|
1196
|
+
for (;;) {
|
|
1197
|
+
const next = await connection.next();
|
|
1198
|
+
if (next.done) {
|
|
1199
|
+
if (next.value === "ended") return;
|
|
1200
|
+
break;
|
|
1201
|
+
}
|
|
1202
|
+
delivered = true;
|
|
1203
|
+
yield next.value;
|
|
1204
|
+
}
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
if (!(error instanceof HubSdkError) || error.code !== "SERVICE_DRAINING" || opts.signal?.aborted) throw error;
|
|
1207
|
+
} finally {
|
|
1208
|
+
await connection.return("interrupted");
|
|
1209
|
+
}
|
|
1210
|
+
idleReopens = delivered ? 0 : idleReopens + 1;
|
|
1211
|
+
if (idleReopens > MAX_IDLE_STREAM_REOPENS) throw new HubSdkError({
|
|
1212
|
+
code: "WORKFLOW_STREAM_INTERRUPTED",
|
|
1213
|
+
message: `the run event stream ended ${MAX_IDLE_STREAM_REOPENS} times without delivering an event`
|
|
1214
|
+
});
|
|
1215
|
+
await abortableDelay(STREAM_REOPEN_DELAY_MS, opts.signal);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
/** One connection's events, then how it ended. */
|
|
1219
|
+
async *watchRunOnce(openStream, path, opts) {
|
|
1220
|
+
const reader = (await openStream(path, {
|
|
878
1221
|
method: "GET",
|
|
879
1222
|
headers: { Accept: "text/event-stream" },
|
|
880
1223
|
...opts.signal ? { signal: opts.signal } : {}
|
|
@@ -896,9 +1239,9 @@ var HubWorkflowsClient = class {
|
|
|
896
1239
|
const event = toRunStreamEvent(parsed.eventType, parsed.data);
|
|
897
1240
|
if (!event) continue;
|
|
898
1241
|
yield event;
|
|
899
|
-
if (event.type === "run.done" || event.type === "run.waiting") return;
|
|
1242
|
+
if (event.type === "run.done" || event.type === "run.waiting") return "ended";
|
|
900
1243
|
}
|
|
901
|
-
if (done) return;
|
|
1244
|
+
if (done) return "interrupted";
|
|
902
1245
|
}
|
|
903
1246
|
} finally {
|
|
904
1247
|
await reader.cancel().catch(() => {});
|
|
@@ -984,11 +1327,7 @@ var HubWorkflowsClient = class {
|
|
|
984
1327
|
* YAML no longer compiles, `CONFLICT` on a concurrent modification.
|
|
985
1328
|
*/
|
|
986
1329
|
async rollback(id, rev) {
|
|
987
|
-
return this.request(`/v1/workflows/${encodeURIComponent(id)}/rollback`, {
|
|
988
|
-
method: "POST",
|
|
989
|
-
body: JSON.stringify({ rev }),
|
|
990
|
-
headers: { "Content-Type": "application/json" }
|
|
991
|
-
});
|
|
1330
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/rollback`, jsonInit("POST", { rev }));
|
|
992
1331
|
}
|
|
993
1332
|
/**
|
|
994
1333
|
* Deterministic health stats over the workflow's last N terminal runs
|
|
@@ -1009,11 +1348,7 @@ var HubWorkflowsClient = class {
|
|
|
1009
1348
|
* until an owner approves.
|
|
1010
1349
|
*/
|
|
1011
1350
|
async propose(id, input) {
|
|
1012
|
-
return this.request(`/v1/workflows/${encodeURIComponent(id)}/proposals`,
|
|
1013
|
-
method: "POST",
|
|
1014
|
-
body: JSON.stringify(input),
|
|
1015
|
-
headers: { "Content-Type": "application/json" }
|
|
1016
|
-
});
|
|
1351
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/proposals`, jsonInit("POST", input));
|
|
1017
1352
|
}
|
|
1018
1353
|
/**
|
|
1019
1354
|
* Pending (proposed) and declined (rejected) revisions, newest first, with
|
|
@@ -1040,11 +1375,7 @@ var HubWorkflowsClient = class {
|
|
|
1040
1375
|
* pending proposal.
|
|
1041
1376
|
*/
|
|
1042
1377
|
async rejectProposal(id, rev, opts = {}) {
|
|
1043
|
-
return this.request(`/v1/workflows/${encodeURIComponent(id)}/proposals/${encodeURIComponent(String(rev))}/reject`, {
|
|
1044
|
-
method: "POST",
|
|
1045
|
-
body: JSON.stringify(opts.reason !== void 0 ? { reason: opts.reason } : {}),
|
|
1046
|
-
headers: { "Content-Type": "application/json" }
|
|
1047
|
-
});
|
|
1378
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/proposals/${encodeURIComponent(String(rev))}/reject`, jsonInit("POST", opts.reason !== void 0 ? { reason: opts.reason } : {}));
|
|
1048
1379
|
}
|
|
1049
1380
|
/**
|
|
1050
1381
|
* Mint a fresh webhook hook token, retiring the current one immediately.
|
|
@@ -1106,51 +1437,6 @@ var HubWorkflowsClient = class {
|
|
|
1106
1437
|
});
|
|
1107
1438
|
return downloadBytes(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(artifactId)}`, { method: "GET" });
|
|
1108
1439
|
}
|
|
1109
|
-
/**
|
|
1110
|
-
* The workflow's whole keyspace as metadata (key, version, updatedAt — never
|
|
1111
|
-
* values), key-ordered. Bounded by the server's per-workflow key cap.
|
|
1112
|
-
*/
|
|
1113
|
-
async listKv(id) {
|
|
1114
|
-
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv`, { method: "GET" });
|
|
1115
|
-
}
|
|
1116
|
-
/** One KV entry (value + version); `HubSdkError(NOT_FOUND)` when absent. */
|
|
1117
|
-
async getKv(id, key) {
|
|
1118
|
-
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, { method: "GET" });
|
|
1119
|
-
}
|
|
1120
|
-
/**
|
|
1121
|
-
* Upsert a key; returns the entry with its NEW version (increments on every
|
|
1122
|
-
* write). Throws `HubSdkError(KV_VALUE_TOO_LARGE)` past the 64KiB value cap
|
|
1123
|
-
* or `KV_KEY_LIMIT` past the per-workflow key cap. `raw` is the caller's
|
|
1124
|
-
* value, stored and read back verbatim, so the client never gives it a shape.
|
|
1125
|
-
*/
|
|
1126
|
-
async putKv(id, key, raw) {
|
|
1127
|
-
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, {
|
|
1128
|
-
method: "PUT",
|
|
1129
|
-
body: JSON.stringify({ value: raw }),
|
|
1130
|
-
headers: { "Content-Type": "application/json" }
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
/**
|
|
1134
|
-
* Compare-and-swap: the write lands only when the stored version still
|
|
1135
|
-
* equals `expectedVersion`; otherwise `HubSdkError(KV_VERSION_CONFLICT)`
|
|
1136
|
-
* (409) — re-read and retry. Creation is {@link putKv}'s job. `raw` is the
|
|
1137
|
-
* caller's value, stored and read back verbatim, so the client never gives it
|
|
1138
|
-
* a shape.
|
|
1139
|
-
*/
|
|
1140
|
-
async casKv(id, key, expectedVersion, raw) {
|
|
1141
|
-
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}/cas`, {
|
|
1142
|
-
method: "POST",
|
|
1143
|
-
body: JSON.stringify({
|
|
1144
|
-
expectedVersion,
|
|
1145
|
-
value: raw
|
|
1146
|
-
}),
|
|
1147
|
-
headers: { "Content-Type": "application/json" }
|
|
1148
|
-
});
|
|
1149
|
-
}
|
|
1150
|
-
/** Delete a key; idempotent — `deleted` reports whether a row was removed. */
|
|
1151
|
-
async deleteKv(id, key) {
|
|
1152
|
-
return this.request(`/v1/workflows/${encodeURIComponent(id)}/kv/${encodeURIComponent(key)}`, { method: "DELETE" });
|
|
1153
|
-
}
|
|
1154
1440
|
/** The curated starter gallery runnable on THIS deployment: metadata,
|
|
1155
1441
|
* parameters, referenced providers, and a concrete `previewYaml` per card. */
|
|
1156
1442
|
async listTemplates() {
|
|
@@ -1165,11 +1451,7 @@ var HubWorkflowsClient = class {
|
|
|
1165
1451
|
* (400); an unknown template id throws `NOT_FOUND`.
|
|
1166
1452
|
*/
|
|
1167
1453
|
async instantiateTemplate(templateId, parameters) {
|
|
1168
|
-
return this.request(`/v1/workflows/templates/${encodeURIComponent(templateId)}/instantiate`, {
|
|
1169
|
-
method: "POST",
|
|
1170
|
-
body: JSON.stringify(parameters ? { parameters } : {}),
|
|
1171
|
-
headers: { "Content-Type": "application/json" }
|
|
1172
|
-
});
|
|
1454
|
+
return this.request(`/v1/workflows/templates/${encodeURIComponent(templateId)}/instantiate`, jsonInit("POST", parameters ? { parameters } : {}));
|
|
1173
1455
|
}
|
|
1174
1456
|
/**
|
|
1175
1457
|
* The caller's open decisions across all workflows — the always-available
|
|
@@ -1196,11 +1478,7 @@ var HubWorkflowsClient = class {
|
|
|
1196
1478
|
* (409 — answered, timed out, or cancelled first).
|
|
1197
1479
|
*/
|
|
1198
1480
|
async resolveRunDecision(id, runId, input) {
|
|
1199
|
-
return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/decision/resolve`,
|
|
1200
|
-
method: "POST",
|
|
1201
|
-
body: JSON.stringify(input),
|
|
1202
|
-
headers: { "Content-Type": "application/json" }
|
|
1203
|
-
});
|
|
1481
|
+
return this.request(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/decision/resolve`, jsonInit("POST", input));
|
|
1204
1482
|
}
|
|
1205
1483
|
/**
|
|
1206
1484
|
* Dry-run a YAML definition against the platform compiler without saving it.
|
|
@@ -1213,17 +1491,203 @@ var HubWorkflowsClient = class {
|
|
|
1213
1491
|
* the unconnected providers is the point.
|
|
1214
1492
|
*/
|
|
1215
1493
|
async validate(yaml, opts = {}) {
|
|
1216
|
-
return this.request("/v1/workflows/validate", {
|
|
1494
|
+
return this.request("/v1/workflows/validate", jsonInit("POST", {
|
|
1495
|
+
yaml,
|
|
1496
|
+
...opts.structural ? { structural: true } : {}
|
|
1497
|
+
}));
|
|
1498
|
+
}
|
|
1499
|
+
async schema() {
|
|
1500
|
+
return this.request("/v1/workflows/schema", { method: "GET" });
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
//#endregion
|
|
1504
|
+
//#region src/client.ts
|
|
1505
|
+
function isDispatchCurrentData(value) {
|
|
1506
|
+
return isRecord$1(value) && typeof value.current === "boolean";
|
|
1507
|
+
}
|
|
1508
|
+
var HubClient = class HubClient extends HubTransport {
|
|
1509
|
+
knowledge;
|
|
1510
|
+
allowances;
|
|
1511
|
+
apps;
|
|
1512
|
+
connections;
|
|
1513
|
+
channels;
|
|
1514
|
+
numbers;
|
|
1515
|
+
eventSubscriptions;
|
|
1516
|
+
permissions;
|
|
1517
|
+
tokens;
|
|
1518
|
+
tools;
|
|
1519
|
+
approvals;
|
|
1520
|
+
audit;
|
|
1521
|
+
githubApp;
|
|
1522
|
+
workflows;
|
|
1523
|
+
lines;
|
|
1524
|
+
constructor(options) {
|
|
1525
|
+
super(options);
|
|
1526
|
+
this.knowledge = new HubKnowledgeClient((path, init) => this.request(path, init));
|
|
1527
|
+
this.allowances = new HubAllowancesClient((path, init) => this.request(path, init));
|
|
1528
|
+
this.apps = new HubAppsClient((path, init) => this.request(path, init));
|
|
1529
|
+
this.numbers = new HubManagedNumbersClient((path, init) => this.request(path, init));
|
|
1530
|
+
this.connections = new HubConnectionsClient((path, init) => this.request(path, init));
|
|
1531
|
+
this.channels = new HubChannelsClient((path, init) => this.request(path, init));
|
|
1532
|
+
this.eventSubscriptions = new HubEventSubscriptionsClient((path, init) => this.request(path, init));
|
|
1533
|
+
this.permissions = new HubPermissionsClient((path, init) => this.request(path, init));
|
|
1534
|
+
this.tokens = new HubTokensClient((path, init) => this.request(path, init));
|
|
1535
|
+
this.tools = new HubToolsClient((path, init) => this.request(path, init));
|
|
1536
|
+
this.approvals = new HubApprovalsClient((path, init) => this.request(path, init));
|
|
1537
|
+
this.audit = new HubAuditClient((path, init) => this.request(path, init));
|
|
1538
|
+
this.githubApp = new HubGithubAppClient((path, init) => this.request(path, init));
|
|
1539
|
+
this.workflows = new HubWorkflowsClient((path, init) => this.request(path, init), (path, init) => this.stream(path, init), (path, init) => this.requestBytes(path, init));
|
|
1540
|
+
this.lines = new HubLinesClient((path, init) => this.request(path, init), (yaml) => this.workflows.create(yaml), (path, init, callbackBearer) => {
|
|
1541
|
+
if (typeof callbackBearer !== "string" || !/^[\x21-\x7e]{16,512}$/.test(callbackBearer)) throw new HubSdkError({
|
|
1542
|
+
code: "HUB_INVALID_INPUT",
|
|
1543
|
+
message: "Application callback bearer is invalid"
|
|
1544
|
+
});
|
|
1545
|
+
const headers = new Headers(init.headers);
|
|
1546
|
+
headers.set("Authorization", `Bearer ${callbackBearer}`);
|
|
1547
|
+
return this.request(path, {
|
|
1548
|
+
...init,
|
|
1549
|
+
headers,
|
|
1550
|
+
credentials: "omit",
|
|
1551
|
+
redirect: "manual"
|
|
1552
|
+
}, {
|
|
1553
|
+
auth: "caller",
|
|
1554
|
+
validateData: isDispatchCurrentData
|
|
1555
|
+
});
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
static fromEnv(options = {}) {
|
|
1559
|
+
const env = options.env ?? readProcessEnv();
|
|
1560
|
+
return new HubClient({
|
|
1561
|
+
baseUrl: resolveHubBaseUrl(env),
|
|
1562
|
+
apiKey: options.apiKey ?? (options.authHeaders ? void 0 : resolveHubAuth(env)),
|
|
1563
|
+
authHeaders: options.authHeaders,
|
|
1564
|
+
fetch: options.fetch
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
async status() {
|
|
1568
|
+
return this.request("/v1/hub/status", { method: "GET" });
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Open a streaming (Server-Sent Events) response and return its raw body.
|
|
1572
|
+
* Shares auth-header building + `fetch` with {@link request}, but does NOT
|
|
1573
|
+
* buffer or JSON-parse the body — the caller consumes the stream.
|
|
1574
|
+
*
|
|
1575
|
+
* A successful stream comes back as `text/event-stream`. Anything else is an
|
|
1576
|
+
* error envelope (404/429/401 JSON) or a transport failure (gateway/HTML
|
|
1577
|
+
* page); it is surfaced the same way {@link request} surfaces errors — a
|
|
1578
|
+
* typed `HubSdkError` from a `{success:false}` envelope, otherwise an
|
|
1579
|
+
* `HUB_HTTP_<status>` transport error — rather than handed back as bogus SSE.
|
|
1580
|
+
*/
|
|
1581
|
+
async stream(path, init) {
|
|
1582
|
+
const response = await this.fetch(`${this.baseUrl}${path}`, {
|
|
1583
|
+
...init,
|
|
1584
|
+
headers: await this.buildHeaders(init.headers)
|
|
1585
|
+
});
|
|
1586
|
+
const status = response.status;
|
|
1587
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1588
|
+
if (!response.ok || !contentType.includes("text/event-stream")) await throwHubResponseError(path, response, status, contentType);
|
|
1589
|
+
if (!response.body) throw httpError(path, status, response.statusText, "event-stream response had no body", contentType);
|
|
1590
|
+
return response.body;
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Fetch raw (non-envelope) bytes — the artifact download route, whose 200 is
|
|
1594
|
+
* the artifact's own content type, not JSON. Error responses on such routes
|
|
1595
|
+
* are still the standard envelope, so a failure surfaces exactly like
|
|
1596
|
+
* {@link request}: a typed `HubSdkError` from a `{success:false}` body,
|
|
1597
|
+
* otherwise an `HUB_HTTP_<status>` transport error.
|
|
1598
|
+
*/
|
|
1599
|
+
async requestBytes(path, init) {
|
|
1600
|
+
const response = await this.fetch(`${this.baseUrl}${path}`, {
|
|
1601
|
+
...init,
|
|
1602
|
+
headers: await this.buildHeaders(init.headers)
|
|
1603
|
+
});
|
|
1604
|
+
const status = response.status;
|
|
1605
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1606
|
+
if (!response.ok) await throwHubResponseError(path, response, status, contentType);
|
|
1607
|
+
return {
|
|
1608
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
1609
|
+
contentType
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
//#endregion
|
|
1614
|
+
//#region src/broker.ts
|
|
1615
|
+
/** Server-only client. App secrets never become a user's general API key.
|
|
1616
|
+
* Every invocation mints and consumes one short-lived, exact-grant token. */
|
|
1617
|
+
var HubAppBrokerClient = class {
|
|
1618
|
+
baseUrl;
|
|
1619
|
+
fetcher;
|
|
1620
|
+
constructor(options) {
|
|
1621
|
+
this.options = options;
|
|
1622
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
1623
|
+
this.fetcher = options.fetch ?? fetch.bind(globalThis);
|
|
1624
|
+
}
|
|
1625
|
+
async token(path, body) {
|
|
1626
|
+
const response = await this.fetcher(`${this.baseUrl}${path}`, {
|
|
1217
1627
|
method: "POST",
|
|
1628
|
+
redirect: "manual",
|
|
1629
|
+
headers: { "Content-Type": "application/json" },
|
|
1218
1630
|
body: JSON.stringify({
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1631
|
+
...body,
|
|
1632
|
+
client_id: this.options.clientId,
|
|
1633
|
+
client_secret: this.options.clientSecret
|
|
1634
|
+
})
|
|
1635
|
+
});
|
|
1636
|
+
const result = await response.json().catch(() => null);
|
|
1637
|
+
if (!response.ok || !result || typeof result !== "object" || Array.isArray(result)) throw new HubSdkError({
|
|
1638
|
+
code: "HUB_CONFIG_MISSING",
|
|
1639
|
+
message: "Application authorization is unavailable"
|
|
1640
|
+
}, { status: response.status });
|
|
1641
|
+
return result;
|
|
1642
|
+
}
|
|
1643
|
+
readToken(body) {
|
|
1644
|
+
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({
|
|
1645
|
+
code: "HUB_CONFIG_MISSING",
|
|
1646
|
+
message: "Invalid application authorization response"
|
|
1223
1647
|
});
|
|
1648
|
+
return {
|
|
1649
|
+
accessToken: body.access_token,
|
|
1650
|
+
expiresIn: body.expires_in,
|
|
1651
|
+
scopes: body.scope.split(" ").filter(Boolean)
|
|
1652
|
+
};
|
|
1224
1653
|
}
|
|
1225
|
-
async
|
|
1226
|
-
|
|
1654
|
+
async exchange(input) {
|
|
1655
|
+
const body = await this.token("/v1/apps/oauth/token", {
|
|
1656
|
+
grant_type: "authorization_code",
|
|
1657
|
+
code: input.code,
|
|
1658
|
+
redirect_uri: input.redirectUri,
|
|
1659
|
+
...input.connectionId === void 0 ? {} : { connection_id: input.connectionId },
|
|
1660
|
+
ttl_seconds: 60
|
|
1661
|
+
});
|
|
1662
|
+
const token = this.readToken(body);
|
|
1663
|
+
if (typeof body.grant_id !== "string" || !body.grant_id || typeof body.connection_id !== "string" || !body.connection_id.trim() || input.connectionId !== void 0 && body.connection_id !== input.connectionId) throw new HubSdkError({
|
|
1664
|
+
code: "HUB_CONFIG_MISSING",
|
|
1665
|
+
message: "Application connection did not match consent"
|
|
1666
|
+
});
|
|
1667
|
+
return {
|
|
1668
|
+
...token,
|
|
1669
|
+
grantId: body.grant_id,
|
|
1670
|
+
connectionId: body.connection_id
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
async mint(grantId) {
|
|
1674
|
+
if (!/^[A-Za-z0-9_-]{1,256}$/.test(grantId)) throw new Error("Invalid application grant");
|
|
1675
|
+
return this.readToken(await this.token(`/v1/apps/grants/${encodeURIComponent(grantId)}/mint-broker-token`, {
|
|
1676
|
+
grant_id: grantId,
|
|
1677
|
+
ttl_seconds: 60
|
|
1678
|
+
}));
|
|
1679
|
+
}
|
|
1680
|
+
async invoke(grantId, connectionId, actionPath, input) {
|
|
1681
|
+
const token = await this.mint(grantId);
|
|
1682
|
+
if (!token.scopes.includes(actionPath)) throw new HubSdkError({
|
|
1683
|
+
code: "HUB_POLICY_DENIED",
|
|
1684
|
+
message: "This action was not granted to the application"
|
|
1685
|
+
});
|
|
1686
|
+
return new HubClient({
|
|
1687
|
+
baseUrl: this.baseUrl,
|
|
1688
|
+
apiKey: token.accessToken,
|
|
1689
|
+
fetch: this.fetcher
|
|
1690
|
+
}).tools.invoke(actionPath, input, { connectionId });
|
|
1227
1691
|
}
|
|
1228
1692
|
};
|
|
1229
1693
|
//#endregion
|
|
@@ -1484,6 +1948,93 @@ function requestFailure(code, status, error, headers) {
|
|
|
1484
1948
|
};
|
|
1485
1949
|
}
|
|
1486
1950
|
//#endregion
|
|
1487
|
-
|
|
1951
|
+
//#region src/knowledge-approval.ts
|
|
1952
|
+
/** Called only after the owner reviews this exact pending effect and clicks
|
|
1953
|
+
* Approve. Re-read first: a stale tab cannot approve a substituted operation. */
|
|
1954
|
+
async function approveKnowledgeSource(input) {
|
|
1955
|
+
const { source, port, approvals } = input;
|
|
1956
|
+
if (source.status !== "awaiting-approval" || !source.approval || !source.refreshRequestId || source.approval.preview.sourceId !== source.id || source.approval.preview.requestId !== source.refreshRequestId) throw new Error("This source has no reviewable pending action. Reload knowledge.");
|
|
1957
|
+
const current = await port.get(source.id);
|
|
1958
|
+
if (current.id !== source.id || current.status !== "awaiting-approval" || current.refreshRequestId !== source.refreshRequestId || JSON.stringify(current.approval) !== JSON.stringify(source.approval)) throw new Error("The requested action changed. Reload knowledge and review it again.");
|
|
1959
|
+
let approvalToken;
|
|
1960
|
+
try {
|
|
1961
|
+
const decision = await approvals.approve(source.approval.id);
|
|
1962
|
+
if (decision.approval.id !== source.approval.id || decision.approval.actionPath !== source.approval.path || decision.approval.connectionId !== source.approval.preview.connectionId || decision.approval.providerId !== source.approval.preview.provider || decision.approval.status !== "consumed" || !decision.capabilityToken?.token) throw new Error("The approval did not return a matching one-time capability. Reload knowledge.");
|
|
1963
|
+
approvalToken = decision.capabilityToken.token;
|
|
1964
|
+
return await port.refresh(source.id, {
|
|
1965
|
+
requestId: source.refreshRequestId,
|
|
1966
|
+
approvalToken
|
|
1967
|
+
});
|
|
1968
|
+
} finally {
|
|
1969
|
+
approvalToken = void 0;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
//#endregion
|
|
1973
|
+
//#region src/line-grants.ts
|
|
1974
|
+
function path(binding, suffix = "") {
|
|
1975
|
+
if (!binding.workspaceId || !binding.workspaceAgentId) throw new Error("A workspace and agent are required for a line grant");
|
|
1976
|
+
return `/v1/hub/line-grants/current${suffix}?${new URLSearchParams({
|
|
1977
|
+
workspaceId: binding.workspaceId,
|
|
1978
|
+
workspaceAgentId: binding.workspaceAgentId
|
|
1979
|
+
})}`;
|
|
1980
|
+
}
|
|
1981
|
+
/** Every call uses HubTransport's authHeaders, so one-use app broker tokens can be minted per request. */
|
|
1982
|
+
var HubLineGrantsClient = class extends HubTransport {
|
|
1983
|
+
current(binding) {
|
|
1984
|
+
return this.request(path(binding), { method: "GET" });
|
|
1985
|
+
}
|
|
1986
|
+
async line(binding) {
|
|
1987
|
+
return (await this.request(path(binding, "/line"), { method: "GET" })).line;
|
|
1988
|
+
}
|
|
1989
|
+
async attach(binding, input) {
|
|
1990
|
+
return (await this.request(path(binding, "/attachment"), jsonInit("PUT", input))).attachment;
|
|
1991
|
+
}
|
|
1992
|
+
detach(binding) {
|
|
1993
|
+
return this.request(path(binding, "/attachment"), { method: "DELETE" });
|
|
1994
|
+
}
|
|
1995
|
+
async threads(binding) {
|
|
1996
|
+
return (await this.request(path(binding, "/threads"), { method: "GET" })).threads;
|
|
1997
|
+
}
|
|
1998
|
+
async messages(binding, threadId) {
|
|
1999
|
+
if (!threadId || threadId.length > 256) throw new Error("A bounded line thread id is required");
|
|
2000
|
+
return (await this.request(path(binding, `/threads/${encodeURIComponent(threadId)}/messages`), { method: "GET" })).messages;
|
|
2001
|
+
}
|
|
2002
|
+
memberApplication(binding) {
|
|
2003
|
+
return this.request(path(binding, "/member-application"), { method: "GET" });
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Route the owner's texts to this app's callback; the incumbent attachment
|
|
2007
|
+
* stays. `replaceSelectionId` moves them from another app on the owner's
|
|
2008
|
+
* explicit choice.
|
|
2009
|
+
*/
|
|
2010
|
+
async selectApplication(binding, input) {
|
|
2011
|
+
return (await this.request(path(binding, "/member-application"), jsonInit("PUT", input))).selection;
|
|
2012
|
+
}
|
|
2013
|
+
/** Return the owner's texts to the incumbent attachment. */
|
|
2014
|
+
async clearApplication(binding, input) {
|
|
2015
|
+
await this.request(path(binding, "/member-application"), jsonInit("DELETE", input));
|
|
2016
|
+
}
|
|
2017
|
+
/** Ask only the verified owner member named by this owner_asks grant. */
|
|
2018
|
+
send(binding, input) {
|
|
2019
|
+
return this.request(path(binding, "/send"), jsonInit("POST", input));
|
|
2020
|
+
}
|
|
2021
|
+
/** Only a send created by this same grant is visible. */
|
|
2022
|
+
outbox(binding, messageId) {
|
|
2023
|
+
if (!/^lmsg_[A-Za-z0-9_-]+$/.test(messageId)) throw new Error("A line message id is required");
|
|
2024
|
+
return this.request(path(binding, `/outbox/${encodeURIComponent(messageId)}`), { method: "GET" });
|
|
2025
|
+
}
|
|
2026
|
+
revoke(binding, expectedGrantId) {
|
|
2027
|
+
const target = path(binding);
|
|
2028
|
+
return this.request(expectedGrantId === void 0 ? target : `${target}&expectedGrantId=${encodeURIComponent(expectedGrantId)}`, { method: "DELETE" });
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
//#endregion
|
|
2032
|
+
//#region src/workflow-definition-types.ts
|
|
2033
|
+
/** Stable identity shared by workflow connection requirements and prerequisite references. */
|
|
2034
|
+
function workflowConnectionRequirementKey(ref) {
|
|
2035
|
+
return `${ref.kind}:${ref.provider}`;
|
|
2036
|
+
}
|
|
2037
|
+
//#endregion
|
|
2038
|
+
export { HUB_API_KEY_ENV_VAR, HUB_CAPABILITY_TOKEN_ENV_VAR, HUB_URL_ENV_VAR, HubAllowancesClient, HubAppBrokerClient, HubApprovalsClient, HubAppsClient, HubAuditClient, HubChannelsClient, HubClient, HubConnectionsClient, HubEventDeliveryError, HubEventSubscriptionsClient, HubGithubAppClient, HubInboundWebhooksClient, HubKnowledgeClient, HubKnowledgeManagementClient, HubLineGrantsClient, HubLinesClient, HubManagedNumbersClient, HubPermissionsClient, HubSdkError, HubTokensClient, HubToolsClient, HubWorkflowsClient, approveKnowledgeSource, authenticateHubEventRequest, deriveHubEventCallbackSecret, parseHubEventDelivery, redactHubValue, resolveHubAuth, resolveHubBaseUrl, verifyHubEventSignature, workflowConnectionRequirementKey };
|
|
1488
2039
|
|
|
1489
2040
|
//# sourceMappingURL=index.js.map
|