Skip to content

Commit aecb313

Browse files
committed
feat: hook up pi runtime to ChatThread
1 parent 75ec760 commit aecb313

73 files changed

Lines changed: 5422 additions & 637 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/code/src/main/di/bindings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ import type {
188188
} from "@posthog/workspace-server/services/mcp-relay/identifiers";
189189
import type {
190190
PI_RPC_CLIENT_FACTORY,
191+
PI_RUNTIME_FACTORY,
191192
PiRpcClientFactory,
193+
PiRuntimeFactory,
192194
} from "@posthog/workspace-server/services/pi-session/identifiers";
193195
import type { PosthogPluginService } from "@posthog/workspace-server/services/posthog-plugin/posthog-plugin";
194196
import type { ProcessTrackingService } from "@posthog/workspace-server/services/process-tracking/process-tracking";
@@ -356,6 +358,8 @@ export interface MainBindings {
356358
[AGENT_LOGGER]: RootLogger;
357359
[PI_RPC_CLIENT_FACTORY]: PiRpcClientFactory;
358360

361+
[PI_RUNTIME_FACTORY]: PiRuntimeFactory;
362+
359363
// Logger
360364
[ROOT_LOGGER]: RootLogger;
361365

apps/code/src/main/di/container.ts

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ import { onboardingImportModule } from "@posthog/workspace-server/services/onboa
189189
import { osModule } from "@posthog/workspace-server/services/os/os.module";
190190
import {
191191
PI_RPC_CLIENT_FACTORY,
192+
PI_RUNTIME_FACTORY,
192193
PI_SESSION_SERVICE,
193194
} from "@posthog/workspace-server/services/pi-session/identifiers";
194195
import type { PiSessionService } from "@posthog/workspace-server/services/pi-session/pi-session";
@@ -230,6 +231,7 @@ import { workspaceMetadataModule } from "@posthog/workspace-server/services/work
230231
import ExternalAppsStoreImpl from "electron-store";
231232
import type { FileWatcherBridge } from "../index";
232233
import { DesktopPiRpcClientFactory } from "../platform-adapters/desktop-pi-rpc-client-factory";
234+
import { DesktopPiRuntimeFactory } from "../platform-adapters/desktop-pi-runtime-factory";
233235
import { ElectronAppLifecycle } from "../platform-adapters/electron-app-lifecycle";
234236
import { ElectronAppMeta } from "../platform-adapters/electron-app-meta";
235237
import { ElectronAppMetrics } from "../platform-adapters/electron-app-metrics";
@@ -325,6 +327,17 @@ import {
325327
WORKTREE_REPOSITORY as MAIN_WORKTREE_REPOSITORY,
326328
} from "./tokens";
327329

330+
async function cancelTaskSessions(
331+
agentService: AgentService,
332+
piSessionService: PiSessionService,
333+
taskId: string,
334+
): Promise<void> {
335+
await Promise.all([
336+
agentService.cancelSessionsByTaskId(taskId),
337+
piSessionService.stop(taskId),
338+
]);
339+
}
340+
328341
export const container = new TypedContainer<MainBindings>({
329342
defaultScope: "Singleton",
330343
});
@@ -366,6 +379,7 @@ container
366379
.bind(MAIN_DEFAULT_ADDITIONAL_DIRECTORY_REPOSITORY)
367380
.toService(DEFAULT_ADDITIONAL_DIRECTORY_REPOSITORY);
368381
container.load(agentModule);
382+
container.bind(PI_RUNTIME_FACTORY).to(DesktopPiRuntimeFactory);
369383
container.load(piSessionModule);
370384
container.bind(AGENT_SLEEP_COORDINATOR).toService(MAIN_SLEEP_SERVICE);
371385
container.bind(AGENT_MCP_APPS).toService(MCP_APPS_SERVICE);
@@ -406,12 +420,12 @@ container.bind(MCP_PROXY_AUTH).toDynamicValue((ctx) => {
406420
});
407421
container.load(archiveModule);
408422
container.bind(ARCHIVE_SESSION_CANCELLER).toDynamicValue((ctx) => ({
409-
cancelSessionsByTaskId: async (taskId: string) => {
410-
await Promise.all([
411-
ctx.get<AgentService>(AGENT_SERVICE).cancelSessionsByTaskId(taskId),
412-
ctx.get<PiSessionService>(PI_SESSION_SERVICE).stop(taskId),
413-
]);
414-
},
423+
cancelSessionsByTaskId: (taskId: string) =>
424+
cancelTaskSessions(
425+
ctx.get<AgentService>(AGENT_SERVICE),
426+
ctx.get<PiSessionService>(PI_SESSION_SERVICE),
427+
taskId,
428+
),
415429
}));
416430
container.bind(ARCHIVE_FILE_WATCHER).toDynamicValue((ctx) => ({
417431
stopWatching: async (worktreePath: string) => {
@@ -422,12 +436,12 @@ container.bind(ARCHIVE_FILE_WATCHER).toDynamicValue((ctx) => ({
422436
}));
423437
container.load(suspensionModule);
424438
container.bind(SUSPENSION_SESSION_CANCELLER).toDynamicValue((ctx) => ({
425-
cancelSessionsByTaskId: async (taskId: string) => {
426-
await Promise.all([
427-
ctx.get<AgentService>(AGENT_SERVICE).cancelSessionsByTaskId(taskId),
428-
ctx.get<PiSessionService>(PI_SESSION_SERVICE).stop(taskId),
429-
]);
430-
},
439+
cancelSessionsByTaskId: (taskId: string) =>
440+
cancelTaskSessions(
441+
ctx.get<AgentService>(AGENT_SERVICE),
442+
ctx.get<PiSessionService>(PI_SESSION_SERVICE),
443+
taskId,
444+
),
431445
}));
432446
container.bind(SUSPENSION_FILE_WATCHER).toDynamicValue((ctx) => ({
433447
stopWatching: async (worktreePath: string) => {
@@ -705,12 +719,12 @@ container.load(workspaceModule);
705719
container.bind(WORKSPACE_AGENT).toDynamicValue((ctx): WorkspaceAgent => {
706720
const agent = ctx.get<AgentService>(AGENT_SERVICE);
707721
return {
708-
cancelSessionsByTaskId: async (taskId) => {
709-
await Promise.all([
710-
agent.cancelSessionsByTaskId(taskId),
711-
ctx.get<PiSessionService>(PI_SESSION_SERVICE).stop(taskId),
712-
]);
713-
},
722+
cancelSessionsByTaskId: (taskId) =>
723+
cancelTaskSessions(
724+
agent,
725+
ctx.get<PiSessionService>(PI_SESSION_SERVICE),
726+
taskId,
727+
),
714728
onAgentFileActivity: (handler) =>
715729
agent.on(AgentServiceEvent.AgentFileActivity, handler),
716730
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { PiRuntime } from "@posthog/agent/pi/runtime";
2+
import type { PiRpcClientFactory } from "@posthog/workspace-server/services/pi-session/identifiers";
3+
import { describe, expect, it, vi } from "vitest";
4+
import { DesktopPiRuntimeFactory } from "./desktop-pi-runtime-factory";
5+
6+
describe("DesktopPiRuntimeFactory", () => {
7+
it("wraps the host-authenticated RPC client", async () => {
8+
const client = { onEvent: vi.fn() };
9+
const clientFactory = {
10+
create: vi.fn(async () => client),
11+
} as unknown as PiRpcClientFactory;
12+
const factory = new DesktopPiRuntimeFactory(clientFactory);
13+
14+
const runtime = await factory.create({ cwd: "/workspace" });
15+
16+
expect(runtime).toBeInstanceOf(PiRuntime);
17+
expect(runtime.client).toBe(client);
18+
expect(clientFactory.create).toHaveBeenCalledWith({ cwd: "/workspace" });
19+
});
20+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { PiRuntime } from "@posthog/agent/pi/runtime";
2+
import {
3+
PI_RPC_CLIENT_FACTORY,
4+
type PiRpcClientFactory,
5+
type PiRuntimeFactory,
6+
} from "@posthog/workspace-server/services/pi-session/identifiers";
7+
import { inject, injectable } from "inversify";
8+
9+
@injectable()
10+
export class DesktopPiRuntimeFactory implements PiRuntimeFactory {
11+
constructor(
12+
@inject(PI_RPC_CLIENT_FACTORY)
13+
private readonly clientFactory: PiRpcClientFactory,
14+
) {}
15+
16+
async create(input: {
17+
cwd: string;
18+
model?: string;
19+
sessionFile?: string;
20+
}): Promise<PiRuntime> {
21+
const client = await this.clientFactory.create(input);
22+
return new PiRuntime(client);
23+
}
24+
}

apps/code/src/renderer/di/bindings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ import {
7575
} from "@posthog/core/onboarding/identifiers";
7676
import { PI_RUNNER } from "@posthog/core/pi-runtime/identifiers";
7777
import type { PiRunner } from "@posthog/core/pi-runtime/piRunner";
78+
import {
79+
PI_SESSION_CLIENT,
80+
type PiSessionClient,
81+
} from "@posthog/core/pi-runtime/piSessionController";
7882
import {
7983
type BundleLocalSkill,
8084
CLOUD_ARTIFACT_BUNDLE_LOCAL_SKILL,
@@ -291,6 +295,7 @@ export interface RendererBindings {
291295
[ANALYTICS_TRACKER]: AnalyticsTracker;
292296
[TASK_CREATION_HOST]: ITaskCreationHost;
293297
[PI_RUNNER]: PiRunner;
298+
[PI_SESSION_CLIENT]: PiSessionClient;
294299
[TASK_CREATION_EFFECTS]: TaskCreationEffects;
295300
[RENDERER_TASK_SERVICE]: TaskService;
296301
[TASK_SERVICE]: TaskService;

apps/code/src/renderer/di/container.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ import type { LlmMessage } from "@posthog/core/llm-gateway/schemas";
3333
import { LOCAL_MCP_WORKSPACE_CLIENT } from "@posthog/core/local-mcp/identifiers";
3434
import type { LocalMcpWorkspaceClient } from "@posthog/core/local-mcp/localMcpImport";
3535
import { PI_RUNNER } from "@posthog/core/pi-runtime/identifiers";
36+
import { piRuntimeModule } from "@posthog/core/pi-runtime/pi-runtime.module";
3637
import type { PiRunner } from "@posthog/core/pi-runtime/piRunner";
38+
import { PI_SESSION_CLIENT } from "@posthog/core/pi-runtime/piSessionController";
3739
import {
3840
CLOUD_ARTIFACT_BUNDLE_LOCAL_SKILL,
3941
CLOUD_ARTIFACT_READ_FILE_AS_BASE64,
@@ -87,6 +89,7 @@ import {
8789
import { WorkspaceSetupService } from "@posthog/core/workspace/WorkspaceSetupService";
8890
import { setRootContainer } from "@posthog/di/container";
8991
import { HOST_TRPC_CLIENT } from "@posthog/host-router/client";
92+
import { TrpcPiSessionClient } from "@posthog/host-router/pi-session-client";
9093
import {
9194
BROWSER_TABS_CLIENT,
9295
type BrowserTabsClient,
@@ -294,6 +297,8 @@ container
294297
// Bind services
295298
container.bind<ITaskCreationHost>(TASK_CREATION_HOST).to(TrpcTaskCreationHost);
296299
container.bind<PiRunner>(PI_RUNNER).to(TrpcPiRunner);
300+
container.bind(PI_SESSION_CLIENT).to(TrpcPiSessionClient);
301+
container.load(piRuntimeModule);
297302
container.bind(TASK_CREATION_EFFECTS).toConstantValue(taskCreationEffects);
298303
container.bind<TaskService>(RENDERER_TASK_SERVICE).to(TaskService);
299304
container.bind<TaskService>(TASK_SERVICE).toService(RENDERER_TASK_SERVICE);

apps/code/src/renderer/platform-adapters/trpc-pi-runner.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,27 @@ import type {
33
PiRunInput,
44
PiRunner,
55
} from "@posthog/core/pi-runtime/piRunner";
6-
import { resolveService } from "@posthog/di/container";
76
import {
87
HOST_TRPC_CLIENT,
98
type HostTrpcClient,
109
} from "@posthog/host-router/client";
10+
import { inject, injectable } from "inversify";
1111

12-
function hostClient(): HostTrpcClient {
13-
return resolveService<HostTrpcClient>(HOST_TRPC_CLIENT);
14-
}
15-
12+
@injectable()
1613
export class TrpcPiRunner implements PiRunner {
14+
constructor(
15+
@inject(HOST_TRPC_CLIENT) private readonly hostClient: HostTrpcClient,
16+
) {}
17+
1718
async create(input: PiRunInput): Promise<void> {
18-
await hostClient().piSession.start.mutate(input);
19+
await this.hostClient.piSession.start.mutate(input);
1920
}
2021

2122
resume(input: PiResumeInput): Promise<void> {
22-
return hostClient().piSession.resume.mutate(input);
23+
return this.hostClient.piSession.resume.mutate(input);
2324
}
2425

2526
stop(taskId: string): Promise<void> {
26-
return hostClient().piSession.stop.mutate({ taskId });
27+
return this.hostClient.piSession.stop.mutate({ taskId });
2728
}
2829
}

apps/web/src/web-container.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import "reflect-metadata";
22
import { TypedContainer } from "@inversifyjs/strongly-typed";
33
import { taskThreadCoreModule } from "@posthog/core/canvas/taskThread.module";
4+
import { piRuntimeModule } from "@posthog/core/pi-runtime/pi-runtime.module";
5+
import {
6+
PI_SESSION_CLIENT,
7+
type PiSessionClient,
8+
} from "@posthog/core/pi-runtime/piSessionController";
49
import { setRootContainer } from "@posthog/di/container";
510
import { ROOT_LOGGER, type RootLogger } from "@posthog/di/logger";
611
import {
712
HOST_TRPC_CLIENT,
813
type HostTrpcClient,
914
} from "@posthog/host-router/client";
15+
import { TrpcPiSessionClient } from "@posthog/host-router/pi-session-client";
1016
import { sandboxProxyHtml } from "@posthog/shared/mcp-sandbox-proxy";
1117
import {
1218
AUTH_SIDE_EFFECTS,
@@ -37,6 +43,7 @@ import { hostTrpcClient } from "./web-trpc";
3743

3844
interface WebBindings {
3945
[HOST_TRPC_CLIENT]: HostTrpcClient;
46+
[PI_SESSION_CLIENT]: PiSessionClient;
4047
[ROOT_LOGGER]: RootLogger;
4148
[FEATURE_FLAGS]: FeatureFlags;
4249
[ANALYTICS_TRACKER]: AnalyticsTracker;
@@ -54,6 +61,8 @@ export const container = new TypedContainer<WebBindings>({
5461

5562
// Keystone: the same typed host client the renderer binds, over HTTP not IPC.
5663
container.bind(HOST_TRPC_CLIENT).toConstantValue(hostTrpcClient);
64+
container.bind(PI_SESSION_CLIENT).to(TrpcPiSessionClient);
65+
container.load(piRuntimeModule);
5766

5867
// Logger: web uses console; electron uses electron-log. Same RootLogger shape.
5968
const scoped = (name?: string): RootLogger => ({

packages/agent/package.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@
3232
"types": "./dist/pi/rpc-client.d.ts",
3333
"import": "./dist/pi/rpc-client.js"
3434
},
35+
"./pi/conversation": {
36+
"types": "./dist/pi/conversation/translatePiConversation.d.ts",
37+
"import": "./dist/pi/conversation/translatePiConversation.js"
38+
},
39+
"./pi/runtime": {
40+
"types": "./dist/pi/runtime.d.ts",
41+
"import": "./dist/pi/runtime.js"
42+
},
43+
"./pi/types": {
44+
"types": "./dist/pi/types.d.ts",
45+
"import": "./dist/pi/types.js"
46+
},
3547
"./pr-url-detector": {
3648
"types": "./dist/pr-url-detector.d.ts",
3749
"import": "./dist/pr-url-detector.js"
@@ -116,7 +128,8 @@
116128
"author": "PostHog",
117129
"license": "MIT",
118130
"scripts": {
119-
"build": "node ../../scripts/rimraf.mjs dist && tsup && node build/verify-local-tools-mcp-server.mjs",
131+
"build": "node ../../scripts/rimraf.mjs dist && tsup && pnpm build:types && node build/verify-local-tools-mcp-server.mjs",
132+
"build:types": "tsc -p tsconfig.build.json",
120133
"dev": "tsup --watch",
121134
"test": "vitest run",
122135
"test:watch": "vitest",
@@ -144,6 +157,8 @@
144157
"@agentclientprotocol/sdk": "1.1.0",
145158
"@anthropic-ai/claude-agent-sdk": "0.3.197",
146159
"@anthropic-ai/sdk": "0.109.0",
160+
"@earendil-works/pi-agent-core": "catalog:",
161+
"@earendil-works/pi-ai": "catalog:",
147162
"@earendil-works/pi-coding-agent": "catalog:",
148163
"@hono/node-server": "^1.19.9",
149164
"@openai/codex": "0.140.0",
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { ToolsOptions } from "@earendil-works/pi-coding-agent";
2+
import type { AgentToolKind } from "@posthog/shared";
3+
4+
export type PiToolName = keyof ToolsOptions;
5+
6+
export const TOOL_KIND_BY_NAME: Record<PiToolName, AgentToolKind> = {
7+
read: "read",
8+
edit: "edit",
9+
write: "edit",
10+
bash: "execute",
11+
grep: "search",
12+
find: "search",
13+
ls: "read",
14+
};

0 commit comments

Comments
 (0)