Skip to content

Commit 0523230

Browse files
committed
feat: hook up pi runtime to ChatThread
1 parent 8c9355a commit 0523230

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
@@ -182,7 +182,9 @@ import type { MCP_PROXY_AUTH } from "@posthog/workspace-server/services/mcp-prox
182182
import type { McpProxyAuth } from "@posthog/workspace-server/services/mcp-proxy/ports";
183183
import type {
184184
PI_RPC_CLIENT_FACTORY,
185+
PI_RUNTIME_FACTORY,
185186
PiRpcClientFactory,
187+
PiRuntimeFactory,
186188
} from "@posthog/workspace-server/services/pi-session/identifiers";
187189
import type { PosthogPluginService } from "@posthog/workspace-server/services/posthog-plugin/posthog-plugin";
188190
import type { ProcessTrackingService } from "@posthog/workspace-server/services/process-tracking/process-tracking";
@@ -350,6 +352,8 @@ export interface MainBindings {
350352
[AGENT_LOGGER]: RootLogger;
351353
[PI_RPC_CLIENT_FACTORY]: PiRpcClientFactory;
352354

355+
[PI_RUNTIME_FACTORY]: PiRuntimeFactory;
356+
353357
// Logger
354358
[ROOT_LOGGER]: RootLogger;
355359

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

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ import { onboardingImportModule } from "@posthog/workspace-server/services/onboa
186186
import { osModule } from "@posthog/workspace-server/services/os/os.module";
187187
import {
188188
PI_RPC_CLIENT_FACTORY,
189+
PI_RUNTIME_FACTORY,
189190
PI_SESSION_SERVICE,
190191
} from "@posthog/workspace-server/services/pi-session/identifiers";
191192
import type { PiSessionService } from "@posthog/workspace-server/services/pi-session/pi-session";
@@ -227,6 +228,7 @@ import { workspaceMetadataModule } from "@posthog/workspace-server/services/work
227228
import ExternalAppsStoreImpl from "electron-store";
228229
import type { FileWatcherBridge } from "../index";
229230
import { DesktopPiRpcClientFactory } from "../platform-adapters/desktop-pi-rpc-client-factory";
231+
import { DesktopPiRuntimeFactory } from "../platform-adapters/desktop-pi-runtime-factory";
230232
import { ElectronAppLifecycle } from "../platform-adapters/electron-app-lifecycle";
231233
import { ElectronAppMeta } from "../platform-adapters/electron-app-meta";
232234
import { ElectronAppMetrics } from "../platform-adapters/electron-app-metrics";
@@ -322,6 +324,17 @@ import {
322324
WORKTREE_REPOSITORY as MAIN_WORKTREE_REPOSITORY,
323325
} from "./tokens";
324326

327+
async function cancelTaskSessions(
328+
agentService: AgentService,
329+
piSessionService: PiSessionService,
330+
taskId: string,
331+
): Promise<void> {
332+
await Promise.all([
333+
agentService.cancelSessionsByTaskId(taskId),
334+
piSessionService.stop(taskId),
335+
]);
336+
}
337+
325338
export const container = new TypedContainer<MainBindings>({
326339
defaultScope: "Singleton",
327340
});
@@ -363,6 +376,7 @@ container
363376
.bind(MAIN_DEFAULT_ADDITIONAL_DIRECTORY_REPOSITORY)
364377
.toService(DEFAULT_ADDITIONAL_DIRECTORY_REPOSITORY);
365378
container.load(agentModule);
379+
container.bind(PI_RUNTIME_FACTORY).to(DesktopPiRuntimeFactory);
366380
container.load(piSessionModule);
367381
container.bind(AGENT_SLEEP_COORDINATOR).toService(MAIN_SLEEP_SERVICE);
368382
container.bind(AGENT_MCP_APPS).toService(MCP_APPS_SERVICE);
@@ -403,12 +417,12 @@ container.bind(MCP_PROXY_AUTH).toDynamicValue((ctx) => {
403417
});
404418
container.load(archiveModule);
405419
container.bind(ARCHIVE_SESSION_CANCELLER).toDynamicValue((ctx) => ({
406-
cancelSessionsByTaskId: async (taskId: string) => {
407-
await Promise.all([
408-
ctx.get<AgentService>(AGENT_SERVICE).cancelSessionsByTaskId(taskId),
409-
ctx.get<PiSessionService>(PI_SESSION_SERVICE).stop(taskId),
410-
]);
411-
},
420+
cancelSessionsByTaskId: (taskId: string) =>
421+
cancelTaskSessions(
422+
ctx.get<AgentService>(AGENT_SERVICE),
423+
ctx.get<PiSessionService>(PI_SESSION_SERVICE),
424+
taskId,
425+
),
412426
}));
413427
container.bind(ARCHIVE_FILE_WATCHER).toDynamicValue((ctx) => ({
414428
stopWatching: async (worktreePath: string) => {
@@ -419,12 +433,12 @@ container.bind(ARCHIVE_FILE_WATCHER).toDynamicValue((ctx) => ({
419433
}));
420434
container.load(suspensionModule);
421435
container.bind(SUSPENSION_SESSION_CANCELLER).toDynamicValue((ctx) => ({
422-
cancelSessionsByTaskId: async (taskId: string) => {
423-
await Promise.all([
424-
ctx.get<AgentService>(AGENT_SERVICE).cancelSessionsByTaskId(taskId),
425-
ctx.get<PiSessionService>(PI_SESSION_SERVICE).stop(taskId),
426-
]);
427-
},
436+
cancelSessionsByTaskId: (taskId: string) =>
437+
cancelTaskSessions(
438+
ctx.get<AgentService>(AGENT_SERVICE),
439+
ctx.get<PiSessionService>(PI_SESSION_SERVICE),
440+
taskId,
441+
),
428442
}));
429443
container.bind(SUSPENSION_FILE_WATCHER).toDynamicValue((ctx) => ({
430444
stopWatching: async (worktreePath: string) => {
@@ -695,12 +709,12 @@ container.load(workspaceModule);
695709
container.bind(WORKSPACE_AGENT).toDynamicValue((ctx): WorkspaceAgent => {
696710
const agent = ctx.get<AgentService>(AGENT_SERVICE);
697711
return {
698-
cancelSessionsByTaskId: async (taskId) => {
699-
await Promise.all([
700-
agent.cancelSessionsByTaskId(taskId),
701-
ctx.get<PiSessionService>(PI_SESSION_SERVICE).stop(taskId),
702-
]);
703-
},
712+
cancelSessionsByTaskId: (taskId) =>
713+
cancelTaskSessions(
714+
agent,
715+
ctx.get<PiSessionService>(PI_SESSION_SERVICE),
716+
taskId,
717+
),
704718
onAgentFileActivity: (handler) =>
705719
agent.on(AgentServiceEvent.AgentFileActivity, handler),
706720
};
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
@@ -67,6 +67,10 @@ import {
6767
} from "@posthog/core/onboarding/identifiers";
6868
import { PI_RUNNER } from "@posthog/core/pi-runtime/identifiers";
6969
import type { PiRunner } from "@posthog/core/pi-runtime/piRunner";
70+
import {
71+
PI_SESSION_CLIENT,
72+
type PiSessionClient,
73+
} from "@posthog/core/pi-runtime/piSessionController";
7074
import {
7175
type BundleLocalSkill,
7276
CLOUD_ARTIFACT_BUNDLE_LOCAL_SKILL,
@@ -279,6 +283,7 @@ export interface RendererBindings {
279283
[ANALYTICS_TRACKER]: AnalyticsTracker;
280284
[TASK_CREATION_HOST]: ITaskCreationHost;
281285
[PI_RUNNER]: PiRunner;
286+
[PI_SESSION_CLIENT]: PiSessionClient;
282287
[TASK_CREATION_EFFECTS]: TaskCreationEffects;
283288
[RENDERER_TASK_SERVICE]: TaskService;
284289
[TASK_SERVICE]: TaskService;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ import { LLM_GATEWAY_SERVICE } from "@posthog/core/llm-gateway/identifiers";
3131
import type { LlmGatewayService } from "@posthog/core/llm-gateway/llm-gateway";
3232
import type { LlmMessage } from "@posthog/core/llm-gateway/schemas";
3333
import { PI_RUNNER } from "@posthog/core/pi-runtime/identifiers";
34+
import { piRuntimeModule } from "@posthog/core/pi-runtime/pi-runtime.module";
3435
import type { PiRunner } from "@posthog/core/pi-runtime/piRunner";
36+
import { PI_SESSION_CLIENT } from "@posthog/core/pi-runtime/piSessionController";
3537
import {
3638
CLOUD_ARTIFACT_BUNDLE_LOCAL_SKILL,
3739
CLOUD_ARTIFACT_READ_FILE_AS_BASE64,
@@ -85,6 +87,7 @@ import {
8587
import { WorkspaceSetupService } from "@posthog/core/workspace/WorkspaceSetupService";
8688
import { setRootContainer } from "@posthog/di/container";
8789
import { HOST_TRPC_CLIENT } from "@posthog/host-router/client";
90+
import { TrpcPiSessionClient } from "@posthog/host-router/pi-session-client";
8891
import {
8992
BROWSER_TABS_CLIENT,
9093
type BrowserTabsClient,
@@ -292,6 +295,8 @@ container
292295
// Bind services
293296
container.bind<ITaskCreationHost>(TASK_CREATION_HOST).to(TrpcTaskCreationHost);
294297
container.bind<PiRunner>(PI_RUNNER).to(TrpcPiRunner);
298+
container.bind(PI_SESSION_CLIENT).to(TrpcPiSessionClient);
299+
container.load(piRuntimeModule);
295300
container.bind(TASK_CREATION_EFFECTS).toConstantValue(taskCreationEffects);
296301
container.bind<TaskService>(RENDERER_TASK_SERVICE).to(TaskService);
297302
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,11 +1,17 @@
11
import "reflect-metadata";
22
import { TypedContainer } from "@inversifyjs/strongly-typed";
3+
import { piRuntimeModule } from "@posthog/core/pi-runtime/pi-runtime.module";
4+
import {
5+
PI_SESSION_CLIENT,
6+
type PiSessionClient,
7+
} from "@posthog/core/pi-runtime/piSessionController";
38
import { setRootContainer } from "@posthog/di/container";
49
import { ROOT_LOGGER, type RootLogger } from "@posthog/di/logger";
510
import {
611
HOST_TRPC_CLIENT,
712
type HostTrpcClient,
813
} from "@posthog/host-router/client";
14+
import { TrpcPiSessionClient } from "@posthog/host-router/pi-session-client";
915
import { sandboxProxyHtml } from "@posthog/shared/mcp-sandbox-proxy";
1016
import {
1117
AUTH_SIDE_EFFECTS,
@@ -36,6 +42,7 @@ import { hostTrpcClient } from "./web-trpc";
3642

3743
interface WebBindings {
3844
[HOST_TRPC_CLIENT]: HostTrpcClient;
45+
[PI_SESSION_CLIENT]: PiSessionClient;
3946
[ROOT_LOGGER]: RootLogger;
4047
[FEATURE_FLAGS]: FeatureFlags;
4148
[ANALYTICS_TRACKER]: AnalyticsTracker;
@@ -53,6 +60,8 @@ export const container = new TypedContainer<WebBindings>({
5360

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

5766
// Logger: web uses console; electron uses electron-log. Same RootLogger shape.
5867
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"
@@ -112,7 +124,8 @@
112124
"author": "PostHog",
113125
"license": "MIT",
114126
"scripts": {
115-
"build": "node ../../scripts/rimraf.mjs dist && tsup && node build/verify-local-tools-mcp-server.mjs",
127+
"build": "node ../../scripts/rimraf.mjs dist && tsup && pnpm build:types && node build/verify-local-tools-mcp-server.mjs",
128+
"build:types": "tsc -p tsconfig.build.json",
116129
"dev": "tsup --watch",
117130
"test": "vitest run",
118131
"test:watch": "vitest",
@@ -140,6 +153,8 @@
140153
"@agentclientprotocol/sdk": "1.1.0",
141154
"@anthropic-ai/claude-agent-sdk": "0.3.197",
142155
"@anthropic-ai/sdk": "0.109.0",
156+
"@earendil-works/pi-agent-core": "catalog:",
157+
"@earendil-works/pi-ai": "catalog:",
143158
"@earendil-works/pi-coding-agent": "catalog:",
144159
"@hono/node-server": "^1.19.9",
145160
"@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)