Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ jobs:
- name: Build git
run: pnpm --filter @posthog/git build

- name: Build enricher
run: pnpm --filter @posthog/enricher build

- name: Build agent
run: pnpm --filter agent build

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ jobs:
pnpm --filter @posthog/platform build &
pnpm --filter @posthog/shared build
pnpm --filter @posthog/git build
pnpm --filter @posthog/enricher build
pnpm --filter agent build &
wait

Expand Down
2 changes: 1 addition & 1 deletion apps/code/forge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ const config: ForgeConfig = {
packagerConfig: {
asar: {
unpack:
"{**/*.node,**/spawn-helper,**/.vite/build/claude-cli/**,**/.vite/build/plugins/posthog/**,**/.vite/build/codex-acp/**,**/node_modules/node-pty/**,**/node_modules/@parcel/**,**/node_modules/file-icon/**,**/node_modules/better-sqlite3/**,**/node_modules/bindings/**,**/node_modules/file-uri-to-path/**}",
"{**/*.node,**/spawn-helper,**/.vite/build/claude-cli/**,**/.vite/build/plugins/posthog/**,**/.vite/build/codex-acp/**,**/.vite/build/grammars/**,**/node_modules/node-pty/**,**/node_modules/@parcel/**,**/node_modules/file-icon/**,**/node_modules/better-sqlite3/**,**/node_modules/bindings/**,**/node_modules/file-uri-to-path/**}",
},
prune: false,
name: "PostHog Code",
Expand Down
47 changes: 47 additions & 0 deletions apps/code/vite.main.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,52 @@ function copyDrizzleMigrations(): Plugin {
};
}

let enricherGrammarsCopied = false;

function copyEnricherGrammars(): Plugin {
return {
name: "copy-enricher-grammars",
writeBundle() {
// `.vite/grammars` is what the bundle resolves at dev-time; electron-forge
// only copies `.vite/build/**` into the packaged app, so we need both.
const destDirs = [
join(__dirname, ".vite/grammars"),
join(__dirname, ".vite/build/grammars"),
];

if (enricherGrammarsCopied && destDirs.every((d) => existsSync(d))) {
return;
}

const candidates = [
join(__dirname, "node_modules/@posthog/enricher/grammars"),
join(__dirname, "../../node_modules/@posthog/enricher/grammars"),
join(__dirname, "../../packages/enricher/grammars"),
];

const sourceDir = candidates.find((p) => existsSync(p));
if (!sourceDir) {
console.warn(
"[copy-enricher-grammars] grammars directory not found. Checked:",
candidates.join(", "),
);
return;
}

for (const destDir of destDirs) {
if (!existsSync(destDir)) {
mkdirSync(destDir, { recursive: true });
}
cpSync(sourceDir, destDir, { recursive: true });
}
enricherGrammarsCopied = true;
console.log(
`Copied enricher grammars from ${sourceDir} to ${destDirs.join(", ")}`,
);
},
};
}

let codexAcpCopied = false;

function copyCodexAcpBinaries(): Plugin {
Expand Down Expand Up @@ -492,6 +538,7 @@ export default defineConfig(({ mode }) => {
copyPosthogPlugin(isDev),
copyDrizzleMigrations(),
copyCodexAcpBinaries(),
copyEnricherGrammars(),
createPosthogPlugin(env, "posthog-code-main"),
].filter(Boolean),
define: {
Expand Down
1 change: 1 addition & 0 deletions packages/agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"@opentelemetry/resources": "^2.0.0",
"@opentelemetry/sdk-logs": "^0.208.0",
"@opentelemetry/semantic-conventions": "^1.28.0",
"@posthog/enricher": "workspace:*",
"@types/jsonwebtoken": "^9.0.10",
"commander": "^14.0.2",
"hono": "^4.11.7",
Expand Down
15 changes: 14 additions & 1 deletion packages/agent/src/adapters/acp-connection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
import type { SessionLogWriter } from "../session-log-writer";
import type { ProcessSpawnedCallback } from "../types";
import type { PostHogAPIConfig, ProcessSpawnedCallback } from "../types";
import { Logger } from "../utils/logger";
import {
createBidirectionalStreams,
Expand All @@ -26,6 +26,10 @@ export type AcpConnectionConfig = {
allowedModelIds?: Set<string>;
/** Callback invoked when the agent calls the create_output tool for structured output */
onStructuredOutput?: (output: Record<string, unknown>) => Promise<void>;
/** PostHog API config; when set, enables file-read enrichment unless disabled. */
posthogApiConfig?: PostHogAPIConfig;
/** Defaults to true when posthogApiConfig is set. Set to false to disable enrichment. */
enricherEnabled?: boolean;
};

export type AcpConnection = {
Expand Down Expand Up @@ -54,6 +58,13 @@ export function createAcpConnection(
return createClaudeConnection(config);
}

function resolveEnricherApiConfig(
config: AcpConnectionConfig,
): PostHogAPIConfig | undefined {
const enabled = !!config.posthogApiConfig && config.enricherEnabled !== false;
return enabled ? config.posthogApiConfig : undefined;
}

function createClaudeConnection(config: AcpConnectionConfig): AcpConnection {
const logger =
config.logger?.child("AcpConnection") ??
Expand Down Expand Up @@ -102,6 +113,7 @@ function createClaudeConnection(config: AcpConnectionConfig): AcpConnection {
agent = new ClaudeAcpAgent(client, {
...config.processCallbacks,
onStructuredOutput: config.onStructuredOutput,
posthogApiConfig: resolveEnricherApiConfig(config),
});
return agent;
}, agentStream);
Expand Down Expand Up @@ -192,6 +204,7 @@ function createCodexConnection(config: AcpConnectionConfig): AcpConnection {
agent = new CodexAcpAgent(client, {
codexProcessOptions: config.codexOptions ?? {},
processCallbacks: config.processCallbacks,
posthogApiConfig: resolveEnricherApiConfig(config),
});
return agent;
}, agentStream);
Expand Down
29 changes: 29 additions & 0 deletions packages/agent/src/adapters/claude/claude-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ import {
POSTHOG_METHODS,
POSTHOG_NOTIFICATIONS,
} from "../../acp-extensions";
import {
createEnrichment,
type Enrichment,
type FileEnrichmentDeps,
} from "../../enrichment/file-enricher";
import type { PostHogAPIConfig } from "../../types";
import { unreachable, withTimeout } from "../../utils/common";
import { Logger } from "../../utils/logger";
import { Pushable } from "../../utils/streams";
Expand All @@ -62,6 +68,7 @@ import {
handleSystemMessage,
handleUserAssistantMessage,
} from "./conversion/sdk-to-acp";
import type { EnrichedReadCache } from "./hooks";
import {
fetchMcpToolMetadata,
getConnectedMcpServerNames,
Expand Down Expand Up @@ -116,6 +123,7 @@ export interface ClaudeAcpAgentOptions {
onProcessExited?: (pid: number) => void;
onMcpServersReady?: (serverNames: string[]) => void;
onStructuredOutput?: (output: Record<string, unknown>) => Promise<void>;
posthogApiConfig?: PostHogAPIConfig;
}

export class ClaudeAcpAgent extends BaseAcpAgent {
Expand All @@ -125,12 +133,29 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
backgroundTerminals: { [key: string]: BackgroundTerminal } = {};
clientCapabilities?: ClientCapabilities;
private options?: ClaudeAcpAgentOptions;
private enrichment?: Enrichment;
private enrichedReadCache: EnrichedReadCache = new Map();

constructor(client: AgentSideConnection, options?: ClaudeAcpAgentOptions) {
super(client);
this.options = options;
this.toolUseCache = {};
this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
this.enrichment = createEnrichment(options?.posthogApiConfig, this.logger);
}

protected getEnrichmentDeps(): FileEnrichmentDeps | undefined {
return this.enrichment?.deps;
}

override async closeSession(): Promise<void> {
try {
await super.closeSession();
} finally {
this.enrichment?.dispose();
this.enrichment = undefined;
this.enrichedReadCache.clear();
}
}

async initialize(request: InitializeRequest): Promise<InitializeResponse> {
Expand Down Expand Up @@ -355,6 +380,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
client: this.client,
toolUseCache: this.toolUseCache,
fileContentCache: this.fileContentCache,
enrichedReadCache: this.enrichedReadCache,
logger: this.logger,
supportsTerminalOutput,
};
Expand Down Expand Up @@ -993,6 +1019,8 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
onProcessSpawned: this.options?.onProcessSpawned,
onProcessExited: this.options?.onProcessExited,
effort,
enrichmentDeps: this.enrichment?.deps,
enrichedReadCache: this.enrichedReadCache,
});

// Use the same abort controller that buildSessionOptions gave to the query
Expand Down Expand Up @@ -1354,6 +1382,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
client: this.client,
toolUseCache: this.toolUseCache,
fileContentCache: this.fileContentCache,
enrichedReadCache: this.enrichedReadCache,
logger: this.logger,
registerHooks: false,
};
Expand Down
16 changes: 14 additions & 2 deletions packages/agent/src/adapters/claude/conversion/sdk-to-acp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { POSTHOG_NOTIFICATIONS } from "@/acp-extensions";
import { image, text } from "../../../utils/acp-content";
import { unreachable } from "../../../utils/common";
import type { Logger } from "../../../utils/logger";
import { registerHookCallback } from "../hooks";
import { type EnrichedReadCache, registerHookCallback } from "../hooks";
import type { Session, ToolUpdateMeta, ToolUseCache } from "../types";
import {
type ClaudePlanEntry,
Expand Down Expand Up @@ -51,6 +51,7 @@ type ChunkHandlerContext = {
sessionId: string;
toolUseCache: ToolUseCache;
fileContentCache: { [key: string]: string };
enrichedReadCache?: EnrichedReadCache;
client: AgentSideConnection;
logger: Logger;
parentToolCallId?: string;
Expand All @@ -67,6 +68,7 @@ export interface MessageHandlerContext {
client: AgentSideConnection;
toolUseCache: ToolUseCache;
fileContentCache: { [key: string]: string };
enrichedReadCache?: EnrichedReadCache;
logger: Logger;
registerHooks?: boolean;
supportsTerminalOutput?: boolean;
Expand Down Expand Up @@ -248,7 +250,7 @@ function extractTextFromContent(content: unknown): string | null {
return null;
}

function stripCatLineNumbers(text: string): string {
export function stripCatLineNumbers(text: string): string {
return text.replace(/^ *\d+[\t→]/gm, "");
}

Expand Down Expand Up @@ -318,6 +320,7 @@ function handleToolResultChunk(
supportsTerminalOutput: ctx.supportsTerminalOutput,
toolUseId: chunk.tool_use_id,
cachedFileContent: ctx.fileContentCache,
enrichedReadCache: ctx.enrichedReadCache,
},
);

Expand Down Expand Up @@ -448,6 +451,7 @@ function toAcpNotifications(
supportsTerminalOutput?: boolean,
cwd?: string,
mcpToolUseResult?: Record<string, unknown>,
enrichedReadCache?: EnrichedReadCache,
): SessionNotification[] {
if (typeof content === "string") {
const update: SessionUpdate = {
Expand All @@ -468,6 +472,7 @@ function toAcpNotifications(
sessionId,
toolUseCache,
fileContentCache,
enrichedReadCache,
client,
logger,
parentToolCallId,
Expand Down Expand Up @@ -498,6 +503,7 @@ function streamEventToAcpNotifications(
registerHooks?: boolean,
supportsTerminalOutput?: boolean,
cwd?: string,
enrichedReadCache?: EnrichedReadCache,
): SessionNotification[] {
const event = message.event;
switch (event.type) {
Expand All @@ -514,6 +520,8 @@ function streamEventToAcpNotifications(
registerHooks,
supportsTerminalOutput,
cwd,
undefined,
enrichedReadCache,
);
case "content_block_delta":
return toAcpNotifications(
Expand All @@ -528,6 +536,8 @@ function streamEventToAcpNotifications(
registerHooks,
supportsTerminalOutput,
cwd,
undefined,
enrichedReadCache,
);
case "message_start":
case "message_delta":
Expand Down Expand Up @@ -717,6 +727,7 @@ export async function handleStreamEvent(
context.registerHooks,
context.supportsTerminalOutput,
context.session.cwd,
context.enrichedReadCache,
)) {
await client.sessionUpdate(notification);
context.session.notificationHistory.push(notification);
Expand Down Expand Up @@ -840,6 +851,7 @@ export async function handleUserAssistantMessage(
context.supportsTerminalOutput,
session.cwd,
mcpToolUseResult,
context.enrichedReadCache,
)) {
await client.sessionUpdate(notification);
session.notificationHistory.push(notification);
Expand Down
19 changes: 18 additions & 1 deletion packages/agent/src/adapters/claude/conversion/tool-use-to-acp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function stripSystemReminders(value: string): string {
}

import { resourceLink, text, toolContent } from "../../../utils/acp-content";
import type { EnrichedReadCache } from "../hooks";
import { getMcpToolMetadata } from "../mcp/tool-metadata";

type ToolInfo = Pick<ToolCall, "title" | "kind" | "content" | "locations">;
Expand Down Expand Up @@ -526,6 +527,7 @@ export function toolUpdateFromToolResult(
supportsTerminalOutput?: boolean;
toolUseId?: string;
cachedFileContent?: Record<string, string>;
enrichedReadCache?: EnrichedReadCache;
},
): Pick<ToolCallUpdate, "title" | "content" | "locations" | "_meta"> {
if (
Expand All @@ -538,7 +540,21 @@ export function toolUpdateFromToolResult(
}

switch (toolUse?.name) {
case "Read":
case "Read": {
const cache = options?.enrichedReadCache;
const enriched =
cache && options?.toolUseId ? cache.get(options.toolUseId) : undefined;
if (enriched !== undefined && cache && options?.toolUseId) {
cache.delete(options.toolUseId);
return {
content: [
{
type: "content" as const,
content: text(markdownEscape(enriched)),
},
],
};
}
if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
return {
content: toolResult.content.map((item) => {
Expand Down Expand Up @@ -582,6 +598,7 @@ export function toolUpdateFromToolResult(
};
}
return {};
}

case "Bash": {
const result = toolResult.content;
Expand Down
Loading
Loading