@hyperdrive.bot/fleet-client 0.3.218 → 0.3.221

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 (37) hide show
  1. package/README.md +39 -7
  2. package/dist/compat/normalize-provider-models.d.ts +2 -2
  3. package/dist/compat/normalize-provider-models.js +13 -4
  4. package/dist/connection/index.d.ts +50 -0
  5. package/dist/connection/index.js +119 -0
  6. package/dist/connection/legacy-workspaces.d.ts +19 -0
  7. package/dist/connection/legacy-workspaces.js +160 -0
  8. package/dist/connection/legacy.d.ts +31 -0
  9. package/dist/connection/legacy.js +245 -0
  10. package/dist/connection/owned.d.ts +36 -0
  11. package/dist/connection/owned.js +210 -0
  12. package/dist/creation/index.d.ts +40 -0
  13. package/dist/creation/index.js +197 -0
  14. package/dist/daemon-client-relay-e2ee-transport.d.ts +1 -0
  15. package/dist/daemon-client-relay-e2ee-transport.js +10 -6
  16. package/dist/daemon-client-transport-types.d.ts +1 -1
  17. package/dist/daemon-client-transport-utils.d.ts +5 -1
  18. package/dist/daemon-client-transport-utils.js +8 -6
  19. package/dist/daemon-client-transport.d.ts +2 -2
  20. package/dist/daemon-client-transport.js +2 -2
  21. package/dist/daemon-client-websocket-transport.d.ts +4 -0
  22. package/dist/daemon-client-websocket-transport.js +23 -3
  23. package/dist/daemon-client.d.ts +1372 -925
  24. package/dist/daemon-client.js +4073 -2691
  25. package/dist/index.d.ts +197 -66
  26. package/dist/index.js +435 -72
  27. package/dist/legacy-usage-icons.d.ts +2 -0
  28. package/dist/legacy-usage-icons.js +16 -0
  29. package/dist/provider-snapshots/index.d.ts +21 -0
  30. package/dist/provider-snapshots/index.js +56 -0
  31. package/dist/terminal-stream-router.d.ts +7 -10
  32. package/dist/terminal-stream-router.js +41 -87
  33. package/dist/terminals/index.d.ts +69 -0
  34. package/dist/terminals/index.js +103 -0
  35. package/dist/timeline-subscription/index.d.ts +25 -0
  36. package/dist/timeline-subscription/index.js +56 -0
  37. package/package.json +10 -6
package/dist/index.js CHANGED
@@ -1,4 +1,10 @@
1
1
  import { DaemonClient } from "./daemon-client.js";
2
+ import { createTerminalActions, } from "./terminals/index.js";
3
+ /**
4
+ * Coding turns routinely run for minutes, so the handle waits far longer than
5
+ * the transport's own conservative default.
6
+ */
7
+ const DEFAULT_WAIT_FOR_FINISH_MS = 10 * 60000;
2
8
  export { DaemonClient };
3
9
  export { DirectorySuggestionsSupersededError, UploadCancelledError, UploadCapExceededError, UploadFailedError, } from "./daemon-client.js";
4
10
  export function createPaseoClient(config) {
@@ -7,144 +13,407 @@ export function createPaseoClient(config) {
7
13
  clientId: config.clientId ?? createGeneratedClientId(),
8
14
  clientType: "cli",
9
15
  });
