@otto-code/client 0.8.12 → 0.8.13
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 +34 -7
- package/dist/compat/normalize-provider-models.js +4 -1
- package/dist/daemon-client-websocket-transport.d.ts +4 -0
- package/dist/daemon-client-websocket-transport.js +14 -2
- package/dist/daemon-client.d.ts +71 -124
- package/dist/daemon-client.js +214 -206
- package/dist/index.d.ts +73 -55
- package/dist/index.js +208 -48
- package/dist/terminal-stream-router.js +1 -0
- package/package.json +7 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { AgentSnapshotPayload, CreateAgentRequestMessage, FetchWorkspacesRequestMessage, FetchWorkspacesResponseMessage, GetProvidersSnapshotResponseMessage, ListAvailableProvidersResponse, ListProviderFeaturesRequestMessage, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ListProviderModesResponseMessage, MutableDaemonConfig, MutableDaemonConfigPatch, ProviderDiagnosticResponseMessage, ProjectPlacementPayload, RefreshProvidersSnapshotResponseMessage, SendAgentMessageRequest, SessionOutboundMessage, WorkspaceDescriptorPayload } from "@otto-code/protocol/messages";
|
|
1
|
+
import type { AgentSnapshotPayload, CreateAgentRequestMessage, FetchWorkspacesRequestMessage, FetchWorkspacesResponseMessage, GetProvidersSnapshotResponseMessage, ListAvailableProvidersResponse, ListProviderFeaturesRequestMessage, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ListProviderModesResponseMessage, MutableDaemonConfig, MutableDaemonConfigPatch, ProviderDiagnosticResponseMessage, ProjectPlacementPayload, RefreshProvidersSnapshotResponseMessage, SendAgentMessageRequest, SessionOutboundMessage, WorkspaceDescriptorPayload, WorkspaceCreateRequest } from "@otto-code/protocol/messages";
|
|
2
2
|
import { DaemonClient } from "./daemon-client.js";
|
|
3
|
-
import type { FetchAgentTimelineCursor, FetchAgentTimelineDirection, FetchAgentTimelinePayload, FetchAgentTimelineProjection } from "./daemon-client.js";
|
|
3
|
+
import type { FetchAgentsEntry, FetchAgentsOptions, FetchAgentsPageInfo, FetchAgentTimelineCursor, FetchAgentTimelineDirection, FetchAgentTimelinePayload, FetchAgentTimelineProjection, WaitForFinishResult } from "./daemon-client.js";
|
|
4
4
|
export { DaemonClient };
|
|
5
5
|
export type { DaemonClientConfig, DaemonEvent, BrowserAutomationExecuteRequestMessage, BrowserAutomationExecuteResponseMessage, CodeRenameApplyOutcome, CodeRenameApplyQuery, CodeRenamePlan, CodeRenameUndoOutcome, HostingAuthStatusPayload, HostingListOwnersPayload, HostingListRepositoriesPayload, HostingSearchPayload, ProjectScaffoldPayload, WebSocketFactory, WebSocketLike, } from "./daemon-client.js";
|
|
6
6
|
export type ConnectionState = {
|
|
@@ -46,6 +46,13 @@ export interface OttoClientConfig {
|
|
|
46
46
|
}
|
|
47
47
|
export type OttoWorkspace = WorkspaceDescriptorPayload;
|
|
48
48
|
export type OttoAgent = AgentSnapshotPayload;
|
|
49
|
+
export type OttoAgentListOptions = FetchAgentsOptions;
|
|
50
|
+
export interface OttoAgentListResult {
|
|
51
|
+
requestId: string;
|
|
52
|
+
subscriptionId?: string | null;
|
|
53
|
+
entries: FetchAgentsEntry[];
|
|
54
|
+
pageInfo: FetchAgentsPageInfo;
|
|
55
|
+
}
|
|
49
56
|
export type OttoWorkspaceListOptions = Omit<FetchWorkspacesRequestMessage, "type" | "requestId"> & {
|
|
50
57
|
requestId?: string;
|
|
51
58
|
};
|
|
@@ -59,11 +66,9 @@ export interface OttoWorkspaceOpenOptions {
|
|
|
59
66
|
cwd: string;
|
|
60
67
|
requestId?: string;
|
|
61
68
|
}
|
|
62
|
-
export
|
|
63
|
-
requestId
|
|
64
|
-
|
|
65
|
-
error: string | null;
|
|
66
|
-
}
|
|
69
|
+
export type OttoWorkspaceCreateOptions = Omit<WorkspaceCreateRequest, "type" | "requestId"> & {
|
|
70
|
+
requestId?: string;
|
|
71
|
+
};
|
|
67
72
|
export interface OttoWorkspaceArchiveResult {
|
|
68
73
|
requestId: string;
|
|
69
74
|
workspaceId: string;
|
|
@@ -74,19 +79,17 @@ export type OttoWorkspaceUpdate = Extract<SessionOutboundMessage, {
|
|
|
74
79
|
type: "workspace_update";
|
|
75
80
|
}>["payload"];
|
|
76
81
|
export type OttoWorkspaceUpdateHandler = (update: OttoWorkspaceUpdate) => void;
|
|
77
|
-
/**
|
|
78
|
-
* A handle is a stable typed reference to a daemon resource. Its identity is the
|
|
79
|
-
* daemon id, and `latest()` only returns the most recent snapshot this handle has
|
|
80
|
-
* seen through construction, `refetch()`, or this handle's local subscription.
|
|
81
|
-
*/
|
|
82
82
|
export interface OttoWorkspaceHandle {
|
|
83
83
|
readonly id: string;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
readonly projectId: string | null;
|
|
85
|
+
readonly directory: string | null;
|
|
86
|
+
readonly name: string | null;
|
|
87
|
+
readonly status: OttoWorkspace["status"] | null;
|
|
88
|
+
readonly agents: {
|
|
89
|
+
create(options: OttoWorkspaceAgentCreateOptions): Promise<OttoAgentHandle>;
|
|
90
|
+
};
|
|
91
|
+
current(): OttoWorkspace | null;
|
|
92
|
+
refresh(options?: {
|
|
90
93
|
requestId?: string;
|
|
91
94
|
}): Promise<OttoWorkspace | null>;
|
|
92
95
|
archive(requestId?: string): Promise<OttoWorkspaceArchiveResult>;
|
|
@@ -101,8 +104,8 @@ export interface OttoWorkspaceHandle {
|
|
|
101
104
|
export interface OttoWorkspaceActions {
|
|
102
105
|
list(options?: OttoWorkspaceListOptions): Promise<OttoWorkspaceListResult>;
|
|
103
106
|
ref(workspace: string | OttoWorkspace): OttoWorkspaceHandle;
|
|
104
|
-
open(input: string | OttoWorkspaceOpenOptions, requestId?: string): Promise<
|
|
105
|
-
create(
|
|
107
|
+
open(input: string | OttoWorkspaceOpenOptions, requestId?: string): Promise<OttoWorkspaceHandle>;
|
|
108
|
+
create(options: OttoWorkspaceCreateOptions): Promise<OttoWorkspaceHandle>;
|
|
106
109
|
archive(workspace: string | OttoWorkspaceHandle, requestId?: string): Promise<OttoWorkspaceArchiveResult>;
|
|
107
110
|
/**
|
|
108
111
|
* Local event subscription over the low-level driver's workspace_update stream.
|
|
@@ -111,23 +114,38 @@ export interface OttoWorkspaceActions {
|
|
|
111
114
|
subscribe(handler: OttoWorkspaceUpdateHandler): () => void;
|
|
112
115
|
}
|
|
113
116
|
type OttoAgentSessionConfig = CreateAgentRequestMessage["config"];
|
|
114
|
-
type OttoAgentProvider = OttoAgentSessionConfig["provider"];
|
|
115
|
-
type
|
|
116
|
-
export interface
|
|
117
|
-
|
|
118
|
-
provider
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
export type OttoAgentProvider = OttoAgentSessionConfig["provider"];
|
|
118
|
+
export type OttoProviderFeatureValues = Record<string, unknown>;
|
|
119
|
+
export interface OttoAgentConfig {
|
|
120
|
+
/** Provider and model in `provider/model` format. */
|
|
121
|
+
provider: string;
|
|
122
|
+
modeId?: OttoAgentSessionConfig["modeId"];
|
|
123
|
+
thinkingOptionId?: OttoAgentSessionConfig["thinkingOptionId"];
|
|
124
|
+
featureValues?: OttoProviderFeatureValues;
|
|
125
|
+
/** JSON-safe provider-native settings, validated by the selected provider. */
|
|
126
|
+
options?: OttoAgentSessionConfig["providerOptions"];
|
|
127
|
+
systemPrompt?: OttoAgentSessionConfig["systemPrompt"];
|
|
128
|
+
toolPolicy?: OttoAgentSessionConfig["toolPolicy"];
|
|
129
|
+
mcpServers?: OttoAgentSessionConfig["mcpServers"];
|
|
130
|
+
}
|
|
131
|
+
export interface OttoAgentCreateOptions {
|
|
132
|
+
config: OttoAgentConfig;
|
|
133
|
+
cwd: string;
|
|
134
|
+
parent?: string | OttoAgentHandle;
|
|
135
|
+
title?: OttoAgentSessionConfig["title"];
|
|
136
|
+
env?: CreateAgentRequestMessage["env"];
|
|
137
|
+
prompt?: string;
|
|
123
138
|
clientMessageId?: string;
|
|
124
139
|
outputSchema?: Record<string, unknown>;
|
|
125
140
|
images?: CreateAgentRequestMessage["images"];
|
|
126
141
|
attachments?: CreateAgentRequestMessage["attachments"];
|
|
127
142
|
git?: CreateAgentRequestMessage["git"];
|
|
143
|
+
worktree?: CreateAgentRequestMessage["worktree"];
|
|
144
|
+
autoArchive?: CreateAgentRequestMessage["autoArchive"];
|
|
128
145
|
requestId?: string;
|
|
129
146
|
labels?: Record<string, string>;
|
|
130
147
|
}
|
|
148
|
+
export type OttoWorkspaceAgentCreateOptions = Omit<OttoAgentCreateOptions, "cwd">;
|
|
131
149
|
export interface OttoAgentRefetchResult {
|
|
132
150
|
agent: OttoAgent;
|
|
133
151
|
project: ProjectPlacementPayload | null;
|
|
@@ -147,6 +165,10 @@ export interface OttoAgentSendOptions {
|
|
|
147
165
|
}>;
|
|
148
166
|
attachments?: SendAgentMessageRequest["attachments"];
|
|
149
167
|
}
|
|
168
|
+
export interface OttoAgentRunOptions extends OttoAgentSendOptions {
|
|
169
|
+
timeoutMs?: number;
|
|
170
|
+
}
|
|
171
|
+
export type OttoAgentRunResult = WaitForFinishResult;
|
|
150
172
|
export type OttoAgentUpdate = Extract<SessionOutboundMessage, {
|
|
151
173
|
type: "agent_update";
|
|
152
174
|
}>["payload"];
|
|
@@ -157,8 +179,8 @@ export type OttoAgentUpdateHandler = (update: OttoAgentUpdate) => void;
|
|
|
157
179
|
export interface OttoAgentTimelineHandle {
|
|
158
180
|
/**
|
|
159
181
|
* Fetches a fresh timeline page through the existing daemon RPC. If the daemon
|
|
160
|
-
* includes an agent snapshot in the response, the parent handle
|
|
161
|
-
*
|
|
182
|
+
* includes an agent snapshot in the response, the parent handle is updated to
|
|
183
|
+
* that value.
|
|
162
184
|
*/
|
|
163
185
|
refetch(options?: OttoAgentTimelineRefetchOptions): Promise<FetchAgentTimelinePayload>;
|
|
164
186
|
/**
|
|
@@ -167,18 +189,19 @@ export interface OttoAgentTimelineHandle {
|
|
|
167
189
|
*/
|
|
168
190
|
subscribe(handler: (event: OttoAgentStream) => void): () => void;
|
|
169
191
|
}
|
|
170
|
-
/**
|
|
171
|
-
* Agent handles follow the same identity/snapshot rule as workspace handles:
|
|
172
|
-
* `id` is stable, while `latest()` is only the newest snapshot observed by this
|
|
173
|
-
* handle through construction, `refetch()`, timeline refetch, archive, or local
|
|
174
|
-
* agent_update subscription.
|
|
175
|
-
*/
|
|
176
192
|
export interface OttoAgentHandle {
|
|
177
193
|
readonly id: string;
|
|
194
|
+
readonly workspaceId: string | null;
|
|
195
|
+
readonly cwd: string | null;
|
|
196
|
+
readonly status: OttoAgent["status"] | null;
|
|
178
197
|
readonly timeline: OttoAgentTimelineHandle;
|
|
179
|
-
|
|
180
|
-
|
|
198
|
+
current(): OttoAgent | null;
|
|
199
|
+
refresh(requestId?: string): Promise<OttoAgentRefetchResult | null>;
|
|
181
200
|
send(text: string, options?: OttoAgentSendOptions): Promise<void>;
|
|
201
|
+
/** Sends a prompt and resolves when that turn finishes or needs attention. */
|
|
202
|
+
run(text: string, options?: OttoAgentRunOptions): Promise<OttoAgentRunResult>;
|
|
203
|
+
/** Waits for the current turn, including one started with `prompt`. */
|
|
204
|
+
waitForFinish(timeoutMs?: number): Promise<OttoAgentRunResult>;
|
|
182
205
|
archive(): Promise<{
|
|
183
206
|
archivedAt: string;
|
|
184
207
|
}>;
|
|
@@ -186,6 +209,7 @@ export interface OttoAgentHandle {
|
|
|
186
209
|
subscribe(handler: (update: OttoAgentUpdate) => void): () => void;
|
|
187
210
|
}
|
|
188
211
|
export interface OttoAgentActions {
|
|
212
|
+
list(options?: OttoAgentListOptions): Promise<OttoAgentListResult>;
|
|
189
213
|
ref(agent: string | OttoAgent): OttoAgentHandle;
|
|
190
214
|
create(options: OttoAgentCreateOptions): Promise<OttoAgentHandle>;
|
|
191
215
|
/**
|
|
@@ -194,19 +218,13 @@ export interface OttoAgentActions {
|
|
|
194
218
|
*/
|
|
195
219
|
subscribe(handler: OttoAgentUpdateHandler): () => void;
|
|
196
220
|
}
|
|
197
|
-
export interface OttoProviderConfig extends OttoProviderConfigInput {
|
|
198
|
-
provider: OttoAgentProvider;
|
|
199
|
-
}
|
|
200
|
-
export type OttoProviderFeatureValues = Record<string, unknown>;
|
|
201
|
-
export interface OttoProviderConfigInput {
|
|
202
|
-
model?: string;
|
|
203
|
-
modeId?: string;
|
|
204
|
-
thinkingOptionId?: string;
|
|
205
|
-
featureValues?: OttoProviderFeatureValues;
|
|
206
|
-
}
|
|
207
221
|
export type OttoProviderModelsResult = ListProviderModelsResponseMessage["payload"];
|
|
208
222
|
export type OttoProviderModesResult = ListProviderModesResponseMessage["payload"];
|
|
209
|
-
|
|
223
|
+
type OttoProviderFeaturesDraft = ListProviderFeaturesRequestMessage["draftConfig"];
|
|
224
|
+
export interface OttoProviderFeaturesInput extends Omit<OttoProviderFeaturesDraft, "provider" | "model"> {
|
|
225
|
+
/** Provider and model in `provider/model` format. */
|
|
226
|
+
provider: string;
|
|
227
|
+
}
|
|
210
228
|
export type OttoProviderFeaturesResult = ListProviderFeaturesResponseMessage["payload"];
|
|
211
229
|
export type OttoProviderAvailabilityResult = ListAvailableProvidersResponse["payload"];
|
|
212
230
|
export type OttoProviderSnapshotResult = GetProvidersSnapshotResponseMessage["payload"];
|
|
@@ -224,12 +242,10 @@ export interface OttoProviderRefreshOptions {
|
|
|
224
242
|
providers?: OttoAgentProvider[];
|
|
225
243
|
requestId?: string;
|
|
226
244
|
}
|
|
245
|
+
export interface OttoProviderWaitOptions extends OttoProviderListOptions {
|
|
246
|
+
timeoutMs?: number;
|
|
247
|
+
}
|
|
227
248
|
export interface OttoProviderActions {
|
|
228
|
-
codex(input?: OttoProviderConfigInput): OttoProviderConfig;
|
|
229
|
-
claude(input?: OttoProviderConfigInput): OttoProviderConfig;
|
|
230
|
-
opencode(input?: OttoProviderConfigInput): OttoProviderConfig;
|
|
231
|
-
copilot(input?: OttoProviderConfigInput): OttoProviderConfig;
|
|
232
|
-
config(provider: OttoAgentProvider, input?: OttoProviderConfigInput): OttoProviderConfig;
|
|
233
249
|
listModels(provider: OttoAgentProvider, options?: OttoProviderListOptions): Promise<OttoProviderModelsResult>;
|
|
234
250
|
listModes(provider: OttoAgentProvider, options?: OttoProviderListOptions): Promise<OttoProviderModesResult>;
|
|
235
251
|
listFeatures(draftConfig: OttoProviderFeaturesInput, options?: {
|
|
@@ -239,6 +255,8 @@ export interface OttoProviderActions {
|
|
|
239
255
|
requestId?: string;
|
|
240
256
|
}): Promise<OttoProviderAvailabilityResult>;
|
|
241
257
|
snapshot(options?: OttoProviderListOptions): Promise<OttoProviderSnapshotResult>;
|
|
258
|
+
/** Resolves after the daemon's lazy provider discovery has finished. */
|
|
259
|
+
waitForReady(options?: OttoProviderWaitOptions): Promise<OttoProviderSnapshotResult>;
|
|
242
260
|
refresh(options?: OttoProviderRefreshOptions): Promise<OttoProviderRefreshResult>;
|
|
243
261
|
diagnostic(provider: OttoAgentProvider, options?: {
|
|
244
262
|
requestId?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,45 +1,74 @@
|
|
|
1
1
|
import { DaemonClient } from "./daemon-client.js";
|
|
2
2
|
export { DaemonClient };
|
|
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;
|
|
3
8
|
export function createOttoClient(config) {
|
|
4
9
|
const daemonClient = new DaemonClient({
|
|
5
10
|
...config,
|
|
6
11
|
clientId: config.clientId ?? createGeneratedClientId(),
|
|
7
12
|
clientType: "cli",
|
|
8
13
|
});
|
|
9
|
-
const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient);
|
|
10
14
|
const createAgentHandle = createAgentHandleFactory(daemonClient);
|
|
15
|
+
const createAgent = async (options, placement) => {
|
|
16
|
+
const { config: agentConfig, cwd, parent, title, prompt, ...requestOptions } = options;
|
|
17
|
+
const { provider: providerModel, options: providerOptions, ...runtimeConfig } = agentConfig;
|
|
18
|
+
const { provider, model } = parseProviderModel(providerModel);
|
|
19
|
+
const effectiveCwd = placement?.cwd ?? cwd;
|
|
20
|
+
const agent = await daemonClient.createAgent({
|
|
21
|
+
...requestOptions,
|
|
22
|
+
config: {
|
|
23
|
+
...runtimeConfig,
|
|
24
|
+
provider,
|
|
25
|
+
model,
|
|
26
|
+
cwd: effectiveCwd,
|
|
27
|
+
...(title !== undefined ? { title } : {}),
|
|
28
|
+
...(providerOptions !== undefined ? { providerOptions } : {}),
|
|
29
|
+
},
|
|
30
|
+
...(placement ? { workspaceId: placement.workspaceId } : {}),
|
|
31
|
+
...(parent ? { callerAgentId: resolveAgentId(parent) } : {}),
|
|
32
|
+
...(prompt !== undefined ? { initialPrompt: prompt } : {}),
|
|
33
|
+
});
|
|
34
|
+
return createAgentHandle(agent);
|
|
35
|
+
};
|
|
36
|
+
const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient, createAgent);
|
|
11
37
|
return {
|
|
12
38
|
workspaces: {
|
|
13
39
|
list: (options) => daemonClient.fetchWorkspaces(options),
|
|
14
40
|
ref: (workspace) => createWorkspaceHandle(workspace),
|
|
15
41
|
open: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
|
|
16
|
-
create: (
|
|
42
|
+
create: async ({ requestId, ...options }) => {
|
|
43
|
+
const result = await daemonClient.createWorkspace(options, requestId);
|
|
44
|
+
if (result.error || !result.workspace) {
|
|
45
|
+
throw new Error(result.error ?? "The daemon did not create a workspace");
|
|
46
|
+
}
|
|
47
|
+
return createWorkspaceHandle(result.workspace);
|
|
48
|
+
},
|
|
17
49
|
archive: (workspace, requestId) => daemonClient.archiveWorkspace(resolveWorkspaceId(workspace), { requestId }),
|
|
18
50
|
subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
|
|
19
51
|
handler(message.payload);
|
|
20
52
|
}),
|
|
21
53
|
},
|
|
22
54
|
agents: {
|
|
55
|
+
list: (options) => daemonClient.fetchAgents(options),
|
|
23
56
|
ref: (agent) => createAgentHandle(agent),
|
|
24
|
-
create:
|
|
25
|
-
const agent = await daemonClient.createAgent(options);
|
|
26
|
-
return createAgentHandle(agent);
|
|
27
|
-
},
|
|
57
|
+
create: (options) => createAgent(options),
|
|
28
58
|
subscribe: (handler) => daemonClient.on("agent_update", (message) => {
|
|
29
59
|
handler(message.payload);
|
|
30
60
|
}),
|
|
31
61
|
},
|
|
32
62
|
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
63
|
listModels: (provider, options) => daemonClient.listProviderModels(provider, options),
|
|
39
64
|
listModes: (provider, options) => daemonClient.listProviderModes(provider, options),
|
|
40
|
-
listFeatures: (draftConfig, options) =>
|
|
65
|
+
listFeatures: ({ provider: providerModel, ...draftConfig }, options) => {
|
|
66
|
+
const { provider, model } = parseProviderModel(providerModel);
|
|
67
|
+
return daemonClient.listProviderFeatures({ ...draftConfig, provider, model }, options);
|
|
68
|
+
},
|
|
41
69
|
listAvailable: (options) => daemonClient.listAvailableProviders(options),
|
|
42
70
|
snapshot: (options) => daemonClient.getProvidersSnapshot(options),
|
|
71
|
+
waitForReady: (options) => waitForProvidersReady(daemonClient, options),
|
|
43
72
|
refresh: (options) => daemonClient.refreshProvidersSnapshot(options),
|
|
44
73
|
diagnostic: (provider, options) => daemonClient.getProviderDiagnostic(provider, options),
|
|
45
74
|
subscribe: (handler) => daemonClient.on("providers_snapshot_update", (message) => {
|
|
@@ -56,39 +85,69 @@ export function createOttoClient(config) {
|
|
|
56
85
|
getConnectionState: () => daemonClient.getConnectionState(),
|
|
57
86
|
};
|
|
58
87
|
}
|
|
59
|
-
function createWorkspaceHandleFactory(daemonClient) {
|
|
88
|
+
function createWorkspaceHandleFactory(daemonClient, createAgent) {
|
|
60
89
|
return (workspace) => {
|
|
61
90
|
const id = typeof workspace === "string" ? workspace : workspace.id;
|
|
62
|
-
let
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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.
|
|
91
|
+
let current = typeof workspace === "string" ? null : workspace;
|
|
92
|
+
const refresh = async (options) => {
|
|
93
|
+
let cursor;
|
|
94
|
+
let requestId = options?.requestId;
|
|
95
|
+
do {
|
|
70
96
|
const result = await daemonClient.fetchWorkspaces({
|
|
71
|
-
requestId
|
|
72
|
-
page: { limit:
|
|
97
|
+
requestId,
|
|
98
|
+
page: { limit: 200, ...(cursor ? { cursor } : {}) },
|
|
73
99
|
});
|
|
74
|
-
|
|
75
|
-
|
|
100
|
+
const match = result.entries.find((entry) => entry.id === id);
|
|
101
|
+
if (match) {
|
|
102
|
+
current = match;
|
|
103
|
+
return current;
|
|
104
|
+
}
|
|
105
|
+
cursor = result.pageInfo.nextCursor ?? undefined;
|
|
106
|
+
requestId = undefined;
|
|
107
|
+
} while (cursor);
|
|
108
|
+
current = null;
|
|
109
|
+
return current;
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
id,
|
|
113
|
+
get projectId() {
|
|
114
|
+
return current?.projectId ?? null;
|
|
115
|
+
},
|
|
116
|
+
get directory() {
|
|
117
|
+
return current?.workspaceDirectory ?? null;
|
|
118
|
+
},
|
|
119
|
+
get name() {
|
|
120
|
+
return current?.name ?? null;
|
|
121
|
+
},
|
|
122
|
+
get status() {
|
|
123
|
+
return current?.status ?? null;
|
|
124
|
+
},
|
|
125
|
+
agents: {
|
|
126
|
+
create: async (options) => {
|
|
127
|
+
const snapshot = current ?? (await refresh());
|
|
128
|
+
if (!snapshot?.workspaceDirectory) {
|
|
129
|
+
throw new Error(`Workspace ${id} has no available directory`);
|
|
130
|
+
}
|
|
131
|
+
return createAgent({ ...options, cwd: snapshot.workspaceDirectory }, { workspaceId: id, cwd: snapshot.workspaceDirectory });
|
|
132
|
+
},
|
|
76
133
|
},
|
|
134
|
+
current: () => current,
|
|
135
|
+
refresh,
|
|
77
136
|
archive: async (requestId) => {
|
|
78
137
|
const result = await daemonClient.archiveWorkspace(id, { requestId });
|
|
79
|
-
if (
|
|
80
|
-
|
|
138
|
+
if (current) {
|
|
139
|
+
current = { ...current, archivingAt: result.archivedAt };
|
|
81
140
|
}
|
|
82
141
|
return result;
|
|
83
142
|
},
|
|
84
143
|
subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
|
|
85
144
|
const update = message.payload;
|
|
86
145
|
if (update.kind === "upsert" && update.workspace.id === id) {
|
|
87
|
-
|
|
146
|
+
current = update.workspace;
|
|
88
147
|
handler(update);
|
|
89
148
|
}
|
|
90
149
|
if (update.kind === "remove" && update.id === id) {
|
|
91
|
-
|
|
150
|
+
current = null;
|
|
92
151
|
handler(update);
|
|
93
152
|
}
|
|
94
153
|
}),
|
|
@@ -98,14 +157,14 @@ function createWorkspaceHandleFactory(daemonClient) {
|
|
|
98
157
|
function createAgentHandleFactory(daemonClient) {
|
|
99
158
|
return (agent) => {
|
|
100
159
|
const id = typeof agent === "string" ? agent : agent.id;
|
|
101
|
-
let
|
|
160
|
+
let current = typeof agent === "string" ? null : agent;
|
|
102
161
|
const handle = {
|
|
103
162
|
id,
|
|
104
163
|
timeline: {
|
|
105
164
|
refetch: async (options) => {
|
|
106
165
|
const result = await daemonClient.fetchAgentTimeline(id, options);
|
|
107
166
|
if (result.agent) {
|
|
108
|
-
|
|
167
|
+
current = result.agent;
|
|
109
168
|
}
|
|
110
169
|
return result;
|
|
111
170
|
},
|
|
@@ -115,10 +174,35 @@ function createAgentHandleFactory(daemonClient) {
|
|
|
115
174
|
}
|
|
116
175
|
}),
|
|
117
176
|
},
|
|
118
|
-
|
|
119
|
-
|
|
177
|
+
get workspaceId() {
|
|
178
|
+
return current?.workspaceId ?? null;
|
|
179
|
+
},
|
|
180
|
+
get cwd() {
|
|
181
|
+
return current?.cwd ?? null;
|
|
182
|
+
},
|
|
183
|
+
get status() {
|
|
184
|
+
return current?.status ?? null;
|
|
185
|
+
},
|
|
186
|
+
current: () => current,
|
|
187
|
+
refresh: async (requestId) => {
|
|
120
188
|
const result = await daemonClient.fetchAgent({ agentId: id, requestId });
|
|
121
|
-
|
|
189
|
+
current = result?.agent ?? null;
|
|
190
|
+
return result;
|
|
191
|
+
},
|
|
192
|
+
run: async (text, options) => {
|
|
193
|
+
const { timeoutMs, ...sendOptions } = options ?? {};
|
|
194
|
+
await daemonClient.sendAgentMessage(id, text, sendOptions);
|
|
195
|
+
const result = await daemonClient.waitForFinish(id, timeoutMs ?? DEFAULT_WAIT_FOR_FINISH_MS);
|
|
196
|
+
if (result.final) {
|
|
197
|
+
current = result.final;
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
},
|
|
201
|
+
waitForFinish: async (timeoutMs) => {
|
|
202
|
+
const result = await daemonClient.waitForFinish(id, timeoutMs ?? DEFAULT_WAIT_FOR_FINISH_MS);
|
|
203
|
+
if (result.final) {
|
|
204
|
+
current = result.final;
|
|
205
|
+
}
|
|
122
206
|
return result;
|
|
123
207
|
},
|
|
124
208
|
send: async (text, options) => {
|
|
@@ -126,8 +210,8 @@ function createAgentHandleFactory(daemonClient) {
|
|
|
126
210
|
},
|
|
127
211
|
archive: async () => {
|
|
128
212
|
const result = await daemonClient.archiveAgent(id);
|
|
129
|
-
if (
|
|
130
|
-
|
|
213
|
+
if (current) {
|
|
214
|
+
current = { ...current, archivedAt: result.archivedAt };
|
|
131
215
|
}
|
|
132
216
|
return result;
|
|
133
217
|
},
|
|
@@ -137,11 +221,11 @@ function createAgentHandleFactory(daemonClient) {
|
|
|
137
221
|
subscribe: (handler) => daemonClient.on("agent_update", (message) => {
|
|
138
222
|
const update = message.payload;
|
|
139
223
|
if (update.kind === "upsert" && update.agent.id === id) {
|
|
140
|
-
|
|
224
|
+
current = update.agent;
|
|
141
225
|
handler(update);
|
|
142
226
|
}
|
|
143
227
|
if (update.kind === "remove" && update.agentId === id) {
|
|
144
|
-
|
|
228
|
+
current = null;
|
|
145
229
|
handler(update);
|
|
146
230
|
}
|
|
147
231
|
}),
|
|
@@ -152,23 +236,99 @@ function createAgentHandleFactory(daemonClient) {
|
|
|
152
236
|
async function openWorkspace(daemonClient, createWorkspaceHandle, input, requestId) {
|
|
153
237
|
const options = typeof input === "string" ? { cwd: input, requestId } : input;
|
|
154
238
|
const result = await daemonClient.openProject(options.cwd, options.requestId);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
239
|
+
if (result.error || !result.workspace) {
|
|
240
|
+
throw new Error(result.error ?? `The daemon did not open a workspace for ${options.cwd}`);
|
|
241
|
+
}
|
|
242
|
+
return createWorkspaceHandle(result.workspace);
|
|
159
243
|
}
|
|
160
244
|
function resolveWorkspaceId(workspace) {
|
|
161
245
|
return typeof workspace === "string" ? workspace : workspace.id;
|
|
162
246
|
}
|
|
163
|
-
function
|
|
247
|
+
function resolveAgentId(agent) {
|
|
248
|
+
return typeof agent === "string" ? agent : agent.id;
|
|
249
|
+
}
|
|
250
|
+
function parseProviderModel(selection) {
|
|
251
|
+
const separator = selection.indexOf("/");
|
|
252
|
+
if (separator <= 0 || separator === selection.length - 1) {
|
|
253
|
+
throw new Error('Expected config.provider in "provider/model" format');
|
|
254
|
+
}
|
|
164
255
|
return {
|
|
165
|
-
provider,
|
|
166
|
-
|
|
167
|
-
...(input.modeId !== undefined ? { modeId: input.modeId } : {}),
|
|
168
|
-
...(input.thinkingOptionId !== undefined ? { thinkingOptionId: input.thinkingOptionId } : {}),
|
|
169
|
-
...(input.featureValues !== undefined ? { featureValues: input.featureValues } : {}),
|
|
256
|
+
provider: selection.slice(0, separator),
|
|
257
|
+
model: selection.slice(separator + 1),
|
|
170
258
|
};
|
|
171
259
|
}
|
|
260
|
+
function waitForProvidersReady(daemonClient, options = {}) {
|
|
261
|
+
// COMPAT(providersSnapshotCwd): added in v0.3.2, remove gate after 2027-02-10.
|
|
262
|
+
if (daemonClient.getLastServerInfoMessage()?.features?.providersSnapshotCwd !== true) {
|
|
263
|
+
return Promise.reject(new Error("Update the host to wait for provider discovery."));
|
|
264
|
+
}
|
|
265
|
+
const { timeoutMs = 60000, ...snapshotOptions } = options;
|
|
266
|
+
return new Promise((resolve, reject) => {
|
|
267
|
+
let settled = false;
|
|
268
|
+
let requestId = null;
|
|
269
|
+
let snapshotCwd;
|
|
270
|
+
const pendingUpdates = new Map();
|
|
271
|
+
let latestEntries = [];
|
|
272
|
+
const cleanup = () => {
|
|
273
|
+
clearTimeout(timeout);
|
|
274
|
+
unsubscribe();
|
|
275
|
+
};
|
|
276
|
+
const finish = (snapshot) => {
|
|
277
|
+
if (settled)
|
|
278
|
+
return;
|
|
279
|
+
settled = true;
|
|
280
|
+
cleanup();
|
|
281
|
+
resolve(snapshot);
|
|
282
|
+
};
|
|
283
|
+
const fail = (error) => {
|
|
284
|
+
if (settled)
|
|
285
|
+
return;
|
|
286
|
+
settled = true;
|
|
287
|
+
cleanup();
|
|
288
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
289
|
+
};
|
|
290
|
+
const updateMatches = (update) => update.cwd === snapshotCwd;
|
|
291
|
+
const unsubscribe = daemonClient.on("providers_snapshot_update", (message) => {
|
|
292
|
+
const update = message.payload;
|
|
293
|
+
if (!requestId) {
|
|
294
|
+
pendingUpdates.set(update.cwd, update);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (!updateMatches(update))
|
|
298
|
+
return;
|
|
299
|
+
latestEntries = update.entries;
|
|
300
|
+
if (update.entries.some((entry) => entry.status === "loading"))
|
|
301
|
+
return;
|
|
302
|
+
finish({ ...update, requestId });
|
|
303
|
+
});
|
|
304
|
+
const timeout = setTimeout(() => {
|
|
305
|
+
const loading = latestEntries
|
|
306
|
+
.filter((entry) => entry.status === "loading")
|
|
307
|
+
.map((entry) => entry.provider)
|
|
308
|
+
.join(", ");
|
|
309
|
+
fail(new Error(loading
|
|
310
|
+
? `Timed out waiting for providers: ${loading}`
|
|
311
|
+
: "Timed out waiting for provider discovery"));
|
|
312
|
+
}, timeoutMs);
|
|
313
|
+
void daemonClient
|
|
314
|
+
.getProvidersSnapshot(snapshotOptions)
|
|
315
|
+
.then((snapshot) => {
|
|
316
|
+
requestId = snapshot.requestId;
|
|
317
|
+
snapshotCwd = snapshot.cwd;
|
|
318
|
+
latestEntries = snapshot.entries;
|
|
319
|
+
if (!snapshot.entries.some((entry) => entry.status === "loading")) {
|
|
320
|
+
finish(snapshot);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const pendingUpdate = pendingUpdates.get(snapshotCwd);
|
|
324
|
+
if (pendingUpdate && !pendingUpdate.entries.some((entry) => entry.status === "loading")) {
|
|
325
|
+
finish({ ...pendingUpdate, requestId });
|
|
326
|
+
}
|
|
327
|
+
return undefined;
|
|
328
|
+
})
|
|
329
|
+
.catch(fail);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
172
332
|
function createGeneratedClientId() {
|
|
173
333
|
const randomId = typeof globalThis.crypto?.randomUUID === "function"
|
|
174
334
|
? globalThis.crypto.randomUUID()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@otto-code/client",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.13",
|
|
4
4
|
"description": "Otto client SDK package",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"files": [
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"./internal/daemon-client-transport-types": {
|
|
22
22
|
"types": "./dist/daemon-client-transport-types.d.ts",
|
|
23
23
|
"default": "./dist/daemon-client-transport-types.js"
|
|
24
|
+
},
|
|
25
|
+
"./internal/daemon-client-websocket-transport": {
|
|
26
|
+
"types": "./dist/daemon-client-websocket-transport.d.ts",
|
|
27
|
+
"default": "./dist/daemon-client-websocket-transport.js"
|
|
24
28
|
}
|
|
25
29
|
},
|
|
26
30
|
"publishConfig": {
|
|
@@ -36,8 +40,8 @@
|
|
|
36
40
|
"test": "vitest run"
|
|
37
41
|
},
|
|
38
42
|
"dependencies": {
|
|
39
|
-
"@otto-code/protocol": "0.8.
|
|
40
|
-
"@otto-code/relay": "0.8.
|
|
43
|
+
"@otto-code/protocol": "0.8.13",
|
|
44
|
+
"@otto-code/relay": "0.8.13",
|
|
41
45
|
"zod": "^4.4.3"
|
|
42
46
|
},
|
|
43
47
|
"devDependencies": {
|