@hlix/sdk 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,693 @@
1
+ // ../api-client/src/client.ts
2
+ import createClient from "openapi-fetch";
3
+ function createHlixClient(options) {
4
+ return createClient({
5
+ baseUrl: options.baseUrl,
6
+ headers: options.headers,
7
+ fetch: options.fetch
8
+ });
9
+ }
10
+
11
+ // src/auth.ts
12
+ var apiKeyCredential = (apiKey) => ({
13
+ kind: "apiKey",
14
+ apiKey
15
+ });
16
+ var sessionCredential = (cookie) => ({
17
+ kind: "session",
18
+ cookie
19
+ });
20
+ function credentialHeaders(credential) {
21
+ return credential.kind === "apiKey" ? { "x-api-key": credential.apiKey } : { cookie: credential.cookie };
22
+ }
23
+ function organizationHeaders(organizationId) {
24
+ return organizationId ? { "X-Organization-Id": organizationId } : {};
25
+ }
26
+ function authHeaders(credential, organizationId) {
27
+ return {
28
+ ...credentialHeaders(credential),
29
+ ...organizationHeaders(organizationId)
30
+ };
31
+ }
32
+
33
+ // src/errors.ts
34
+ function errorMessageOf(body) {
35
+ if (typeof body !== "object" || body === null) return null;
36
+ const { error } = body;
37
+ return typeof error === "string" ? error : null;
38
+ }
39
+ var HlixError = class extends Error {
40
+ status;
41
+ body;
42
+ requestId;
43
+ method;
44
+ url;
45
+ constructor(message, init) {
46
+ super(message);
47
+ this.name = new.target.name;
48
+ this.status = init.status;
49
+ this.body = init.body;
50
+ this.requestId = init.requestId ?? null;
51
+ this.method = init.method;
52
+ this.url = init.url;
53
+ }
54
+ };
55
+ var HlixValidationError = class extends HlixError {
56
+ /** The decoded `ZodIssue[]`; empty when the server sent a plain message. */
57
+ issues;
58
+ constructor(message, init, issues) {
59
+ super(message, init);
60
+ this.issues = issues;
61
+ }
62
+ };
63
+ var HlixBadRequestError = class extends HlixError {
64
+ };
65
+ var HlixAuthenticationError = class extends HlixError {
66
+ };
67
+ var HlixPermissionError = class extends HlixError {
68
+ };
69
+ var HlixNotFoundError = class extends HlixError {
70
+ };
71
+ var HlixConflictError = class extends HlixError {
72
+ };
73
+ var HlixRateLimitError = class extends HlixError {
74
+ retryAfter;
75
+ constructor(message, init, retryAfter) {
76
+ super(message, init);
77
+ this.retryAfter = retryAfter;
78
+ }
79
+ };
80
+ var HlixNotImplementedError = class extends HlixError {
81
+ };
82
+ var HlixServerError = class extends HlixError {
83
+ };
84
+ var HlixTransportError = class extends Error {
85
+ method;
86
+ url;
87
+ constructor(message, init) {
88
+ super(message, { cause: init.cause });
89
+ this.name = "HlixTransportError";
90
+ this.method = init.method;
91
+ this.url = init.url;
92
+ }
93
+ };
94
+ function validationIssues(body) {
95
+ if (typeof body !== "object" || body === null) return null;
96
+ const candidate = body;
97
+ if (candidate.success !== false) return null;
98
+ const raw = candidate.error?.message;
99
+ if (typeof raw !== "string") return null;
100
+ try {
101
+ const parsed = JSON.parse(raw);
102
+ return Array.isArray(parsed) ? parsed : [];
103
+ } catch {
104
+ return [];
105
+ }
106
+ }
107
+ function retryAfterSeconds(headers) {
108
+ const raw = headers.get("retry-after");
109
+ if (!raw) return null;
110
+ const seconds = Number(raw);
111
+ return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;
112
+ }
113
+ function errorForResponse(response, body, request) {
114
+ const init = {
115
+ status: response.status,
116
+ body,
117
+ requestId: response.headers.get("x-request-id"),
118
+ method: request.method,
119
+ url: request.url
120
+ };
121
+ const stated = errorMessageOf(body);
122
+ const fallback = `${request.method} ${request.url} failed with ${response.status}`;
123
+ const issues = validationIssues(body);
124
+ if (issues) {
125
+ return new HlixValidationError(
126
+ `${request.method} ${request.url}: request body failed validation`,
127
+ init,
128
+ issues
129
+ );
130
+ }
131
+ const message = stated ?? fallback;
132
+ switch (response.status) {
133
+ case 400:
134
+ return new HlixBadRequestError(message, init);
135
+ case 401:
136
+ return new HlixAuthenticationError(message, init);
137
+ case 403:
138
+ return new HlixPermissionError(message, init);
139
+ case 404:
140
+ return new HlixNotFoundError(message, init);
141
+ case 409:
142
+ return new HlixConflictError(message, init);
143
+ case 429:
144
+ return new HlixRateLimitError(message, init, retryAfterSeconds(response.headers));
145
+ case 501:
146
+ return new HlixNotImplementedError(message, init);
147
+ default:
148
+ return response.status >= 500 ? new HlixServerError(message, init) : new HlixError(message, init);
149
+ }
150
+ }
151
+
152
+ // src/retry.ts
153
+ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
154
+ "GET",
155
+ "HEAD",
156
+ "PUT",
157
+ "DELETE",
158
+ "OPTIONS",
159
+ "TRACE"
160
+ ]);
161
+ function isIdempotent(method) {
162
+ return IDEMPOTENT_METHODS.has(method.toUpperCase());
163
+ }
164
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
165
+ 429,
166
+ 500,
167
+ 502,
168
+ 503,
169
+ 504
170
+ ]);
171
+ function isRetryableStatus(status) {
172
+ return RETRYABLE_STATUSES.has(status);
173
+ }
174
+ var DEFAULT_RETRY = {
175
+ attempts: 3,
176
+ baseDelayMs: 200,
177
+ maxDelayMs: 5e3
178
+ };
179
+ function backoffDelayMs(attempt, policy, retryAfterSeconds3, random = Math.random) {
180
+ if (retryAfterSeconds3 !== null) {
181
+ return Math.min(retryAfterSeconds3 * 1e3, policy.maxDelayMs);
182
+ }
183
+ const ceiling = Math.min(policy.baseDelayMs * 2 ** attempt, policy.maxDelayMs);
184
+ return Math.round(random() * ceiling);
185
+ }
186
+ function shouldRetry(input) {
187
+ if (!isIdempotent(input.method)) return false;
188
+ if (input.attempt + 1 >= input.policy.attempts) return false;
189
+ return input.status === null || isRetryableStatus(input.status);
190
+ }
191
+
192
+ // src/http.ts
193
+ var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
194
+ function retryAfterSeconds2(response) {
195
+ const raw = response.headers.get("retry-after");
196
+ if (!raw) return null;
197
+ const seconds = Number(raw);
198
+ return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;
199
+ }
200
+ function retryingFetch(options) {
201
+ const { base, policy } = options;
202
+ const sleep = options.sleep ?? wait;
203
+ const random = options.random ?? Math.random;
204
+ return async (input) => {
205
+ const method = input.method.toUpperCase();
206
+ const replayable = isIdempotent(method) ? input.clone() : null;
207
+ for (let attempt = 0; ; attempt += 1) {
208
+ const request = attempt === 0 ? input : replayable.clone();
209
+ let response;
210
+ try {
211
+ response = await base(request);
212
+ } catch (cause) {
213
+ if (!replayable || !shouldRetry({ method, attempt, policy, status: null })) {
214
+ throw new HlixTransportError(
215
+ `${method} ${input.url}: ${cause instanceof Error ? cause.message : String(cause)}`,
216
+ { method, url: input.url, cause }
217
+ );
218
+ }
219
+ await sleep(backoffDelayMs(attempt, policy, null, random));
220
+ continue;
221
+ }
222
+ if (!shouldRetry({ method, attempt, policy, status: response.status })) {
223
+ return response;
224
+ }
225
+ if (!replayable) return response;
226
+ await sleep(backoffDelayMs(attempt, policy, retryAfterSeconds2(response), random));
227
+ }
228
+ };
229
+ }
230
+ function unwrap(result, request) {
231
+ if (result.response.ok) return result.data;
232
+ throw errorForResponse(result.response, result.error ?? result.data, request);
233
+ }
234
+
235
+ // src/streaming.ts
236
+ var emptyFrame = () => ({ event: null, data: [], id: null, retry: null });
237
+ function applyField(frame, name, value) {
238
+ switch (name) {
239
+ case "event":
240
+ frame.event = value;
241
+ return;
242
+ case "data":
243
+ frame.data.push(value);
244
+ return;
245
+ case "id":
246
+ if (!value.includes("\0")) frame.id = value;
247
+ return;
248
+ case "retry": {
249
+ if (/^\d+$/.test(value)) frame.retry = Number(value);
250
+ return;
251
+ }
252
+ default:
253
+ return;
254
+ }
255
+ }
256
+ function dispatch(frame) {
257
+ const data = frame.data.join("\n");
258
+ const event = frame.event;
259
+ const id = frame.id;
260
+ frame.data = [];
261
+ frame.event = null;
262
+ if (data === "") return null;
263
+ return { event, data, id, retry: frame.retry };
264
+ }
265
+ function consumeLine(frame, line) {
266
+ if (line === "") return dispatch(frame);
267
+ if (line.startsWith(":")) return null;
268
+ const colon = line.indexOf(":");
269
+ if (colon === -1) {
270
+ applyField(frame, line, "");
271
+ return null;
272
+ }
273
+ const name = line.slice(0, colon);
274
+ let value = line.slice(colon + 1);
275
+ if (value.startsWith(" ")) value = value.slice(1);
276
+ applyField(frame, name, value);
277
+ return null;
278
+ }
279
+ function splitLines(buffer) {
280
+ const lines = [];
281
+ let start = 0;
282
+ for (let i = 0; i < buffer.length; i += 1) {
283
+ const ch = buffer[i];
284
+ if (ch !== "\n" && ch !== "\r") continue;
285
+ lines.push(buffer.slice(start, i));
286
+ if (ch === "\r" && buffer[i + 1] === "\n") i += 1;
287
+ start = i + 1;
288
+ }
289
+ return { lines, rest: buffer.slice(start) };
290
+ }
291
+ async function* parseSseStream(body, signal) {
292
+ const reader = body.getReader();
293
+ const decoder = new TextDecoder();
294
+ const frame = emptyFrame();
295
+ let buffer = "";
296
+ const abort = () => void reader.cancel().catch(() => {
297
+ });
298
+ signal?.addEventListener("abort", abort, { once: true });
299
+ try {
300
+ for (; ; ) {
301
+ const { done, value } = await reader.read();
302
+ if (done) break;
303
+ buffer += decoder.decode(value, { stream: true });
304
+ const { lines, rest } = splitLines(buffer);
305
+ buffer = rest;
306
+ for (const line of lines) {
307
+ const event = consumeLine(frame, line);
308
+ if (event) yield event;
309
+ }
310
+ if (signal?.aborted) return;
311
+ }
312
+ buffer += decoder.decode();
313
+ if (buffer !== "") {
314
+ const event = consumeLine(frame, buffer);
315
+ if (event) yield event;
316
+ }
317
+ const trailing = dispatch(frame);
318
+ if (trailing) yield trailing;
319
+ } finally {
320
+ signal?.removeEventListener("abort", abort);
321
+ reader.releaseLock();
322
+ }
323
+ }
324
+
325
+ // src/index.ts
326
+ var Hlix = class {
327
+ /** The un-opinionated transport, for anything this facade does not wrap. */
328
+ raw;
329
+ baseUrl;
330
+ headers;
331
+ fetcher;
332
+ constructor(options) {
333
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
334
+ this.headers = authHeaders(options.credential, options.organizationId);
335
+ const policy = { ...DEFAULT_RETRY, ...options.retry };
336
+ const base = options.fetch ?? ((input) => globalThis.fetch(input));
337
+ this.fetcher = retryingFetch({ base, policy });
338
+ this.raw = createHlixClient({
339
+ baseUrl: this.baseUrl,
340
+ headers: this.headers,
341
+ fetch: this.fetcher
342
+ });
343
+ }
344
+ at(method, path) {
345
+ return { method, url: `${this.baseUrl}${path}` };
346
+ }
347
+ projects = {
348
+ list: async () => unwrap(
349
+ await this.raw.GET("/v1/api/projects"),
350
+ this.at("GET", "/v1/api/projects")
351
+ ),
352
+ get: async (id) => unwrap(
353
+ await this.raw.GET("/v1/api/projects/{id}", { params: { path: { id } } }),
354
+ this.at("GET", `/v1/api/projects/${id}`)
355
+ ),
356
+ create: async (body) => unwrap(
357
+ await this.raw.POST("/v1/api/projects", { body }),
358
+ this.at("POST", "/v1/api/projects")
359
+ ),
360
+ delete: async (id) => unwrap(
361
+ await this.raw.DELETE("/v1/api/projects/{id}", {
362
+ params: { path: { id } }
363
+ }),
364
+ this.at("DELETE", `/v1/api/projects/${id}`)
365
+ ),
366
+ start: async (id, body) => unwrap(
367
+ await this.raw.POST("/v1/api/projects/{id}/start", {
368
+ params: { path: { id } },
369
+ body
370
+ }),
371
+ this.at("POST", `/v1/api/projects/${id}/start`)
372
+ )
373
+ };
374
+ imports = {
375
+ create: async (body) => {
376
+ const result = await this.raw.POST("/v1/api/imports", { body });
377
+ return unwrap(result, this.at("POST", "/v1/api/imports"));
378
+ },
379
+ get: async (id) => unwrap(
380
+ await this.raw.GET("/v1/api/imports/{id}", {
381
+ params: { path: { id } }
382
+ }),
383
+ this.at("GET", `/v1/api/imports/${id}`)
384
+ ),
385
+ finalize: async (id, body) => {
386
+ const result = await this.raw.POST("/v1/api/imports/{id}/finalize", {
387
+ params: { path: { id } },
388
+ body
389
+ });
390
+ return unwrap(
391
+ result,
392
+ this.at("POST", `/v1/api/imports/${id}/finalize`)
393
+ );
394
+ },
395
+ uploadBundle: async (upload, bundle, options = {}) => {
396
+ const chunkSize = options.chunkSize ?? 8 * 1024 * 1024;
397
+ if (chunkSize <= 0 || chunkSize % (256 * 1024) !== 0) {
398
+ throw new Error("Import upload chunkSize must be a positive multiple of 256 KiB");
399
+ }
400
+ const url = upload.url.startsWith("/") ? `${this.baseUrl}${upload.url}` : upload.url;
401
+ if (upload.kind === "hlix-stream") {
402
+ const request = new Request(url, {
403
+ method: "PUT",
404
+ headers: {
405
+ ...this.headers,
406
+ "content-type": "application/x-git-bundle",
407
+ "content-length": String(bundle.size)
408
+ },
409
+ body: bundle,
410
+ signal: options.signal
411
+ });
412
+ const response = await this.fetcher(request);
413
+ if (!response.ok) await this.throwUploadError(response, url);
414
+ options.onProgress?.(bundle.size, bundle.size);
415
+ return;
416
+ }
417
+ let offset = 0;
418
+ while (offset < bundle.size) {
419
+ const end = Math.min(offset + chunkSize, bundle.size);
420
+ const request = new Request(url, {
421
+ method: "PUT",
422
+ headers: {
423
+ "content-type": "application/x-git-bundle",
424
+ "content-length": String(end - offset),
425
+ "content-range": `bytes ${offset}-${end - 1}/${bundle.size}`
426
+ },
427
+ body: bundle.slice(offset, end),
428
+ signal: options.signal
429
+ });
430
+ const response = await this.fetcher(request);
431
+ if (response.status !== 308 && !response.ok) {
432
+ await this.throwUploadError(response, url);
433
+ }
434
+ const acknowledged = response.headers.get("range")?.match(/bytes=0-(\d+)/)?.[1];
435
+ offset = acknowledged ? Number(acknowledged) + 1 : end;
436
+ if (!Number.isSafeInteger(offset) || offset < end || offset > bundle.size) {
437
+ throw new Error("Import upload server returned an invalid acknowledged range");
438
+ }
439
+ options.onProgress?.(offset, bundle.size);
440
+ }
441
+ }
442
+ };
443
+ revisions = {
444
+ head: async (projectId) => unwrap(
445
+ await this.raw.GET("/v1/api/projects/{id}/revisions/head", {
446
+ params: { path: { id: projectId } }
447
+ }),
448
+ this.at("GET", `/v1/api/projects/${projectId}/revisions/head`)
449
+ ),
450
+ protectedFiles: async (projectId) => unwrap(
451
+ await this.raw.GET("/v1/api/projects/{id}/protected-files", {
452
+ params: { path: { id: projectId } }
453
+ }),
454
+ this.at("GET", `/v1/api/projects/${projectId}/protected-files`)
455
+ ),
456
+ createUpload: async (projectId, body) => {
457
+ const result = await this.raw.POST("/v1/api/projects/{id}/revisions/uploads", {
458
+ params: { path: { id: projectId } },
459
+ body
460
+ });
461
+ return unwrap(result, this.at("POST", `/v1/api/projects/${projectId}/revisions/uploads`));
462
+ },
463
+ finalize: async (projectId, uploadId, body) => {
464
+ const result = await this.raw.POST(
465
+ "/v1/api/projects/{id}/revisions/uploads/{uploadId}/finalize",
466
+ { params: { path: { id: projectId, uploadId } }, body }
467
+ );
468
+ return unwrap(
469
+ result,
470
+ this.at("POST", `/v1/api/projects/${projectId}/revisions/uploads/${uploadId}/finalize`)
471
+ );
472
+ },
473
+ uploadBundle: async (upload, bundle, options = {}) => this.imports.uploadBundle(upload, bundle, options),
474
+ downloadBundle: async (projectId, revisionId) => {
475
+ const path = `/v1/api/projects/${projectId}/revisions/${revisionId}/bundle`;
476
+ const request = new Request(`${this.baseUrl}${path}`, {
477
+ method: "GET",
478
+ headers: this.headers
479
+ });
480
+ const response = await this.fetcher(request);
481
+ if (!response.ok) await this.throwUploadError(response, `${this.baseUrl}${path}`);
482
+ return response.blob();
483
+ }
484
+ };
485
+ resources = {
486
+ importSkill: async (projectId, body) => unwrap(
487
+ await this.raw.POST("/v1/api/projects/{id}/resources/skill", {
488
+ params: { path: { id: projectId } },
489
+ body
490
+ }),
491
+ this.at("POST", `/v1/api/projects/${projectId}/resources/skill`)
492
+ ),
493
+ importAgent: async (projectId, body) => unwrap(
494
+ await this.raw.POST("/v1/api/projects/{id}/resources/agent", {
495
+ params: { path: { id: projectId } },
496
+ body
497
+ }),
498
+ this.at("POST", `/v1/api/projects/${projectId}/resources/agent`)
499
+ ),
500
+ importMcp: async (projectId, body) => unwrap(
501
+ await this.raw.POST("/v1/api/projects/{id}/resources/mcp", {
502
+ params: { path: { id: projectId } },
503
+ body
504
+ }),
505
+ this.at("POST", `/v1/api/projects/${projectId}/resources/mcp`)
506
+ )
507
+ };
508
+ async throwUploadError(response, url) {
509
+ let body = null;
510
+ try {
511
+ body = await response.json();
512
+ } catch {
513
+ body = await response.text().catch(() => null);
514
+ }
515
+ throw errorForResponse(response, body, { method: "PUT", url });
516
+ }
517
+ tasks = {
518
+ list: async (query) => unwrap(
519
+ await this.raw.GET("/v1/api/tasks", { params: { query: query ?? {} } }),
520
+ this.at("GET", "/v1/api/tasks")
521
+ ),
522
+ get: async (id) => unwrap(
523
+ await this.raw.GET("/v1/api/tasks/{id}", { params: { path: { id } } }),
524
+ this.at("GET", `/v1/api/tasks/${id}`)
525
+ ),
526
+ create: async (body) => unwrap(
527
+ await this.raw.POST("/v1/api/tasks", { body }),
528
+ this.at("POST", "/v1/api/tasks")
529
+ ),
530
+ update: async (id, body) => unwrap(
531
+ await this.raw.PATCH("/v1/api/tasks/{id}", {
532
+ params: { path: { id } },
533
+ body
534
+ }),
535
+ this.at("PATCH", `/v1/api/tasks/${id}`)
536
+ ),
537
+ listComments: async (id) => unwrap(
538
+ await this.raw.GET("/v1/api/tasks/{id}/comments", {
539
+ params: { path: { id } }
540
+ }),
541
+ this.at("GET", `/v1/api/tasks/${id}/comments`)
542
+ ),
543
+ addComment: async (id, body) => unwrap(
544
+ await this.raw.POST("/v1/api/tasks/{id}/comments", {
545
+ params: { path: { id } },
546
+ body
547
+ }),
548
+ this.at("POST", `/v1/api/tasks/${id}/comments`)
549
+ ),
550
+ /** The verified review evidence — the same observation QA reviews. */
551
+ review: async (id) => unwrap(
552
+ await this.raw.GET("/v1/api/tasks/{id}/review", {
553
+ params: { path: { id } }
554
+ }),
555
+ this.at("GET", `/v1/api/tasks/${id}/review`)
556
+ ),
557
+ /** Live status frames. A terminal status closes the stream server-side. */
558
+ stream: (id, init) => this.stream(`/v1/api/tasks/${id}/stream`, init)
559
+ };
560
+ cycles = {
561
+ list: async (query) => unwrap(
562
+ await this.raw.GET("/v1/api/cycles", {
563
+ params: { query: query ?? {} }
564
+ }),
565
+ this.at("GET", "/v1/api/cycles")
566
+ ),
567
+ get: async (id) => unwrap(
568
+ await this.raw.GET("/v1/api/cycles/{id}", {
569
+ params: { path: { id } }
570
+ }),
571
+ this.at("GET", `/v1/api/cycles/${id}`)
572
+ ),
573
+ /** Creation is a PROPOSAL — the orchestrator owns the roadmap. */
574
+ propose: async (body) => unwrap(
575
+ await this.raw.POST("/v1/api/cycles", { body }),
576
+ this.at("POST", "/v1/api/cycles")
577
+ ),
578
+ stream: (id, init) => this.stream(`/v1/api/cycles/${id}/stream`, init)
579
+ };
580
+ /**
581
+ * Invoices the agency issues to its own customer.
582
+ *
583
+ * NOT the hlix subscription — that is Polar's, and the two never meet.
584
+ *
585
+ * There is no `update`, because there is no route that could edit an issued
586
+ * invoice: a correction is `void` plus a new draft naming it through
587
+ * `replacesInvoiceId`. And `create` names no customer — the customer is
588
+ * derived from the billables being invoiced.
589
+ */
590
+ clientInvoices = {
591
+ list: async (query) => unwrap(
592
+ await this.raw.GET("/v1/api/client-billing/invoices", {
593
+ params: { query: query ?? {} }
594
+ }),
595
+ this.at("GET", "/v1/api/client-billing/invoices")
596
+ ),
597
+ get: async (id) => unwrap(
598
+ await this.raw.GET("/v1/api/client-billing/invoices/{id}", {
599
+ params: { path: { id } }
600
+ }),
601
+ this.at("GET", `/v1/api/client-billing/invoices/${id}`)
602
+ ),
603
+ createDraft: async (body) => unwrap(
604
+ await this.raw.POST("/v1/api/client-billing/invoices", { body }),
605
+ this.at("POST", "/v1/api/client-billing/invoices")
606
+ ),
607
+ /** The number is assigned by the server, gapless per workspace. */
608
+ issue: async (id) => unwrap(
609
+ await this.raw.POST("/v1/api/client-billing/invoices/{id}/issue", {
610
+ params: { path: { id } },
611
+ body: {}
612
+ }),
613
+ this.at("POST", `/v1/api/client-billing/invoices/${id}/issue`)
614
+ ),
615
+ void: async (id, reason) => unwrap(
616
+ await this.raw.POST("/v1/api/client-billing/invoices/{id}/void", {
617
+ params: { path: { id } },
618
+ body: { reason }
619
+ }),
620
+ this.at("POST", `/v1/api/client-billing/invoices/${id}/void`)
621
+ ),
622
+ pay: async (id) => unwrap(
623
+ await this.raw.POST("/v1/api/client-billing/invoices/{id}/pay", {
624
+ params: { path: { id } },
625
+ body: {}
626
+ }),
627
+ this.at("POST", `/v1/api/client-billing/invoices/${id}/pay`)
628
+ ),
629
+ /**
630
+ * Open a PROVIDER-HOSTED checkout. Returns a URL and nothing else — no card
631
+ * field and no payment credential ever passes through hlix or this client.
632
+ * It does not mark the invoice paid: that happens only when the provider's
633
+ * signed webhook reconciles.
634
+ */
635
+ paymentLink: async (id, body = {}) => unwrap(
636
+ await this.raw.POST(
637
+ "/v1/api/client-billing/invoices/{id}/payment-link",
638
+ { params: { path: { id } }, body }
639
+ ),
640
+ this.at("POST", `/v1/api/client-billing/invoices/${id}/payment-link`)
641
+ )
642
+ };
643
+ /**
644
+ * Consume any `text/event-stream` route.
645
+ *
646
+ * Deliberately NOT routed through the typed client: `openapi-fetch` parses
647
+ * the body, and a stream must not be parsed — it has to be read as it
648
+ * arrives. Retry does not apply either; a stream that drops mid-flight has
649
+ * already delivered events, and silently restarting it would replay them.
650
+ */
651
+ async *stream(path, init) {
652
+ const url = `${this.baseUrl}${path}`;
653
+ const request = new Request(url, {
654
+ headers: { ...this.headers, accept: "text/event-stream" },
655
+ signal: init?.signal
656
+ });
657
+ const response = await globalThis.fetch(request);
658
+ if (!response.ok || !response.body) {
659
+ let body = null;
660
+ try {
661
+ body = await response.json();
662
+ } catch {
663
+ body = null;
664
+ }
665
+ throw errorForResponse(response, body, { method: "GET", url });
666
+ }
667
+ yield* parseSseStream(response.body, init?.signal);
668
+ }
669
+ };
670
+ function createHlix(options) {
671
+ return new Hlix(options);
672
+ }
673
+ export {
674
+ DEFAULT_RETRY,
675
+ Hlix,
676
+ HlixAuthenticationError,
677
+ HlixBadRequestError,
678
+ HlixConflictError,
679
+ HlixError,
680
+ HlixNotFoundError,
681
+ HlixNotImplementedError,
682
+ HlixPermissionError,
683
+ HlixRateLimitError,
684
+ HlixServerError,
685
+ HlixTransportError,
686
+ HlixValidationError,
687
+ IDEMPOTENT_METHODS,
688
+ apiKeyCredential,
689
+ createHlix,
690
+ isIdempotent,
691
+ parseSseStream,
692
+ sessionCredential
693
+ };