@camerontaylor/paseo-client 0.8.0-fork.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 +44 -0
- package/dist/compat/normalize-provider-models.d.ts +11 -0
- package/dist/compat/normalize-provider-models.js +51 -0
- package/dist/connection/index.d.ts +42 -0
- package/dist/connection/index.js +111 -0
- package/dist/creation/index.d.ts +47 -0
- package/dist/creation/index.js +216 -0
- package/dist/daemon-client-relay-e2ee-transport.d.ts +8 -0
- package/dist/daemon-client-relay-e2ee-transport.js +161 -0
- package/dist/daemon-client-runtime-metrics.d.ts +39 -0
- package/dist/daemon-client-runtime-metrics.js +173 -0
- package/dist/daemon-client-transport-types.d.ts +36 -0
- package/dist/daemon-client-transport-types.js +2 -0
- package/dist/daemon-client-transport-utils.d.ts +13 -0
- package/dist/daemon-client-transport-utils.js +123 -0
- package/dist/daemon-client-transport.d.ts +5 -0
- package/dist/daemon-client-transport.js +4 -0
- package/dist/daemon-client-websocket-transport.d.ts +12 -0
- package/dist/daemon-client-websocket-transport.js +140 -0
- package/dist/daemon-client.d.ts +1245 -0
- package/dist/daemon-client.js +4783 -0
- package/dist/index.d.ts +383 -0
- package/dist/index.js +407 -0
- package/dist/provider-snapshots/index.d.ts +23 -0
- package/dist/provider-snapshots/index.js +60 -0
- package/dist/terminal-stream-router.d.ts +28 -0
- package/dist/terminal-stream-router.js +109 -0
- package/dist/terminals/index.d.ts +69 -0
- package/dist/terminals/index.js +103 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @getpaseo/client
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for building integrations on top of a Paseo daemon.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @getpaseo/client
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { createPaseoClient } from "@camerontaylor/paseo-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
|
+
```
|
|
26
|
+
|
|
27
|
+
The public API is the package root. Imports under `@camerontaylor/paseo-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, terminals, 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
|
+
The client advertises its supported protocol capabilities by default. Optional `capabilities`
|
|
38
|
+
overrides extend or override that declaration; browser hosting must be supplied by the caller.
|
|
39
|
+
Connecting alone does not subscribe to agent timelines or catalog events. See the
|
|
40
|
+
[event guide](https://paseo.sh/docs/sdk/events) for subscription lifetimes and timeline replacements.
|
|
41
|
+
|
|
42
|
+
## Stability
|
|
43
|
+
|
|
44
|
+
The high-level API exported from `@camerontaylor/paseo-client` is the supported SDK surface. The SDK and daemon remain protocol-compatible across versions, but newly added capabilities can require a newer daemon.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { GetProvidersSnapshotResponseMessage, ListProviderModelsResponseMessage, SessionOutboundMessage } from "@camerontaylor/paseo-protocol/messages";
|
|
2
|
+
type ListProviderModelsPayload = ListProviderModelsResponseMessage["payload"];
|
|
3
|
+
type GetProvidersSnapshotPayload = GetProvidersSnapshotResponseMessage["payload"];
|
|
4
|
+
type ProvidersSnapshotUpdatePayload = Extract<SessionOutboundMessage, {
|
|
5
|
+
type: "providers_snapshot_update";
|
|
6
|
+
}>["payload"];
|
|
7
|
+
export declare function normalizeListProviderModelsPayload(payload: ListProviderModelsPayload): ListProviderModelsPayload;
|
|
8
|
+
export declare function normalizeProvidersSnapshotPayload<T extends GetProvidersSnapshotPayload | ProvidersSnapshotUpdatePayload>(payload: T, expand?: boolean): T;
|
|
9
|
+
export declare function normalizeProviderSnapshotUpdateMessage(msg: SessionOutboundMessage, expand?: boolean): SessionOutboundMessage;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=normalize-provider-models.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { normalizeAgentModelDefinition } from "@camerontaylor/paseo-protocol/agent-types";
|
|
2
|
+
import { expandProviderSnapshot } from "@camerontaylor/paseo-protocol/provider-snapshot-codec";
|
|
3
|
+
// COMPAT(model-normalize): daemon normalizes at source (provider-registry) — shim covers older daemons; drop when floor >= v0.1.104
|
|
4
|
+
function normalizeAgentModels(models) {
|
|
5
|
+
if (!models) {
|
|
6
|
+
return models;
|
|
7
|
+
}
|
|
8
|
+
let changed = false;
|
|
9
|
+
const normalized = models.map((model) => {
|
|
10
|
+
const next = normalizeAgentModelDefinition(model);
|
|
11
|
+
changed || (changed = next !== model);
|
|
12
|
+
return next;
|
|
13
|
+
});
|
|
14
|
+
return changed ? normalized : models;
|
|
15
|
+
}
|
|
16
|
+
function normalizeProviderSnapshotEntry(entry) {
|
|
17
|
+
const models = normalizeAgentModels(entry.models);
|
|
18
|
+
return models === entry.models ? entry : { ...entry, models };
|
|
19
|
+
}
|
|
20
|
+
function normalizeProviderSnapshotEntries(entries) {
|
|
21
|
+
let changed = false;
|
|
22
|
+
const normalized = entries.map((entry) => {
|
|
23
|
+
const next = normalizeProviderSnapshotEntry(entry);
|
|
24
|
+
changed || (changed = next !== entry);
|
|
25
|
+
return next;
|
|
26
|
+
});
|
|
27
|
+
return changed ? normalized : entries;
|
|
28
|
+
}
|
|
29
|
+
export function normalizeListProviderModelsPayload(payload) {
|
|
30
|
+
const models = normalizeAgentModels(payload.models);
|
|
31
|
+
return models === payload.models ? payload : { ...payload, models };
|
|
32
|
+
}
|
|
33
|
+
export function normalizeProvidersSnapshotPayload(payload, expand = true) {
|
|
34
|
+
if (payload.compactSnapshot && !expand)
|
|
35
|
+
return payload;
|
|
36
|
+
const decoded = payload.compactSnapshot
|
|
37
|
+
? expandProviderSnapshot(payload.compactSnapshot)
|
|
38
|
+
: normalizeProviderSnapshotEntries(payload.entries);
|
|
39
|
+
const freshness = payload.fetchedAt;
|
|
40
|
+
const entries = freshness && expand
|
|
41
|
+
? decoded.map((entry) => freshness[entry.provider] ? { ...entry, fetchedAt: freshness[entry.provider] } : entry)
|
|
42
|
+
: decoded;
|
|
43
|
+
return entries === payload.entries ? payload : { ...payload, entries };
|
|
44
|
+
}
|
|
45
|
+
export function normalizeProviderSnapshotUpdateMessage(msg, expand = true) {
|
|
46
|
+
if (msg.type !== "providers_snapshot_update") {
|
|
47
|
+
return msg;
|
|
48
|
+
}
|
|
49
|
+
return { ...msg, payload: normalizeProvidersSnapshotPayload(msg.payload, expand) };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=normalize-provider-models.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { SessionEventSubscription } from "@camerontaylor/paseo-protocol/messages";
|
|
2
|
+
export declare const DEFAULT_CLIENT_CAPABILITIES: {
|
|
3
|
+
all_providers: true;
|
|
4
|
+
selective_agent_timeline: true;
|
|
5
|
+
reasoning_merge_enum: true;
|
|
6
|
+
custom_mode_icons: true;
|
|
7
|
+
terminal_reflowable_snapshot: true;
|
|
8
|
+
provider_subagents: true;
|
|
9
|
+
side_conversations: true;
|
|
10
|
+
project_updates: true;
|
|
11
|
+
compact_provider_snapshots: true;
|
|
12
|
+
provider_snapshot_references: true;
|
|
13
|
+
timeline_replacement_invalidation: true;
|
|
14
|
+
timeline_notifications: true;
|
|
15
|
+
plugin_timeline_items: true;
|
|
16
|
+
workspace_setup_blocked: true;
|
|
17
|
+
explicit_event_subscriptions: true;
|
|
18
|
+
};
|
|
19
|
+
/** Calling releases demand; ready acknowledges the initial daemon membership. */
|
|
20
|
+
export type TimelineSubscription = (() => void) & {
|
|
21
|
+
readonly ready: Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
/** Owns connection demand, independently of individual facades and React lifetimes. */
|
|
24
|
+
export declare class ConnectionSubscriptions {
|
|
25
|
+
private readonly send;
|
|
26
|
+
private viewed;
|
|
27
|
+
private timelines;
|
|
28
|
+
private events;
|
|
29
|
+
constructor(send: {
|
|
30
|
+
timelines(agentIds: string[]): Promise<void> | null;
|
|
31
|
+
events(events: SessionEventSubscription[]): Promise<void>;
|
|
32
|
+
failed(error: unknown): void;
|
|
33
|
+
});
|
|
34
|
+
private agentIds;
|
|
35
|
+
setViewed(agentIds: string[]): Promise<void>;
|
|
36
|
+
observeTimeline(agentId: string): TimelineSubscription;
|
|
37
|
+
private syncTimelines;
|
|
38
|
+
setEvents(events: SessionEventSubscription[]): void;
|
|
39
|
+
restore(): void;
|
|
40
|
+
close(): void;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { CLIENT_CAPS } from "@camerontaylor/paseo-protocol/client-capabilities";
|
|
2
|
+
// Protocol support belongs to the installed client. Only browser hosting needs
|
|
3
|
+
// a resource supplied by the caller. Keep this exhaustive as the protocol evolves.
|
|
4
|
+
export const DEFAULT_CLIENT_CAPABILITIES = {
|
|
5
|
+
[CLIENT_CAPS.allProviders]: true,
|
|
6
|
+
[CLIENT_CAPS.selectiveAgentTimeline]: true,
|
|
7
|
+
[CLIENT_CAPS.reasoningMergeEnum]: true,
|
|
8
|
+
[CLIENT_CAPS.customModeIcons]: true,
|
|
9
|
+
[CLIENT_CAPS.terminalReflowableSnapshot]: true,
|
|
10
|
+
[CLIENT_CAPS.providerSubagents]: true,
|
|
11
|
+
// Fork-only capability, so this entry is fork-only too. The map is
|
|
12
|
+
// exhaustive over ClientCapability and the fork adds sideConversations to
|
|
13
|
+
// CLIENT_CAPS, so omitting it is a TS1360 the moment the fork merges.
|
|
14
|
+
// Nothing conflicts here: the file is upstream-new (#4470), the fork never
|
|
15
|
+
// edits it, and git never asks. Carried as a branch, not a resolution.
|
|
16
|
+
[CLIENT_CAPS.sideConversations]: true,
|
|
17
|
+
[CLIENT_CAPS.projectUpdates]: true,
|
|
18
|
+
[CLIENT_CAPS.compactProviderSnapshots]: true,
|
|
19
|
+
[CLIENT_CAPS.providerSnapshotReferences]: true,
|
|
20
|
+
[CLIENT_CAPS.timelineReplacementInvalidation]: true,
|
|
21
|
+
[CLIENT_CAPS.timelineNotifications]: true,
|
|
22
|
+
[CLIENT_CAPS.pluginTimelineItems]: true,
|
|
23
|
+
[CLIENT_CAPS.workspaceSetupBlocked]: true,
|
|
24
|
+
[CLIENT_CAPS.explicitEventSubscriptions]: true,
|
|
25
|
+
};
|
|
26
|
+
class TimelineInterest {
|
|
27
|
+
constructor() {
|
|
28
|
+
this.ready = new Promise((resolve, reject) => {
|
|
29
|
+
this.resolve = resolve;
|
|
30
|
+
this.reject = reject;
|
|
31
|
+
});
|
|
32
|
+
// Readiness is optional for fire-and-forget listeners.
|
|
33
|
+
void this.ready.catch(() => { });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Owns connection demand, independently of individual facades and React lifetimes. */
|
|
37
|
+
export class ConnectionSubscriptions {
|
|
38
|
+
constructor(send) {
|
|
39
|
+
this.send = send;
|
|
40
|
+
this.viewed = new Set();
|
|
41
|
+
this.timelines = new Map();
|
|
42
|
+
this.events = [];
|
|
43
|
+
}
|
|
44
|
+
agentIds() {
|
|
45
|
+
return [...new Set([...this.viewed, ...this.timelines.keys()])].sort();
|
|
46
|
+
}
|
|
47
|
+
setViewed(agentIds) {
|
|
48
|
+
this.viewed = new Set(agentIds);
|
|
49
|
+
return this.syncTimelines();
|
|
50
|
+
}
|
|
51
|
+
observeTimeline(agentId) {
|
|
52
|
+
const interests = this.timelines.get(agentId) ?? new Set();
|
|
53
|
+
const interest = new TimelineInterest();
|
|
54
|
+
interests.add(interest);
|
|
55
|
+
this.timelines.set(agentId, interests);
|
|
56
|
+
// Each caller gets acknowledgement for its own registration, even when an
|
|
57
|
+
// existing listener first subscribed on an earlier connection.
|
|
58
|
+
void this.syncTimelines().catch(this.send.failed);
|
|
59
|
+
let active = true;
|
|
60
|
+
return Object.assign(() => {
|
|
61
|
+
if (!active)
|
|
62
|
+
return;
|
|
63
|
+
active = false;
|
|
64
|
+
interest.reject(new Error("Timeline subscription released before it was ready"));
|
|
65
|
+
interests.delete(interest);
|
|
66
|
+
if (interests.size === 0) {
|
|
67
|
+
this.timelines.delete(agentId);
|
|
68
|
+
void this.syncTimelines().catch(this.send.failed);
|
|
69
|
+
}
|
|
70
|
+
}, { ready: interest.ready });
|
|
71
|
+
}
|
|
72
|
+
async syncTimelines() {
|
|
73
|
+
const interests = [];
|
|
74
|
+
for (const listeners of this.timelines.values()) {
|
|
75
|
+
for (const interest of listeners)
|
|
76
|
+
interests.push(interest);
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const sent = this.send.timelines(this.agentIds());
|
|
80
|
+
if (!sent)
|
|
81
|
+
return;
|
|
82
|
+
await sent;
|
|
83
|
+
for (const interest of interests)
|
|
84
|
+
interest.resolve();
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
for (const interest of interests)
|
|
88
|
+
interest.reject(error);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
setEvents(events) {
|
|
93
|
+
if (JSON.stringify(this.events) === JSON.stringify(events))
|
|
94
|
+
return;
|
|
95
|
+
this.events = events;
|
|
96
|
+
void this.send.events(events).catch(this.send.failed);
|
|
97
|
+
}
|
|
98
|
+
restore() {
|
|
99
|
+
if (this.agentIds().length)
|
|
100
|
+
void this.syncTimelines().catch(this.send.failed);
|
|
101
|
+
if (this.events.length)
|
|
102
|
+
void this.send.events(this.events).catch(this.send.failed);
|
|
103
|
+
}
|
|
104
|
+
close() {
|
|
105
|
+
for (const interests of this.timelines.values()) {
|
|
106
|
+
for (const interest of interests)
|
|
107
|
+
interest.reject(new Error("Daemon client closed"));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AgentSnapshotPayload, CreationSnapshot, WorkspaceCreateResponse } from "@camerontaylor/paseo-protocol/messages";
|
|
2
|
+
import type { CreateAgentRequestOptions, CreateWorkspaceRequestOptions } from "../daemon-client.js";
|
|
3
|
+
type Kind = CreationSnapshot["kind"];
|
|
4
|
+
export interface CreationResult {
|
|
5
|
+
agent?: AgentSnapshotPayload | null;
|
|
6
|
+
workspace?: WorkspaceCreateResponse["payload"]["workspace"];
|
|
7
|
+
creation?: CreationSnapshot;
|
|
8
|
+
error: string | null;
|
|
9
|
+
errorCode?: string;
|
|
10
|
+
setupTerminalId?: string | null;
|
|
11
|
+
setupSkippedReason?: string;
|
|
12
|
+
requestId?: string;
|
|
13
|
+
}
|
|
14
|
+
interface Dependencies {
|
|
15
|
+
supports: () => boolean;
|
|
16
|
+
connected: () => boolean;
|
|
17
|
+
requestId: () => string;
|
|
18
|
+
request: (kind: Kind, input: Record<string, unknown>) => Promise<CreationResult>;
|
|
19
|
+
subscribe: (kind: Kind, key: string, enabled?: boolean) => Promise<CreationSnapshot | null>;
|
|
20
|
+
legacyAgent: (input: CreateAgentRequestOptions) => Promise<AgentSnapshotPayload>;
|
|
21
|
+
legacyWorkspace: (input: CreateWorkspaceRequestOptions) => Promise<WorkspaceCreateResponse["payload"]>;
|
|
22
|
+
sendMessage: (id: string, text: string, options: {
|
|
23
|
+
messageId: string;
|
|
24
|
+
images?: CreateAgentRequestOptions["images"];
|
|
25
|
+
attachments?: CreateAgentRequestOptions["attachments"];
|
|
26
|
+
}) => Promise<unknown>;
|
|
27
|
+
}
|
|
28
|
+
/** Client-owned observation and compatibility. UI callbacks never advance creation. */
|
|
29
|
+
export declare class CreationClient {
|
|
30
|
+
private readonly deps;
|
|
31
|
+
private readonly operations;
|
|
32
|
+
constructor(deps: Dependencies);
|
|
33
|
+
createAgent(input: CreateAgentRequestOptions): Promise<CreationResult>;
|
|
34
|
+
createWorkspace(input: CreateWorkspaceRequestOptions): Promise<CreationResult>;
|
|
35
|
+
private legacyAgent;
|
|
36
|
+
private start;
|
|
37
|
+
private submit;
|
|
38
|
+
receive(snapshot: CreationSnapshot): void;
|
|
39
|
+
private recover;
|
|
40
|
+
reconnect(): void;
|
|
41
|
+
close(): void;
|
|
42
|
+
private finish;
|
|
43
|
+
private fail;
|
|
44
|
+
private notify;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/** Client-owned observation and compatibility. UI callbacks never advance creation. */
|
|
2
|
+
export class CreationClient {
|
|
3
|
+
constructor(deps) {
|
|
4
|
+
this.deps = deps;
|
|
5
|
+
this.operations = new Map();
|
|
6
|
+
}
|
|
7
|
+
createAgent(input) {
|
|
8
|
+
const { onEvent, ...request } = input;
|
|
9
|
+
return this.start("agent", request, onEvent, () => this.legacyAgent(request));
|
|
10
|
+
}
|
|
11
|
+
createWorkspace(input) {
|
|
12
|
+
const { onEvent, ...request } = input;
|
|
13
|
+
return this.start("workspace", request, onEvent, async (operation) => {
|
|
14
|
+
if (request.workspaceId || request.agent?.agentId)
|
|
15
|
+
throw new Error("Update the host to use caller-selected creation IDs.");
|
|
16
|
+
const { agent, ...workspaceInput } = request;
|
|
17
|
+
const workspace = await this.deps.legacyWorkspace(workspaceInput);
|
|
18
|
+
if (workspace.error || !workspace.workspace)
|
|
19
|
+
return workspace;
|
|
20
|
+
this.receive({
|
|
21
|
+
kind: "workspace",
|
|
22
|
+
idempotencyKey: operation.key,
|
|
23
|
+
revision: 1,
|
|
24
|
+
phase: "workspace_ready",
|
|
25
|
+
workspaceId: workspace.workspace.id,
|
|
26
|
+
agentId: null,
|
|
27
|
+
workspace: workspace.workspace,
|
|
28
|
+
setupSkippedReason: workspace.setupSkippedReason,
|
|
29
|
+
error: null,
|
|
30
|
+
});
|
|
31
|
+
if (!agent)
|
|
32
|
+
return workspace;
|
|
33
|
+
const sourceCwd = request.source.kind === "directory" ? request.source.path : request.source.cwd;
|
|
34
|
+
const cwd = sourceCwd
|
|
35
|
+
? remapDirectory(agent.config.cwd, sourceCwd, workspace.workspace.workspaceDirectory)
|
|
36
|
+
: workspace.workspace.workspaceDirectory;
|
|
37
|
+
const created = await this.legacyAgent({
|
|
38
|
+
...agent,
|
|
39
|
+
config: { ...agent.config, cwd },
|
|
40
|
+
workspaceId: workspace.workspace.id,
|
|
41
|
+
idempotencyKey: `${operation.key}:agent`,
|
|
42
|
+
});
|
|
43
|
+
return { ...workspace, agent: created.agent };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async legacyAgent(input) {
|
|
47
|
+
if (input.agentId)
|
|
48
|
+
throw new Error("Update the host to use caller-selected creation IDs.");
|
|
49
|
+
// COMPAT(creationLifecycle): added in v0.8.0, remove after 2027-03-11 once the daemon floor supports creationLifecycle.
|
|
50
|
+
// Old keyed creates reject initialPrompt. Keep this adaptation private to the SDK.
|
|
51
|
+
if (input.idempotencyKey) {
|
|
52
|
+
const { initialPrompt = "", images, attachments, ...creation } = input;
|
|
53
|
+
const hasPrompt = Boolean(initialPrompt.trim() || images?.length || attachments?.length);
|
|
54
|
+
if (hasPrompt && input.outputSchema)
|
|
55
|
+
throw new Error("Update the host to create a keyed agent with structured initial output.");
|
|
56
|
+
const agent = await this.deps.legacyAgent(creation);
|
|
57
|
+
if (hasPrompt)
|
|
58
|
+
await this.deps.sendMessage(agent.id, initialPrompt, {
|
|
59
|
+
messageId: input.clientMessageId ?? `${input.idempotencyKey}:initial-message`,
|
|
60
|
+
images,
|
|
61
|
+
attachments,
|
|
62
|
+
});
|
|
63
|
+
return { agent, error: null };
|
|
64
|
+
}
|
|
65
|
+
return { agent: await this.deps.legacyAgent(input), error: null };
|
|
66
|
+
}
|
|
67
|
+
start(kind, input, observer, legacy) {
|
|
68
|
+
const key = typeof input.idempotencyKey === "string" ? input.idempotencyKey : this.deps.requestId();
|
|
69
|
+
const identity = JSON.stringify([kind, key]);
|
|
70
|
+
const { requestId: _requestId, ...intent } = input;
|
|
71
|
+
const fingerprint = JSON.stringify(intent, (_key, value) => value && typeof value === "object" && !Array.isArray(value)
|
|
72
|
+
? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)))
|
|
73
|
+
: value);
|
|
74
|
+
const previous = this.operations.get(identity);
|
|
75
|
+
if (previous) {
|
|
76
|
+
if (previous.fingerprint !== fingerprint)
|
|
77
|
+
return Promise.reject(new Error(`${kind}_request_key_conflict`));
|
|
78
|
+
if (observer) {
|
|
79
|
+
previous.observers.add(observer);
|
|
80
|
+
if (previous.snapshot)
|
|
81
|
+
this.notify(observer, previous.snapshot);
|
|
82
|
+
}
|
|
83
|
+
return previous.result;
|
|
84
|
+
}
|
|
85
|
+
let resolve;
|
|
86
|
+
let reject;
|
|
87
|
+
const result = new Promise((done, fail) => {
|
|
88
|
+
resolve = done;
|
|
89
|
+
reject = fail;
|
|
90
|
+
});
|
|
91
|
+
const operation = {
|
|
92
|
+
kind,
|
|
93
|
+
key,
|
|
94
|
+
input: { ...input, idempotencyKey: key },
|
|
95
|
+
fingerprint,
|
|
96
|
+
observers: new Set(observer ? [observer] : []),
|
|
97
|
+
result,
|
|
98
|
+
resolve,
|
|
99
|
+
reject,
|
|
100
|
+
settled: false,
|
|
101
|
+
modern: this.deps.supports(),
|
|
102
|
+
recovering: false,
|
|
103
|
+
};
|
|
104
|
+
this.operations.set(identity, operation);
|
|
105
|
+
if (operation.modern)
|
|
106
|
+
void this.submit(operation);
|
|
107
|
+
else
|
|
108
|
+
void legacy(operation).then((value) => this.finish(operation, value), (error) => this.fail(operation, error));
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
async submit(operation) {
|
|
112
|
+
try {
|
|
113
|
+
const result = await this.deps.request(operation.kind, {
|
|
114
|
+
...operation.input,
|
|
115
|
+
subscribe: true,
|
|
116
|
+
});
|
|
117
|
+
if (result.creation)
|
|
118
|
+
this.receive(result.creation);
|
|
119
|
+
this.finish(operation, result);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (operation.settled || !this.deps.connected())
|
|
123
|
+
return;
|
|
124
|
+
this.fail(operation, error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
receive(snapshot) {
|
|
128
|
+
const operation = this.operations.get(JSON.stringify([snapshot.kind, snapshot.idempotencyKey]));
|
|
129
|
+
if (!operation || (operation.snapshot && operation.snapshot.revision >= snapshot.revision))
|
|
130
|
+
return;
|
|
131
|
+
operation.snapshot = snapshot;
|
|
132
|
+
for (const observer of operation.observers)
|
|
133
|
+
this.notify(observer, snapshot);
|
|
134
|
+
if (operation.recovering && (snapshot.phase === "completed" || snapshot.phase === "failed")) {
|
|
135
|
+
this.finish(operation, {
|
|
136
|
+
agent: snapshot.agent,
|
|
137
|
+
workspace: snapshot.workspace,
|
|
138
|
+
creation: snapshot,
|
|
139
|
+
setupSkippedReason: snapshot.setupSkippedReason,
|
|
140
|
+
error: snapshot.error,
|
|
141
|
+
errorCode: snapshot.errorCode,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async recover(operation) {
|
|
146
|
+
operation.recovering = true;
|
|
147
|
+
const snapshot = await this.deps.subscribe(operation.kind, operation.key);
|
|
148
|
+
if (!snapshot) {
|
|
149
|
+
await this.submit(operation);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
this.receive(snapshot);
|
|
153
|
+
if (snapshot.phase === "completed" || snapshot.phase === "failed") {
|
|
154
|
+
this.finish(operation, {
|
|
155
|
+
agent: snapshot.agent,
|
|
156
|
+
workspace: snapshot.workspace,
|
|
157
|
+
creation: snapshot,
|
|
158
|
+
setupSkippedReason: snapshot.setupSkippedReason,
|
|
159
|
+
error: snapshot.error,
|
|
160
|
+
errorCode: snapshot.errorCode,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
reconnect() {
|
|
165
|
+
for (const operation of this.operations.values()) {
|
|
166
|
+
if (operation.settled || !operation.modern)
|
|
167
|
+
continue;
|
|
168
|
+
void this.recover(operation).catch((error) => {
|
|
169
|
+
if (this.deps.connected())
|
|
170
|
+
this.fail(operation, error);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
close() {
|
|
175
|
+
for (const operation of this.operations.values())
|
|
176
|
+
if (!operation.settled)
|
|
177
|
+
this.fail(operation, new Error("Daemon client closed"));
|
|
178
|
+
this.operations.clear();
|
|
179
|
+
}
|
|
180
|
+
finish(operation, result) {
|
|
181
|
+
if (operation.settled)
|
|
182
|
+
return;
|
|
183
|
+
operation.settled = true;
|
|
184
|
+
if (operation.recovering)
|
|
185
|
+
void this.deps.subscribe(operation.kind, operation.key, false).catch(() => undefined);
|
|
186
|
+
operation.resolve(result);
|
|
187
|
+
operation.observers.clear();
|
|
188
|
+
this.operations.delete(JSON.stringify([operation.kind, operation.key]));
|
|
189
|
+
}
|
|
190
|
+
fail(operation, error) {
|
|
191
|
+
if (operation.settled)
|
|
192
|
+
return;
|
|
193
|
+
operation.settled = true;
|
|
194
|
+
operation.reject(error instanceof Error ? error : new Error(String(error)));
|
|
195
|
+
operation.observers.clear();
|
|
196
|
+
this.operations.delete(JSON.stringify([operation.kind, operation.key]));
|
|
197
|
+
}
|
|
198
|
+
notify(observer, snapshot) {
|
|
199
|
+
try {
|
|
200
|
+
observer(snapshot);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
/* Observation does not control execution. */
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function remapDirectory(cwd, source, target) {
|
|
208
|
+
const normalize = (value) => value.replace(/\\/g, "/").replace(/\/$/, "");
|
|
209
|
+
const normalized = normalize(cwd), root = normalize(source);
|
|
210
|
+
if (normalized === root)
|
|
211
|
+
return target;
|
|
212
|
+
if (!normalized.startsWith(`${root}/`))
|
|
213
|
+
throw new Error("Agent directory must be inside the workspace source");
|
|
214
|
+
return `${normalize(target)}${normalized.slice(root.length)}`;
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DaemonTransport, DaemonTransportFactory, TransportLogger } from "./daemon-client-transport-types.js";
|
|
2
|
+
export declare function createRelayE2eeTransportFactory(args: {
|
|
3
|
+
baseFactory: DaemonTransportFactory;
|
|
4
|
+
daemonPublicKeyB64: string;
|
|
5
|
+
logger: TransportLogger;
|
|
6
|
+
}): DaemonTransportFactory;
|
|
7
|
+
export declare function createEncryptedTransport(base: DaemonTransport, daemonPublicKeyB64: string, logger: TransportLogger): DaemonTransport;
|
|
8
|
+
//# sourceMappingURL=daemon-client-relay-e2ee-transport.d.ts.map
|