@posthog/agent 2.3.326 → 2.3.346
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/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +9 -0
- package/dist/adapters/claude/conversion/tool-use-to-acp.js +15 -1
- package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -1
- package/dist/adapters/claude/permissions/permission-options.js +18 -11
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
- package/dist/adapters/claude/session/jsonl-hydration.js.map +1 -1
- package/dist/adapters/claude/session/models.d.ts +2 -2
- package/dist/adapters/claude/session/models.js +12 -6
- package/dist/adapters/claude/session/models.js.map +1 -1
- package/dist/adapters/claude/tools.js +15 -13
- package/dist/adapters/claude/tools.js.map +1 -1
- package/dist/adapters/reasoning-effort.d.ts +1 -1
- package/dist/adapters/reasoning-effort.js +11 -5
- package/dist/adapters/reasoning-effort.js.map +1 -1
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +6975 -613
- package/dist/agent.js.map +1 -1
- package/dist/execution-mode.d.ts +1 -1
- package/dist/execution-mode.js +14 -12
- package/dist/execution-mode.js.map +1 -1
- package/dist/posthog-api.d.ts +5 -3
- package/dist/posthog-api.js +12 -22
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.d.ts +8 -1
- package/dist/server/agent-server.js +11362 -4894
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +11423 -4954
- package/dist/server/bin.cjs.map +1 -1
- package/dist/types.d.ts +11 -1
- package/package.json +7 -6
- package/src/adapters/acp-connection.ts +14 -1
- package/src/adapters/claude/UPSTREAM.md +24 -4
- package/src/adapters/claude/claude-agent.ts +161 -14
- package/src/adapters/claude/conversion/acp-to-sdk.test.ts +49 -0
- package/src/adapters/claude/conversion/acp-to-sdk.ts +23 -6
- package/src/adapters/claude/conversion/sdk-to-acp.ts +14 -2
- package/src/adapters/claude/conversion/tool-use-to-acp.ts +18 -1
- package/src/adapters/claude/hooks.test.ts +189 -0
- package/src/adapters/claude/hooks.ts +93 -3
- package/src/adapters/claude/permissions/permission-handlers.ts +2 -1
- package/src/adapters/claude/permissions/permission-options.ts +5 -0
- package/src/adapters/claude/session/models.ts +11 -5
- package/src/adapters/claude/session/options.ts +19 -3
- package/src/adapters/claude/session/settings.ts +17 -9
- package/src/adapters/claude/tools.ts +1 -1
- package/src/adapters/claude/types.ts +8 -1
- package/src/adapters/codex/codex-agent.ts +15 -2
- package/src/adapters/codex/codex-client.test.ts +112 -0
- package/src/adapters/codex/codex-client.ts +14 -1
- package/src/adapters/reasoning-effort.ts +6 -1
- package/src/agent.ts +6 -0
- package/src/enrichment/file-enricher.test.ts +163 -0
- package/src/enrichment/file-enricher.ts +82 -0
- package/src/execution-mode.test.ts +1 -0
- package/src/execution-mode.ts +13 -11
- package/src/posthog-api.test.ts +32 -0
- package/src/posthog-api.ts +13 -30
- package/src/server/agent-server.test.ts +96 -0
- package/src/server/agent-server.ts +207 -11
- package/src/server/bin.ts +1 -1
- package/src/server/schemas.test.ts +10 -0
- package/src/server/schemas.ts +25 -6
- package/src/server/types.ts +1 -1
- package/src/test/mocks/msw-handlers.ts +4 -1
- package/src/types.ts +10 -1
package/dist/types.d.ts
CHANGED
|
@@ -39,10 +39,12 @@ interface Task {
|
|
|
39
39
|
};
|
|
40
40
|
latest_run?: TaskRun;
|
|
41
41
|
}
|
|
42
|
-
type ArtifactType = "plan" | "context" | "reference" | "output" | "artifact" | "tree_snapshot";
|
|
42
|
+
type ArtifactType = "plan" | "context" | "reference" | "output" | "artifact" | "tree_snapshot" | "user_attachment";
|
|
43
43
|
interface TaskRunArtifact {
|
|
44
|
+
id?: string;
|
|
44
45
|
name: string;
|
|
45
46
|
type: ArtifactType;
|
|
47
|
+
source?: string;
|
|
46
48
|
size?: number;
|
|
47
49
|
content_type?: string;
|
|
48
50
|
storage_path?: string;
|
|
@@ -112,6 +114,14 @@ interface AgentConfig {
|
|
|
112
114
|
skipLogPersistence?: boolean;
|
|
113
115
|
/** Local cache path for instant log loading (e.g., ~/.posthog-code) */
|
|
114
116
|
localCachePath?: string;
|
|
117
|
+
/**
|
|
118
|
+
* Annotate files the agent reads with PostHog enrichment (event volume,
|
|
119
|
+
* flag rollout/staleness, experiment links). Defaults to enabled when
|
|
120
|
+
* `posthog` config is present; set `{ enabled: false }` to opt out.
|
|
121
|
+
*/
|
|
122
|
+
enricher?: {
|
|
123
|
+
enabled?: boolean;
|
|
124
|
+
};
|
|
115
125
|
debug?: boolean;
|
|
116
126
|
onLog?: OnLogCallback;
|
|
117
127
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/agent",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.346",
|
|
4
4
|
"repository": "https://github.com/PostHog/code",
|
|
5
5
|
"description": "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
6
6
|
"exports": {
|
|
@@ -86,14 +86,14 @@
|
|
|
86
86
|
"tsx": "^4.20.6",
|
|
87
87
|
"typescript": "^5.5.0",
|
|
88
88
|
"vitest": "^2.1.8",
|
|
89
|
-
"@posthog/
|
|
90
|
-
"@posthog/
|
|
89
|
+
"@posthog/git": "1.0.0",
|
|
90
|
+
"@posthog/shared": "1.0.0"
|
|
91
91
|
},
|
|
92
92
|
"dependencies": {
|
|
93
|
-
"@agentclientprotocol/sdk": "0.
|
|
93
|
+
"@agentclientprotocol/sdk": "0.19.0",
|
|
94
94
|
"ajv": "^8.17.1",
|
|
95
95
|
"@anthropic-ai/claude-agent-sdk": "0.2.112",
|
|
96
|
-
"@anthropic-ai/sdk": "
|
|
96
|
+
"@anthropic-ai/sdk": "0.89.0",
|
|
97
97
|
"@hono/node-server": "^1.19.9",
|
|
98
98
|
"@opentelemetry/api-logs": "^0.208.0",
|
|
99
99
|
"@opentelemetry/exporter-logs-otlp-http": "^0.208.0",
|
|
@@ -108,7 +108,8 @@
|
|
|
108
108
|
"tar": "^7.5.0",
|
|
109
109
|
"uuid": "13.0.0",
|
|
110
110
|
"yoga-wasm-web": "^0.3.3",
|
|
111
|
-
"zod": "^4.2.0"
|
|
111
|
+
"zod": "^4.2.0",
|
|
112
|
+
"@posthog/enricher": "1.0.0"
|
|
112
113
|
},
|
|
113
114
|
"files": [
|
|
114
115
|
"dist/**/*",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
|
|
2
2
|
import type { SessionLogWriter } from "../session-log-writer";
|
|
3
|
-
import type { ProcessSpawnedCallback } from "../types";
|
|
3
|
+
import type { PostHogAPIConfig, ProcessSpawnedCallback } from "../types";
|
|
4
4
|
import { Logger } from "../utils/logger";
|
|
5
5
|
import {
|
|
6
6
|
createBidirectionalStreams,
|
|
@@ -26,6 +26,10 @@ export type AcpConnectionConfig = {
|
|
|
26
26
|
allowedModelIds?: Set<string>;
|
|
27
27
|
/** Callback invoked when the agent calls the create_output tool for structured output */
|
|
28
28
|
onStructuredOutput?: (output: Record<string, unknown>) => Promise<void>;
|
|
29
|
+
/** PostHog API config; when set, enables file-read enrichment unless disabled. */
|
|
30
|
+
posthogApiConfig?: PostHogAPIConfig;
|
|
31
|
+
/** Defaults to true when posthogApiConfig is set. Set to false to disable enrichment. */
|
|
32
|
+
enricherEnabled?: boolean;
|
|
29
33
|
};
|
|
30
34
|
|
|
31
35
|
export type AcpConnection = {
|
|
@@ -54,6 +58,13 @@ export function createAcpConnection(
|
|
|
54
58
|
return createClaudeConnection(config);
|
|
55
59
|
}
|
|
56
60
|
|
|
61
|
+
function resolveEnricherApiConfig(
|
|
62
|
+
config: AcpConnectionConfig,
|
|
63
|
+
): PostHogAPIConfig | undefined {
|
|
64
|
+
const enabled = !!config.posthogApiConfig && config.enricherEnabled !== false;
|
|
65
|
+
return enabled ? config.posthogApiConfig : undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
function createClaudeConnection(config: AcpConnectionConfig): AcpConnection {
|
|
58
69
|
const logger =
|
|
59
70
|
config.logger?.child("AcpConnection") ??
|
|
@@ -102,6 +113,7 @@ function createClaudeConnection(config: AcpConnectionConfig): AcpConnection {
|
|
|
102
113
|
agent = new ClaudeAcpAgent(client, {
|
|
103
114
|
...config.processCallbacks,
|
|
104
115
|
onStructuredOutput: config.onStructuredOutput,
|
|
116
|
+
posthogApiConfig: resolveEnricherApiConfig(config),
|
|
105
117
|
});
|
|
106
118
|
return agent;
|
|
107
119
|
}, agentStream);
|
|
@@ -192,6 +204,7 @@ function createCodexConnection(config: AcpConnectionConfig): AcpConnection {
|
|
|
192
204
|
agent = new CodexAcpAgent(client, {
|
|
193
205
|
codexProcessOptions: config.codexOptions ?? {},
|
|
194
206
|
processCallbacks: config.processCallbacks,
|
|
207
|
+
posthogApiConfig: resolveEnricherApiConfig(config),
|
|
195
208
|
});
|
|
196
209
|
return agent;
|
|
197
210
|
}, agentStream);
|
|
@@ -5,8 +5,8 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth
|
|
|
5
5
|
## Fork Point
|
|
6
6
|
|
|
7
7
|
- **Forked**: v0.10.9, commit `5411e0f4`, Dec 2 2025
|
|
8
|
-
- **Last sync**: v0.
|
|
9
|
-
- **SDK**: `@anthropic-ai/claude-agent-sdk` 0.2.
|
|
8
|
+
- **Last sync**: v0.30.0, commit `e9dd452`, April 20 2026
|
|
9
|
+
- **SDK**: `@anthropic-ai/claude-agent-sdk` 0.2.112 (0.2.114 breaks session init, see agentclientprotocol/claude-agent-acp#575), `@agentclientprotocol/sdk` 0.19.0
|
|
10
10
|
|
|
11
11
|
## File Mapping
|
|
12
12
|
|
|
@@ -50,11 +50,31 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth
|
|
|
50
50
|
| permissionMode | Hardcoded `"default"` | Reads from `meta.permissionMode` | More flexible mode selection |
|
|
51
51
|
| Session storage | `this.sessions[sessionId]` (multi) | `this.session` (single) | Architectural choice |
|
|
52
52
|
| bypassPermissions | `updatedPermissions` with `destination: "session"` | No `updatedPermissions` | Different permission persistence |
|
|
53
|
-
| Auth methods |
|
|
53
|
+
| Auth methods | `claude-ai-login` + `console-login` | Returns empty `authMethods` | Auth handled externally |
|
|
54
|
+
| Session fingerprinting | Implicit teardown on cwd/mcp change | Explicit `refreshSession()` | Caller-initiated is more predictable |
|
|
55
|
+
| Shutdown on ACP close | Process exits | No standalone process | Agent is embedded in server |
|
|
56
|
+
|
|
57
|
+
## Changes Ported in v0.30.0 Sync
|
|
58
|
+
|
|
59
|
+
- **SDK bumps**: claude-agent-sdk 0.2.112 -> 0.2.114, ACP SDK 0.16.1 -> 0.19.0, anthropic SDK -> 0.89.0
|
|
60
|
+
- **Null-safe usage tokens** (v0.29.2): Guard against null usage fields from SDK
|
|
61
|
+
- **SettingsManager race fix** (v0.25.0): `initPromise` prevents concurrent `initialize()`/`setCwd()` corruption
|
|
62
|
+
- **Malformed settings warning** (v0.25.0): Log warning for non-ENOENT settings file errors
|
|
63
|
+
- **Idle state end-of-turn** (v0.23.0): `CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1` + `session_state_changed` idle handler
|
|
64
|
+
- **Mid-stream usage updates** (v0.29.1): Fire `usage_update` from `message_start`/`message_delta` stream events
|
|
65
|
+
- **Raw SDK message relay** (v0.27.0): `emitRawSDKMessages` on `NewSessionMeta` for opt-in diagnostics
|
|
66
|
+
- **Effort level sync** (v0.25.x): `xhigh` level added, `applyFlagSettings` on effort change
|
|
67
|
+
- **Auto permission mode** (v0.25.0): Added to `CODE_EXECUTION_MODES`, available modes, ExitPlanMode options
|
|
68
|
+
|
|
69
|
+
## Skipped in v0.30.0 Sync
|
|
70
|
+
|
|
71
|
+
- **Separate auth methods** (v0.25.0): PostHog returns empty authMethods
|
|
72
|
+
- **Session fingerprinting** (v0.25.3): PostHog uses explicit `refreshSession()` instead
|
|
73
|
+
- **Process exit on ACP close** (v0.27.0): PostHog embeds agent in server
|
|
54
74
|
|
|
55
75
|
## Next Sync
|
|
56
76
|
|
|
57
|
-
1. Check upstream changelog since v0.
|
|
77
|
+
1. Check upstream changelog since v0.30.0
|
|
58
78
|
2. Diff upstream source against PostHog Code using the file mapping above
|
|
59
79
|
3. Port in phases: bug fixes first, then features
|
|
60
80
|
4. After each phase: `pnpm --filter agent typecheck && pnpm --filter agent build && pnpm lint`
|
|
@@ -51,6 +51,12 @@ import {
|
|
|
51
51
|
POSTHOG_METHODS,
|
|
52
52
|
POSTHOG_NOTIFICATIONS,
|
|
53
53
|
} from "../../acp-extensions";
|
|
54
|
+
import {
|
|
55
|
+
createEnrichment,
|
|
56
|
+
type Enrichment,
|
|
57
|
+
type FileEnrichmentDeps,
|
|
58
|
+
} from "../../enrichment/file-enricher";
|
|
59
|
+
import type { PostHogAPIConfig } from "../../types";
|
|
54
60
|
import { unreachable, withTimeout } from "../../utils/common";
|
|
55
61
|
import { Logger } from "../../utils/logger";
|
|
56
62
|
import { Pushable } from "../../utils/streams";
|
|
@@ -62,6 +68,7 @@ import {
|
|
|
62
68
|
handleSystemMessage,
|
|
63
69
|
handleUserAssistantMessage,
|
|
64
70
|
} from "./conversion/sdk-to-acp";
|
|
71
|
+
import type { EnrichedReadCache } from "./hooks";
|
|
65
72
|
import {
|
|
66
73
|
fetchMcpToolMetadata,
|
|
67
74
|
getConnectedMcpServerNames,
|
|
@@ -92,6 +99,7 @@ import type {
|
|
|
92
99
|
BackgroundTerminal,
|
|
93
100
|
EffortLevel,
|
|
94
101
|
NewSessionMeta,
|
|
102
|
+
SDKMessageFilter,
|
|
95
103
|
Session,
|
|
96
104
|
ToolUseCache,
|
|
97
105
|
} from "./types";
|
|
@@ -111,11 +119,25 @@ function sanitizeTitle(text: string): string {
|
|
|
111
119
|
return `${sanitized.slice(0, MAX_TITLE_LENGTH - 1)}…`;
|
|
112
120
|
}
|
|
113
121
|
|
|
122
|
+
function shouldEmitRawMessage(
|
|
123
|
+
config: boolean | SDKMessageFilter[],
|
|
124
|
+
message: { type: string; subtype?: string },
|
|
125
|
+
): boolean {
|
|
126
|
+
if (config === true) return true;
|
|
127
|
+
if (config === false) return false;
|
|
128
|
+
return config.some(
|
|
129
|
+
(f) =>
|
|
130
|
+
f.type === message.type &&
|
|
131
|
+
(f.subtype === undefined || f.subtype === message.subtype),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
114
135
|
export interface ClaudeAcpAgentOptions {
|
|
115
136
|
onProcessSpawned?: (info: ProcessSpawnedInfo) => void;
|
|
116
137
|
onProcessExited?: (pid: number) => void;
|
|
117
138
|
onMcpServersReady?: (serverNames: string[]) => void;
|
|
118
139
|
onStructuredOutput?: (output: Record<string, unknown>) => Promise<void>;
|
|
140
|
+
posthogApiConfig?: PostHogAPIConfig;
|
|
119
141
|
}
|
|
120
142
|
|
|
121
143
|
export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
@@ -125,12 +147,29 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
125
147
|
backgroundTerminals: { [key: string]: BackgroundTerminal } = {};
|
|
126
148
|
clientCapabilities?: ClientCapabilities;
|
|
127
149
|
private options?: ClaudeAcpAgentOptions;
|
|
150
|
+
private enrichment?: Enrichment;
|
|
151
|
+
private enrichedReadCache: EnrichedReadCache = new Map();
|
|
128
152
|
|
|
129
153
|
constructor(client: AgentSideConnection, options?: ClaudeAcpAgentOptions) {
|
|
130
154
|
super(client);
|
|
131
155
|
this.options = options;
|
|
132
156
|
this.toolUseCache = {};
|
|
133
157
|
this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
|
|
158
|
+
this.enrichment = createEnrichment(options?.posthogApiConfig, this.logger);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
protected getEnrichmentDeps(): FileEnrichmentDeps | undefined {
|
|
162
|
+
return this.enrichment?.deps;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
override async closeSession(): Promise<void> {
|
|
166
|
+
try {
|
|
167
|
+
await super.closeSession();
|
|
168
|
+
} finally {
|
|
169
|
+
this.enrichment?.dispose();
|
|
170
|
+
this.enrichment = undefined;
|
|
171
|
+
this.enrichedReadCache.clear();
|
|
172
|
+
}
|
|
134
173
|
}
|
|
135
174
|
|
|
136
175
|
async initialize(request: InitializeRequest): Promise<InitializeResponse> {
|
|
@@ -331,6 +370,12 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
331
370
|
this.session.promptRunning = true;
|
|
332
371
|
let handedOff = false;
|
|
333
372
|
let lastAssistantTotalUsage: number | null = null;
|
|
373
|
+
let lastStreamUsage = {
|
|
374
|
+
input_tokens: 0,
|
|
375
|
+
output_tokens: 0,
|
|
376
|
+
cache_read_input_tokens: 0,
|
|
377
|
+
cache_creation_input_tokens: 0,
|
|
378
|
+
};
|
|
334
379
|
if (this.session.lastContextWindowSize == null) {
|
|
335
380
|
this.session.lastContextWindowSize = this.getContextWindowForModel(
|
|
336
381
|
this.session.modelId ?? "",
|
|
@@ -355,6 +400,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
355
400
|
client: this.client,
|
|
356
401
|
toolUseCache: this.toolUseCache,
|
|
357
402
|
fileContentCache: this.fileContentCache,
|
|
403
|
+
enrichedReadCache: this.enrichedReadCache,
|
|
358
404
|
logger: this.logger,
|
|
359
405
|
supportsTerminalOutput,
|
|
360
406
|
};
|
|
@@ -375,6 +421,16 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
375
421
|
break;
|
|
376
422
|
}
|
|
377
423
|
|
|
424
|
+
if (
|
|
425
|
+
this.session.emitRawSDKMessages &&
|
|
426
|
+
shouldEmitRawMessage(this.session.emitRawSDKMessages, message)
|
|
427
|
+
) {
|
|
428
|
+
await this.client.extNotification("_claude/sdkMessage", {
|
|
429
|
+
sessionId: params.sessionId,
|
|
430
|
+
message: message as Record<string, unknown>,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
378
434
|
switch (message.type) {
|
|
379
435
|
case "system":
|
|
380
436
|
if (message.subtype === "compact_boundary") {
|
|
@@ -394,6 +450,35 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
394
450
|
if (message.subtype === "local_command_output") {
|
|
395
451
|
promptReplayed = true;
|
|
396
452
|
}
|
|
453
|
+
if (
|
|
454
|
+
message.subtype === "session_state_changed" &&
|
|
455
|
+
(message as Record<string, unknown>).state === "idle"
|
|
456
|
+
) {
|
|
457
|
+
if (!promptReplayed) {
|
|
458
|
+
this.logger.debug("Skipping idle state before prompt replay", {
|
|
459
|
+
sessionId: params.sessionId,
|
|
460
|
+
});
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const acc = this.session.accumulatedUsage;
|
|
465
|
+
const totalUsed =
|
|
466
|
+
acc.inputTokens +
|
|
467
|
+
acc.outputTokens +
|
|
468
|
+
acc.cachedReadTokens +
|
|
469
|
+
acc.cachedWriteTokens;
|
|
470
|
+
|
|
471
|
+
await this.client.sessionUpdate({
|
|
472
|
+
sessionId: params.sessionId,
|
|
473
|
+
update: {
|
|
474
|
+
sessionUpdate: "usage_update",
|
|
475
|
+
used: totalUsed,
|
|
476
|
+
size: lastContextWindowSize,
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
return { stopReason: "end_turn" };
|
|
481
|
+
}
|
|
397
482
|
await handleSystemMessage(message, context);
|
|
398
483
|
break;
|
|
399
484
|
|
|
@@ -411,15 +496,15 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
411
496
|
return { stopReason: "cancelled" };
|
|
412
497
|
}
|
|
413
498
|
|
|
414
|
-
// Accumulate usage from this result
|
|
499
|
+
// Accumulate usage from this result (guard against null from SDK)
|
|
415
500
|
this.session.accumulatedUsage.inputTokens +=
|
|
416
|
-
message.usage.input_tokens;
|
|
501
|
+
message.usage.input_tokens ?? 0;
|
|
417
502
|
this.session.accumulatedUsage.outputTokens +=
|
|
418
|
-
message.usage.output_tokens;
|
|
503
|
+
message.usage.output_tokens ?? 0;
|
|
419
504
|
this.session.accumulatedUsage.cachedReadTokens +=
|
|
420
|
-
message.usage.cache_read_input_tokens;
|
|
505
|
+
message.usage.cache_read_input_tokens ?? 0;
|
|
421
506
|
this.session.accumulatedUsage.cachedWriteTokens +=
|
|
422
|
-
message.usage.cache_creation_input_tokens;
|
|
507
|
+
message.usage.cache_creation_input_tokens ?? 0;
|
|
423
508
|
|
|
424
509
|
// SDK can underreport context window (e.g. 200k for 1M models).
|
|
425
510
|
// Use SDK value only if it's larger than what gateway reported.
|
|
@@ -514,9 +599,56 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
514
599
|
return { stopReason: result.stopReason ?? "end_turn", usage };
|
|
515
600
|
}
|
|
516
601
|
|
|
517
|
-
case "stream_event":
|
|
602
|
+
case "stream_event": {
|
|
603
|
+
if (
|
|
604
|
+
message.parent_tool_use_id === null &&
|
|
605
|
+
(message.event.type === "message_start" ||
|
|
606
|
+
message.event.type === "message_delta")
|
|
607
|
+
) {
|
|
608
|
+
if (message.event.type === "message_start") {
|
|
609
|
+
const u = message.event.message.usage;
|
|
610
|
+
lastStreamUsage = {
|
|
611
|
+
input_tokens: u.input_tokens ?? 0,
|
|
612
|
+
output_tokens: u.output_tokens ?? 0,
|
|
613
|
+
cache_read_input_tokens: u.cache_read_input_tokens ?? 0,
|
|
614
|
+
cache_creation_input_tokens:
|
|
615
|
+
u.cache_creation_input_tokens ?? 0,
|
|
616
|
+
};
|
|
617
|
+
} else {
|
|
618
|
+
const u = message.event.usage;
|
|
619
|
+
lastStreamUsage = {
|
|
620
|
+
input_tokens: u.input_tokens ?? lastStreamUsage.input_tokens,
|
|
621
|
+
output_tokens: u.output_tokens,
|
|
622
|
+
cache_read_input_tokens:
|
|
623
|
+
u.cache_read_input_tokens ??
|
|
624
|
+
lastStreamUsage.cache_read_input_tokens,
|
|
625
|
+
cache_creation_input_tokens:
|
|
626
|
+
u.cache_creation_input_tokens ??
|
|
627
|
+
lastStreamUsage.cache_creation_input_tokens,
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const nextTotal =
|
|
632
|
+
lastStreamUsage.input_tokens +
|
|
633
|
+
lastStreamUsage.output_tokens +
|
|
634
|
+
lastStreamUsage.cache_read_input_tokens +
|
|
635
|
+
lastStreamUsage.cache_creation_input_tokens;
|
|
636
|
+
|
|
637
|
+
if (nextTotal !== lastAssistantTotalUsage) {
|
|
638
|
+
lastAssistantTotalUsage = nextTotal;
|
|
639
|
+
await this.client.sessionUpdate({
|
|
640
|
+
sessionId: params.sessionId,
|
|
641
|
+
update: {
|
|
642
|
+
sessionUpdate: "usage_update",
|
|
643
|
+
used: nextTotal,
|
|
644
|
+
size: lastContextWindowSize,
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
518
649
|
await handleStreamEvent(message, context);
|
|
519
650
|
break;
|
|
651
|
+
}
|
|
520
652
|
|
|
521
653
|
case "user":
|
|
522
654
|
case "assistant": {
|
|
@@ -565,16 +697,16 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
565
697
|
const usage = (
|
|
566
698
|
message.message as unknown as Record<string, unknown>
|
|
567
699
|
).usage as {
|
|
568
|
-
input_tokens: number;
|
|
569
|
-
output_tokens: number;
|
|
570
|
-
cache_read_input_tokens: number;
|
|
571
|
-
cache_creation_input_tokens: number;
|
|
700
|
+
input_tokens: number | null;
|
|
701
|
+
output_tokens: number | null;
|
|
702
|
+
cache_read_input_tokens: number | null;
|
|
703
|
+
cache_creation_input_tokens: number | null;
|
|
572
704
|
};
|
|
573
705
|
lastAssistantTotalUsage =
|
|
574
|
-
usage.input_tokens +
|
|
575
|
-
usage.output_tokens +
|
|
576
|
-
usage.cache_read_input_tokens +
|
|
577
|
-
usage.cache_creation_input_tokens;
|
|
706
|
+
(usage.input_tokens ?? 0) +
|
|
707
|
+
(usage.output_tokens ?? 0) +
|
|
708
|
+
(usage.cache_read_input_tokens ?? 0) +
|
|
709
|
+
(usage.cache_creation_input_tokens ?? 0);
|
|
578
710
|
|
|
579
711
|
await this.client.sessionUpdate({
|
|
580
712
|
sessionId: params.sessionId,
|
|
@@ -858,6 +990,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
858
990
|
const newEffort = resolvedValue as EffortLevel;
|
|
859
991
|
this.session.effort = newEffort;
|
|
860
992
|
this.session.queryOptions.effort = newEffort;
|
|
993
|
+
await this.session.query.applyFlagSettings({
|
|
994
|
+
// @ts-expect-error SDK Settings.effortLevel omits "max" but runtime accepts it
|
|
995
|
+
effortLevel: newEffort,
|
|
996
|
+
});
|
|
861
997
|
}
|
|
862
998
|
|
|
863
999
|
this.session.configOptions = this.session.configOptions.map((o) =>
|
|
@@ -993,6 +1129,8 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
993
1129
|
onProcessSpawned: this.options?.onProcessSpawned,
|
|
994
1130
|
onProcessExited: this.options?.onProcessExited,
|
|
995
1131
|
effort,
|
|
1132
|
+
enrichmentDeps: this.enrichment?.deps,
|
|
1133
|
+
enrichedReadCache: this.enrichedReadCache,
|
|
996
1134
|
});
|
|
997
1135
|
|
|
998
1136
|
// Use the same abort controller that buildSessionOptions gave to the query
|
|
@@ -1019,6 +1157,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1019
1157
|
promptRunning: false,
|
|
1020
1158
|
pendingMessages: new Map(),
|
|
1021
1159
|
nextPendingOrder: 0,
|
|
1160
|
+
emitRawSDKMessages: meta?.claudeCode?.emitRawSDKMessages ?? false,
|
|
1022
1161
|
|
|
1023
1162
|
// Custom properties
|
|
1024
1163
|
cwd,
|
|
@@ -1297,6 +1436,9 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1297
1436
|
if (this.session.effort) {
|
|
1298
1437
|
this.session.effort = undefined;
|
|
1299
1438
|
this.session.queryOptions.effort = undefined;
|
|
1439
|
+
void this.session.query.applyFlagSettings({
|
|
1440
|
+
effortLevel: undefined,
|
|
1441
|
+
});
|
|
1300
1442
|
}
|
|
1301
1443
|
return;
|
|
1302
1444
|
}
|
|
@@ -1310,6 +1452,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1310
1452
|
if (resolvedValue !== currentValue && this.session.effort) {
|
|
1311
1453
|
this.session.effort = resolvedValue as EffortLevel;
|
|
1312
1454
|
this.session.queryOptions.effort = resolvedValue as EffortLevel;
|
|
1455
|
+
void this.session.query.applyFlagSettings({
|
|
1456
|
+
// @ts-expect-error SDK Settings.effortLevel omits "max" but runtime accepts it
|
|
1457
|
+
effortLevel: resolvedValue,
|
|
1458
|
+
});
|
|
1313
1459
|
}
|
|
1314
1460
|
|
|
1315
1461
|
const effortConfig: SessionConfigOption = {
|
|
@@ -1354,6 +1500,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1354
1500
|
client: this.client,
|
|
1355
1501
|
toolUseCache: this.toolUseCache,
|
|
1356
1502
|
fileContentCache: this.fileContentCache,
|
|
1503
|
+
enrichedReadCache: this.enrichedReadCache,
|
|
1357
1504
|
logger: this.logger,
|
|
1358
1505
|
registerHooks: false,
|
|
1359
1506
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { promptToClaude } from "./acp-to-sdk";
|
|
3
|
+
|
|
4
|
+
describe("promptToClaude", () => {
|
|
5
|
+
it("renders file resource links as explicit workspace attachments", () => {
|
|
6
|
+
const result = promptToClaude({
|
|
7
|
+
sessionId: "session-1",
|
|
8
|
+
prompt: [
|
|
9
|
+
{
|
|
10
|
+
type: "resource_link",
|
|
11
|
+
uri: "file:///tmp/workspace/.posthog/attachments/run-1/report.pdf",
|
|
12
|
+
name: "report.pdf",
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
expect(result.message.content).toEqual([
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: [
|
|
21
|
+
"Attached file available in the workspace:",
|
|
22
|
+
"- name: report.pdf",
|
|
23
|
+
"- path: /tmp/workspace/.posthog/attachments/run-1/report.pdf",
|
|
24
|
+
"Use the available tools to inspect this file if needed.",
|
|
25
|
+
].join("\n"),
|
|
26
|
+
},
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("preserves non-file resource links as links", () => {
|
|
31
|
+
const result = promptToClaude({
|
|
32
|
+
sessionId: "session-1",
|
|
33
|
+
prompt: [
|
|
34
|
+
{
|
|
35
|
+
type: "resource_link",
|
|
36
|
+
uri: "https://example.com/report.pdf",
|
|
37
|
+
name: "report.pdf",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(result.message.content).toEqual([
|
|
43
|
+
{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: "https://example.com/report.pdf",
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import type { PromptRequest } from "@agentclientprotocol/sdk";
|
|
3
4
|
import type { SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
4
5
|
import type { ContentBlockParam } from "@anthropic-ai/sdk/resources";
|
|
@@ -11,11 +12,6 @@ function sdkText(value: string): ContentBlockParam {
|
|
|
11
12
|
|
|
12
13
|
function formatUriAsLink(uri: string): string {
|
|
13
14
|
try {
|
|
14
|
-
if (uri.startsWith("file://")) {
|
|
15
|
-
const filePath = uri.slice(7);
|
|
16
|
-
const name = path.basename(filePath) || filePath;
|
|
17
|
-
return `[@${name}](${uri})`;
|
|
18
|
-
}
|
|
19
15
|
if (uri.startsWith("zed://")) {
|
|
20
16
|
const name = path.basename(uri) || uri;
|
|
21
17
|
return `[@${name}](${uri})`;
|
|
@@ -26,6 +22,21 @@ function formatUriAsLink(uri: string): string {
|
|
|
26
22
|
}
|
|
27
23
|
}
|
|
28
24
|
|
|
25
|
+
function formatFileAttachment(uri: string): string {
|
|
26
|
+
try {
|
|
27
|
+
const filePath = fileURLToPath(uri);
|
|
28
|
+
const name = path.basename(filePath) || filePath;
|
|
29
|
+
return [
|
|
30
|
+
"Attached file available in the workspace:",
|
|
31
|
+
`- name: ${name}`,
|
|
32
|
+
`- path: ${filePath}`,
|
|
33
|
+
"Use the available tools to inspect this file if needed.",
|
|
34
|
+
].join("\n");
|
|
35
|
+
} catch {
|
|
36
|
+
return `Attached file available at ${uri}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
function transformMcpCommand(text: string): string {
|
|
30
41
|
const mcpMatch = text.match(/^\/mcp:([^:\s]+):(\S+)(\s+.*)?$/);
|
|
31
42
|
if (mcpMatch) {
|
|
@@ -46,7 +57,13 @@ function processPromptChunk(
|
|
|
46
57
|
break;
|
|
47
58
|
|
|
48
59
|
case "resource_link":
|
|
49
|
-
content.push(
|
|
60
|
+
content.push(
|
|
61
|
+
sdkText(
|
|
62
|
+
chunk.uri.startsWith("file://")
|
|
63
|
+
? formatFileAttachment(chunk.uri)
|
|
64
|
+
: formatUriAsLink(chunk.uri),
|
|
65
|
+
),
|
|
66
|
+
);
|
|
50
67
|
break;
|
|
51
68
|
|
|
52
69
|
case "resource":
|
|
@@ -21,7 +21,7 @@ import { POSTHOG_NOTIFICATIONS } from "@/acp-extensions";
|
|
|
21
21
|
import { image, text } from "../../../utils/acp-content";
|
|
22
22
|
import { unreachable } from "../../../utils/common";
|
|
23
23
|
import type { Logger } from "../../../utils/logger";
|
|
24
|
-
import { registerHookCallback } from "../hooks";
|
|
24
|
+
import { type EnrichedReadCache, registerHookCallback } from "../hooks";
|
|
25
25
|
import type { Session, ToolUpdateMeta, ToolUseCache } from "../types";
|
|
26
26
|
import {
|
|
27
27
|
type ClaudePlanEntry,
|
|
@@ -51,6 +51,7 @@ type ChunkHandlerContext = {
|
|
|
51
51
|
sessionId: string;
|
|
52
52
|
toolUseCache: ToolUseCache;
|
|
53
53
|
fileContentCache: { [key: string]: string };
|
|
54
|
+
enrichedReadCache?: EnrichedReadCache;
|
|
54
55
|
client: AgentSideConnection;
|
|
55
56
|
logger: Logger;
|
|
56
57
|
parentToolCallId?: string;
|
|
@@ -67,6 +68,7 @@ export interface MessageHandlerContext {
|
|
|
67
68
|
client: AgentSideConnection;
|
|
68
69
|
toolUseCache: ToolUseCache;
|
|
69
70
|
fileContentCache: { [key: string]: string };
|
|
71
|
+
enrichedReadCache?: EnrichedReadCache;
|
|
70
72
|
logger: Logger;
|
|
71
73
|
registerHooks?: boolean;
|
|
72
74
|
supportsTerminalOutput?: boolean;
|
|
@@ -248,7 +250,7 @@ function extractTextFromContent(content: unknown): string | null {
|
|
|
248
250
|
return null;
|
|
249
251
|
}
|
|
250
252
|
|
|
251
|
-
function stripCatLineNumbers(text: string): string {
|
|
253
|
+
export function stripCatLineNumbers(text: string): string {
|
|
252
254
|
return text.replace(/^ *\d+[\t→]/gm, "");
|
|
253
255
|
}
|
|
254
256
|
|
|
@@ -318,6 +320,7 @@ function handleToolResultChunk(
|
|
|
318
320
|
supportsTerminalOutput: ctx.supportsTerminalOutput,
|
|
319
321
|
toolUseId: chunk.tool_use_id,
|
|
320
322
|
cachedFileContent: ctx.fileContentCache,
|
|
323
|
+
enrichedReadCache: ctx.enrichedReadCache,
|
|
321
324
|
},
|
|
322
325
|
);
|
|
323
326
|
|
|
@@ -448,6 +451,7 @@ function toAcpNotifications(
|
|
|
448
451
|
supportsTerminalOutput?: boolean,
|
|
449
452
|
cwd?: string,
|
|
450
453
|
mcpToolUseResult?: Record<string, unknown>,
|
|
454
|
+
enrichedReadCache?: EnrichedReadCache,
|
|
451
455
|
): SessionNotification[] {
|
|
452
456
|
if (typeof content === "string") {
|
|
453
457
|
const update: SessionUpdate = {
|
|
@@ -468,6 +472,7 @@ function toAcpNotifications(
|
|
|
468
472
|
sessionId,
|
|
469
473
|
toolUseCache,
|
|
470
474
|
fileContentCache,
|
|
475
|
+
enrichedReadCache,
|
|
471
476
|
client,
|
|
472
477
|
logger,
|
|
473
478
|
parentToolCallId,
|
|
@@ -498,6 +503,7 @@ function streamEventToAcpNotifications(
|
|
|
498
503
|
registerHooks?: boolean,
|
|
499
504
|
supportsTerminalOutput?: boolean,
|
|
500
505
|
cwd?: string,
|
|
506
|
+
enrichedReadCache?: EnrichedReadCache,
|
|
501
507
|
): SessionNotification[] {
|
|
502
508
|
const event = message.event;
|
|
503
509
|
switch (event.type) {
|
|
@@ -514,6 +520,8 @@ function streamEventToAcpNotifications(
|
|
|
514
520
|
registerHooks,
|
|
515
521
|
supportsTerminalOutput,
|
|
516
522
|
cwd,
|
|
523
|
+
undefined,
|
|
524
|
+
enrichedReadCache,
|
|
517
525
|
);
|
|
518
526
|
case "content_block_delta":
|
|
519
527
|
return toAcpNotifications(
|
|
@@ -528,6 +536,8 @@ function streamEventToAcpNotifications(
|
|
|
528
536
|
registerHooks,
|
|
529
537
|
supportsTerminalOutput,
|
|
530
538
|
cwd,
|
|
539
|
+
undefined,
|
|
540
|
+
enrichedReadCache,
|
|
531
541
|
);
|
|
532
542
|
case "message_start":
|
|
533
543
|
case "message_delta":
|
|
@@ -717,6 +727,7 @@ export async function handleStreamEvent(
|
|
|
717
727
|
context.registerHooks,
|
|
718
728
|
context.supportsTerminalOutput,
|
|
719
729
|
context.session.cwd,
|
|
730
|
+
context.enrichedReadCache,
|
|
720
731
|
)) {
|
|
721
732
|
await client.sessionUpdate(notification);
|
|
722
733
|
context.session.notificationHistory.push(notification);
|
|
@@ -840,6 +851,7 @@ export async function handleUserAssistantMessage(
|
|
|
840
851
|
context.supportsTerminalOutput,
|
|
841
852
|
session.cwd,
|
|
842
853
|
mcpToolUseResult,
|
|
854
|
+
context.enrichedReadCache,
|
|
843
855
|
)) {
|
|
844
856
|
await client.sessionUpdate(notification);
|
|
845
857
|
session.notificationHistory.push(notification);
|