@hlix/sdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,7 +7,6 @@ function createHlixClient(options) {
7
7
  fetch: options.fetch
8
8
  });
9
9
  }
10
-
11
10
  // src/auth.ts
12
11
  var apiKeyCredential = (apiKey) => ({
13
12
  kind: "apiKey",
@@ -17,8 +16,19 @@ var sessionCredential = (cookie) => ({
17
16
  kind: "session",
18
17
  cookie
19
18
  });
19
+ var bearerCredential = (token) => ({
20
+ kind: "bearer",
21
+ token
22
+ });
20
23
  function credentialHeaders(credential) {
21
- return credential.kind === "apiKey" ? { "x-api-key": credential.apiKey } : { cookie: credential.cookie };
24
+ switch (credential.kind) {
25
+ case "apiKey":
26
+ return { "x-api-key": credential.apiKey };
27
+ case "session":
28
+ return { cookie: credential.cookie };
29
+ case "bearer":
30
+ return { authorization: `Bearer ${credential.token}` };
31
+ }
22
32
  }
23
33
  function organizationHeaders(organizationId) {
24
34
  return organizationId ? { "X-Organization-Id": organizationId } : {};
@@ -32,11 +42,13 @@ function authHeaders(credential, organizationId) {
32
42
 
33
43
  // src/errors.ts
34
44
  function errorMessageOf(body) {
35
- if (typeof body !== "object" || body === null) return null;
45
+ if (typeof body !== "object" || body === null)
46
+ return null;
36
47
  const { error } = body;
37
48
  return typeof error === "string" ? error : null;
38
49
  }
39
- var HlixError = class extends Error {
50
+
51
+ class HlixError extends Error {
40
52
  status;
41
53
  body;
42
54
  requestId;
@@ -51,37 +63,46 @@ var HlixError = class extends Error {
51
63
  this.method = init.method;
52
64
  this.url = init.url;
53
65
  }
54
- };
55
- var HlixValidationError = class extends HlixError {
56
- /** The decoded `ZodIssue[]`; empty when the server sent a plain message. */
66
+ }
67
+
68
+ class HlixValidationError extends HlixError {
57
69
  issues;
58
70
  constructor(message, init, issues) {
59
71
  super(message, init);
60
72
  this.issues = issues;
61
73
  }
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
+ }
75
+
76
+ class HlixBadRequestError extends HlixError {
77
+ }
78
+
79
+ class HlixAuthenticationError extends HlixError {
80
+ }
81
+
82
+ class HlixPermissionError extends HlixError {
83
+ }
84
+
85
+ class HlixNotFoundError extends HlixError {
86
+ }
87
+
88
+ class HlixConflictError extends HlixError {
89
+ }
90
+
91
+ class HlixRateLimitError extends HlixError {
74
92
  retryAfter;
75
93
  constructor(message, init, retryAfter) {
76
94
  super(message, init);
77
95
  this.retryAfter = retryAfter;
78
96
  }
79
- };
80
- var HlixNotImplementedError = class extends HlixError {
81
- };
82
- var HlixServerError = class extends HlixError {
83
- };
84
- var HlixTransportError = class extends Error {
97
+ }
98
+
99
+ class HlixNotImplementedError extends HlixError {
100
+ }
101
+
102
+ class HlixServerError extends HlixError {
103
+ }
104
+
105
+ class HlixTransportError extends Error {
85
106
  method;
86
107
  url;
87
108
  constructor(message, init) {
@@ -90,13 +111,16 @@ var HlixTransportError = class extends Error {
90
111
  this.method = init.method;
91
112
  this.url = init.url;
92
113
  }
93
- };
114
+ }
94
115
  function validationIssues(body) {
95
- if (typeof body !== "object" || body === null) return null;
116
+ if (typeof body !== "object" || body === null)
117
+ return null;
96
118
  const candidate = body;
97
- if (candidate.success !== false) return null;
119
+ if (candidate.success !== false)
120
+ return null;
98
121
  const raw = candidate.error?.message;
99
- if (typeof raw !== "string") return null;
122
+ if (typeof raw !== "string")
123
+ return null;
100
124
  try {
101
125
  const parsed = JSON.parse(raw);
102
126
  return Array.isArray(parsed) ? parsed : [];
@@ -106,7 +130,8 @@ function validationIssues(body) {
106
130
  }
107
131
  function retryAfterSeconds(headers) {
108
132
  const raw = headers.get("retry-after");
109
- if (!raw) return null;
133
+ if (!raw)
134
+ return null;
110
135
  const seconds = Number(raw);
111
136
  return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;
112
137
  }
@@ -122,11 +147,7 @@ function errorForResponse(response, body, request) {
122
147
  const fallback = `${request.method} ${request.url} failed with ${response.status}`;
123
148
  const issues = validationIssues(body);
124
149
  if (issues) {
125
- return new HlixValidationError(
126
- `${request.method} ${request.url}: request body failed validation`,
127
- init,
128
- issues
129
- );
150
+ return new HlixValidationError(`${request.method} ${request.url}: request body failed validation`, init, issues);
130
151
  }
131
152
  const message = stated ?? fallback;
132
153
  switch (response.status) {
@@ -150,7 +171,7 @@ function errorForResponse(response, body, request) {
150
171
  }
151
172
 
152
173
  // src/retry.ts
153
- var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
174
+ var IDEMPOTENT_METHODS = new Set([
154
175
  "GET",
155
176
  "HEAD",
156
177
  "PUT",
@@ -161,7 +182,7 @@ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
161
182
  function isIdempotent(method) {
162
183
  return IDEMPOTENT_METHODS.has(method.toUpperCase());
163
184
  }
164
- var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
185
+ var RETRYABLE_STATUSES = new Set([
165
186
  429,
166
187
  500,
167
188
  502,
@@ -174,18 +195,20 @@ function isRetryableStatus(status) {
174
195
  var DEFAULT_RETRY = {
175
196
  attempts: 3,
176
197
  baseDelayMs: 200,
177
- maxDelayMs: 5e3
198
+ maxDelayMs: 5000
178
199
  };
179
- function backoffDelayMs(attempt, policy, retryAfterSeconds3, random = Math.random) {
180
- if (retryAfterSeconds3 !== null) {
181
- return Math.min(retryAfterSeconds3 * 1e3, policy.maxDelayMs);
200
+ function backoffDelayMs(attempt, policy, retryAfterSeconds, random = Math.random) {
201
+ if (retryAfterSeconds !== null) {
202
+ return Math.min(retryAfterSeconds * 1000, policy.maxDelayMs);
182
203
  }
183
204
  const ceiling = Math.min(policy.baseDelayMs * 2 ** attempt, policy.maxDelayMs);
184
205
  return Math.round(random() * ceiling);
185
206
  }
186
207
  function shouldRetry(input) {
187
- if (!isIdempotent(input.method)) return false;
188
- if (input.attempt + 1 >= input.policy.attempts) return false;
208
+ if (!isIdempotent(input.method))
209
+ return false;
210
+ if (input.attempt + 1 >= input.policy.attempts)
211
+ return false;
189
212
  return input.status === null || isRetryableStatus(input.status);
190
213
  }
191
214
 
@@ -193,7 +216,8 @@ function shouldRetry(input) {
193
216
  var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
194
217
  function retryAfterSeconds2(response) {
195
218
  const raw = response.headers.get("retry-after");
196
- if (!raw) return null;
219
+ if (!raw)
220
+ return null;
197
221
  const seconds = Number(raw);
198
222
  return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;
199
223
  }
@@ -204,17 +228,14 @@ function retryingFetch(options) {
204
228
  return async (input) => {
205
229
  const method = input.method.toUpperCase();
206
230
  const replayable = isIdempotent(method) ? input.clone() : null;
207
- for (let attempt = 0; ; attempt += 1) {
231
+ for (let attempt = 0;; attempt += 1) {
208
232
  const request = attempt === 0 ? input : replayable.clone();
209
233
  let response;
210
234
  try {
211
235
  response = await base(request);
212
236
  } catch (cause) {
213
237
  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
- );
238
+ throw new HlixTransportError(`${method} ${input.url}: ${cause instanceof Error ? cause.message : String(cause)}`, { method, url: input.url, cause });
218
239
  }
219
240
  await sleep(backoffDelayMs(attempt, policy, null, random));
220
241
  continue;
@@ -222,16 +243,53 @@ function retryingFetch(options) {
222
243
  if (!shouldRetry({ method, attempt, policy, status: response.status })) {
223
244
  return response;
224
245
  }
225
- if (!replayable) return response;
246
+ if (!replayable)
247
+ return response;
226
248
  await sleep(backoffDelayMs(attempt, policy, retryAfterSeconds2(response), random));
227
249
  }
228
250
  };
229
251
  }
230
252
  function unwrap(result, request) {
231
- if (result.response.ok) return result.data;
253
+ if (result.response.ok)
254
+ return result.data;
232
255
  throw errorForResponse(result.response, result.error ?? result.data, request);
233
256
  }
234
257
 
258
+ // src/observe.ts
259
+ var text = (value) => typeof value === "string" ? value : "";
260
+ function parseObserveFrame(data) {
261
+ let parsed;
262
+ try {
263
+ parsed = JSON.parse(data);
264
+ } catch {
265
+ return null;
266
+ }
267
+ if (typeof parsed !== "object" || parsed === null)
268
+ return null;
269
+ const frame = parsed;
270
+ switch (frame.type) {
271
+ case "idle":
272
+ return { type: "idle" };
273
+ case "done":
274
+ return { type: "done" };
275
+ case "active":
276
+ return typeof frame.runId === "string" ? { type: "active", runId: frame.runId } : { type: "active" };
277
+ case "on_chat_model_stream":
278
+ return { type: "on_chat_model_stream", delta: text(frame.delta) };
279
+ case "reasoning":
280
+ return { type: "reasoning", delta: text(frame.delta) };
281
+ case "on_tool_start":
282
+ return { type: "on_tool_start", name: text(frame.name), input: frame.input };
283
+ case "on_tool_end":
284
+ return { type: "on_tool_end", name: text(frame.name), output: frame.output };
285
+ case "error":
286
+ return { type: "error", message: text(frame.message) };
287
+ default:
288
+ return null;
289
+ }
290
+ }
291
+ var isTerminalObserveFrame = (frame) => frame.type === "done" || frame.type === "idle" || frame.type === "error";
292
+
235
293
  // src/streaming.ts
236
294
  var emptyFrame = () => ({ event: null, data: [], id: null, retry: null });
237
295
  function applyField(frame, name, value) {
@@ -243,10 +301,12 @@ function applyField(frame, name, value) {
243
301
  frame.data.push(value);
244
302
  return;
245
303
  case "id":
246
- if (!value.includes("\0")) frame.id = value;
304
+ if (!value.includes("\x00"))
305
+ frame.id = value;
247
306
  return;
248
307
  case "retry": {
249
- if (/^\d+$/.test(value)) frame.retry = Number(value);
308
+ if (/^\d+$/.test(value))
309
+ frame.retry = Number(value);
250
310
  return;
251
311
  }
252
312
  default:
@@ -254,17 +314,21 @@ function applyField(frame, name, value) {
254
314
  }
255
315
  }
256
316
  function dispatch(frame) {
257
- const data = frame.data.join("\n");
317
+ const data = frame.data.join(`
318
+ `);
258
319
  const event = frame.event;
259
320
  const id = frame.id;
260
321
  frame.data = [];
261
322
  frame.event = null;
262
- if (data === "") return null;
323
+ if (data === "")
324
+ return null;
263
325
  return { event, data, id, retry: frame.retry };
264
326
  }
265
327
  function consumeLine(frame, line) {
266
- if (line === "") return dispatch(frame);
267
- if (line.startsWith(":")) return null;
328
+ if (line === "")
329
+ return dispatch(frame);
330
+ if (line.startsWith(":"))
331
+ return null;
268
332
  const colon = line.indexOf(":");
269
333
  if (colon === -1) {
270
334
  applyField(frame, line, "");
@@ -272,66 +336,117 @@ function consumeLine(frame, line) {
272
336
  }
273
337
  const name = line.slice(0, colon);
274
338
  let value = line.slice(colon + 1);
275
- if (value.startsWith(" ")) value = value.slice(1);
339
+ if (value.startsWith(" "))
340
+ value = value.slice(1);
276
341
  applyField(frame, name, value);
277
342
  return null;
278
343
  }
279
344
  function splitLines(buffer) {
280
345
  const lines = [];
281
346
  let start = 0;
282
- for (let i = 0; i < buffer.length; i += 1) {
347
+ for (let i = 0;i < buffer.length; i += 1) {
283
348
  const ch = buffer[i];
284
- if (ch !== "\n" && ch !== "\r") continue;
349
+ if (ch !== `
350
+ ` && ch !== "\r")
351
+ continue;
285
352
  lines.push(buffer.slice(start, i));
286
- if (ch === "\r" && buffer[i + 1] === "\n") i += 1;
353
+ if (ch === "\r" && buffer[i + 1] === `
354
+ `)
355
+ i += 1;
287
356
  start = i + 1;
288
357
  }
289
358
  return { lines, rest: buffer.slice(start) };
290
359
  }
291
360
  async function* parseSseStream(body, signal) {
292
361
  const reader = body.getReader();
293
- const decoder = new TextDecoder();
362
+ const decoder = new TextDecoder;
294
363
  const frame = emptyFrame();
295
364
  let buffer = "";
296
- const abort = () => void reader.cancel().catch(() => {
297
- });
365
+ const abort = () => void reader.cancel().catch(() => {});
298
366
  signal?.addEventListener("abort", abort, { once: true });
299
367
  try {
300
- for (; ; ) {
368
+ for (;; ) {
301
369
  const { done, value } = await reader.read();
302
- if (done) break;
370
+ if (done)
371
+ break;
303
372
  buffer += decoder.decode(value, { stream: true });
304
373
  const { lines, rest } = splitLines(buffer);
305
374
  buffer = rest;
306
375
  for (const line of lines) {
307
376
  const event = consumeLine(frame, line);
308
- if (event) yield event;
377
+ if (event)
378
+ yield event;
309
379
  }
310
- if (signal?.aborted) return;
380
+ if (signal?.aborted)
381
+ return;
311
382
  }
312
383
  buffer += decoder.decode();
313
384
  if (buffer !== "") {
314
385
  const event = consumeLine(frame, buffer);
315
- if (event) yield event;
386
+ if (event)
387
+ yield event;
316
388
  }
317
389
  const trailing = dispatch(frame);
318
- if (trailing) yield trailing;
390
+ if (trailing)
391
+ yield trailing;
319
392
  } finally {
320
393
  signal?.removeEventListener("abort", abort);
321
394
  reader.releaseLock();
322
395
  }
323
396
  }
324
397
 
398
+ // src/revision-watch.ts
399
+ function parseRevisionWatchFrame(data) {
400
+ let parsed;
401
+ try {
402
+ parsed = JSON.parse(data);
403
+ } catch {
404
+ return null;
405
+ }
406
+ if (typeof parsed !== "object" || parsed === null)
407
+ return null;
408
+ const frame = parsed;
409
+ if (frame.kind === "ready")
410
+ return { kind: "ready" };
411
+ if (frame.kind === "workspace")
412
+ return { kind: "workspace" };
413
+ if (frame.kind === "head" && typeof frame.revisionId === "string" && typeof frame.generation === "number") {
414
+ return { kind: "head", revisionId: frame.revisionId, generation: frame.generation };
415
+ }
416
+ return null;
417
+ }
418
+ function isPermanentWatchFailure(error) {
419
+ return error instanceof HlixError && error.status >= 400 && error.status < 500 && error.status !== 408 && error.status !== 429;
420
+ }
421
+ var WATCH_RECONNECT = { firstDelayMs: 1000, maxDelayMs: 30000 };
422
+ function pause(ms, signal) {
423
+ return new Promise((resolve) => {
424
+ if (signal?.aborted)
425
+ return resolve();
426
+ const timer = setTimeout(done, ms);
427
+ function done() {
428
+ clearTimeout(timer);
429
+ signal?.removeEventListener("abort", done);
430
+ resolve();
431
+ }
432
+ signal?.addEventListener("abort", done, { once: true });
433
+ });
434
+ }
435
+
325
436
  // src/index.ts
326
- var Hlix = class {
327
- /** The un-opinionated transport, for anything this facade does not wrap. */
437
+ class Hlix {
328
438
  raw;
329
439
  baseUrl;
330
440
  headers;
331
441
  fetcher;
332
442
  constructor(options) {
333
443
  this.baseUrl = options.baseUrl.replace(/\/+$/, "");
334
- this.headers = authHeaders(options.credential, options.organizationId);
444
+ const auth = authHeaders(options.credential, options.organizationId);
445
+ const owned = new Set(Object.keys(auth).map((name) => name.toLowerCase()));
446
+ this.headers = {
447
+ ...Object.fromEntries(Object.entries(options.headers ?? {}).filter(([name]) => !owned.has(name.toLowerCase()))),
448
+ ...auth
449
+ };
335
450
  const policy = { ...DEFAULT_RETRY, ...options.retry };
336
451
  const base = options.fetch ?? ((input) => globalThis.fetch(input));
337
452
  this.fetcher = retryingFetch({ base, policy });
@@ -344,53 +459,135 @@ var Hlix = class {
344
459
  at(method, path) {
345
460
  return { method, url: `${this.baseUrl}${path}` };
346
461
  }
462
+ workspaces = {
463
+ list: async (options = {}) => unwrap(await this.raw.GET("/v1/api/workspaces", options), this.at("GET", "/v1/api/workspaces"))
464
+ };
347
465
  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}", {
466
+ list: async () => unwrap(await this.raw.GET("/v1/api/projects"), this.at("GET", "/v1/api/projects")),
467
+ get: async (id, options = {}) => unwrap(await this.raw.GET("/v1/api/projects/{id}", { ...options, params: { path: { id } } }), this.at("GET", `/v1/api/projects/${id}`)),
468
+ create: async (body) => unwrap(await this.raw.POST("/v1/api/projects", { body }), this.at("POST", "/v1/api/projects")),
469
+ delete: async (id) => unwrap(await this.raw.DELETE("/v1/api/projects/{id}", {
470
+ params: { path: { id } }
471
+ }), this.at("DELETE", `/v1/api/projects/${id}`)),
472
+ start: async (id, body) => unwrap(await this.raw.POST("/v1/api/projects/{id}/start", {
473
+ params: { path: { id } },
474
+ body
475
+ }), this.at("POST", `/v1/api/projects/${id}/start`)),
476
+ refreshPreview: async (id) => unwrap(await this.raw.POST("/v1/api/projects/{id}/preview/refresh", {
477
+ params: { path: { id } }
478
+ }), this.at("POST", `/v1/api/projects/${id}/preview/refresh`)),
479
+ stop: async (id) => unwrap(await this.raw.POST("/v1/api/projects/{id}/stop", { params: { path: { id } } }), this.at("POST", `/v1/api/projects/${id}/stop`)),
480
+ status: async (id, options = {}) => unwrap(await this.raw.GET("/v1/api/projects/{id}/status", { ...options, params: { path: { id } } }), this.at("GET", `/v1/api/projects/${id}/status`)),
481
+ preview: async (id) => unwrap(await this.raw.GET("/v1/api/projects/{id}/preview", { params: { path: { id } } }), this.at("GET", `/v1/api/projects/${id}/preview`)),
482
+ deliver: async (id) => unwrap(await this.raw.POST("/v1/api/projects/{id}/delivery", { params: { path: { id } } }), this.at("POST", `/v1/api/projects/${id}/delivery`)),
483
+ connectRepo: async (id, body) => {
484
+ const result = await this.raw.POST("/v1/api/projects/{id}/repos/connect", {
485
+ params: { path: { id } },
486
+ body
487
+ });
488
+ return unwrap(result, this.at("POST", `/v1/api/projects/${id}/repos/connect`));
489
+ },
490
+ terminal: async (id) => unwrap(await this.raw.GET("/v1/api/projects/{id}/terminal", { params: { path: { id } } }), this.at("GET", `/v1/api/projects/${id}/terminal`)),
491
+ observeAgent: (projectId, agentId, init) => this.observeAgent(projectId, agentId, init),
492
+ workspace: (id) => ({
493
+ environment: async () => unwrap(await this.raw.GET("/v1/api/projects/{id}/environment", {
362
494
  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", {
495
+ }), this.at("GET", `/v1/api/projects/${id}/environment`)),
496
+ fs: async (body) => unwrap(await this.raw.POST("/v1/api/projects/{id}/workspace/fs", {
497
+ params: { path: { id } },
498
+ body
499
+ }), this.at("POST", `/v1/api/projects/${id}/workspace/fs`)),
500
+ exec: async (body) => unwrap(await this.raw.POST("/v1/api/projects/{id}/workspace/exec", {
368
501
  params: { path: { id } },
369
502
  body
370
- }),
371
- this.at("POST", `/v1/api/projects/${id}/start`)
372
- )
503
+ }), this.at("POST", `/v1/api/projects/${id}/workspace/exec`))
504
+ })
505
+ };
506
+ secrets = {
507
+ listEntries: async (projectId) => unwrap(await this.raw.GET("/v1/api/secrets/project/{projectId}/entries", {
508
+ params: { path: { projectId } }
509
+ }), this.at("GET", `/v1/api/secrets/project/${projectId}/entries`)),
510
+ upsertEntries: async (projectId, body) => unwrap(await this.raw.PUT("/v1/api/secrets/project/{projectId}/entries", {
511
+ params: { path: { projectId } },
512
+ body
513
+ }), this.at("PUT", `/v1/api/secrets/project/${projectId}/entries`)),
514
+ importFiles: async (projectId, files) => unwrap(await this.raw.PUT("/v1/api/secrets/project/{projectId}/files", {
515
+ params: { path: { projectId } },
516
+ body: { files }
517
+ }), this.at("PUT", `/v1/api/secrets/project/${projectId}/files`)),
518
+ deleteEntry: async (projectId, key, source) => unwrap(await this.raw.DELETE("/v1/api/secrets/project/{projectId}/entries/{key}", {
519
+ params: {
520
+ path: { projectId, key },
521
+ ...source === undefined ? {} : { query: { source } }
522
+ }
523
+ }), this.at("DELETE", `/v1/api/secrets/project/${projectId}/entries/${encodeURIComponent(key)}`)),
524
+ exportEntries: async (projectId, options = {}) => {
525
+ const path = `/v1/api/secrets/project/${projectId}/export`;
526
+ const url = `${this.baseUrl}${path}`;
527
+ const response = await this.fetcher(new Request(url, {
528
+ method: "POST",
529
+ headers: { ...this.headers, "content-type": "application/json" },
530
+ body: JSON.stringify({
531
+ ...options.source === undefined ? {} : { source: options.source },
532
+ ...options.format === undefined ? {} : { format: options.format }
533
+ })
534
+ }));
535
+ if (!response.ok) {
536
+ throw errorForResponse(response, await response.json().catch(() => {
537
+ return;
538
+ }), { method: "POST", url });
539
+ }
540
+ return response.text();
541
+ }
373
542
  };
374
543
  imports = {
375
544
  create: async (body) => {
376
545
  const result = await this.raw.POST("/v1/api/imports", { body });
377
546
  return unwrap(result, this.at("POST", "/v1/api/imports"));
378
547
  },
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) => {
548
+ get: async (id, options = {}) => unwrap(await this.raw.GET("/v1/api/imports/{id}", {
549
+ signal: options.signal,
550
+ params: { path: { id } }
551
+ }), this.at("GET", `/v1/api/imports/${id}`)),
552
+ finalize: async (id, body, options = {}) => {
386
553
  const result = await this.raw.POST("/v1/api/imports/{id}/finalize", {
554
+ signal: options.signal,
387
555
  params: { path: { id } },
388
556
  body
389
557
  });
390
- return unwrap(
391
- result,
392
- this.at("POST", `/v1/api/imports/${id}/finalize`)
393
- );
558
+ return unwrap(result, this.at("POST", `/v1/api/imports/${id}/finalize`));
559
+ },
560
+ finalizeAndWait: async (id, body, options = {}) => {
561
+ const deadline = AbortSignal.timeout(options.timeoutMs ?? 600000);
562
+ const signal = options.signal ? AbortSignal.any([options.signal, deadline]) : deadline;
563
+ try {
564
+ return await this.imports.finalize(id, body, { signal });
565
+ } catch (error) {
566
+ signal.throwIfAborted();
567
+ if (!(error instanceof HlixServerError || error instanceof HlixTransportError || error instanceof HlixConflictError))
568
+ throw error;
569
+ }
570
+ return this.imports.waitUntilReady(id, { ...options, signal });
571
+ },
572
+ waitUntilReady: async (id, options = {}) => {
573
+ const deadline = AbortSignal.timeout(options.timeoutMs ?? 600000);
574
+ const signal = options.signal ? AbortSignal.any([options.signal, deadline]) : deadline;
575
+ while (true) {
576
+ signal.throwIfAborted();
577
+ try {
578
+ const result = await this.imports.get(id, { signal });
579
+ if (result.import.status === "ready")
580
+ return result;
581
+ if (result.import.status !== "finalizing") {
582
+ throw new Error(`Import ${id} is ${result.import.status}: ${result.import.error ?? "rerun hlix import to resume this session"}`);
583
+ }
584
+ } catch (error) {
585
+ signal.throwIfAborted();
586
+ if (!(error instanceof HlixServerError || error instanceof HlixTransportError))
587
+ throw error;
588
+ }
589
+ await pause(options.pollIntervalMs ?? 2000, signal);
590
+ }
394
591
  },
395
592
  uploadBundle: async (upload, bundle, options = {}) => {
396
593
  const chunkSize = options.chunkSize ?? 8 * 1024 * 1024;
@@ -410,7 +607,8 @@ var Hlix = class {
410
607
  signal: options.signal
411
608
  });
412
609
  const response = await this.fetcher(request);
413
- if (!response.ok) await this.throwUploadError(response, url);
610
+ if (!response.ok)
611
+ await this.throwUploadError(response, url);
414
612
  options.onProgress?.(bundle.size, bundle.size);
415
613
  return;
416
614
  }
@@ -441,18 +639,13 @@ var Hlix = class {
441
639
  }
442
640
  };
443
641
  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
- ),
642
+ head: async (projectId, options = {}) => unwrap(await this.raw.GET("/v1/api/projects/{id}/revisions/head", {
643
+ ...options,
644
+ params: { path: { id: projectId } }
645
+ }), this.at("GET", `/v1/api/projects/${projectId}/revisions/head`)),
646
+ protectedFiles: async (projectId) => unwrap(await this.raw.GET("/v1/api/projects/{id}/protected-files", {
647
+ params: { path: { id: projectId } }
648
+ }), this.at("GET", `/v1/api/projects/${projectId}/protected-files`)),
456
649
  createUpload: async (projectId, body) => {
457
650
  const result = await this.raw.POST("/v1/api/projects/{id}/revisions/uploads", {
458
651
  params: { path: { id: projectId } },
@@ -461,16 +654,11 @@ var Hlix = class {
461
654
  return unwrap(result, this.at("POST", `/v1/api/projects/${projectId}/revisions/uploads`));
462
655
  },
463
656
  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
- );
657
+ const result = await this.raw.POST("/v1/api/projects/{id}/revisions/uploads/{uploadId}/finalize", { params: { path: { id: projectId, uploadId } }, body });
658
+ return unwrap(result, this.at("POST", `/v1/api/projects/${projectId}/revisions/uploads/${uploadId}/finalize`));
472
659
  },
473
660
  uploadBundle: async (upload, bundle, options = {}) => this.imports.uploadBundle(upload, bundle, options),
661
+ watch: (projectId, init) => this.watchRevisions(projectId, init),
474
662
  downloadBundle: async (projectId, revisionId) => {
475
663
  const path = `/v1/api/projects/${projectId}/revisions/${revisionId}/bundle`;
476
664
  const request = new Request(`${this.baseUrl}${path}`, {
@@ -478,32 +666,24 @@ var Hlix = class {
478
666
  headers: this.headers
479
667
  });
480
668
  const response = await this.fetcher(request);
481
- if (!response.ok) await this.throwUploadError(response, `${this.baseUrl}${path}`);
669
+ if (!response.ok)
670
+ await this.throwUploadError(response, `${this.baseUrl}${path}`);
482
671
  return response.blob();
483
672
  }
484
673
  };
485
674
  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
- )
675
+ importSkill: async (projectId, body) => unwrap(await this.raw.POST("/v1/api/projects/{id}/resources/skill", {
676
+ params: { path: { id: projectId } },
677
+ body
678
+ }), this.at("POST", `/v1/api/projects/${projectId}/resources/skill`)),
679
+ importAgent: async (projectId, body) => unwrap(await this.raw.POST("/v1/api/projects/{id}/resources/agent", {
680
+ params: { path: { id: projectId } },
681
+ body
682
+ }), this.at("POST", `/v1/api/projects/${projectId}/resources/agent`)),
683
+ importMcp: async (projectId, body) => unwrap(await this.raw.POST("/v1/api/projects/{id}/resources/mcp", {
684
+ params: { path: { id: projectId } },
685
+ body
686
+ }), this.at("POST", `/v1/api/projects/${projectId}/resources/mcp`))
507
687
  };
508
688
  async throwUploadError(response, url) {
509
689
  let body = null;
@@ -515,140 +695,127 @@ var Hlix = class {
515
695
  throw errorForResponse(response, body, { method: "PUT", url });
516
696
  }
517
697
  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. */
698
+ list: async (query) => unwrap(await this.raw.GET("/v1/api/tasks", { params: { query: query ?? {} } }), this.at("GET", "/v1/api/tasks")),
699
+ get: async (id) => unwrap(await this.raw.GET("/v1/api/tasks/{id}", { params: { path: { id } } }), this.at("GET", `/v1/api/tasks/${id}`)),
700
+ create: async (body) => unwrap(await this.raw.POST("/v1/api/tasks", { body }), this.at("POST", "/v1/api/tasks")),
701
+ update: async (id, body) => unwrap(await this.raw.PATCH("/v1/api/tasks/{id}", {
702
+ params: { path: { id } },
703
+ body
704
+ }), this.at("PATCH", `/v1/api/tasks/${id}`)),
705
+ listComments: async (id) => unwrap(await this.raw.GET("/v1/api/tasks/{id}/comments", {
706
+ params: { path: { id } }
707
+ }), this.at("GET", `/v1/api/tasks/${id}/comments`)),
708
+ addComment: async (id, body) => unwrap(await this.raw.POST("/v1/api/tasks/{id}/comments", {
709
+ params: { path: { id } },
710
+ body
711
+ }), this.at("POST", `/v1/api/tasks/${id}/comments`)),
712
+ review: async (id) => unwrap(await this.raw.GET("/v1/api/tasks/{id}/review", {
713
+ params: { path: { id } }
714
+ }), this.at("GET", `/v1/api/tasks/${id}/review`)),
558
715
  stream: (id, init) => this.stream(`/v1/api/tasks/${id}/stream`, init)
559
716
  };
717
+ modelConfig = {
718
+ setProjectHarness: async (projectId, harness) => unwrap(await this.raw.PUT("/v1/api/model-config/harness/{projectId}", {
719
+ params: { path: { projectId } },
720
+ body: { harness }
721
+ }), this.at("PUT", `/v1/api/model-config/harness/${projectId}`)),
722
+ clearProjectHarness: async (projectId) => unwrap(await this.raw.DELETE("/v1/api/model-config/harness/{projectId}", {
723
+ params: { path: { projectId } }
724
+ }), this.at("DELETE", `/v1/api/model-config/harness/${projectId}`)),
725
+ setHarness: async (harness) => unwrap(await this.raw.PUT("/v1/api/model-config/harness", { body: { harness } }), this.at("PUT", "/v1/api/model-config/harness")),
726
+ clearHarness: async () => unwrap(await this.raw.DELETE("/v1/api/model-config/harness"), this.at("DELETE", "/v1/api/model-config/harness")),
727
+ resolvedHarness: async () => unwrap(await this.raw.GET("/v1/api/model-config/harness/resolved"), this.at("GET", "/v1/api/model-config/harness/resolved")),
728
+ resolvedProjectHarness: async (projectId) => unwrap(await this.raw.GET("/v1/api/model-config/harness/{projectId}/resolved", {
729
+ params: { path: { projectId } }
730
+ }), this.at("GET", `/v1/api/model-config/harness/${projectId}/resolved`))
731
+ };
732
+ ai = {
733
+ models: async (workspaceId, init) => unwrap(await this.raw.GET("/v1/api/ai/w/{workspaceId}/models", {
734
+ params: { path: { workspaceId } },
735
+ ...init?.signal ? { signal: init.signal } : {}
736
+ }), this.at("GET", `/v1/api/ai/w/${workspaceId}/models`))
737
+ };
560
738
  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
- ),
739
+ list: async (query) => unwrap(await this.raw.GET("/v1/api/cycles", {
740
+ params: { query: query ?? {} }
741
+ }), this.at("GET", "/v1/api/cycles")),
742
+ get: async (id) => unwrap(await this.raw.GET("/v1/api/cycles/{id}", {
743
+ params: { path: { id } }
744
+ }), this.at("GET", `/v1/api/cycles/${id}`)),
745
+ propose: async (body) => unwrap(await this.raw.POST("/v1/api/cycles", { body }), this.at("POST", "/v1/api/cycles")),
578
746
  stream: (id, init) => this.stream(`/v1/api/cycles/${id}/stream`, init)
579
747
  };
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
- */
748
+ chat = {
749
+ send: async (body) => unwrap(await this.raw.POST("/v1/api/chat/turns", { body }), this.at("POST", "/v1/api/chat/turns")),
750
+ turn: async (turnId, scope) => unwrap(await this.raw.GET("/v1/api/chat/turns/{turnId}", {
751
+ params: { path: { turnId }, query: scope }
752
+ }), this.at("GET", `/v1/api/chat/turns/${turnId}`))
753
+ };
754
+ pendingRequests = {
755
+ list: async (query) => unwrap(await this.raw.GET("/v1/api/pending-requests", { params: { query: query ?? {} } }), this.at("GET", "/v1/api/pending-requests")),
756
+ resolve: async (id, body) => unwrap(await this.raw.POST("/v1/api/pending-requests/{id}/resolve", {
757
+ params: { path: { id } },
758
+ body
759
+ }), this.at("POST", `/v1/api/pending-requests/${id}/resolve`))
760
+ };
590
761
  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
- )
762
+ list: async (query) => unwrap(await this.raw.GET("/v1/api/client-billing/invoices", {
763
+ params: { query: query ?? {} }
764
+ }), this.at("GET", "/v1/api/client-billing/invoices")),
765
+ get: async (id) => unwrap(await this.raw.GET("/v1/api/client-billing/invoices/{id}", {
766
+ params: { path: { id } }
767
+ }), this.at("GET", `/v1/api/client-billing/invoices/${id}`)),
768
+ createDraft: async (body) => unwrap(await this.raw.POST("/v1/api/client-billing/invoices", { body }), this.at("POST", "/v1/api/client-billing/invoices")),
769
+ issue: async (id) => unwrap(await this.raw.POST("/v1/api/client-billing/invoices/{id}/issue", {
770
+ params: { path: { id } },
771
+ body: {}
772
+ }), this.at("POST", `/v1/api/client-billing/invoices/${id}/issue`)),
773
+ void: async (id, reason) => unwrap(await this.raw.POST("/v1/api/client-billing/invoices/{id}/void", {
774
+ params: { path: { id } },
775
+ body: { reason }
776
+ }), this.at("POST", `/v1/api/client-billing/invoices/${id}/void`)),
777
+ pay: async (id) => unwrap(await this.raw.POST("/v1/api/client-billing/invoices/{id}/pay", {
778
+ params: { path: { id } },
779
+ body: {}
780
+ }), this.at("POST", `/v1/api/client-billing/invoices/${id}/pay`)),
781
+ paymentLink: async (id, body = {}) => unwrap(await this.raw.POST("/v1/api/client-billing/invoices/{id}/payment-link", { params: { path: { id } }, body }), this.at("POST", `/v1/api/client-billing/invoices/${id}/payment-link`))
642
782
  };
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) {
783
+ async* observeAgent(projectId, agentId, init) {
784
+ const path = `/v1/api/projects/${projectId}/agents/${agentId}/observe`;
785
+ for await (const event of this.stream(path, init)) {
786
+ const frame = parseObserveFrame(event.data);
787
+ if (!frame)
788
+ continue;
789
+ yield frame;
790
+ if (isTerminalObserveFrame(frame))
791
+ return;
792
+ }
793
+ }
794
+ async* watchRevisions(projectId, init) {
795
+ const path = `/v1/api/projects/${projectId}/revisions/watch`;
796
+ const signal = init?.signal;
797
+ let delay = WATCH_RECONNECT.firstDelayMs;
798
+ while (!signal?.aborted) {
799
+ try {
800
+ for await (const event of this.stream(path, init)) {
801
+ const frame = parseRevisionWatchFrame(event.data);
802
+ if (!frame)
803
+ continue;
804
+ if (frame.kind === "ready")
805
+ delay = WATCH_RECONNECT.firstDelayMs;
806
+ yield frame;
807
+ }
808
+ } catch (error) {
809
+ if (signal?.aborted)
810
+ return;
811
+ if (isPermanentWatchFailure(error))
812
+ throw error;
813
+ }
814
+ await pause(delay, signal);
815
+ delay = Math.min(delay * 2, WATCH_RECONNECT.maxDelayMs);
816
+ }
817
+ }
818
+ async* stream(path, init) {
652
819
  const url = `${this.baseUrl}${path}`;
653
820
  const request = new Request(url, {
654
821
  headers: { ...this.headers, accept: "text/event-stream" },
@@ -666,7 +833,7 @@ var Hlix = class {
666
833
  }
667
834
  yield* parseSseStream(response.body, init?.signal);
668
835
  }
669
- };
836
+ }
670
837
  function createHlix(options) {
671
838
  return new Hlix(options);
672
839
  }
@@ -686,8 +853,13 @@ export {
686
853
  HlixValidationError,
687
854
  IDEMPOTENT_METHODS,
688
855
  apiKeyCredential,
856
+ bearerCredential,
689
857
  createHlix,
690
858
  isIdempotent,
859
+ isTerminalObserveFrame,
860
+ parseObserveFrame,
861
+ parseRevisionWatchFrame,
691
862
  parseSseStream,
863
+ pause,
692
864
  sessionCredential
693
865
  };