10
- const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient);
11
- const createAgentHandle = createAgentHandleFactory(daemonClient);
16
+ const api = createPaseoApi(daemonClient);
12
17
  return {
18
+ ...api,
19
+ connect: () => daemonClient.connect(),
20
+ close: async () => {
21
+ try {
22
+ await api.dispose();
23
+ }
24
+ finally {
25
+ await daemonClient.close();
26
+ }
27
+ },
28
+ ensureConnected: () => daemonClient.ensureConnected(),
29
+ getConnectionState: () => daemonClient.getConnectionState(),
30
+ };
31
+ }
32
+ function toDaemonAgentCreateOptions(options, placement) {
33
+ const { config: agentConfig, cwd, parent, title, prompt, ...requestOptions } = options;
34
+ const { provider: providerModel, options: providerOptions, ...runtimeConfig } = agentConfig;
35
+ const { provider, model } = parseProviderModel(providerModel);
36
+ return {
37
+ ...requestOptions,
38
+ config: {
39
+ ...runtimeConfig,
40
+ provider,
41
+ model,
42
+ cwd: placement?.cwd ?? cwd,
43
+ ...(title !== undefined ? { title } : {}),
44
+ ...(providerOptions !== undefined ? { providerOptions } : {}),
45
+ },
46
+ ...(placement ? { workspaceId: placement.workspaceId } : {}),
47
+ ...(parent ? { callerAgentId: resolveAgentId(parent) } : {}),
48
+ ...(prompt !== undefined ? { initialPrompt: prompt } : {}),
49
+ };
50
+ }
51
+ export function createPaseoApi(daemonClient, scopeOptions) {
52
+ const handles = new Set();
53
+ const agentListeners = new Set();
54
+ const workspaceListeners = new Set();
55
+ const lifetime = new AbortController();
56
+ const own = (create) => {
57
+ if (lifetime.signal.aborted)
58
+ throw new Error("Paseo API is disposed");
59
+ const handle = create();
60
+ handles.add(handle);
61
+ const release = handle.release.bind(handle);
62
+ handle.release = async () => {
63
+ await release();
64
+ handles.delete(handle);
65
+ };
66
+ return handle;
67
+ };
68
+ const listenAgents = (handler) => {
69
+ if (lifetime.signal.aborted)
70
+ throw new Error("Paseo API is disposed");
71
+ agentListeners.add(handler);
72
+ return () => {
73
+ agentListeners.delete(handler);
74
+ };
75
+ };
76
+ const listenWorkspaces = (handler) => {
77
+ if (lifetime.signal.aborted)
78
+ throw new Error("Paseo API is disposed");
79
+ workspaceListeners.add(handler);
80
+ return () => {
81
+ workspaceListeners.delete(handler);
82
+ };
83
+ };
84
+ const createAgentHandle = createAgentHandleFactory(daemonClient, listenAgents, (agentId, handler) => own(() => daemonClient.subscribeAgentTimeline(agentId, handler)));
85
+ const createAgent = async (options, placement) => {
86
+ const agent = await daemonClient.createAgent(toDaemonAgentCreateOptions(options, placement));
87
+ return createAgentHandle(agent);
88
+ };
89
+ const terminals = createTerminalActions(daemonClient, async (workspaceId) => {
90
+ const workspace = await createWorkspaceHandle(workspaceId).refresh();
91
+ if (!workspace?.workspaceDirectory) {
92
+ throw new Error(`Workspace ${workspaceId} is not active or has no available directory`);
93
+ }
94
+ return workspace.workspaceDirectory;
95
+ });
96
+ const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient, createAgent, terminals, listenWorkspaces);
97
+ let disposal = null;
98
+ const dispose = () => {
99
+ if (disposal)
100
+ return disposal;
101
+ lifetime.abort();
102
+ scopeOptions?.signal?.removeEventListener("abort", abort);
103
+ agentListeners.clear();
104
+ workspaceListeners.clear();
105
+ disposal = Promise.allSettled([...handles].map((handle) => handle.release())).then((results) => {
106
+ handles.clear();
107
+ const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
108
+ if (failures.length)
109
+ throw new AggregateError(failures, "Failed to release API subscriptions");
110
+ return undefined;
111
+ });
112
+ return disposal;
113
+ };
114
+ const abort = () => {
115
+ void dispose().catch((error) => console.error("API subscription cleanup failed", error));
116
+ };
117
+ if (scopeOptions?.signal?.aborted)
118
+ abort();
119
+ else
120
+ scopeOptions?.signal?.addEventListener("abort", abort, { once: true });
121
+ const observeEvents = (events, options) => own(() => daemonClient.observeEvents(events, options));
122
+ const subscribeEvent = (event, update) => {
123
+ const observation = observeEvents([event]);
124
+ observation.subscribe({ snapshot: () => { }, update });
125
+ return () => {
126
+ void observation
127
+ .release()
128
+ .catch((error) => console.error("Event subscription cleanup failed", error));
129
+ };
130
+ };
131
+ async function listWorkspaces(options) {
132
+ if (!options?.subscribe)
133
+ return daemonClient.fetchWorkspaces(options);
134
+ if (options.subscribe.subscriptionId !== undefined)
135
+ throw new Error("Subscription IDs are assigned by the host");
136
+ const subscription = own(() => daemonClient.observeWorkspaces(options));
137
+ subscription.subscribe({
138
+ snapshot: () => { },
139
+ update: (message) => {
140
+ if (message.type === "workspace_update")
141
+ for (const listener of workspaceListeners)
142
+ listener(message.payload);
143
+ },
144
+ });
145
+ return { ...(await subscription.ready), subscription };
146
+ }
147
+ async function listAgents(options) {
148
+ if (!options?.subscribe)
149
+ return daemonClient.fetchAgents(options);
150
+ if (options.subscribe.subscriptionId !== undefined)
151
+ throw new Error("Subscription IDs are assigned by the host");
152
+ const subscription = own(() => daemonClient.observeAgents(options));
153
+ subscription.subscribe({
154
+ snapshot: () => { },
155
+ update: (message) => {
156
+ if (message.type === "agent_update")
157
+ for (const listener of agentListeners)
158
+ listener(message.payload);
159
+ },
160
+ });
161
+ return { ...(await subscription.ready), subscription };
162
+ }
163
+ return {
164
+ dispose,
165
+ observeEvents,
166
+ terminals,
167
+ projects: {
168
+ list: (options) => daemonClient.listProjects(options),
169
+ subscribe: (handler) => {
170
+ return subscribeEvent("project.update", (message) => {
171
+ if (message.type === "project.update")
172
+ handler(message.payload);
173
+ });
174
+ },
175
+ },
13
176
  workspaces: {
14
- list: (options) => daemonClient.fetchWorkspaces(options),
177
+ list: listWorkspaces,
15
178
  ref: (workspace) => createWorkspaceHandle(workspace),
16
179
  open: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
17
- create: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
180
+ create: async ({ requestId, agent, ...options }) => {
181
+ const result = await daemonClient.createWorkspace({ ...options, ...(agent ? { agent: toDaemonAgentCreateOptions(agent) } : {}) }, requestId);
182
+ if (result.error || !result.workspace) {
183
+ throw new Error(result.error ?? "The daemon did not create a workspace");
184
+ }
185
+ return createWorkspaceHandle(result.workspace);
186
+ },
18
187
  archive: (workspace, requestId) => daemonClient.archiveWorkspace(resolveWorkspaceId(workspace), requestId),
19
- subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
20
- handler(message.payload);
21
- }),
188
+ subscribe: listenWorkspaces,
22
189
  },
