@slopus/happy-agent-client 0.0.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.
Files changed (90) hide show
  1. package/README.md +17 -0
  2. package/dist/HappyAgentApiError.d.ts +41 -0
  3. package/dist/HappyAgentApiError.d.ts.map +1 -0
  4. package/dist/HappyAgentApiError.js +57 -0
  5. package/dist/HappyAgentApiError.js.map +1 -0
  6. package/dist/HappyAgentClient.d.ts +256 -0
  7. package/dist/HappyAgentClient.d.ts.map +1 -0
  8. package/dist/HappyAgentClient.js +739 -0
  9. package/dist/HappyAgentClient.js.map +1 -0
  10. package/dist/endpointUrl.d.ts +14 -0
  11. package/dist/endpointUrl.d.ts.map +1 -0
  12. package/dist/endpointUrl.js +27 -0
  13. package/dist/endpointUrl.js.map +1 -0
  14. package/dist/index.d.ts +26 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +22 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/protocol/agents.d.ts +116 -0
  19. package/dist/protocol/agents.d.ts.map +1 -0
  20. package/dist/protocol/agents.js +3 -0
  21. package/dist/protocol/agents.js.map +1 -0
  22. package/dist/protocol/bootstrap.d.ts +28 -0
  23. package/dist/protocol/bootstrap.d.ts.map +1 -0
  24. package/dist/protocol/bootstrap.js +3 -0
  25. package/dist/protocol/bootstrap.js.map +1 -0
  26. package/dist/protocol/common.d.ts +104 -0
  27. package/dist/protocol/common.d.ts.map +1 -0
  28. package/dist/protocol/common.js +92 -0
  29. package/dist/protocol/common.js.map +1 -0
  30. package/dist/protocol/daemon.d.ts +486 -0
  31. package/dist/protocol/daemon.d.ts.map +1 -0
  32. package/dist/protocol/daemon.js +210 -0
  33. package/dist/protocol/daemon.js.map +1 -0
  34. package/dist/protocol/events.d.ts +208 -0
  35. package/dist/protocol/events.d.ts.map +1 -0
  36. package/dist/protocol/events.js +9 -0
  37. package/dist/protocol/events.js.map +1 -0
  38. package/dist/protocol/files.d.ts +74 -0
  39. package/dist/protocol/files.d.ts.map +1 -0
  40. package/dist/protocol/files.js +8 -0
  41. package/dist/protocol/files.js.map +1 -0
  42. package/dist/protocol/git.d.ts +55 -0
  43. package/dist/protocol/git.d.ts.map +1 -0
  44. package/dist/protocol/git.js +3 -0
  45. package/dist/protocol/git.js.map +1 -0
  46. package/dist/protocol/messages.d.ts +210 -0
  47. package/dist/protocol/messages.d.ts.map +1 -0
  48. package/dist/protocol/messages.js +8 -0
  49. package/dist/protocol/messages.js.map +1 -0
  50. package/dist/protocol/processes.d.ts +26 -0
  51. package/dist/protocol/processes.d.ts.map +1 -0
  52. package/dist/protocol/processes.js +3 -0
  53. package/dist/protocol/processes.js.map +1 -0
  54. package/dist/protocol/profile.d.ts +40 -0
  55. package/dist/protocol/profile.d.ts.map +1 -0
  56. package/dist/protocol/profile.js +23 -0
  57. package/dist/protocol/profile.js.map +1 -0
  58. package/dist/protocol/projects.d.ts +377 -0
  59. package/dist/protocol/projects.d.ts.map +1 -0
  60. package/dist/protocol/projects.js +107 -0
  61. package/dist/protocol/projects.js.map +1 -0
  62. package/dist/protocol/questions.d.ts +54 -0
  63. package/dist/protocol/questions.d.ts.map +1 -0
  64. package/dist/protocol/questions.js +3 -0
  65. package/dist/protocol/questions.js.map +1 -0
  66. package/dist/protocol/terminals.d.ts +50 -0
  67. package/dist/protocol/terminals.d.ts.map +1 -0
  68. package/dist/protocol/terminals.js +3 -0
  69. package/dist/protocol/terminals.js.map +1 -0
  70. package/dist/protocol/usage.d.ts +27 -0
  71. package/dist/protocol/usage.d.ts.map +1 -0
  72. package/dist/protocol/usage.js +9 -0
  73. package/dist/protocol/usage.js.map +1 -0
  74. package/dist/protocol/workspaces.d.ts +216 -0
  75. package/dist/protocol/workspaces.d.ts.map +1 -0
  76. package/dist/protocol/workspaces.js +87 -0
  77. package/dist/protocol/workspaces.js.map +1 -0
  78. package/dist/readEventStream.d.ts +19 -0
  79. package/dist/readEventStream.d.ts.map +1 -0
  80. package/dist/readEventStream.js +46 -0
  81. package/dist/readEventStream.js.map +1 -0
  82. package/dist/readSseFrames.d.ts +20 -0
  83. package/dist/readSseFrames.d.ts.map +1 -0
  84. package/dist/readSseFrames.js +77 -0
  85. package/dist/readSseFrames.js.map +1 -0
  86. package/dist/requestOptions.d.ts +44 -0
  87. package/dist/requestOptions.d.ts.map +1 -0
  88. package/dist/requestOptions.js +9 -0
  89. package/dist/requestOptions.js.map +1 -0
  90. package/package.json +42 -0
