Skip to content

Commit 98059aa

Browse files
committed
feat(harness): integrate pi runtime into desktop app
1 parent 1e4cd6d commit 98059aa

76 files changed

Lines changed: 3203 additions & 232 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/electron-builder.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ const config: Configuration = {
4949
".vite/build/plugins/posthog/**",
5050
".vite/build/codex-acp/**",
5151
".vite/build/grammars/**",
52+
".vite/build/rpc-host.js",
53+
".vite/build/rpc-host.js.map",
5254
...asarUnpackGlobs,
5355
],
5456

apps/code/electron.vite.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
copyCodexAcpBinaries,
2323
copyDrizzleMigrations,
2424
copyEnricherGrammars,
25+
copyPiRpcHost,
2526
copyPosthogPlugin,
2627
fixFilenameCircularRef,
2728
getBuildDate,
@@ -93,6 +94,7 @@ export default defineConfig(({ mode }) => {
9394
autoServicesPlugin(path.join(__dirname, "src/main/services")),
9495
fixFilenameCircularRef(),
9596
copyClaudeExecutable(),
97+
copyPiRpcHost(),
9698
copyPosthogPlugin(isDev),
9799
copyDrizzleMigrations(),
98100
copyCodexAcpBinaries(),

apps/code/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
"reflect-metadata": "^0.2.2",
153153
"semver": "^7.8.1",
154154
"shadcn": "^4.1.2",
155+
"superjson": "catalog:",
155156
"smol-toml": "^1.6.1",
156157
"tailwindcss-scroll-mask": "^0.0.3",
157158
"tw-animate-css": "^1.4.0",

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ import type {
186186
MCP_RELAY_SERVICE,
187187
McpRelayService,
188188
} from "@posthog/workspace-server/services/mcp-relay/identifiers";
189+
import type {
190+
PI_RPC_CLIENT_FACTORY,
191+
PiRpcClientFactory,
192+
} from "@posthog/workspace-server/services/pi-session/identifiers";
189193
import type { PosthogPluginService } from "@posthog/workspace-server/services/posthog-plugin/posthog-plugin";
190194
import type { ProcessTrackingService } from "@posthog/workspace-server/services/process-tracking/process-tracking";
191195
import type {
@@ -350,6 +354,7 @@ export interface MainBindings {
350354
[AGENT_REPO_FILES]: unknown;
351355
[AGENT_AUTH]: unknown;
352356
[AGENT_LOGGER]: RootLogger;
357+
[PI_RPC_CLIENT_FACTORY]: PiRpcClientFactory;
353358

354359
// Logger
355360
[ROOT_LOGGER]: RootLogger;

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

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,12 @@ import { OAUTH_CALLBACK_SERVER } from "@posthog/workspace-server/services/oauth-
187187
import { oauthCallbackModule } from "@posthog/workspace-server/services/oauth-callback/oauth-callback.module";
188188
import { onboardingImportModule } from "@posthog/workspace-server/services/onboarding-import/onboarding-import.module";
189189
import { osModule } from "@posthog/workspace-server/services/os/os.module";
190+
import {
191+
PI_RPC_CLIENT_FACTORY,
192+
PI_SESSION_SERVICE,
193+
} from "@posthog/workspace-server/services/pi-session/identifiers";
194+
import type { PiSessionService } from "@posthog/workspace-server/services/pi-session/pi-session";
195+
import { piSessionModule } from "@posthog/workspace-server/services/pi-session/pi-session.module";
190196
import { POSTHOG_PLUGIN_SERVICE } from "@posthog/workspace-server/services/posthog-plugin/identifiers";
191197
import { posthogPluginModule } from "@posthog/workspace-server/services/posthog-plugin/posthog-plugin.module";
192198
import { PROCESS_TRACKING_SERVICE } from "@posthog/workspace-server/services/process-tracking/identifiers";
@@ -223,6 +229,7 @@ import { workspaceModule } from "@posthog/workspace-server/services/workspace/wo
223229
import { workspaceMetadataModule } from "@posthog/workspace-server/services/workspace-metadata/workspace-metadata.module";
224230
import ExternalAppsStoreImpl from "electron-store";
225231
import type { FileWatcherBridge } from "../index";
232+
import { DesktopPiRpcClientFactory } from "../platform-adapters/desktop-pi-rpc-client-factory";
226233
import { ElectronAppLifecycle } from "../platform-adapters/electron-app-lifecycle";
227234
import { ElectronAppMeta } from "../platform-adapters/electron-app-meta";
228235
import { ElectronAppMetrics } from "../platform-adapters/electron-app-metrics";
@@ -359,10 +366,15 @@ container
359366
.bind(MAIN_DEFAULT_ADDITIONAL_DIRECTORY_REPOSITORY)
360367
.toService(DEFAULT_ADDITIONAL_DIRECTORY_REPOSITORY);
361368
container.load(agentModule);
369+
container.load(piSessionModule);
362370
container.bind(AGENT_SLEEP_COORDINATOR).toService(MAIN_SLEEP_SERVICE);
363371
container.bind(AGENT_MCP_APPS).toService(MCP_APPS_SERVICE);
364372
container.bind(AGENT_REPO_FILES).toService(MAIN_FS_SERVICE);
365373
container.bind(AGENT_AUTH).toService(MAIN_AUTH_SERVICE);
374+
container
375+
.bind(PI_RPC_CLIENT_FACTORY)
376+
.to(DesktopPiRpcClientFactory)
377+
.inSingletonScope();
366378
container.bind(AGENT_LOGGER).toConstantValue(logger);
367379
container.load(osModule);
368380
container.bind<RootLogger>(ROOT_LOGGER).toConstantValue(logger);
@@ -394,8 +406,12 @@ container.bind(MCP_PROXY_AUTH).toDynamicValue((ctx) => {
394406
});
395407
container.load(archiveModule);
396408
container.bind(ARCHIVE_SESSION_CANCELLER).toDynamicValue((ctx) => ({
397-
cancelSessionsByTaskId: (taskId: string) =>
398-
ctx.get<AgentService>(AGENT_SERVICE).cancelSessionsByTaskId(taskId),
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+
},
399415
}));
400416
container.bind(ARCHIVE_FILE_WATCHER).toDynamicValue((ctx) => ({
401417
stopWatching: async (worktreePath: string) => {
@@ -406,8 +422,12 @@ container.bind(ARCHIVE_FILE_WATCHER).toDynamicValue((ctx) => ({
406422
}));
407423
container.load(suspensionModule);
408424
container.bind(SUSPENSION_SESSION_CANCELLER).toDynamicValue((ctx) => ({
409-
cancelSessionsByTaskId: (taskId: string) =>
410-
ctx.get<AgentService>(AGENT_SERVICE).cancelSessionsByTaskId(taskId),
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+
},
411431
}));
412432
container.bind(SUSPENSION_FILE_WATCHER).toDynamicValue((ctx) => ({
413433
stopWatching: async (worktreePath: string) => {
@@ -685,7 +705,12 @@ container.load(workspaceModule);
685705
container.bind(WORKSPACE_AGENT).toDynamicValue((ctx): WorkspaceAgent => {
686706
const agent = ctx.get<AgentService>(AGENT_SERVICE);
687707
return {
688-
cancelSessionsByTaskId: (taskId) => agent.cancelSessionsByTaskId(taskId),
708+
cancelSessionsByTaskId: async (taskId) => {
709+
await Promise.all([
710+
agent.cancelSessionsByTaskId(taskId),
711+
ctx.get<PiSessionService>(PI_SESSION_SERVICE).stop(taskId),
712+
]);
713+
},
689714
onAgentFileActivity: (handler) =>
690715
agent.on(AgentServiceEvent.AgentFileActivity, handler),
691716
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { PiRpcClient } from "@posthog/agent/pi/rpc-client";
2+
import { getLlmGatewayUrl } from "@posthog/agent/posthog-api";
3+
import { getCloudUrlFromRegion } from "@posthog/shared";
4+
import type { AgentAuth } from "@posthog/workspace-server/services/agent/ports";
5+
import type { AuthProxyService } from "@posthog/workspace-server/services/auth-proxy/auth-proxy";
6+
import { describe, expect, it, vi } from "vitest";
7+
import { DesktopPiRpcClientFactory } from "./desktop-pi-rpc-client-factory";
8+
9+
const createPiRpcClient = vi.hoisted(() => vi.fn());
10+
11+
vi.mock("@posthog/agent/pi/rpc-client", () => ({ createPiRpcClient }));
12+
13+
describe("DesktopPiRpcClientFactory", () => {
14+
it("routes Pi through the shared host auth proxy", async () => {
15+
const auth = {
16+
getOAuthCredentials: vi.fn(async () => ({
17+
access: "access-token",
18+
refresh: "refresh-token",
19+
expires: 1,
20+
region: "eu" as const,
21+
})),
22+
} as unknown as AgentAuth;
23+
const authProxy = {
24+
start: vi.fn(async () => "http://127.0.0.1:1234"),
25+
} as unknown as AuthProxyService;
26+
const client = {} as PiRpcClient;
27+
createPiRpcClient.mockReturnValue(client);
28+
const factory = new DesktopPiRpcClientFactory(auth, authProxy);
29+
30+
await expect(factory.create({ cwd: "/workspace" })).resolves.toBe(client);
31+
expect(authProxy.start).toHaveBeenCalledWith(
32+
getLlmGatewayUrl(getCloudUrlFromRegion("eu")),
33+
);
34+
expect(createPiRpcClient).toHaveBeenCalledWith({
35+
cwd: "/workspace",
36+
providerOptions: {
37+
region: "eu",
38+
baseUrl: "http://127.0.0.1:1234",
39+
apiKey: "posthog-code-auth-proxy",
40+
},
41+
});
42+
});
43+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {
2+
createPiRpcClient,
3+
type PiRpcClient,
4+
} from "@posthog/agent/pi/rpc-client";
5+
import { getLlmGatewayUrl } from "@posthog/agent/posthog-api";
6+
import { type CloudRegion, getCloudUrlFromRegion } from "@posthog/shared";
7+
import { AGENT_AUTH } from "@posthog/workspace-server/services/agent/identifiers";
8+
import type { AgentAuth } from "@posthog/workspace-server/services/agent/ports";
9+
import type { AuthProxyService } from "@posthog/workspace-server/services/auth-proxy/auth-proxy";
10+
import { AUTH_PROXY_SERVICE } from "@posthog/workspace-server/services/auth-proxy/identifiers";
11+
import type { PiRpcClientFactory } from "@posthog/workspace-server/services/pi-session/identifiers";
12+
import { inject, injectable } from "inversify";
13+
14+
const PROXY_API_KEY = "posthog-code-auth-proxy";
15+
16+
@injectable()
17+
export class DesktopPiRpcClientFactory implements PiRpcClientFactory {
18+
private proxyRegion?: CloudRegion;
19+
private proxyUrlPromise?: Promise<string>;
20+
21+
constructor(
22+
@inject(AGENT_AUTH) private readonly auth: AgentAuth,
23+
@inject(AUTH_PROXY_SERVICE)
24+
private readonly authProxy: AuthProxyService,
25+
) {}
26+
27+
async create(input: {
28+
cwd: string;
29+
model?: string;
30+
sessionFile?: string;
31+
}): Promise<PiRpcClient> {
32+
const credentials = await this.auth.getOAuthCredentials();
33+
if (!credentials) {
34+
throw new Error("Pi requires PostHog authentication");
35+
}
36+
37+
const baseUrl = await this.getProxyUrl(credentials.region);
38+
39+
return createPiRpcClient({
40+
...input,
41+
providerOptions: {
42+
region: credentials.region,
43+
baseUrl,
44+
apiKey: PROXY_API_KEY,
45+
},
46+
});
47+
}
48+
49+
private getProxyUrl(region: CloudRegion): Promise<string> {
50+
if (this.proxyRegion !== region || !this.proxyUrlPromise) {
51+
this.proxyRegion = region;
52+
const gatewayUrl = getLlmGatewayUrl(getCloudUrlFromRegion(region));
53+
this.proxyUrlPromise = this.authProxy.start(gatewayUrl);
54+
}
55+
56+
return this.proxyUrlPromise;
57+
}
58+
}

apps/code/src/main/trpc/router.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { notificationRouter } from "@posthog/host-router/routers/notification.ro
3737
import { oauthRouter } from "@posthog/host-router/routers/oauth.router";
3838
import { onboardingImportRouter } from "@posthog/host-router/routers/onboarding-import.router";
3939
import { osRouter } from "@posthog/host-router/routers/os.router";
40+
import { piSessionRouter } from "@posthog/host-router/routers/pi-session.router";
4041
import { processTrackingRouter } from "@posthog/host-router/routers/process-tracking.router";
4142
import { provisioningRouter } from "@posthog/host-router/routers/provisioning.router";
4243
import { secureStoreRouter } from "@posthog/host-router/routers/secure-store.router";
@@ -97,6 +98,7 @@ export const trpcRouter = router({
9798
onboardingImport: onboardingImportRouter,
9899
logs: logsRouter,
99100
os: osRouter,
101+
piSession: piSessionRouter,
100102
processTracking: processTrackingRouter,
101103
provisioning: provisioningRouter,
102104
sleep: sleepRouter,

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ import {
7373
GITHUB_CONNECT_CLIENT,
7474
type GithubConnectClient,
7575
} from "@posthog/core/onboarding/identifiers";
76+
import { PI_RUNNER } from "@posthog/core/pi-runtime/identifiers";
77+
import type { PiRunner } from "@posthog/core/pi-runtime/piRunner";
7678
import {
7779
type BundleLocalSkill,
7880
CLOUD_ARTIFACT_BUNDLE_LOCAL_SKILL,
@@ -288,6 +290,7 @@ export interface RendererBindings {
288290
[SHELL_PROCESS_READER]: ShellProcessReader;
289291
[ANALYTICS_TRACKER]: AnalyticsTracker;
290292
[TASK_CREATION_HOST]: ITaskCreationHost;
293+
[PI_RUNNER]: PiRunner;
291294
[TASK_CREATION_EFFECTS]: TaskCreationEffects;
292295
[RENDERER_TASK_SERVICE]: TaskService;
293296
[TASK_SERVICE]: TaskService;

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import type { LlmGatewayService } from "@posthog/core/llm-gateway/llm-gateway";
3232
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";
35+
import { PI_RUNNER } from "@posthog/core/pi-runtime/identifiers";
36+
import type { PiRunner } from "@posthog/core/pi-runtime/piRunner";
3537
import {
3638
CLOUD_ARTIFACT_BUNDLE_LOCAL_SKILL,
3739
CLOUD_ARTIFACT_READ_FILE_AS_BASE64,
@@ -151,6 +153,7 @@ import { trpcClient } from "@renderer/trpc";
151153
import { hostTrpcClient } from "@renderer/trpc/client";
152154
import type { TRPCClient } from "@trpc/client";
153155
import { hostLog, logger } from "@utils/logger";
156+
import { TrpcPiRunner } from "../platform-adapters/trpc-pi-runner";
154157
import type { RendererBindings } from "./bindings";
155158
import { TASK_SERVICE as RENDERER_TASK_SERVICE, TRPC_CLIENT } from "./tokens";
156159

@@ -290,6 +293,7 @@ container
290293

291294
// Bind services
292295
container.bind<ITaskCreationHost>(TASK_CREATION_HOST).to(TrpcTaskCreationHost);
296+
container.bind<PiRunner>(PI_RUNNER).to(TrpcPiRunner);
293297
container.bind(TASK_CREATION_EFFECTS).toConstantValue(taskCreationEffects);
294298
container.bind<TaskService>(RENDERER_TASK_SERVICE).to(TaskService);
295299
container.bind<TaskService>(TASK_SERVICE).toService(RENDERER_TASK_SERVICE);

0 commit comments

Comments
 (0)