23
190
  agents: {
191
+ list: listAgents,
24
192
  ref: (agent) => createAgentHandle(agent),
25
- create: async (options) => {
26
- const agent = await daemonClient.createAgent(options);
27
- return createAgentHandle(agent);
28
- },
29
- subscribe: (handler) => daemonClient.on("agent_update", (message) => {
30
- handler(message.payload);
31
- }),
193
+ create: (options) => createAgent(options),
194
+ subscribe: listenAgents,
32
195
  },
33
196
  providers: {
34
- codex: (input) => providerConfig("codex", input),
35
- claude: (input) => providerConfig("claude", input),
36
- opencode: (input) => providerConfig("opencode", input),
37
- copilot: (input) => providerConfig("copilot", input),
38
- config: (provider, input) => providerConfig(provider, input),
39
197
  listModels: (provider, options) => daemonClient.listProviderModels(provider, options),
40
198
  listModes: (provider, options) => daemonClient.listProviderModes(provider, options),
41
- listFeatures: (draftConfig, options) => daemonClient.listProviderFeatures(draftConfig, options),
199
+ listFeatures: ({ provider: providerModel, ...draftConfig }, options) => {
200
+ const { provider, model } = parseProviderModel(providerModel);
201
+ return daemonClient.listProviderFeatures({ ...draftConfig, provider, model }, options);
202
+ },
42
203
  listAvailable: (options) => daemonClient.listAvailableProviders(options),
43
204
  snapshot: (options) => daemonClient.getProvidersSnapshot(options),
205
+ waitForReady: (options) => waitForProvidersReady(daemonClient, observeEvents(["providers_snapshot_update"]), lifetime.signal, options),
44
206
  refresh: (options) => daemonClient.refreshProvidersSnapshot(options),
45
207
  // Re-read agents.providers from the daemon's config.json (add/update/remove).
46
208
  // Distinct from `refresh`, which only re-probes availability of the
47
209
  // providers the daemon already knows about.
48
210
  reloadConfig: (options) => daemonClient.reloadProviderConfig(options),
49
211
  diagnostic: (provider, options) => daemonClient.getProviderDiagnostic(provider, options),
50
- subscribe: (handler) => daemonClient.on("providers_snapshot_update", (message) => {
51
- handler(message.payload);
52
- }),
212
+ listUsage: (options) => listProviderUsage(daemonClient, options),
213
+ subscribe: (handler) => {
214
+ return subscribeEvent("providers_snapshot_update", (message) => {
215
+ if (message.type === "providers_snapshot_update")
216
+ handler(message.payload);
217
+ });
218
+ },
53
219
  },