@@ -0,0 +1,739 @@
1
+ import { endpointUrl } from "./endpointUrl.js";
2
+ import { readApiError } from "./HappyAgentApiError.js";
3
+ import { EventStreamProtocolError, readEventStream } from "./readEventStream.js";
4
+ /**
5
+ * A thin, faithful client for the Happy agent HTTP API.
6
+ *
7
+ * Every request-response route of `packages/happy-agent/API.md` is one method,
8
+ * and the event journal is available both as pages and as the live stream. The
9
+ * client keeps no state beyond the endpoint and the token: it does not cache
10
+ * resources, reconcile versions, retry, or reconnect. Those are decisions for
11
+ * whatever builds a live view on top of it.
12
+ *
13
+ * It is built on `fetch`, streams, `AbortController`, and standard timers
14
+ * alone, so one build runs unchanged in Node and in a browser. Nothing is
15
+ * validated at runtime; the types are a promise about what the specification
16
+ * says the daemon sends.
17
+ */
18
+ export class HappyAgentClient {
19
+ #endpoint;
20
+ #token;
21
+ #fetch;
22
+ constructor(options) {
23
+ this.#endpoint = options.endpoint.toString();
24
+ this.#token = options.token;
25
+ this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
26
+ }
27
+ /** Where this client talks, as it was given. */
28
+ get endpoint() {
29
+ return this.#endpoint;
30
+ }
31
+ // The daemon
32
+ /** `GET /` — a greeting confirming the caller reached a Happy agent. */
33
+ async getGreeting(options = {}) {
34
+ return await this.#json({ method: "GET", path: "", signal: options.signal });
35
+ }
36
+ /**
37
+ * `GET /v0/health` — whether the daemon is alive and ready to serve.
38
+ *
39
+ * The only route guaranteed to answer during startup; a client polls it
40
+ * until `ready` is `true` before using the rest of the API.
41
+ */
42
+ async getHealth(options = {}) {
43
+ return await this.#json({ method: "GET", path: "v0/health", signal: options.signal });
44
+ }
45
+ /** `GET /v0/config` — the sanitized effective configuration. */
46
+ async getConfig(options = {}) {
47
+ return await this.#json({ method: "GET", path: "v0/config", signal: options.signal });
48
+ }
49
+ /**
50
+ * `PATCH /v0/config` — requests a runtime settings change.
51
+ *
52
+ * A daemon that cannot reconfigure itself at runtime answers `409`.
53
+ */
54
+ async patchConfig(patch, options = {}) {
55
+ return await this.#json({
56
+ method: "PATCH",
57
+ path: "v0/config",
58
+ json: patch,
59
+ signal: options.signal,
60
+ });
61
+ }
62
+ /** `GET /v0/config/instructions` — the global instructions document. */
63
+ async getInstructions(options = {}) {
64
+ return await this.#json({
65
+ method: "GET",
66
+ path: "v0/config/instructions",
67
+ signal: options.signal,
68
+ });
69
+ }
70
+ /** `PUT /v0/config/instructions` — replaces it; the maximum size is 256 KB. */
71
+ async putInstructions(instructions, options = {}) {
72
+ return await this.#json({
73
+ method: "PUT",
74
+ path: "v0/config/instructions",
75
+ json: { instructions },
76
+ signal: options.signal,
77
+ });
78
+ }
79
+ /** `GET /v0/config/security` — the security policy document. */
80
+ async getSecurityPolicy(options = {}) {
81
+ return await this.#json({
82
+ method: "GET",
83
+ path: "v0/config/security",
84
+ signal: options.signal,
85
+ });
86
+ }
87
+ /** `PUT /v0/config/security` — replaces it; the maximum size is 32 KB. */
88
+ async putSecurityPolicy(policy, options = {}) {
89
+ return await this.#json({
90
+ method: "PUT",
91
+ path: "v0/config/security",
92
+ json: { policy },
93
+ signal: options.signal,
94
+ });
95
+ }
96
+ /** `POST /v0/shutdown` — asks the daemon to shut down; it answers first. */
97
+ async shutdown(options = {}) {
98
+ return await this.#json({ method: "POST", path: "v0/shutdown", signal: options.signal });
99
+ }
100
+ /** `POST /v0/debug/inspector` — starts the Node.js inspector on the daemon. */
101
+ async startInspector(options = {}) {
102
+ return await this.#json({
103
+ method: "POST",
104
+ path: "v0/debug/inspector",
105
+ signal: options.signal,
106
+ });
107
+ }
108
+ /** `DELETE /v0/debug/inspector` — stops it. */
109
+ async stopInspector(options = {}) {
110
+ return await this.#json({
111
+ method: "DELETE",
112
+ path: "v0/debug/inspector",
113
+ signal: options.signal,
114
+ });
115
+ }
116
+ // Profile
117
+ /** `GET /v0/profile` — always succeeds; an untouched profile is all `null`. */
118
+ async getProfile(options = {}) {
119
+ return await this.#json({ method: "GET", path: "v0/profile", signal: options.signal });
120
+ }
121
+ /** `PATCH /v0/profile` — a partial update; a field set to `null` is cleared. */
122
+ async updateProfile(request, options) {
123
+ return await this.#json({
124
+ method: "PATCH",
125
+ path: "v0/profile",
126
+ json: request,
127
+ ifMatch: options.ifMatch,
128
+ signal: options.signal,
129
+ });
130
+ }
131
+ /** `GET /v0/profile/photo` — the photo bytes; `null` when unchanged. */
132
+ async getProfilePhoto(options = {}) {
133
+ return await this.#binary({
134
+ method: "GET",
135
+ path: "v0/profile/photo",
136
+ ifNoneMatch: options.ifNoneMatch,
137
+ signal: options.signal,
138
+ });
139
+ }
140
+ /** `PUT /v0/profile/photo` — the daemon computes the ThumbHash. */
141
+ async setProfilePhoto(image, options = {}) {
142
+ return await this.#json({
143
+ method: "PUT",
144
+ path: "v0/profile/photo",
145
+ binary: image,
146
+ ifMatch: options.ifMatch,
147
+ signal: options.signal,
148
+ });
149
+ }
150
+ /** `DELETE /v0/profile/photo` */
151
+ async deleteProfilePhoto(options = {}) {
152
+ return await this.#json({
153
+ method: "DELETE",
154
+ path: "v0/profile/photo",
155
+ ifMatch: options.ifMatch,
156
+ signal: options.signal,
157
+ });
158
+ }
159
+ // Onboarding
160
+ /** `GET /v0/onboarding` — what a fresh installation still needs. */
161
+ async getOnboarding(options = {}) {
162
+ return await this.#json({ method: "GET", path: "v0/onboarding", signal: options.signal });
163
+ }
164
+ /** `POST /v0/onboarding/complete` — idempotent. */
165
+ async completeOnboarding(options = {}) {
166
+ return await this.#json({
167
+ method: "POST",
168
+ path: "v0/onboarding/complete",
169
+ signal: options.signal,
170
+ });
171
+ }
172
+ // Projects
173
+ /** `GET /v0/projects` — every project, archived ones included. */
174
+ async listProjects(options = {}) {
175
+ return await this.#json({ method: "GET", path: "v0/projects", signal: options.signal });
176
+ }
177
+ /** `POST /v0/projects` — registers an existing local folder. */
178
+ async registerProject(request, options = {}) {
179
+ return await this.#json({
180
+ method: "POST",
181
+ path: "v0/projects",
182
+ json: request,
183
+ signal: options.signal,
184
+ });
185
+ }
186
+ /** `POST /v0/projects/clone` — the clone itself runs in the background. */
187
+ async cloneProject(request, options = {}) {
188
+ return await this.#json({
189
+ method: "POST",
190
+ path: "v0/projects/clone",
191
+ json: request,
192
+ signal: options.signal,
193
+ });
194
+ }
195
+ /** `GET /v0/projects/:projectId` */
196
+ async getProject(projectId, options = {}) {
197
+ return await this.#json({
198
+ method: "GET",
199
+ path: `v0/projects/${encodeURIComponent(projectId)}`,
200
+ signal: options.signal,
201
+ });
202
+ }
203
+ /** `PATCH /v0/projects/:projectId` — renames it; `nameSource` becomes `"user"`. */
204
+ async renameProject(projectId, request, options) {
205
+ return await this.#json({
206
+ method: "PATCH",
207
+ path: `v0/projects/${encodeURIComponent(projectId)}`,
208
+ json: request,
209
+ ifMatch: options.ifMatch,
210
+ signal: options.signal,
211
+ });
212
+ }
213
+ /** `PUT /v0/projects/:projectId/settings` — replaces the settings. */
214
+ async replaceProjectSettings(projectId, request, options) {
215
+ return await this.#json({
216
+ method: "PUT",
217
+ path: `v0/projects/${encodeURIComponent(projectId)}/settings`,
218
+ json: request,
219
+ ifMatch: options.ifMatch,
220
+ signal: options.signal,
221
+ });
222
+ }
223
+ /** `POST /v0/projects/:projectId/refresh` — re-runs project setup. */
224
+ async refreshProject(projectId, options = {}) {
225
+ return await this.#json({
226
+ method: "POST",
227
+ path: `v0/projects/${encodeURIComponent(projectId)}/refresh`,
228
+ signal: options.signal,
229
+ });
230
+ }
231
+ /** `POST /v0/projects/:projectId/reorder` — neighbours move through events. */
232
+ async reorderProject(projectId, request, options) {
233
+ return await this.#json({
234
+ method: "POST",
235
+ path: `v0/projects/${encodeURIComponent(projectId)}/reorder`,
236
+ json: request,
237
+ ifMatch: options.ifMatch,
238
+ signal: options.signal,
239
+ });
240
+ }
241
+ /** `POST /v0/projects/:projectId/archive` — idempotent; cleanup is background work. */
242
+ async archiveProject(projectId, options) {
243
+ return await this.#json({
244
+ method: "POST",
245
+ path: `v0/projects/${encodeURIComponent(projectId)}/archive`,
246
+ json: bodyOf(options),
247
+ ifMatch: options.ifMatch,
248
+ signal: options.signal,
249
+ });
250
+ }
251
+ /** `GET /v0/projects/:projectId/avatar` — the picture bytes; `null` when unchanged. */
252
+ async getProjectAvatar(projectId, options = {}) {
253
+ return await this.#binary({
254
+ method: "GET",
255
+ path: `v0/projects/${encodeURIComponent(projectId)}/avatar`,
256
+ ifNoneMatch: options.ifNoneMatch,
257
+ signal: options.signal,
258
+ });
259
+ }
260
+ /** `PUT /v0/projects/:projectId/avatar` — the limit is 8 MB. */
261
+ async setProjectAvatar(projectId, image, options) {
262
+ return await this.#json({
263
+ method: "PUT",
264
+ path: `v0/projects/${encodeURIComponent(projectId)}/avatar`,
265
+ binary: image,
266
+ ifMatch: options.ifMatch,
267
+ signal: options.signal,
268
+ });
269
+ }
270
+ /** `DELETE /v0/projects/:projectId/avatar` */
271
+ async deleteProjectAvatar(projectId, options) {
272
+ return await this.#json({
273
+ method: "DELETE",
274
+ path: `v0/projects/${encodeURIComponent(projectId)}/avatar`,
275
+ ifMatch: options.ifMatch,
276
+ signal: options.signal,
277
+ });
278
+ }
279
+ // Workspaces
280
+ /** `GET /v0/workspaces` — a flat array; clients build the tree from `parentId`. */
281
+ async listWorkspaces(query = {}, options = {}) {
282
+ return await this.#json({
283
+ method: "GET",
284
+ path: "v0/workspaces",
285
+ query: { projectId: query.projectId, includeArchived: query.includeArchived },
286
+ signal: options.signal,
287
+ });
288
+ }
289
+ /** `POST /v0/workspaces` — the checkout builds in the background. */
290
+ async createWorkspace(request, options = {}) {
291
+ return await this.#json({
292
+ method: "POST",
293
+ path: "v0/workspaces",
294
+ json: request,
295
+ signal: options.signal,
296
+ });
297
+ }
298
+ /** `GET /v0/workspaces/:workspaceId` — a project ID answers with its root workspace. */
299
+ async getWorkspace(workspaceId, options = {}) {
300
+ return await this.#json({
301
+ method: "GET",
302
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}`,
303
+ signal: options.signal,
304
+ });
305
+ }
306
+ /** `PATCH /v0/workspaces/:workspaceId` — the Git branch moves in the background. */
307
+ async renameWorkspace(workspaceId, request, options) {
308
+ return await this.#json({
309
+ method: "PATCH",
310
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}`,
311
+ json: request,
312
+ ifMatch: options.ifMatch,
313
+ signal: options.signal,
314
+ });
315
+ }
316
+ /** `POST /v0/workspaces/:workspaceId/archive` — archives it and everything beneath. */
317
+ async archiveWorkspace(workspaceId, options) {
318
+ return await this.#json({
319
+ method: "POST",
320
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}/archive`,
321
+ json: bodyOf(options),
322
+ ifMatch: options.ifMatch,
323
+ signal: options.signal,
324
+ });
325
+ }
326
+ /** `POST /v0/workspaces/:workspaceId/reorder` — moves it among its siblings. */
327
+ async reorderWorkspace(workspaceId, request, options) {
328
+ return await this.#json({
329
+ method: "POST",
330
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}/reorder`,
331
+ json: request,
332
+ ifMatch: options.ifMatch,
333
+ signal: options.signal,
334
+ });
335
+ }
336
+ // Terminals
337
+ /** `GET /v0/workspaces/:workspaceId/terminals` */
338
+ async listTerminals(workspaceId, options = {}) {
339
+ return await this.#json({
340
+ method: "GET",
341
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}/terminals`,
342
+ signal: options.signal,
343
+ });
344
+ }
345
+ /** `POST /v0/workspaces/:workspaceId/terminals` — a workspace holds at most 32. */
346
+ async openTerminal(workspaceId, request = {}, options = {}) {
347
+ return await this.#json({
348
+ method: "POST",
349
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}/terminals`,
350
+ json: request,
351
+ signal: options.signal,
352
+ });
353
+ }
354
+ /** `PATCH /v0/workspaces/:workspaceId/terminals/:terminalId` */
355
+ async resizeTerminal(workspaceId, terminalId, request, options = {}) {
356
+ return await this.#json({
357
+ method: "PATCH",
358
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}/terminals/${encodeURIComponent(terminalId)}`,
359
+ json: request,
360
+ signal: options.signal,
361
+ });
362
+ }
363
+ /** `DELETE /v0/workspaces/:workspaceId/terminals/:terminalId` — stops it and its process. */
364
+ async stopTerminal(workspaceId, terminalId, options = {}) {
365
+ return await this.#json({
366
+ method: "DELETE",
367
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}/terminals/${encodeURIComponent(terminalId)}`,
368
+ signal: options.signal,
369
+ });
370
+ }
371
+ /**
372
+ * The URL of `GET /v0/workspaces/:workspaceId/terminals/:terminalId/attach`.
373
+ *
374
+ * The attachment is a WebSocket upgrade carrying binary terminal frames,
375
+ * which `fetch` cannot open. This client hands back the address so a caller
376
+ * can attach with whatever WebSocket its runtime provides; the bearer token
377
+ * goes on that request the same way it does here.
378
+ */
379
+ terminalAttachUrl(workspaceId, terminalId) {
380
+ return endpointUrl(this.#endpoint, `v0/workspaces/${encodeURIComponent(workspaceId)}/terminals/${encodeURIComponent(terminalId)}/attach`);
381
+ }
382
+ /**
383
+ * The URL of `CONNECT /v0/workspaces/:workspaceId/proxy`.
384
+ *
385
+ * The tunnel is a raw byte stream that `fetch` cannot open either, so the
386
+ * address is what this client offers.
387
+ */
388
+ workspaceProxyUrl(workspaceId) {
389
+ return endpointUrl(this.#endpoint, `v0/workspaces/${encodeURIComponent(workspaceId)}/proxy`);
390
+ }
391
+ // Files
392
+ /** `GET /v0/workspaces/:workspaceId/files` — case-insensitive filename search. */
393
+ async searchFiles(workspaceId, query, options = {}) {
394
+ return await this.#json({
395
+ method: "GET",
396
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}/files`,
397
+ query: { query: query.query, limit: query.limit },
398
+ signal: options.signal,
399
+ });
400
+ }
401
+ /** `GET /v0/workspaces/:workspaceId/file-tree` — one directory level, paginated. */
402
+ async getFileTree(workspaceId, query = {}, options = {}) {
403
+ return await this.#json({
404
+ method: "GET",
405
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}/file-tree`,
406
+ query: { path: query.path, cursor: query.cursor, limit: query.limit },
407
+ signal: options.signal,
408
+ });
409
+ }
410
+ /** `GET /v0/workspaces/:workspaceId/file` — base64 content and its sha256. */
411
+ async readFile(workspaceId, path, options = {}) {
412
+ return await this.#json({
413
+ method: "GET",
414
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}/file`,
415
+ query: { path },
416
+ signal: options.signal,
417
+ });
418
+ }
419
+ /** `PUT /v0/workspaces/:workspaceId/file` — `expectedHash` is the compare-and-swap. */
420
+ async writeFile(workspaceId, request, options = {}) {
421
+ return await this.#json({
422
+ method: "PUT",
423
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}/file`,
424
+ json: request,
425
+ signal: options.signal,
426
+ });
427
+ }
428
+ /** `GET /v0/workspaces/:workspaceId/file-revision` — one file as of a revision. */
429
+ async readFileRevision(workspaceId, query, options = {}) {
430
+ return await this.#json({
431
+ method: "GET",
432
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}/file-revision`,
433
+ query: { path: query.path, revision: query.revision },
434
+ signal: options.signal,
435
+ });
436
+ }
437
+ // Git
438
+ /** `GET /v0/workspaces/:workspaceId/git` — scans if nothing fresh is cached. */
439
+ async getWorkspaceGit(workspaceId, options = {}) {
440
+ return await this.#json({
441
+ method: "GET",
442
+ path: `v0/workspaces/${encodeURIComponent(workspaceId)}/git`,
443
+ signal: options.signal,
444
+ });
445
+ }
446
+ /**
447
+ * `POST /v0/git/watch` — subscribes workspaces to the live git watcher.
448
+ *
449
+ * Registrations are held per subscription set: a client re-posts its
450
+ * current interest, and workspaces it stops naming age out of the watcher.
451
+ */
452
+ async watchGit(request, options = {}) {
453
+ return await this.#json({
454
+ method: "POST",
455
+ path: "v0/git/watch",
456
+ json: request,
457
+ signal: options.signal,
458
+ });
459
+ }
460
+ // Agents
461
+ /** `POST /v0/agents` — creation always makes a top-level agent. */
462
+ async createAgent(request, options = {}) {
463
+ return await this.#json({
464
+ method: "POST",
465
+ path: "v0/agents",
466
+ json: request,
467
+ signal: options.signal,
468
+ });
469
+ }
470
+ /** `GET /v0/agents/:agentId` */
471
+ async getAgent(agentId, options = {}) {
472
+ return await this.#json({
473
+ method: "GET",
474
+ path: `v0/agents/${encodeURIComponent(agentId)}`,
475
+ signal: options.signal,
476
+ });
477
+ }
478
+ /**
479
+ * `POST /v0/agents/:agentId/send` — queues a message, or steers the run.
480
+ *
481
+ * The message is pending until inference accepts it; the acceptance travels
482
+ * as a `message.updated` event, and the `runId` it brings is the handle for
483
+ * `abortAgent`.
484
+ */
485
+ async sendMessage(agentId, request, options = {}) {
486
+ return await this.#json({
487
+ method: "POST",
488
+ path: `v0/agents/${encodeURIComponent(agentId)}/send`,
489
+ json: request,
490
+ signal: options.signal,
491
+ });
492
+ }
493
+ /**
494
+ * `GET /v0/agents/:agentId/messages` — history, grouped by run.
495
+ *
496
+ * `limit` is a lower bound: a page always contains whole runs and may
497
+ * overflow well past it, so buffers are sized for the runs received.
498
+ */
499
+ async getMessages(agentId, query = {}, options = {}) {
500
+ return await this.#json({
501
+ method: "GET",
502
+ path: `v0/agents/${encodeURIComponent(agentId)}/messages`,
503
+ query: {
504
+ before: query.before,
505
+ after: query.after,
506
+ limit: query.limit,
507
+ omitToolData: query.omitToolData,
508
+ },
509
+ signal: options.signal,
510
+ });
511
+ }
512
+ /** `GET /v0/agents/:agentId/question` — the open question, or `null`. */
513
+ async getPendingQuestion(agentId, options = {}) {
514
+ return await this.#json({
515
+ method: "GET",
516
+ path: `v0/agents/${encodeURIComponent(agentId)}/question`,
517
+ signal: options.signal,
518
+ });
519
+ }
520
+ /**
521
+ * `POST /v0/agents/:agentId/question/:questionId/answer` — the run resumes.
522
+ *
523
+ * Answering is first-write-wins across clients: a second answer is `409`
524
+ * carrying the question as it now stands.
525
+ */
526
+ async answerQuestion(agentId, questionId, request, options = {}) {
527
+ return await this.#json({
528
+ method: "POST",
529
+ path: `v0/agents/${encodeURIComponent(agentId)}/question/${encodeURIComponent(questionId)}/answer`,
530
+ json: request,
531
+ signal: options.signal,
532
+ });
533
+ }
534
+ /** `POST /v0/agents/:agentId/abort` — stops the current run. */
535
+ async abortAgent(agentId, request = {}, options = {}) {
536
+ return await this.#json({
537
+ method: "POST",
538
+ path: `v0/agents/${encodeURIComponent(agentId)}/abort`,
539
+ json: request,
540
+ signal: options.signal,
541
+ });
542
+ }
543
+ /** `POST /v0/agents/:agentId/compact` — summarizes older context for the model. */
544
+ async compactAgent(agentId, request = {}, options = {}) {
545
+ return await this.#json({
546
+ method: "POST",
547
+ path: `v0/agents/${encodeURIComponent(agentId)}/compact`,
548
+ json: request,
549
+ signal: options.signal,
550
+ });
551
+ }
552
+ /** `POST /v0/agents/:agentId/read` — clears `unread`. */
553
+ async markAgentRead(agentId, request = {}, options = {}) {
554
+ return await this.#json({
555
+ method: "POST",
556
+ path: `v0/agents/${encodeURIComponent(agentId)}/read`,
557
+ json: request,
558
+ signal: options.signal,
559
+ });
560
+ }
561
+ /** `POST /v0/agents/:agentId/archive` — keeps the history; aborts a running turn. */
562
+ async archiveAgent(agentId, request = {}, options = {}) {
563
+ return await this.#json({
564
+ method: "POST",
565
+ path: `v0/agents/${encodeURIComponent(agentId)}/archive`,
566
+ json: request,
567
+ signal: options.signal,
568
+ });
569
+ }
570
+ /** `POST /v0/agents/:agentId/unarchive` — idempotent. */
571
+ async unarchiveAgent(agentId, request = {}, options = {}) {
572
+ return await this.#json({
573
+ method: "POST",
574
+ path: `v0/agents/${encodeURIComponent(agentId)}/unarchive`,
575
+ json: request,
576
+ signal: options.signal,
577
+ });
578
+ }
579
+ /** `POST /v0/agents/:agentId/reorder` — moves the agent in the list order. */
580
+ async reorderAgent(agentId, request, options = {}) {
581
+ return await this.#json({
582
+ method: "POST",
583
+ path: `v0/agents/${encodeURIComponent(agentId)}/reorder`,
584
+ json: request,
585
+ signal: options.signal,
586
+ });
587
+ }
588
+ /** `PUT /v0/agents/:agentId/draft` — saves or clears the composer draft. */
589
+ async saveAgentDraft(agentId, request, options = {}) {
590
+ return await this.#json({
591
+ method: "PUT",
592
+ path: `v0/agents/${encodeURIComponent(agentId)}/draft`,
593
+ json: request,
594
+ signal: options.signal,
595
+ });
596
+ }
597
+ /** `GET /v0/agents/:agentId/usage` — the agent's whole life, subagents included. */
598
+ async getAgentUsage(agentId, options = {}) {
599
+ return await this.#json({
600
+ method: "GET",
601
+ path: `v0/agents/${encodeURIComponent(agentId)}/usage`,
602
+ signal: options.signal,
603
+ });
604
+ }
605
+ /** `GET /v0/agents/:agentId/activity` — every subagent and process it set in motion. */
606
+ async getAgentActivity(agentId, options = {}) {
607
+ return await this.#json({
608
+ method: "GET",
609
+ path: `v0/agents/${encodeURIComponent(agentId)}/activity`,
610
+ signal: options.signal,
611
+ });
612
+ }
613
+ /** `DELETE /v0/agents/:agentId/processes/:processId` — stops a running process. */
614
+ async stopProcess(agentId, processId, options = {}) {
615
+ return await this.#json({
616
+ method: "DELETE",
617
+ path: `v0/agents/${encodeURIComponent(agentId)}/processes/${encodeURIComponent(processId)}`,
618
+ signal: options.signal,
619
+ });
620
+ }
621
+ /** `GET /v0/usage` — the whole daemon, in rolling windows ending now. */
622
+ async getUsage(options = {}) {
623
+ return await this.#json({ method: "GET", path: "v0/usage", signal: options.signal });
624
+ }
625
+ // Events
626
+ /**
627
+ * `GET /v0/events` — pulls events, oldest first.
628
+ *
629
+ * A cursor that fell out of the bounded journal answers `409` with code
630
+ * `cursor_unavailable`, carrying the cursor to resync from.
631
+ */
632
+ async getEvents(query = {}, options = {}) {
633
+ return await this.#json({
634
+ method: "GET",
635
+ path: "v0/events",
636
+ query: { after: query.after, until: query.until, limit: query.limit },
637
+ signal: options.signal,
638
+ });
639
+ }
640
+ /**
641
+ * `GET /v0/events/stream` — the same journal, live.
642
+ *
643
+ * The iteration yields the opening `hello` frame and then every event, and
644
+ * ends when the daemon closes the stream or the signal aborts. One
645
+ * connection is one iteration: reconnecting is the caller's decision, made
646
+ * with the last cursor it received and honored through `after` or
647
+ * `lastEventId`.
648
+ */
649
+ async *streamEvents(options = {}) {
650
+ const response = await this.#send({
651
+ method: "GET",
652
+ path: "v0/events/stream",
653
+ query: { after: options.after },
654
+ accept: "text/event-stream",
655
+ lastEventId: options.lastEventId,
656
+ signal: options.signal,
657
+ });
658
+ if (response.body === null) {
659
+ throw new EventStreamProtocolError("The event stream carried no body.");
660
+ }
661
+ yield* readEventStream(response.body);
662
+ }
663
+ // Bootstrap
664
+ /**
665
+ * `GET /v0/bootstrap/desktop` — one request that gets a client on screen.
666
+ *
667
+ * The `cursor` it carries is the newest event as of the snapshot, so a
668
+ * stream opened from it leaves no window for a change to fall through.
669
+ */
670
+ async getDesktopBootstrap(options = {}) {
671
+ return await this.#json({
672
+ method: "GET",
673
+ path: "v0/bootstrap/desktop",
674
+ signal: options.signal,
675
+ });
676
+ }
677
+ // Making the request
678
+ async #json(request) {
679
+ const response = await this.#send(request);
680
+ return (await response.json());
681
+ }
682
+ async #binary(request) {
683
+ const response = await this.#send({
684
+ ...request,
685
+ accept: "image/*",
686
+ allowNotModified: true,
687
+ });
688
+ if (response.status === 304)
689
+ return null;
690
+ return {
691
+ contentType: response.headers.get("content-type") ?? "application/octet-stream",
692
+ data: await response.arrayBuffer(),
693
+ etag: response.headers.get("etag"),
694
+ };
695
+ }
696
+ async #send(request) {
697
+ const headers = {
698
+ accept: request.accept ?? "application/json",
699
+ authorization: `Bearer ${this.#token}`,
700
+ };
701
+ if (request.ifMatch !== undefined)
702
+ headers["if-match"] = request.ifMatch;
703
+ if (request.ifNoneMatch !== undefined)
704
+ headers["if-none-match"] = request.ifNoneMatch;
705
+ if (request.lastEventId !== undefined)
706
+ headers["last-event-id"] = request.lastEventId;
707
+ let body = null;
708
+ if (request.binary !== undefined) {
709
+ headers["content-type"] = request.binary.contentType;
710
+ body = request.binary.data;
711
+ }
712
+ else if (request.json !== undefined) {
713
+ headers["content-type"] = "application/json; charset=utf-8";
714
+ body = JSON.stringify(request.json);
715
+ }
716
+ const response = await this.#fetch(endpointUrl(this.#endpoint, request.path, request.query), {
717
+ method: request.method,
718
+ headers,
719
+ body,
720
+ signal: request.signal ?? null,
721
+ });
722
+ if (response.ok)
723
+ return response;
724
+ if (response.status === 304 && request.allowNotModified === true)
725
+ return response;
726
+ throw await readApiError(response);
727
+ }
728
+ }
729
+ /**
730
+ * The JSON body of a mutation whose only field is the optional mutation echo.
731
+ *
732
+ * Archiving names nothing but the resource itself, so one options object
733
+ * carries the `If-Match` version, the signal, and the single body field; this
734
+ * keeps the transport parts out of what is sent.
735
+ */
736
+ function bodyOf(options) {
737
+ return options.mutationId === undefined ? {} : { mutationId: options.mutationId };
738
+ }
739
+ //# sourceMappingURL=HappyAgentClient.js.map