@getpaseo/client 0.3.0 → 0.4.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,39 @@
1
1
  # @getpaseo/client
2
2
 
3
- Paseo's JavaScript/TypeScript client package.
3
+ TypeScript SDK for building integrations on top of a Paseo daemon.
4
4
 
5
- ## Stability
5
+ ```bash
6
+ npm install @getpaseo/client
7
+ ```
8
+
9
+ ```ts
10
+ import { createPaseoClient } from "@getpaseo/client";
11
+
12
+ const client = createPaseoClient({ url: "ws://127.0.0.1:6767/ws" });
13
+ await client.connect();
14
+
15
+ const agent = await client.agents.create({
16
+ config: { provider: "codex/gpt-5.5" },
17
+ cwd: "/Users/me/dev/storefront",
18
+ prompt: "Review the current diff and name the riskiest change.",
19
+ });
20
+
21
+ const result = await agent.waitForFinish();
22
+ console.log(result.lastMessage);
23
+
24
+ await client.close();
25
+ ```
6
26
 
7
- This package is public so Paseo's published packages can depend on it cleanly.
8
- It is not a stable public SDK yet.
27
+ The public API is the package root. Imports under `@getpaseo/client/internal/*` are unsupported implementation details used by Paseo's own packages.
28
+
29
+ Read the [SDK documentation](https://paseo.sh/docs/sdk) for agents, workspaces, provider discovery, events, recipes, and the API reference. Runnable TypeScript patterns also live in [`examples/`](./examples/README.md).
30
+
31
+ ## Runtime
32
+
33
+ The client needs a WebSocket implementation. Modern browsers and Node.js 22 provide one globally.
34
+
35
+ Use a WebSocket URL ending in `/ws`, such as `ws://127.0.0.1:6767/ws`. Pass `password` when the daemon requires authentication.
36
+
37
+ ## Stability
9
38
 
10
- APIs, exports, runtime behavior, and types may change or disappear in any
11
- release without advance notice. Use it outside Paseo at your own risk until the
12
- package is explicitly documented as stable.
39
+ The high-level API exported from `@getpaseo/client` is the supported SDK surface. The SDK and daemon remain protocol-compatible across versions, but newly added capabilities can require a newer daemon.
@@ -4,7 +4,7 @@ import type { AgentAttentionNotificationPayload } from "@getpaseo/protocol/agent
4
4
  import { AgentRefreshedStatusPayloadSchema, CheckoutRenameBranchResponseSchema, RenameTerminalResponseSchema, RestartRequestedStatusPayloadSchema, ShutdownRequestedStatusPayloadSchema, DaemonUpdateResponseSchema, type ServerInfoStatusPayload } from "@getpaseo/protocol/messages";
5
5
  import type { AgentStreamEventPayload, AgentSnapshotPayload, ProjectPlacementPayload, AgentPermissionResolvedMessage, CreateAgentRequestMessage, CreatePaseoWorktreeRequest, FileDownloadTokenResponse, FileUploadResponse, FileExplorerResponse, FileVersion, FileWriteResult, FetchAgentTimelineResponseMessage, AgentForkContextResponseMessage, GitSetupOptions, CheckoutStatusResponse, CheckoutCommit, ParsedDiffFile, CheckoutCommitResponse, CheckoutMergeResponse, CheckoutMergeFromBaseResponse, CheckoutPullResponse, CheckoutPushResponse, CheckoutRefreshResponse, CheckoutPrCreateResponse, CheckoutPrMergeResponse, CheckoutPrMergeMethod, CheckoutForgeSetAutoMergeResponse, CheckoutGithubSetAutoMergeResponse, CheckoutForgeGetCheckDetailsResponse, CheckoutGithubGetCheckDetailsResponse, CheckoutPrStatusResponse, PullRequestTimelineResponse, CheckoutSwitchBranchResponse, StashSaveResponse, StashPopResponse, StashListResponse, ValidateBranchResponse, BranchSuggestionsResponse, ForgeSearchResponse, ForgeSearchRequest, GitHubSearchResponse, GitHubSearchRequest, DirectorySuggestionsResponse, PaseoWorktreeListResponse, PaseoWorktreeArchiveResponse, ProjectIconSource, ProjectIconResponse, ProjectIconGetResponse, ProjectAddResponse, ProjectCreateDirectoryResponse, OpenProjectResponseMessage, WorkspaceGithubSearchRepositoriesResponse, ProjectGithubCloneProtocol, ProjectGithubCloneResponse, ArchiveWorkspaceResponseMessage, WorkspaceSetupStatusResponseMessage, ListCommandsResponse, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ListProviderModesResponseMessage, ListAvailableProvidersResponse, GetProvidersSnapshotResponseMessage, RefreshProvidersSnapshotResponseMessage, ProviderDiagnosticResponseMessage, ProviderUsageListResponseMessage, DaemonGetStatusResponse, DaemonGetPairingOfferResponse, DiagnosticsResponse, AgentRewindResponseMessage, ListTerminalsResponse, CreateTerminalResponse, SubscribeTerminalResponse, SubscribeTerminalRequest, CloseItemsResponse, KillTerminalResponse, CaptureTerminalResponse, TerminalInput, SessionInboundMessage, SessionOutboundMessage, SendAgentMessageRequest, PaseoConfigRaw, PaseoConfigRevision, WorkspaceCreateRequest, WorkspaceRecoveryState } from "@getpaseo/protocol/messages";
6
6
  import type { AgentPermissionRequest, AgentPermissionResponse, AgentPersistenceHandle, AgentProviderNotice, AgentProvider, AgentSessionConfig } from "@getpaseo/protocol/agent-types";
7
- import type { MutableDaemonConfig, MutableDaemonConfigPatch } from "@getpaseo/protocol/messages";
7
+ import type { AgentConfigApply, MutableDaemonConfig, MutableDaemonConfigPatch } from "@getpaseo/protocol/messages";
8
8
  import { type DaemonTransportFactory, type WebSocketFactory } from "./daemon-client-transport.js";
9
9
  import { type TerminalStreamEvent } from "./terminal-stream-router.js";
10
10
  import type { BrowserAutomationExecuteRequest, BrowserAutomationExecuteResponse } from "@getpaseo/protocol/browser-automation/rpc-schemas";
@@ -108,7 +108,6 @@ export interface DaemonClientConfig {
108
108
  runtimeGeneration?: number | null;
109
109
  password?: string;
110
110
  authHeader?: string;
111
- headers?: Record<string, string>;
112
111
  suppressSendErrors?: boolean;
113
112
  transportFactory?: DaemonTransportFactory;
114
113
  webSocketFactory?: WebSocketFactory;
@@ -271,42 +270,6 @@ type SubscribeTerminalPayload = SubscribeTerminalResponse["payload"];
271
270
  type CloseItemsPayload = CloseItemsResponse["payload"];
272
271
  type KillTerminalPayload = KillTerminalResponse["payload"];
273
272
  type CaptureTerminalPayload = CaptureTerminalResponse["payload"];
274
- type ChatCreatePayload = Extract<SessionOutboundMessage, {
275
- type: "chat/create/response";
276
- }>["payload"];
277
- type ChatListPayload = Extract<SessionOutboundMessage, {
278
- type: "chat/list/response";
279
- }>["payload"];
280
- type ChatInspectPayload = Extract<SessionOutboundMessage, {
281
- type: "chat/inspect/response";
282
- }>["payload"];
283
- type ChatDeletePayload = Extract<SessionOutboundMessage, {
284
- type: "chat/delete/response";
285
- }>["payload"];
286
- type ChatPostPayload = Extract<SessionOutboundMessage, {
287
- type: "chat/post/response";
288
- }>["payload"];
289
- type ChatReadPayload = Extract<SessionOutboundMessage, {
290
- type: "chat/read/response";
291
- }>["payload"];
292
- type ChatWaitPayload = Extract<SessionOutboundMessage, {
293
- type: "chat/wait/response";
294
- }>["payload"];
295
- type LoopRunPayload = Extract<SessionOutboundMessage, {
296
- type: "loop/run/response";
297
- }>["payload"];
298
- type LoopListPayload = Extract<SessionOutboundMessage, {
299
- type: "loop/list/response";
300
- }>["payload"];
301
- type LoopInspectPayload = Extract<SessionOutboundMessage, {
302
- type: "loop/inspect/response";
303
- }>["payload"];
304
- type LoopLogsPayload = Extract<SessionOutboundMessage, {
305
- type: "loop/logs/response";
306
- }>["payload"];
307
- type LoopStopPayload = Extract<SessionOutboundMessage, {
308
- type: "loop/stop/response";
309
- }>["payload"];
310
273
  type ScheduleCreatePayload = Extract<SessionOutboundMessage, {
311
274
  type: "schedule/create/response";
312
275
  }>["payload"];
@@ -438,70 +401,6 @@ export type FetchWorkspacesPageInfo = FetchWorkspacesPayload["pageInfo"];
438
401
  export type ProjectListPayload = Extract<SessionOutboundMessage, {
439
402
  type: "project.list.response";
440
403
  }>["payload"];
441
- export interface CreateChatRoomOptions {
442
- name: string;
443
- purpose?: string | null;
444
- requestId?: string;
445
- }
446
- export interface InspectChatRoomOptions {
447
- room: string;
448
- requestId?: string;
449
- }
450
- export interface DeleteChatRoomOptions {
451
- room: string;
452
- requestId?: string;
453
- }
454
- export interface PostChatMessageOptions {
455
- room: string;
456
- body: string;
457
- authorAgentId?: string;
458
- replyToMessageId?: string | null;
459
- requestId?: string;
460
- }
461
- export interface ReadChatMessagesOptions {
462
- room: string;
463
- limit?: number;
464
- since?: string;
465
- authorAgentId?: string;
466
- requestId?: string;
467
- timeout?: number;
468
- }
469
- export interface WaitForChatMessagesOptions {
470
- room: string;
471
- afterMessageId?: string | null;
472
- timeoutMs?: number;
473
- requestId?: string;
474
- }
475
- export interface RunLoopOptions {
476
- prompt: string;
477
- cwd: string;
478
- provider?: string;
479
- model?: string;
480
- modeId?: string;
481
- verifierProvider?: string;
482
- verifierModel?: string;
483
- verifierModeId?: string;
484
- verifyPrompt?: string | null;
485
- verifyChecks?: string[];
486
- name?: string | null;
487
- sleepMs?: number;
488
- maxIterations?: number;
489
- maxTimeMs?: number;
490
- requestId?: string;
491
- }
492
- export interface InspectLoopOptions {
493
- id: string;
494
- requestId?: string;
495
- }
496
- export interface LoopLogsOptions {
497
- id: string;
498
- afterSeq?: number;
499
- requestId?: string;
500
- }
501
- export interface StopLoopOptions {
502
- id: string;
503
- requestId?: string;
504
- }
505
404
  export interface CreateScheduleOptions {
506
405
  prompt: string;
507
406
  name?: string | null;
@@ -527,11 +426,7 @@ export interface CreateScheduleOptions {
527
426
  archiveOnFinish?: boolean;
528
427
  isolation?: "local" | "worktree";
529
428
  title?: string | null;
530
- approvalPolicy?: string;
531
- sandboxMode?: string;
532
- networkAccess?: boolean;
533
- webSearch?: boolean;
534
- extra?: AgentSessionConfig["extra"];
429
+ providerOptions?: AgentSessionConfig["providerOptions"];
535
430
  systemPrompt?: string;
536
431
  mcpServers?: AgentSessionConfig["mcpServers"];
537
432
  };
@@ -595,6 +490,21 @@ export interface WaitForFinishResult {
595
490
  error: string | null;
596
491
  lastMessage: string | null;
597
492
  }
493
+ type GetDaemonConfigResponse = Extract<SessionOutboundMessage, {
494
+ type: "get_daemon_config_response";
495
+ }>;
496
+ type SetDaemonConfigResponse = Extract<SessionOutboundMessage, {
497
+ type: "set_daemon_config_response";
498
+ }>;
499
+ type CorrelatedResponseMessage = Extract<SessionOutboundMessage, {
500
+ payload: {
501
+ requestId: string;
502
+ };
503
+ }> | GetDaemonConfigResponse | SetDaemonConfigResponse;
504
+ type CorrelatedResponseType = CorrelatedResponseMessage["type"];
505
+ type CorrelatedResponsePayload<TType extends CorrelatedResponseType> = Extract<CorrelatedResponseMessage, {
506
+ type: TType;
507
+ }>["payload"];
598
508
  export declare class DaemonClient {
599
509
  private config;
600
510
  private transport;
@@ -698,6 +608,7 @@ export declare class DaemonClient {
698
608
  appVisibilityChangedAt?: string;
699
609
  }): void;
700
610
  registerPushToken(token: string): void;
611
+ unregisterPushToken(token: string): Promise<void>;
701
612
  ping(params?: {
702
613
  requestId?: string;
703
614
  timeoutMs?: number;
@@ -804,6 +715,14 @@ export declare class DaemonClient {
804
715
  setAgentModel(agentId: string, modelId: string | null): Promise<void>;
805
716
  setAgentFeature(agentId: string, featureId: string, value: unknown): Promise<void>;
806
717
  setAgentThinkingOption(agentId: string, thinkingOptionId: string | null): Promise<AgentProviderNotice | null>;
718
+ /**
719
+ * Applies a whole agent-config bundle in one request. Use this instead of
720
+ * chaining the single-field setters when the values belong together so client
721
+ * interruption and other mutations cannot interleave between steps. A
722
+ * provider rejection can still leave earlier steps applied.
723
+ * Gated on `server_info.features.agentConfigApply`.
724
+ */
725
+ applyAgentConfig(agentId: string, config: AgentConfigApply): Promise<AgentProviderNotice | null>;
807
726
  restartServer(reason?: string, requestId?: string): Promise<RestartRequestedStatusPayload>;
808
727
  shutdownServer(options?: ShutdownServerOptions): Promise<ShutdownRequestedStatusPayload>;
809
728
  updateDaemon(requestId?: string): Promise<DaemonUpdateResponse["payload"]>;
@@ -973,6 +892,28 @@ export declare class DaemonClient {
973
892
  expectedModifiedAt: string;
974
893
  expectedRevision?: string;
975
894
  }): Promise<FileWriteResult>;
895
+ createFileEntry(input: {
896
+ cwd: string;
897
+ parentPath: string;
898
+ name: string;
899
+ kind: "file" | "directory";
900
+ }): Promise<CorrelatedResponsePayload<"fs.entry.create.response">>;
901
+ renameFileEntry(input: {
902
+ cwd: string;
903
+ path: string;
904
+ name: string;
905
+ }): Promise<CorrelatedResponsePayload<"fs.entry.rename.response">>;
906
+ duplicateFileEntry(input: {
907
+ cwd: string;
908
+ path: string;
909
+ }): Promise<CorrelatedResponsePayload<"fs.entry.duplicate.response">>;
910
+ deleteFileEntry(input: {
911
+ cwd: string;
912
+ path: string;
913
+ }): Promise<CorrelatedResponsePayload<"fs.entry.delete.response">>;
914
+ checkoutDiscardChanges(cwd: string, input: {
915
+ paths: string[];
916
+ }): Promise<CorrelatedResponsePayload<"checkout.discard_changes.response">>;
976
917
  uploadFile(input: FileUploadInput): Promise<FileUploadResult>;
977
918
  requestDownloadToken(cwd: string, path: string, requestId?: string): Promise<FileDownloadTokenPayload>;
978
919
  requestProjectIcon(cwd: string, requestId?: string): Promise<ProjectIconResponse["payload"]>;
@@ -1100,13 +1041,6 @@ export declare class DaemonClient {
1100
1041
  end?: number;
1101
1042
  stripAnsi?: boolean;
1102
1043
  }, requestId?: string): Promise<CaptureTerminalPayload>;
1103
- createChatRoom(options: CreateChatRoomOptions): Promise<ChatCreatePayload>;
1104
- listChatRooms(requestId?: string): Promise<ChatListPayload>;
1105
- inspectChatRoom(options: InspectChatRoomOptions): Promise<ChatInspectPayload>;
1106
- deleteChatRoom(options: DeleteChatRoomOptions): Promise<ChatDeletePayload>;
1107
- postChatMessage(options: PostChatMessageOptions): Promise<ChatPostPayload>;
1108
- readChatMessages(options: ReadChatMessagesOptions): Promise<ChatReadPayload>;
1109
- waitForChatMessages(options: WaitForChatMessagesOptions): Promise<ChatWaitPayload>;
1110
1044
  scheduleCreate(options: CreateScheduleOptions): Promise<ScheduleCreatePayload>;
1111
1045
  scheduleList(requestId?: string): Promise<ScheduleListPayload>;
1112
1046
  scheduleInspect(options: InspectScheduleOptions): Promise<ScheduleInspectPayload>;
@@ -1116,11 +1050,6 @@ export declare class DaemonClient {
1116
1050
  scheduleDelete(options: InspectScheduleOptions): Promise<ScheduleDeletePayload>;
1117
1051
  scheduleRunOnce(options: InspectScheduleOptions): Promise<ScheduleRunOncePayload>;
1118
1052
  scheduleUpdate(options: UpdateScheduleOptions): Promise<ScheduleUpdatePayload>;
1119
- loopRun(options: RunLoopOptions): Promise<LoopRunPayload>;
1120
- loopList(requestId?: string): Promise<LoopListPayload>;
1121
- loopInspect(options: string | InspectLoopOptions): Promise<LoopInspectPayload>;
1122
- loopLogs(options: string | LoopLogsOptions, afterSeq?: number): Promise<LoopLogsPayload>;
1123
- loopStop(options: string | StopLoopOptions): Promise<LoopStopPayload>;
1124
1053
  onTerminalStreamEvent(handler: (event: TerminalStreamEvent) => void): () => void;
1125
1054
  waitForTerminalStreamEvent(predicate: (event: TerminalStreamEvent) => boolean, timeout?: number): Promise<TerminalStreamEvent>;
1126
1055
  private createRequestId;
@@ -112,6 +112,7 @@ function toTimeoutError(error, label, timeoutMs) {
112
112
  const DEFAULT_RECONNECT_BASE_DELAY_MS = 1500;
113
113
  const DEFAULT_RECONNECT_MAX_DELAY_MS = 30000;
114
114
  const DEFAULT_SESSION_RPC_TIMEOUT_MS = 60000;
115
+ const PUSH_TOKEN_REVOCATION_TIMEOUT_MS = 2000;
115
116
  const DEFAULT_CONNECT_TIMEOUT_MS = 15000;
116
117
  const DEFAULT_LIVENESS_TIMEOUT_MS = 5000;
117
118
  const LIVENESS_HEARTBEAT_INTERVAL_MS = 10000;
@@ -331,7 +332,7 @@ export class DaemonClient {
331
332
  if (this.connectionState.status === "connecting") {
332
333
  return;
333
334
  }
334
- const headers = { ...this.config.headers };
335
+ const headers = {};
335
336
  const password = normalizePassword(this.config.password);
336
337
  if (password) {
337
338
  headers.Authorization = `Bearer ${password}`;
@@ -902,6 +903,15 @@ export class DaemonClient {
902
903
  token,
903
904
  });
904
905
  }
906
+ async unregisterPushToken(token) {
907
+ const requestId = this.createRequestId();
908
+ await this.sendCorrelatedSessionRequest({
909
+ requestId,
910
+ message: { type: "push.unregister.request", token, requestId },
911
+ responseType: "push.unregister.response",
912
+ timeout: PUSH_TOKEN_REVOCATION_TIMEOUT_MS,
913
+ });
914
+ }
905
915
  async ping(params) {
906
916
  const requestId = params?.requestId ?? `ping-${Date.now()}-${Math.random().toString(36).slice(2)}`;
907
917
  const clientSentAt = Date.now();
@@ -1985,6 +1995,40 @@ export class DaemonClient {
1985
1995
  }
1986
1996
  return payload.notice ?? null;
1987
1997
  }
1998
+ /**
1999
+ * Applies a whole agent-config bundle in one request. Use this instead of
2000
+ * chaining the single-field setters when the values belong together so client
2001
+ * interruption and other mutations cannot interleave between steps. A
2002
+ * provider rejection can still leave earlier steps applied.
2003
+ * Gated on `server_info.features.agentConfigApply`.
2004
+ */
2005
+ async applyAgentConfig(agentId, config) {
2006
+ const requestId = this.createRequestId();
2007
+ const message = SessionInboundMessageSchema.parse({
2008
+ type: "agent.config.apply.request",
2009
+ agentId,
2010
+ config,
2011
+ requestId,
2012
+ });
2013
+ const payload = await this.sendRequest({
2014
+ requestId,
2015
+ message,
2016
+ options: { skipQueue: true },
2017
+ select: (msg) => {
2018
+ if (msg.type !== "agent.config.apply.response") {
2019
+ return null;
2020
+ }
2021
+ if (msg.payload.requestId !== requestId) {
2022
+ return null;
2023
+ }
2024
+ return msg.payload;
2025
+ },
2026
+ });
2027
+ if (!payload.accepted) {
2028
+ throw new Error(payload.error ?? "applyAgentConfig rejected");
2029
+ }
2030
+ return payload.notice ?? null;
2031
+ }
1988
2032
  async restartServer(reason, requestId) {
1989
2033
  const resolvedRequestId = this.createRequestId(requestId);
1990
2034
  const message = SessionInboundMessageSchema.parse({
@@ -2851,6 +2895,31 @@ export class DaemonClient {
2851
2895
  });
2852
2896
  return payload.result;
2853
2897
  }
2898
+ async createFileEntry(input) {
2899
+ return this.sendNamespacedCorrelatedSessionRequest({
2900
+ message: { type: "fs.entry.create.request", ...input },
2901
+ });
2902
+ }
2903
+ async renameFileEntry(input) {
2904
+ return this.sendNamespacedCorrelatedSessionRequest({
2905
+ message: { type: "fs.entry.rename.request", ...input },
2906
+ });
2907
+ }
2908
+ async duplicateFileEntry(input) {
2909
+ return this.sendNamespacedCorrelatedSessionRequest({
2910
+ message: { type: "fs.entry.duplicate.request", ...input },
2911
+ });
2912
+ }
2913
+ async deleteFileEntry(input) {
2914
+ return this.sendNamespacedCorrelatedSessionRequest({
2915
+ message: { type: "fs.entry.delete.request", ...input },
2916
+ });
2917
+ }
2918
+ async checkoutDiscardChanges(cwd, input) {
2919
+ return this.sendNamespacedCorrelatedSessionRequest({
2920
+ message: { type: "checkout.discard_changes.request", cwd, paths: input.paths },
2921
+ });
2922
+ }
2854
2923
  async uploadFile(input) {
2855
2924
  const bytes = asUint8Array(input.bytes);
2856
2925
  if (!bytes) {
@@ -3436,86 +3505,6 @@ export class DaemonClient {
3436
3505
  options: { skipQueue: true },
3437
3506
  });
3438
3507
  }
3439
- async createChatRoom(options) {
3440
- return this.sendCorrelatedSessionRequest({
3441
- requestId: options.requestId,
3442
- message: {
3443
- type: "chat/create",
3444
- name: options.name,
3445
- ...(options.purpose ? { purpose: options.purpose } : {}),
3446
- },
3447
- responseType: "chat/create/response",
3448
- });
3449
- }
3450
- async listChatRooms(requestId) {
3451
- return this.sendCorrelatedSessionRequest({
3452
- requestId,
3453
- message: {
3454
- type: "chat/list",
3455
- },
3456
- responseType: "chat/list/response",
3457
- });
3458
- }
3459
- async inspectChatRoom(options) {
3460
- return this.sendCorrelatedSessionRequest({
3461
- requestId: options.requestId,
3462
- message: {
3463
- type: "chat/inspect",
3464
- room: options.room,
3465
- },
3466
- responseType: "chat/inspect/response",
3467
- });
3468
- }
3469
- async deleteChatRoom(options) {
3470
- return this.sendCorrelatedSessionRequest({
3471
- requestId: options.requestId,
3472
- message: {
3473
- type: "chat/delete",
3474
- room: options.room,
3475
- },
3476
- responseType: "chat/delete/response",
3477
- });
3478
- }
3479
- async postChatMessage(options) {
3480
- return this.sendCorrelatedSessionRequest({
3481
- requestId: options.requestId,
3482
- message: {
3483
- type: "chat/post",
3484
- room: options.room,
3485
- body: options.body,
3486
- ...(options.authorAgentId ? { authorAgentId: options.authorAgentId } : {}),
3487
- ...(options.replyToMessageId ? { replyToMessageId: options.replyToMessageId } : {}),
3488
- },
3489
- responseType: "chat/post/response",
3490
- });
3491
- }
3492
- async readChatMessages(options) {
3493
- return this.sendCorrelatedSessionRequest({
3494
- requestId: options.requestId,
3495
- message: {
3496
- type: "chat/read",
3497
- room: options.room,
3498
- ...(typeof options.limit === "number" ? { limit: options.limit } : {}),
3499
- ...(options.since ? { since: options.since } : {}),
3500
- ...(options.authorAgentId ? { authorAgentId: options.authorAgentId } : {}),
3501
- },
3502
- responseType: "chat/read/response",
3503
- timeout: options.timeout,
3504
- });
3505
- }
3506
- async waitForChatMessages(options) {
3507
- return this.sendCorrelatedSessionRequest({
3508
- requestId: options.requestId,
3509
- message: {
3510
- type: "chat/wait",
3511
- room: options.room,
3512
- ...(options.afterMessageId ? { afterMessageId: options.afterMessageId } : {}),
3513
- ...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
3514
- },
3515
- responseType: "chat/wait/response",
3516
- timeout: (options.timeoutMs ?? 0) + 10000,
3517
- });
3518
- }
3519
3508
  async scheduleCreate(options) {
3520
3509
  return this.sendCorrelatedSessionRequest({
3521
3510
  requestId: options.requestId,
@@ -3617,76 +3606,6 @@ export class DaemonClient {
3617
3606
  responseType: "schedule/update/response",
3618
3607
  });
3619
3608
  }
3620
- async loopRun(options) {
3621
- return this.sendCorrelatedSessionRequest({
3622
- requestId: options.requestId,
3623
- message: {
3624
- type: "loop/run",
3625
- prompt: options.prompt,
3626
- cwd: options.cwd,
3627
- ...(options.provider ? { provider: options.provider } : {}),
3628
- ...(options.model ? { model: options.model } : {}),
3629
- ...(options.modeId ? { modeId: options.modeId } : {}),
3630
- ...(options.verifierProvider ? { verifierProvider: options.verifierProvider } : {}),
3631
- ...(options.verifierModel ? { verifierModel: options.verifierModel } : {}),
3632
- ...(options.verifierModeId ? { verifierModeId: options.verifierModeId } : {}),
3633
- ...(options.verifyPrompt ? { verifyPrompt: options.verifyPrompt } : {}),
3634
- ...(options.verifyChecks && options.verifyChecks.length > 0
3635
- ? { verifyChecks: options.verifyChecks }
3636
- : {}),
3637
- ...(options.name ? { name: options.name } : {}),
3638
- ...(typeof options.sleepMs === "number" ? { sleepMs: options.sleepMs } : {}),
3639
- ...(typeof options.maxIterations === "number"
3640
- ? { maxIterations: options.maxIterations }
3641
- : {}),
3642
- ...(typeof options.maxTimeMs === "number" ? { maxTimeMs: options.maxTimeMs } : {}),
3643
- },
3644
- responseType: "loop/run/response",
3645
- });
3646
- }
3647
- async loopList(requestId) {
3648
- return this.sendCorrelatedSessionRequest({
3649
- requestId,
3650
- message: {
3651
- type: "loop/list",
3652
- },
3653
- responseType: "loop/list/response",
3654
- });
3655
- }
3656
- async loopInspect(options) {
3657
- const normalized = typeof options === "string" ? { id: options } : options;
3658
- return this.sendCorrelatedSessionRequest({
3659
- requestId: normalized.requestId,
3660
- message: {
3661
- type: "loop/inspect",
3662
- id: normalized.id,
3663
- },
3664
- responseType: "loop/inspect/response",
3665
- });
3666
- }
3667
- async loopLogs(options, afterSeq) {
3668
- const normalized = typeof options === "string" ? { id: options, afterSeq } : options;
3669
- return this.sendCorrelatedSessionRequest({
3670
- requestId: normalized.requestId,
3671
- message: {
3672
- type: "loop/logs",
3673
- id: normalized.id,
3674
- ...(typeof normalized.afterSeq === "number" ? { afterSeq: normalized.afterSeq } : {}),
3675
- },
3676
- responseType: "loop/logs/response",
3677
- });
3678
- }
3679
- async loopStop(options) {
3680
- const normalized = typeof options === "string" ? { id: options } : options;
3681
- return this.sendCorrelatedSessionRequest({
3682
- requestId: normalized.requestId,
3683
- message: {
3684
- type: "loop/stop",
3685
- id: normalized.id,
3686
- },
3687
- responseType: "loop/stop/response",
3688
- });
3689
- }
3690
3609
  onTerminalStreamEvent(handler) {
3691
3610
  return this.terminalStreams.onEvent(handler);
3692
3611
  }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,5 @@
1
- import type { AgentSnapshotPayload, CreateAgentRequestMessage, FetchWorkspacesRequestMessage, FetchWorkspacesResponseMessage, GetProvidersSnapshotResponseMessage, ListAvailableProvidersResponse, ListProviderFeaturesRequestMessage, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ListProviderModesResponseMessage, MutableDaemonConfig, MutableDaemonConfigPatch, ProviderDiagnosticResponseMessage, ProjectPlacementPayload, RefreshProvidersSnapshotResponseMessage, SendAgentMessageRequest, SessionOutboundMessage, WorkspaceDescriptorPayload } from "@getpaseo/protocol/messages";
2
- import { DaemonClient } from "./daemon-client.js";
3
- import type { FetchAgentTimelineCursor, FetchAgentTimelineDirection, FetchAgentTimelinePayload, FetchAgentTimelineProjection } from "./daemon-client.js";
4
- export { DaemonClient };
5
- export type { DaemonClientConfig, DaemonEvent, BrowserAutomationExecuteRequestMessage, BrowserAutomationExecuteResponseMessage, WebSocketFactory, WebSocketLike, } from "./daemon-client.js";
1
+ import type { AgentSnapshotPayload, CreateAgentRequestMessage, FetchWorkspacesRequestMessage, FetchWorkspacesResponseMessage, GetProvidersSnapshotResponseMessage, ListAvailableProvidersResponse, ListProviderFeaturesRequestMessage, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ListProviderModesResponseMessage, MutableDaemonConfig, MutableDaemonConfigPatch, ProviderDiagnosticResponseMessage, ProjectPlacementPayload, RefreshProvidersSnapshotResponseMessage, SendAgentMessageRequest, SessionOutboundMessage, WorkspaceDescriptorPayload, WorkspaceCreateRequest } from "@getpaseo/protocol/messages";
2
+ import type { FetchAgentsEntry, FetchAgentsOptions, FetchAgentsPageInfo, FetchAgentTimelineCursor, FetchAgentTimelineDirection, FetchAgentTimelinePayload, FetchAgentTimelineProjection, WaitForFinishResult } from "./daemon-client.js";
6
3
  export type ConnectionState = {
7
4
  status: "idle";
8
5
  } | {
@@ -29,7 +26,6 @@ export interface PaseoClientConfig {
29
26
  runtimeGeneration?: number | null;
30
27
  password?: string;
31
28
  authHeader?: string;
32
- headers?: Record<string, string>;
33
29
  suppressSendErrors?: boolean;
34
30
  logger?: PaseoLogger;
35
31
  connectTimeoutMs?: number;
@@ -47,6 +43,13 @@ export interface PaseoClientConfig {
47
43
  }
48
44
  export type PaseoWorkspace = WorkspaceDescriptorPayload;
49
45
  export type PaseoAgent = AgentSnapshotPayload;
46
+ export type PaseoAgentListOptions = FetchAgentsOptions;
47
+ export interface PaseoAgentListResult {
48
+ requestId: string;
49
+ subscriptionId?: string | null;
50
+ entries: FetchAgentsEntry[];
51
+ pageInfo: FetchAgentsPageInfo;
52
+ }
50
53
  export type PaseoWorkspaceListOptions = Omit<FetchWorkspacesRequestMessage, "type" | "requestId"> & {
51
54
  requestId?: string;
52
55
  };
@@ -60,11 +63,9 @@ export interface PaseoWorkspaceOpenOptions {
60
63
  cwd: string;
61
64
  requestId?: string;
62
65
  }
63
- export interface PaseoWorkspaceOpenResult {
64
- requestId: string;
65
- workspace: PaseoWorkspaceHandle | null;
66
- error: string | null;
67
- }
66
+ export type PaseoWorkspaceCreateOptions = Omit<WorkspaceCreateRequest, "type" | "requestId"> & {
67
+ requestId?: string;
68
+ };
68
69
  export interface PaseoWorkspaceArchiveResult {
69
70
  requestId: string;
70
71
  workspaceId: string;
@@ -75,19 +76,17 @@ export type PaseoWorkspaceUpdate = Extract<SessionOutboundMessage, {
75
76
  type: "workspace_update";
76
77
  }>["payload"];
77
78
  export type PaseoWorkspaceUpdateHandler = (update: PaseoWorkspaceUpdate) => void;
78
- /**
79
- * A handle is a stable typed reference to a daemon resource. Its identity is the
80
- * daemon id, and `latest()` only returns the most recent snapshot this handle has
81
- * seen through construction, `refetch()`, or this handle's local subscription.
82
- */
83
79
  export interface PaseoWorkspaceHandle {
84
80
  readonly id: string;
85
- latest(): PaseoWorkspace | null;
86
- /**
87
- * Fetches a fresh workspace snapshot through the existing workspace list RPC,
88
- * exact-matches this handle id from the result, and updates `latest()`.
89
- */
90
- refetch(options?: {
81
+ readonly projectId: string | null;
82
+ readonly directory: string | null;
83
+ readonly name: string | null;
84
+ readonly status: PaseoWorkspace["status"] | null;
85
+ readonly agents: {
86
+ create(options: PaseoWorkspaceAgentCreateOptions): Promise<PaseoAgentHandle>;
87
+ };
88
+ current(): PaseoWorkspace | null;
89
+ refresh(options?: {
91
90
  requestId?: string;
92
91
  }): Promise<PaseoWorkspace | null>;
93
92
  archive(requestId?: string): Promise<PaseoWorkspaceArchiveResult>;
@@ -102,8 +101,8 @@ export interface PaseoWorkspaceHandle {
102
101
  export interface PaseoWorkspaceActions {
103
102
  list(options?: PaseoWorkspaceListOptions): Promise<PaseoWorkspaceListResult>;
104
103
  ref(workspace: string | PaseoWorkspace): PaseoWorkspaceHandle;
105
- open(input: string | PaseoWorkspaceOpenOptions, requestId?: string): Promise<PaseoWorkspaceOpenResult>;
106
- create(input: string | PaseoWorkspaceOpenOptions, requestId?: string): Promise<PaseoWorkspaceOpenResult>;
104
+ open(input: string | PaseoWorkspaceOpenOptions, requestId?: string): Promise<PaseoWorkspaceHandle>;
105
+ create(options: PaseoWorkspaceCreateOptions): Promise<PaseoWorkspaceHandle>;
107
106
  archive(workspace: string | PaseoWorkspaceHandle, requestId?: string): Promise<PaseoWorkspaceArchiveResult>;
108
107
  /**
109
108
  * Local event subscription over the low-level driver's workspace_update stream.
@@ -112,23 +111,38 @@ export interface PaseoWorkspaceActions {
112
111
  subscribe(handler: PaseoWorkspaceUpdateHandler): () => void;
113
112
  }
114
113
  type PaseoAgentSessionConfig = CreateAgentRequestMessage["config"];
115
- type PaseoAgentProvider = PaseoAgentSessionConfig["provider"];
116
- type PaseoAgentConfigOverrides = Partial<Omit<PaseoAgentSessionConfig, "provider" | "cwd">>;
117
- export interface PaseoAgentCreateOptions extends PaseoAgentConfigOverrides {
118
- config?: PaseoAgentSessionConfig;
119
- provider?: CreateAgentRequestMessage["config"]["provider"];
120
- cwd?: string;
121
- workspaceId?: string;
122
- callerAgentId?: string;
123
- initialPrompt?: string;
114
+ export type PaseoAgentProvider = PaseoAgentSessionConfig["provider"];
115
+ export type PaseoProviderFeatureValues = Record<string, unknown>;
116
+ export interface PaseoAgentConfig {
117
+ /** Provider and model in `provider/model` format. */
118
+ provider: string;
119
+ modeId?: PaseoAgentSessionConfig["modeId"];
120
+ thinkingOptionId?: PaseoAgentSessionConfig["thinkingOptionId"];
121
+ featureValues?: PaseoProviderFeatureValues;
122
+ /** JSON-safe provider-native settings, validated by the selected provider. */
123
+ options?: PaseoAgentSessionConfig["providerOptions"];
124
+ systemPrompt?: PaseoAgentSessionConfig["systemPrompt"];
125
+ toolPolicy?: PaseoAgentSessionConfig["toolPolicy"];
126
+ mcpServers?: PaseoAgentSessionConfig["mcpServers"];
127
+ }
128
+ export interface PaseoAgentCreateOptions {
129
+ config: PaseoAgentConfig;
130
+ cwd: string;
131
+ parent?: string | PaseoAgentHandle;
132
+ title?: PaseoAgentSessionConfig["title"];
133
+ env?: CreateAgentRequestMessage["env"];
134
+ prompt?: string;
124
135
  clientMessageId?: string;
125
136
  outputSchema?: Record<string, unknown>;
126
137
  images?: CreateAgentRequestMessage["images"];
127
138
  attachments?: CreateAgentRequestMessage["attachments"];
128
139
  git?: CreateAgentRequestMessage["git"];
140
+ worktree?: CreateAgentRequestMessage["worktree"];
141
+ autoArchive?: CreateAgentRequestMessage["autoArchive"];
129
142
  requestId?: string;
130
143
  labels?: Record<string, string>;
131
144
  }
145
+ export type PaseoWorkspaceAgentCreateOptions = Omit<PaseoAgentCreateOptions, "cwd">;
132
146
  export interface PaseoAgentRefetchResult {
133
147
  agent: PaseoAgent;
134
148
  project: ProjectPlacementPayload | null;
@@ -148,6 +162,10 @@ export interface PaseoAgentSendOptions {
148
162
  }>;
149
163
  attachments?: SendAgentMessageRequest["attachments"];
150
164
  }
165
+ export interface PaseoAgentRunOptions extends PaseoAgentSendOptions {
166
+ timeoutMs?: number;
167
+ }
168
+ export type PaseoAgentRunResult = WaitForFinishResult;
151
169
  export type PaseoAgentUpdate = Extract<SessionOutboundMessage, {
152
170
  type: "agent_update";
153
171
  }>["payload"];
@@ -158,8 +176,8 @@ export type PaseoAgentUpdateHandler = (update: PaseoAgentUpdate) => void;
158
176
  export interface PaseoAgentTimelineHandle {
159
177
  /**
160
178
  * Fetches a fresh timeline page through the existing daemon RPC. If the daemon
161
- * includes an agent snapshot in the response, the parent handle's `latest()`
162
- * is updated to that snapshot.
179
+ * includes an agent snapshot in the response, the parent handle is updated to
180
+ * that value.
163
181
  */
164
182
  refetch(options?: PaseoAgentTimelineRefetchOptions): Promise<FetchAgentTimelinePayload>;
165
183
  /**
@@ -168,18 +186,19 @@ export interface PaseoAgentTimelineHandle {
168
186
  */
169
187
  subscribe(handler: (event: PaseoAgentStream) => void): () => void;
170
188
  }
171
- /**
172
- * Agent handles follow the same identity/snapshot rule as workspace handles:
173
- * `id` is stable, while `latest()` is only the newest snapshot observed by this
174
- * handle through construction, `refetch()`, timeline refetch, archive, or local
175
- * agent_update subscription.
176
- */
177
189
  export interface PaseoAgentHandle {
178
190
  readonly id: string;
191
+ readonly workspaceId: string | null;
192
+ readonly cwd: string | null;
193
+ readonly status: PaseoAgent["status"] | null;
179
194
  readonly timeline: PaseoAgentTimelineHandle;
180
- latest(): PaseoAgent | null;
181
- refetch(requestId?: string): Promise<PaseoAgentRefetchResult | null>;
195
+ current(): PaseoAgent | null;
196
+ refresh(requestId?: string): Promise<PaseoAgentRefetchResult | null>;
182
197
  send(text: string, options?: PaseoAgentSendOptions): Promise<void>;
198
+ /** Sends a prompt and resolves when that turn finishes or needs attention. */
199
+ run(text: string, options?: PaseoAgentRunOptions): Promise<PaseoAgentRunResult>;
200
+ /** Waits for the current turn, including one started with `prompt`. */
201
+ waitForFinish(timeoutMs?: number): Promise<PaseoAgentRunResult>;
183
202
  archive(): Promise<{
184
203
  archivedAt: string;
185
204
  }>;
@@ -187,6 +206,7 @@ export interface PaseoAgentHandle {
187
206
  subscribe(handler: (update: PaseoAgentUpdate) => void): () => void;
188
207
  }
189
208
  export interface PaseoAgentActions {
209
+ list(options?: PaseoAgentListOptions): Promise<PaseoAgentListResult>;
190
210
  ref(agent: string | PaseoAgent): PaseoAgentHandle;
191
211
  create(options: PaseoAgentCreateOptions): Promise<PaseoAgentHandle>;
192
212
  /**
@@ -195,19 +215,13 @@ export interface PaseoAgentActions {
195
215
  */
196
216
  subscribe(handler: PaseoAgentUpdateHandler): () => void;
197
217
  }
198
- export interface PaseoProviderConfig extends PaseoProviderConfigInput {
199
- provider: PaseoAgentProvider;
200
- }
201
- export type PaseoProviderFeatureValues = Record<string, unknown>;
202
- export interface PaseoProviderConfigInput {
203
- model?: string;
204
- modeId?: string;
205
- thinkingOptionId?: string;
206
- featureValues?: PaseoProviderFeatureValues;
207
- }
208
218
  export type PaseoProviderModelsResult = ListProviderModelsResponseMessage["payload"];
209
219
  export type PaseoProviderModesResult = ListProviderModesResponseMessage["payload"];
210
- export type PaseoProviderFeaturesInput = ListProviderFeaturesRequestMessage["draftConfig"];
220
+ type PaseoProviderFeaturesDraft = ListProviderFeaturesRequestMessage["draftConfig"];
221
+ export interface PaseoProviderFeaturesInput extends Omit<PaseoProviderFeaturesDraft, "provider" | "model"> {
222
+ /** Provider and model in `provider/model` format. */
223
+ provider: string;
224
+ }
211
225
  export type PaseoProviderFeaturesResult = ListProviderFeaturesResponseMessage["payload"];
212
226
  export type PaseoProviderAvailabilityResult = ListAvailableProvidersResponse["payload"];
213
227
  export type PaseoProviderSnapshotResult = GetProvidersSnapshotResponseMessage["payload"];
@@ -225,12 +239,10 @@ export interface PaseoProviderRefreshOptions {
225
239
  providers?: PaseoAgentProvider[];
226
240
  requestId?: string;
227
241
  }
242
+ export interface PaseoProviderWaitOptions extends PaseoProviderListOptions {
243
+ timeoutMs?: number;
244
+ }
228
245
  export interface PaseoProviderActions {
229
- codex(input?: PaseoProviderConfigInput): PaseoProviderConfig;
230
- claude(input?: PaseoProviderConfigInput): PaseoProviderConfig;
231
- opencode(input?: PaseoProviderConfigInput): PaseoProviderConfig;
232
- copilot(input?: PaseoProviderConfigInput): PaseoProviderConfig;
233
- config(provider: PaseoAgentProvider, input?: PaseoProviderConfigInput): PaseoProviderConfig;
234
246
  listModels(provider: PaseoAgentProvider, options?: PaseoProviderListOptions): Promise<PaseoProviderModelsResult>;
235
247
  listModes(provider: PaseoAgentProvider, options?: PaseoProviderListOptions): Promise<PaseoProviderModesResult>;
236
248
  listFeatures(draftConfig: PaseoProviderFeaturesInput, options?: {
@@ -240,6 +252,8 @@ export interface PaseoProviderActions {
240
252
  requestId?: string;
241
253
  }): Promise<PaseoProviderAvailabilityResult>;
242
254
  snapshot(options?: PaseoProviderListOptions): Promise<PaseoProviderSnapshotResult>;
255
+ /** Resolves after the daemon's lazy provider discovery has finished. */
256
+ waitForReady(options?: PaseoProviderWaitOptions): Promise<PaseoProviderSnapshotResult>;
243
257
  refresh(options?: PaseoProviderRefreshOptions): Promise<PaseoProviderRefreshResult>;
244
258
  diagnostic(provider: PaseoAgentProvider, options?: {
245
259
  requestId?: string;
@@ -279,4 +293,5 @@ export interface PaseoClient {
279
293
  getConnectionState(): ConnectionState;
280
294
  }
281
295
  export declare function createPaseoClient(config: PaseoClientConfig): PaseoClient;
296
+ export {};
282
297
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,45 +1,73 @@
1
1
  import { DaemonClient } from "./daemon-client.js";
2
- export { DaemonClient };
2
+ /**
3
+ * Coding turns routinely run for minutes, so the handle waits far longer than
4
+ * the transport's own conservative default.
5
+ */
6
+ const DEFAULT_WAIT_FOR_FINISH_MS = 10 * 60000;
3
7
  export function createPaseoClient(config) {
4
8
  const daemonClient = new DaemonClient({
5
9
  ...config,
6
10
  clientId: config.clientId ?? createGeneratedClientId(),
7
11
  clientType: "cli",
8
12
  });
9
- const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient);
10
13
  const createAgentHandle = createAgentHandleFactory(daemonClient);
14
+ const createAgent = async (options, placement) => {
15
+ const { config: agentConfig, cwd, parent, title, prompt, ...requestOptions } = options;
16
+ const { provider: providerModel, options: providerOptions, ...runtimeConfig } = agentConfig;
17
+ const { provider, model } = parseProviderModel(providerModel);
18
+ const effectiveCwd = placement?.cwd ?? cwd;
19
+ const agent = await daemonClient.createAgent({
20
+ ...requestOptions,
21
+ config: {
22
+ ...runtimeConfig,
23
+ provider,
24
+ model,
25
+ cwd: effectiveCwd,
26
+ ...(title !== undefined ? { title } : {}),
27
+ ...(providerOptions !== undefined ? { providerOptions } : {}),
28
+ },
29
+ ...(placement ? { workspaceId: placement.workspaceId } : {}),
30
+ ...(parent ? { callerAgentId: resolveAgentId(parent) } : {}),
31
+ ...(prompt !== undefined ? { initialPrompt: prompt } : {}),
32
+ });
33
+ return createAgentHandle(agent);
34
+ };
35
+ const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient, createAgent);
11
36
  return {
12
37
  workspaces: {
13
38
  list: (options) => daemonClient.fetchWorkspaces(options),
14
39
  ref: (workspace) => createWorkspaceHandle(workspace),
15
40
  open: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
16
- create: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
41
+ create: async ({ requestId, ...options }) => {
42
+ const result = await daemonClient.createWorkspace(options, requestId);
43
+ if (result.error || !result.workspace) {
44
+ throw new Error(result.error ?? "The daemon did not create a workspace");
45
+ }
46
+ return createWorkspaceHandle(result.workspace);
47
+ },
17
48
  archive: (workspace, requestId) => daemonClient.archiveWorkspace(resolveWorkspaceId(workspace), requestId),
18
49
  subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
19
50
  handler(message.payload);
20
51
  }),
21
52
  },
22
53
  agents: {
54
+ list: (options) => daemonClient.fetchAgents(options),
23
55
  ref: (agent) => createAgentHandle(agent),
24
- create: async (options) => {
25
- const agent = await daemonClient.createAgent(options);
26
- return createAgentHandle(agent);
27
- },
56
+ create: (options) => createAgent(options),
28
57
  subscribe: (handler) => daemonClient.on("agent_update", (message) => {
29
58
  handler(message.payload);
30
59
  }),
31
60
  },
32
61
  providers: {
33
- codex: (input) => providerConfig("codex", input),
34
- claude: (input) => providerConfig("claude", input),
35
- opencode: (input) => providerConfig("opencode", input),
36
- copilot: (input) => providerConfig("copilot", input),
37
- config: (provider, input) => providerConfig(provider, input),
38
62
  listModels: (provider, options) => daemonClient.listProviderModels(provider, options),
39
63
  listModes: (provider, options) => daemonClient.listProviderModes(provider, options),
40
- listFeatures: (draftConfig, options) => daemonClient.listProviderFeatures(draftConfig, options),
64
+ listFeatures: ({ provider: providerModel, ...draftConfig }, options) => {
65
+ const { provider, model } = parseProviderModel(providerModel);
66
+ return daemonClient.listProviderFeatures({ ...draftConfig, provider, model }, options);
67
+ },
41
68
  listAvailable: (options) => daemonClient.listAvailableProviders(options),
42
69
  snapshot: (options) => daemonClient.getProvidersSnapshot(options),
70
+ waitForReady: (options) => waitForProvidersReady(daemonClient, options),
43
71
  refresh: (options) => daemonClient.refreshProvidersSnapshot(options),
44
72
  diagnostic: (provider, options) => daemonClient.getProviderDiagnostic(provider, options),
45
73
  subscribe: (handler) => daemonClient.on("providers_snapshot_update", (message) => {
@@ -56,39 +84,69 @@ export function createPaseoClient(config) {
56
84
  getConnectionState: () => daemonClient.getConnectionState(),
57
85
  };
58
86
  }
59
- function createWorkspaceHandleFactory(daemonClient) {
87
+ function createWorkspaceHandleFactory(daemonClient, createAgent) {
60
88
  return (workspace) => {
61
89
  const id = typeof workspace === "string" ? workspace : workspace.id;
62
- let latest = typeof workspace === "string" ? null : workspace;
63
- return {
64
- id,
65
- latest: () => latest,
66
- refetch: async (options) => {
67
- // Best-effort: fetches one page and matches by id client-side, so a workspace beyond
68
- // the first page won't be found. TODO: add a "get workspace by id" lookup and resolve
69
- // by exact id instead of paging.
90
+ let current = typeof workspace === "string" ? null : workspace;
91
+ const refresh = async (options) => {
92
+ let cursor;
93
+ let requestId = options?.requestId;
94
+ do {
70
95
  const result = await daemonClient.fetchWorkspaces({
71
- requestId: options?.requestId,
72
- page: { limit: 25 },
96
+ requestId,
97
+ page: { limit: 200, ...(cursor ? { cursor } : {}) },
73
98
  });
74
- latest = result.entries.find((entry) => entry.id === id) ?? null;
75
- return latest;
99
+ const match = result.entries.find((entry) => entry.id === id);
100
+ if (match) {
101
+ current = match;
102
+ return current;
103
+ }
104
+ cursor = result.pageInfo.nextCursor ?? undefined;
105
+ requestId = undefined;
106
+ } while (cursor);
107
+ current = null;
108
+ return current;
109
+ };
110
+ return {
111
+ id,
112
+ get projectId() {
113
+ return current?.projectId ?? null;
114
+ },
115
+ get directory() {
116
+ return current?.workspaceDirectory ?? null;
117
+ },
118
+ get name() {
119
+ return current?.name ?? null;
120
+ },
121
+ get status() {
122
+ return current?.status ?? null;
123
+ },
124
+ agents: {
125
+ create: async (options) => {
126
+ const snapshot = current ?? (await refresh());
127
+ if (!snapshot?.workspaceDirectory) {
128
+ throw new Error(`Workspace ${id} has no available directory`);
129
+ }
130
+ return createAgent({ ...options, cwd: snapshot.workspaceDirectory }, { workspaceId: id, cwd: snapshot.workspaceDirectory });
131
+ },
76
132
  },
133
+ current: () => current,
134
+ refresh,
77
135
  archive: async (requestId) => {
78
136
  const result = await daemonClient.archiveWorkspace(id, requestId);
79
- if (latest) {
80
- latest = { ...latest, archivingAt: result.archivedAt };
137
+ if (current) {
138
+ current = { ...current, archivingAt: result.archivedAt };
81
139
  }
82
140
  return result;
83
141
  },
84
142
  subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
85
143
  const update = message.payload;
86
144
  if (update.kind === "upsert" && update.workspace.id === id) {
87
- latest = update.workspace;
145
+ current = update.workspace;
88
146
  handler(update);
89
147
  }
90
148
  if (update.kind === "remove" && update.id === id) {
91
- latest = null;
149
+ current = null;
92
150
  handler(update);
93
151
  }
94
152
  }),
@@ -98,14 +156,14 @@ function createWorkspaceHandleFactory(daemonClient) {
98
156
  function createAgentHandleFactory(daemonClient) {
99
157
  return (agent) => {
100
158
  const id = typeof agent === "string" ? agent : agent.id;
101
- let latest = typeof agent === "string" ? null : agent;
159
+ let current = typeof agent === "string" ? null : agent;
102
160
  const handle = {
103
161
  id,
104
162
  timeline: {
105
163
  refetch: async (options) => {
106
164
  const result = await daemonClient.fetchAgentTimeline(id, options);
107
165
  if (result.agent) {
108
- latest = result.agent;
166
+ current = result.agent;
109
167
  }
110
168
  return result;
111
169
  },
@@ -115,19 +173,44 @@ function createAgentHandleFactory(daemonClient) {
115
173
  }
116
174
  }),
117
175
  },
118
- latest: () => latest,
119
- refetch: async (requestId) => {
176
+ get workspaceId() {
177
+ return current?.workspaceId ?? null;
178
+ },
179
+ get cwd() {
180
+ return current?.cwd ?? null;
181
+ },
182
+ get status() {
183
+ return current?.status ?? null;
184
+ },
185
+ current: () => current,
186
+ refresh: async (requestId) => {
120
187
  const result = await daemonClient.fetchAgent({ agentId: id, requestId });
121
- latest = result?.agent ?? null;
188
+ current = result?.agent ?? null;
122
189
  return result;
123
190
  },
124
191
  send: async (text, options) => {
125
192
  await daemonClient.sendAgentMessage(id, text, options);
126
193
  },
194
+ run: async (text, options) => {
195
+ const { timeoutMs, ...sendOptions } = options ?? {};
196
+ await daemonClient.sendAgentMessage(id, text, sendOptions);
197
+ const result = await daemonClient.waitForFinish(id, timeoutMs ?? DEFAULT_WAIT_FOR_FINISH_MS);
198
+ if (result.final) {
199
+ current = result.final;
200
+ }
201
+ return result;
202
+ },
203
+ waitForFinish: async (timeoutMs) => {
204
+ const result = await daemonClient.waitForFinish(id, timeoutMs ?? DEFAULT_WAIT_FOR_FINISH_MS);
205
+ if (result.final) {
206
+ current = result.final;
207
+ }
208
+ return result;
209
+ },
127
210
  archive: async () => {
128
211
  const result = await daemonClient.archiveAgent(id);
129
- if (latest) {
130
- latest = { ...latest, archivedAt: result.archivedAt };
212
+ if (current) {
213
+ current = { ...current, archivedAt: result.archivedAt };
131
214
  }
132
215
  return result;
133
216
  },
@@ -137,11 +220,11 @@ function createAgentHandleFactory(daemonClient) {
137
220
  subscribe: (handler) => daemonClient.on("agent_update", (message) => {
138
221
  const update = message.payload;
139
222
  if (update.kind === "upsert" && update.agent.id === id) {
140
- latest = update.agent;
223
+ current = update.agent;
141
224
  handler(update);
142
225
  }
143
226
  if (update.kind === "remove" && update.agentId === id) {
144
- latest = null;
227
+ current = null;
145
228
  handler(update);
146
229
  }
147
230
  }),
@@ -152,23 +235,99 @@ function createAgentHandleFactory(daemonClient) {
152
235
  async function openWorkspace(daemonClient, createWorkspaceHandle, input, requestId) {
153
236
  const options = typeof input === "string" ? { cwd: input, requestId } : input;
154
237
  const result = await daemonClient.openProject(options.cwd, options.requestId);
155
- return {
156
- ...result,
157
- workspace: result.workspace ? createWorkspaceHandle(result.workspace) : null,
158
- };
238
+ if (result.error || !result.workspace) {
239
+ throw new Error(result.error ?? `The daemon did not open a workspace for ${options.cwd}`);
240
+ }
241
+ return createWorkspaceHandle(result.workspace);
159
242
  }
160
243
  function resolveWorkspaceId(workspace) {
161
244
  return typeof workspace === "string" ? workspace : workspace.id;
162
245
  }
163
- function providerConfig(provider, input = {}) {
246
+ function resolveAgentId(agent) {
247
+ return typeof agent === "string" ? agent : agent.id;
248
+ }
249
+ function parseProviderModel(selection) {
250
+ const separator = selection.indexOf("/");
251
+ if (separator <= 0 || separator === selection.length - 1) {
252
+ throw new Error('Expected config.provider in "provider/model" format');
253
+ }
164
254
  return {
165
- provider,
166
- ...(input.model !== undefined ? { model: input.model } : {}),
167
- ...(input.modeId !== undefined ? { modeId: input.modeId } : {}),
168
- ...(input.thinkingOptionId !== undefined ? { thinkingOptionId: input.thinkingOptionId } : {}),
169
- ...(input.featureValues !== undefined ? { featureValues: input.featureValues } : {}),
255
+ provider: selection.slice(0, separator),
256
+ model: selection.slice(separator + 1),
170
257
  };
171
258
  }
259
+ function waitForProvidersReady(daemonClient, options = {}) {
260
+ // COMPAT(providersSnapshotCwd): added in v0.3.2, remove gate after 2027-02-10.
261
+ if (daemonClient.getLastServerInfoMessage()?.features?.providersSnapshotCwd !== true) {
262
+ return Promise.reject(new Error("Update the host to wait for provider discovery."));
263
+ }
264
+ const { timeoutMs = 60000, ...snapshotOptions } = options;
265
+ return new Promise((resolve, reject) => {
266
+ let settled = false;
267
+ let requestId = null;
268
+ let snapshotCwd;
269
+ const pendingUpdates = new Map();
270
+ let latestEntries = [];
271
+ const cleanup = () => {
272
+ clearTimeout(timeout);
273
+ unsubscribe();
274
+ };
275
+ const finish = (snapshot) => {
276
+ if (settled)
277
+ return;
278
+ settled = true;
279
+ cleanup();
280
+ resolve(snapshot);
281
+ };
282
+ const fail = (error) => {
283
+ if (settled)
284
+ return;
285
+ settled = true;
286
+ cleanup();
287
+ reject(error instanceof Error ? error : new Error(String(error)));
288
+ };
289
+ const updateMatches = (update) => update.cwd === snapshotCwd;
290
+ const unsubscribe = daemonClient.on("providers_snapshot_update", (message) => {
291
+ const update = message.payload;
292
+ if (!requestId) {
293
+ pendingUpdates.set(update.cwd, update);
294
+ return;
295
+ }
296
+ if (!updateMatches(update))
297
+ return;
298
+ latestEntries = update.entries;
299
+ if (update.entries.some((entry) => entry.status === "loading"))
300
+ return;
301
+ finish({ ...update, requestId });
302
+ });
303
+ const timeout = setTimeout(() => {
304
+ const loading = latestEntries
305
+ .filter((entry) => entry.status === "loading")
306
+ .map((entry) => entry.provider)
307
+ .join(", ");
308
+ fail(new Error(loading
309
+ ? `Timed out waiting for providers: ${loading}`
310
+ : "Timed out waiting for provider discovery"));
311
+ }, timeoutMs);
312
+ void daemonClient
313
+ .getProvidersSnapshot(snapshotOptions)
314
+ .then((snapshot) => {
315
+ requestId = snapshot.requestId;
316
+ snapshotCwd = snapshot.cwd;
317
+ latestEntries = snapshot.entries;
318
+ if (!snapshot.entries.some((entry) => entry.status === "loading")) {
319
+ finish(snapshot);
320
+ return;
321
+ }
322
+ const pendingUpdate = pendingUpdates.get(snapshotCwd);
323
+ if (pendingUpdate && !pendingUpdate.entries.some((entry) => entry.status === "loading")) {
324
+ finish({ ...pendingUpdate, requestId });
325
+ }
326
+ return undefined;
327
+ })
328
+ .catch(fail);
329
+ });
330
+ }
172
331
  function createGeneratedClientId() {
173
332
  const randomId = typeof globalThis.crypto?.randomUUID === "function"
174
333
  ? globalThis.crypto.randomUUID()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/client",
3
- "version": "0.3.0",
3
+ "version": "0.4.0-beta.1",
4
4
  "description": "Paseo client SDK package",
5
5
  "files": [
6
6
  "dist",
@@ -39,8 +39,8 @@
39
39
  "test": "vitest run"
40
40
  },
41
41
  "dependencies": {
42
- "@getpaseo/protocol": "0.3.0",
43
- "@getpaseo/relay": "0.3.0",
42
+ "@getpaseo/protocol": "0.4.0-beta.1",
43
+ "@getpaseo/relay": "0.4.0-beta.1",
44
44
  "zod": "^4.4.3"
45
45
  },
46
46
  "devDependencies": {