54
220
  config: {
55
221
  get: (requestId) => daemonClient.getDaemonConfig(requestId),
56
222
  patch: (patch, requestId) => daemonClient.patchDaemonConfig(patch, requestId),
57
223
  },
58
- connect: () => daemonClient.connect(),
59
- close: () => daemonClient.close(),
60
- ensureConnected: () => daemonClient.ensureConnected(),
61
- getConnectionState: () => daemonClient.getConnectionState(),
62
224
  };
63
225
  }
64
- function createWorkspaceHandleFactory(daemonClient) {
226
+ function createWorkspaceHandleFactory(daemonClient, createAgent, terminals, listen) {
65
227
  return (workspace) => {
66
228
  const id = typeof workspace === "string" ? workspace : workspace.id;
67
- let latest = typeof workspace === "string" ? null : workspace;
68
- return {
69
- id,
70
- latest: () => latest,
71
- refetch: async (options) => {
72
- // Best-effort: fetches one page and matches by id client-side, so a workspace beyond
73
- // the first page won't be found. TODO: add a "get workspace by id" lookup and resolve
74
- // by exact id instead of paging.
229
+ let current = typeof workspace === "string" ? null : workspace;
230
+ const refresh = async (options) => {
231
+ let cursor;
232
+ let requestId = options?.requestId;
233
+ do {
75
234
  const result = await daemonClient.fetchWorkspaces({
76
- requestId: options?.requestId,
77
- page: { limit: 25 },
235
+ requestId,
236
+ page: { limit: 200, ...(cursor ? { cursor } : {}) },
78
237
  });
79
- latest = result.entries.find((entry) => entry.id === id) ?? null;
80
- return latest;
238
+ const match = result.entries.find((entry) => entry.id === id);
239
+ if (match) {
240
+ current = match;
241
+ return current;
242
+ }
243
+ cursor = result.pageInfo.nextCursor ?? undefined;
244
+ requestId = undefined;
245
+ } while (cursor);
246
+ current = null;
247
+ return current;
248
+ };
249
+ return {
250
+ id,
251
+ get projectId() {
252
+ return current?.projectId ?? null;
253
+ },
254
+ get directory() {
255
+ return current?.workspaceDirectory ?? null;
256
+ },
257
+ get name() {
258
+ return current?.name ?? null;
259
+ },
260
+ get status() {
261
+ return current?.status ?? null;
262
+ },
263
+ agents: {
264
+ create: async (options) => {
265
+ const snapshot = current ?? (await refresh());
266
+ if (!snapshot?.workspaceDirectory) {
267
+ throw new Error(`Workspace ${id} has no available directory`);
268
+ }
269
+ return createAgent({ ...options, cwd: snapshot.workspaceDirectory }, { workspaceId: id, cwd: snapshot.workspaceDirectory });
270
+ },
271
+ },
272
+ terminals: {
273
+ create: (options) => terminals.create({ ...options, workspaceId: id }),
274
+ list: (options) => terminals.list({ ...options, workspaceId: id }),
81
275
  },
276
+ current: () => current,
277
+ refresh,
278
+ setTitle: (title, requestId) => daemonClient.setWorkspaceTitle(id, title, requestId),
82
279
  archive: async (requestId) => {
83
280
  const result = await daemonClient.archiveWorkspace(id, requestId);
84
- if (latest) {
85
- latest = { ...latest, archivingAt: result.archivedAt };
281
+ if (current) {
282
+ current = { ...current, archivingAt: result.archivedAt };
86
283
  }
87
284
  return result;
88
285
  },
89
- subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
90
- const update = message.payload;
286
+ subscribe: (handler) => listen((update) => {
91
287
  if (update.kind === "upsert" && update.workspace.id === id) {
92
- latest = update.workspace;
288
+ current = update.workspace;
93
289
  handler(update);
94
290
  }
95
291
  if (update.kind === "remove" && update.id === id) {
96
- latest = null;
97
292
  handler(update);
98
293
  }
99
294
  }),
100
295
  };
101
296
  };
102
297
  }
103
- function createAgentHandleFactory(daemonClient) {
298
+ function createAgentHandleFactory(daemonClient, listen, subscribeTimeline) {
104
299
  return (agent) => {
105
300
  const id = typeof agent === "string" ? agent : agent.id;
106
- let latest = typeof agent === "string" ? null : agent;
301
+ let current = typeof agent === "string" ? null : agent;
107
302
  const handle = {
108
303
  id,
109
304
  timeline: {
305
+ append: (item) => daemonClient.appendAgentTimelineItem(id, item),
110
306
  refetch: async (options) => {
111
307
  const result = await daemonClient.fetchAgentTimeline(id, options);
112
308
  if (result.agent) {
113
- latest = result.agent;
309
+ current = result.agent;
114
310
  }
115
311
  return result;
116
312
  },
117
- subscribe: (handler) => daemonClient.on("agent_stream", (message) => {
118
- if (message.payload.agentId === id) {
119
- handler(message.payload);
313
+ subscribe: (handler) => subscribeTimeline(id, (message) => {
314
+ switch (message.type) {
315
+ case "agent_stream":
316
+ return handler(message.payload);
317
+ case "agent.timeline.subscription_restored":
318
+ return handler({
319
+ agentId: id,
320
+ subscriptionId: message.payload.subscriptionId,
321
+ event: { type: "subscription_restored" },
322
+ });
323
+ case "agent.timeline.error":
324
+ return handler({
325
+ agentId: id,
326
+ event: { type: "error", error: message.payload.error },
327
+ });
328
+ case "agent.timeline.replacement":
329
+ return handler({
330
+ agentId: id,
331
+ event: { type: "replacement", epoch: message.payload.epoch },
332
+ });
120
333
  }
121
334
  }),
122
335
  },
123
- latest: () => latest,
124
- refetch: async (requestId) => {
336
+ get workspaceId() {
337
+ return current?.workspaceId ?? null;
338
+ },
339
+ get cwd() {
340
+ return current?.cwd ?? null;
341
+ },
342
+ get status() {
343
+ return current?.status ?? null;
344
+ },
345
+ get capabilities() {
346
+ return current?.capabilities ?? null;
347
+ },
348
+ get availableModes() {
349
+ return current?.availableModes ?? null;
350
+ },
351
+ get pendingPermissions() {
352
+ return current?.pendingPermissions ?? null;
353
+ },
354
+ get activeTurn() {
355
+ return current?.activeTurn ?? null;
356
+ },
357
+ get lastUsage() {
358
+ return current?.lastUsage ?? null;
359
+ },
360
+ get lastError() {
361
+ return current?.lastError ?? null;
362
+ },
363
+ get features() {
364
+ return current?.features ?? null;
365
+ },
366
+ get runtimeInfo() {
367
+ return current?.runtimeInfo ?? null;
368
+ },
369
+ get archivedAt() {
370
+ return current?.archivedAt ?? null;
371
+ },
372
+ current: () => current,
373
+ refresh: async (requestId) => {
125
374
  const result = await daemonClient.fetchAgent({ agentId: id, requestId });
126
- latest = result?.agent ?? null;
375
+ current = result?.agent ?? null;
127
376
  return result;
128
377
  },
129
- send: (text, options) => daemonClient.sendAgentMessage(id, text, options),
378
+ send: async (text, options) => {
379
+ await daemonClient.sendAgentMessage(id, text, options);
380
+ },
381
+ respondToPermission: async ({ requestId, response }) => {
382
+ await daemonClient.respondToPermission(id, requestId, response);
383
+ },
384
+ run: async (text, options) => {
385
+ const { timeoutMs, ...sendOptions } = options ?? {};
386
+ await daemonClient.sendAgentMessage(id, text, sendOptions);
387
+ const result = await daemonClient.waitForFinish(id, timeoutMs ?? DEFAULT_WAIT_FOR_FINISH_MS);
388
+ if (result.final) {
389
+ current = result.final;
390
+ }
391
+ return result;
392
+ },
393
+ waitForFinish: async (timeoutMs) => {
394
+ const result = await daemonClient.waitForFinish(id, timeoutMs ?? DEFAULT_WAIT_FOR_FINISH_MS);
395
+ if (result.final) {
396
+ current = result.final;
397
+ }
398
+ return result;
399
+ },
400
+ commands: (options) => daemonClient.listCommands({ agentId: id, ...options }),
130
401
  archive: async () => {
131
402
  const result = await daemonClient.archiveAgent(id);
132
- if (latest) {
133
- latest = { ...latest, archivedAt: result.archivedAt };
403
+ if (current) {
404
+ current = { ...current, archivedAt: result.archivedAt };
134
405
  }
135
406
  return result;
136
407
  },
137
408
  detach: async () => {
138
409
  await daemonClient.detachAgent(id);
139
410
  },
140
- subscribe: (handler) => daemonClient.on("agent_update", (message) => {
141
- const update = message.payload;
411
+ subscribe: (handler) => listen((update) => {
142
412
  if (update.kind === "upsert" && update.agent.id === id) {
143
- latest = update.agent;
413
+ current = update.agent;
144
414
  handler(update);
145
415
  }
146
416
  if (update.kind === "remove" && update.agentId === id) {
147
- latest = null;
148
417
  handler(update);
149
418
  }
150
419
  }),
@@ -155,23 +424,117 @@ function createAgentHandleFactory(daemonClient) {
155
424
  async function openWorkspace(daemonClient, createWorkspaceHandle, input, requestId) {
156
425
  const options = typeof input === "string" ? { cwd: input, requestId } : input;
157
426
  const result = await daemonClient.openProject(options.cwd, options.requestId);
158
- return {
159
- ...result,
160
- workspace: result.workspace ? createWorkspaceHandle(result.workspace) : null,
161
- };
427
+ if (result.error || !result.workspace) {
428
+ throw new Error(result.error ?? `The daemon did not open a workspace for ${options.cwd}`);
429
+ }
430
+ return createWorkspaceHandle(result.workspace);
162
431
  }
163
432
  function resolveWorkspaceId(workspace) {
164
433
  return typeof workspace === "string" ? workspace : workspace.id;
165
434
  }
166
- function providerConfig(provider, input = {}) {
435
+ function resolveAgentId(agent) {
436
+ return typeof agent === "string" ? agent : agent.id;
437
+ }
438
+ function parseProviderModel(selection) {
439
+ const separator = selection.indexOf("/");
440
+ if (separator <= 0 || separator === selection.length - 1) {
441
+ throw new Error('Expected config.provider in "provider/model" format');
442
+ }
167
443
  return {
168
- provider,
169
- ...(input.model !== undefined ? { model: input.model } : {}),
170
- ...(input.modeId !== undefined ? { modeId: input.modeId } : {}),
171
- ...(input.thinkingOptionId !== undefined ? { thinkingOptionId: input.thinkingOptionId } : {}),
172
- ...(input.featureValues !== undefined ? { featureValues: input.featureValues } : {}),
444
+ provider: selection.slice(0, separator),
445
+ model: selection.slice(separator + 1),
173
446
  };
174
447
  }
448
+ function listProviderUsage(daemonClient, options) {
449
+ // COMPAT(providerUsageList): added in v0.1.98, remove after 2027-02-28 once daemon floor >= v0.1.98.
450
+ if (daemonClient.getLastServerInfoMessage()?.features?.providerUsageList !== true) {
451
+ return Promise.reject(new Error("Update the host to list provider usage."));
452
+ }
453
+ return daemonClient.listProviderUsage(options);
454
+ }
455
+ async function waitForProvidersReady(daemonClient, observation, signal, options = {}) {
456
+ const { timeoutMs = 60000, ...snapshotOptions } = options;
457
+ try {
458
+ await observation.ready;
459
+ signal.throwIfAborted();
460
+ return await new Promise((resolve, reject) => {
461
+ let settled = false;
462
+ let requestId = null;
463
+ let snapshotCwd;
464
+ const pendingUpdates = new Map();
465
+ let latestEntries = [];
466
+ const cleanup = () => {
467
+ clearTimeout(timeout);
468
+ unsubscribe();
469
+ signal.removeEventListener("abort", abort);
470
+ };
471
+ const finish = (snapshot) => {
472
+ if (settled)
473
+ return;
474
+ settled = true;
475
+ cleanup();
476
+ resolve(snapshot);
477
+ };
478
+ const fail = (error) => {
479
+ if (settled)
480
+ return;
481
+ settled = true;
482
+ cleanup();
483
+ reject(error instanceof Error ? error : new Error(String(error)));
484
+ };
485
+ const updateMatches = (update) => update.cwd === snapshotCwd;
486
+ const unsubscribe = observation.subscribe({
487
+ snapshot: () => { },
488
+ update: (message) => {
489
+ if (message.type !== "providers_snapshot_update")
490
+ return;
491
+ const update = message.payload;
492
+ if (!requestId) {
493
+ pendingUpdates.set(update.cwd, update);
494
+ return;
495
+ }
496
+ if (!updateMatches(update))
497
+ return;
498
+ latestEntries = update.entries;
499
+ if (update.entries.some((entry) => entry.status === "loading"))
500
+ return;
501
+ finish({ ...update, requestId });
502
+ },
503
+ });
504
+ const abort = () => fail(new Error("Paseo API is disposed"));
505
+ signal.addEventListener("abort", abort, { once: true });
506
+ const timeout = setTimeout(() => {
507
+ const loading = latestEntries
508
+ .filter((entry) => entry.status === "loading")
509
+ .map((entry) => entry.provider)
510
+ .join(", ");
511
+ fail(new Error(loading
512
+ ? `Timed out waiting for providers: ${loading}`
513
+ : "Timed out waiting for provider discovery"));
514
+ }, timeoutMs);
515
+ void daemonClient
516
+ .getProvidersSnapshot(snapshotOptions)
517
+ .then((snapshot) => {
518
+ requestId = snapshot.requestId;
519
+ snapshotCwd = snapshot.cwd;
520
+ latestEntries = snapshot.entries;
521
+ if (!snapshot.entries.some((entry) => entry.status === "loading")) {
522
+ finish(snapshot);
523
+ return;
524
+ }
525
+ const pendingUpdate = pendingUpdates.get(snapshotCwd);
526
+ if (pendingUpdate && !pendingUpdate.entries.some((entry) => entry.status === "loading")) {
527
+ finish({ ...pendingUpdate, requestId });
528
+ }
529
+ return undefined;
530
+ })
531
+ .catch(fail);
532
+ });
533
+ }
534
+ finally {
535
+ await observation.release();
536
+ }
537
+ }
175
538
  function createGeneratedClientId() {
176
539
  const randomId = typeof globalThis.crypto?.randomUUID === "function"
177
540
  ? globalThis.crypto.randomUUID()
@@ -0,0 +1,2 @@
1
+ export declare function legacyUsageIcon(sourceId: string): string | undefined;
2
+ //# sourceMappingURL=legacy-usage-icons.d.ts.map
@@ -0,0 +1,16 @@
1
+ // COMPAT(providerUsageList): added in v0.1.98, remove after 2027-03-26.
2
+ // Released hosts omit source icons. Delete these copies with the list conversion.
3
+ const ICONS = {
4
+ claude: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"/></svg>\n',
5
+ codex: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd"><path d="M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.553 5.553 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.02.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.002zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z"/></svg>\n',
6
+ copilot: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 416" fill="currentColor"><path d="M181.33 266.143c0-11.497 9.32-20.818 20.818-20.818 11.498 0 20.819 9.321 20.819 20.818v38.373c0 11.497-9.321 20.818-20.819 20.818-11.497 0-20.818-9.32-20.818-20.818v-38.373zM308.807 245.325c-11.477 0-20.798 9.321-20.798 20.818v38.373c0 11.497 9.32 20.818 20.798 20.818 11.497 0 20.818-9.32 20.818-20.818v-38.373c0-11.497-9.32-20.818-20.818-20.818z"\n fill-rule="nonzero"/><path d="M512.002 246.393v57.384c-.02 7.411-3.696 14.638-9.67 19.011C431.767 374.444 344.695 416 256 416c-98.138 0-196.379-56.542-246.33-93.21-5.975-4.374-9.65-11.6-9.671-19.012v-57.384a35.347 35.347 0 016.857-20.922l15.583-21.085c8.336-11.312 20.757-14.31 33.98-14.31 4.988-56.953 16.794-97.604 45.024-127.354C155.194 5.77 226.56 0 256 0c29.441 0 100.807 5.77 154.557 62.722 28.19 29.75 40.036 70.401 45.025 127.354 13.263 0 25.602 2.936 33.958 14.31l15.583 21.127c4.476 6.077 6.878 13.345 6.878 20.88zm-97.666-26.075c-.677-13.058-11.292-18.19-22.338-21.824-11.64 7.309-25.848 10.183-39.46 10.183-14.454 0-41.432-3.47-63.872-25.869-5.667-5.625-9.527-14.454-12.155-24.247a212.902 212.902 0 00-20.469-1.088c-6.098 0-13.099.349-20.551 1.088-2.628 9.793-6.509 18.622-12.155 24.247-22.4 22.4-49.418 25.87-63.872 25.87-13.612 0-27.86-2.855-39.501-10.184-11.005 3.613-21.558 8.828-22.277 21.824-1.17 24.555-1.272 49.11-1.375 73.645-.041 12.318-.082 24.658-.288 36.976.062 7.166 4.374 13.818 10.882 16.774 52.97 24.124 103.045 36.278 149.137 36.278 46.01 0 96.085-12.154 149.014-36.278 6.508-2.956 10.84-9.608 10.881-16.774.637-36.832.124-73.809-1.642-110.62h.041zM107.521 168.97c8.643 8.623 24.966 14.392 42.56 14.392 13.448 0 39.03-2.874 60.156-24.329 9.28-8.951 15.05-31.35 14.413-54.079-.657-18.231-5.769-33.28-13.448-39.665-8.315-7.371-27.203-10.574-48.33-8.644-22.399 2.238-41.267 9.588-50.875 19.833-20.798 22.728-16.323 80.317-4.476 92.492zm130.556-56.008c.637 3.51.965 7.35 1.273 11.517 0 2.875 0 5.77-.308 8.952 6.406-.636 11.847-.636 16.959-.636s10.553 0 16.959.636c-.329-3.182-.329-6.077-.329-8.952.329-4.167.657-8.007 1.294-11.517-6.735-.637-12.812-.965-17.924-.965s-11.21.328-17.924.965zm49.275-8.008c-.637 22.728 5.133 45.128 14.413 54.08 21.105 21.454 46.708 24.328 60.155 24.328 17.596 0 33.918-5.769 42.561-14.392 11.847-12.175 16.322-69.764-4.476-92.492-9.608-10.245-28.476-17.595-50.875-19.833-21.127-1.93-40.015 1.273-48.33 8.644-7.679 6.385-12.791 21.434-13.448 39.665z"/></svg>\n',
7
+ cursor: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 466.73 532.09">\n <path fill="currentColor" d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"/>\n</svg>\n',
8
+ zai: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" fill-rule="evenodd"><title>Z.ai</title><path d="M8.07 1.333L6.618 3.302H.435L1.887 1.333h6.184zM15.503 12.699L14.052 14.667h-6.161l1.449-1.968h6.163zM16 1.333L6.176 14.667H0L9.824 1.333H16z"/></svg>\n',
9
+ grok: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2M20 14h2M9 13v2M15 13v2"/></svg>\n',
10
+ kimi: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">\n <path fill="currentColor" d="M19.514 7.342C19.711 7.862 19.814 8.413 19.816 8.969C19.816 12.494 15.998 14.697 12.946 12.934C11.529 12.116 10.657 10.605 10.657 8.969C10.657 5.646 14.085 3.431 17.115 4.793C15.26 3.373 12.99 2.607 10.655 2.612C4.772 2.614 0.005 7.381 0 13.265C0.002 19.148 4.772 23.918 10.655 23.92C16.538 23.916 21.306 19.147 21.31 13.265C21.312 11.155 20.687 9.095 19.514 7.342ZM14.841 4.666C14.841 8.191 18.657 10.395 21.709 8.632C23.127 7.814 24 6.302 24 4.666C24 1.14 20.184-1.061 17.13 0.699C15.714 1.519 14.841 3.03 14.841 4.666"/>\n</svg>\n',
11
+ minimax: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z"/></svg>\n',
12
+ };
13
+ export function legacyUsageIcon(sourceId) {
14
+ return Object.hasOwn(ICONS, sourceId) ? ICONS[sourceId] : undefined;
15
+ }
16
+ //# sourceMappingURL=legacy-usage-icons.js.map
@@ -0,0 +1,21 @@
1
+ import type { GetProvidersSnapshotResponseMessage, SessionOutboundMessage } from "@hyperdrive.bot/fleet-protocol/messages";
2
+ type Update = Extract<SessionOutboundMessage, {
3
+ type: "providers_snapshot_update";
4
+ }>;
5
+ type Snapshot = GetProvidersSnapshotResponseMessage["payload"];
6
+ /** Resolve announcements only while observed, coalescing changes during a fetch. */
7
+ export declare class ProviderSnapshotUpdates {
8
+ private readonly host;
9
+ private pending;
10
+ constructor(host: {
11
+ active(update: Update): boolean;
12
+ fetch(cwd: string | undefined): Promise<Snapshot>;
13
+ emit(update: Update): void;
14
+ failed(error: unknown): void;
15
+ });
16
+ clear(): void;
17
+ receive(update: Update): void;
18
+ private resolve;
19
+ }
20
+ export {};
21
+ //# sourceMappingURL=index.d.ts.map