@posthog/agent 2.1.35 → 2.1.47
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/{agent-DcBmoTR4.d.ts → agent-BJ7Uacyp.d.ts} +2 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +161 -60
- package/dist/agent.js.map +1 -1
- package/dist/gateway-models.js +21 -2
- package/dist/gateway-models.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +292 -193
- package/dist/index.js.map +1 -1
- package/dist/server/agent-server.js +282 -193
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +276 -187
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/acp-connection.ts +6 -1
- package/src/adapters/claude/claude-agent.ts +107 -50
- package/src/adapters/claude/session/options.ts +3 -7
- package/src/agent.ts +1 -0
- package/src/gateway-models.ts +42 -2
package/package.json
CHANGED
|
@@ -23,6 +23,8 @@ export type AcpConnectionConfig = {
|
|
|
23
23
|
/** Deployment environment - "local" for desktop, "cloud" for cloud sandbox */
|
|
24
24
|
deviceType?: "local" | "cloud";
|
|
25
25
|
logger?: Logger;
|
|
26
|
+
/** Enable dev-only instrumentation (timing, verbose logging) */
|
|
27
|
+
debug?: boolean;
|
|
26
28
|
processCallbacks?: ProcessSpawnedCallback;
|
|
27
29
|
codexOptions?: CodexProcessOptions;
|
|
28
30
|
allowedModelIds?: Set<string>;
|
|
@@ -194,7 +196,10 @@ function createClaudeConnection(config: AcpConnectionConfig): AcpConnection {
|
|
|
194
196
|
|
|
195
197
|
let agent: ClaudeAcpAgent | null = null;
|
|
196
198
|
const agentConnection = new AgentSideConnection((client) => {
|
|
197
|
-
agent = new ClaudeAcpAgent(client, logWriter,
|
|
199
|
+
agent = new ClaudeAcpAgent(client, logWriter, {
|
|
200
|
+
...config.processCallbacks,
|
|
201
|
+
debug: config.debug,
|
|
202
|
+
});
|
|
198
203
|
logger.info(`Created ${agent.adapterName} agent`);
|
|
199
204
|
return agent;
|
|
200
205
|
}, agentStream);
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type SDKMessage,
|
|
30
30
|
type SDKUserMessage,
|
|
31
31
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
32
|
+
import { createTimingCollector } from "@posthog/shared";
|
|
32
33
|
import { v7 as uuidv7 } from "uuid";
|
|
33
34
|
import packageJson from "../../../package.json" with { type: "json" };
|
|
34
35
|
import type { SessionContext } from "../../otel-log-writer.js";
|
|
@@ -69,6 +70,8 @@ import type {
|
|
|
69
70
|
export interface ClaudeAcpAgentOptions {
|
|
70
71
|
onProcessSpawned?: (info: ProcessSpawnedInfo) => void;
|
|
71
72
|
onProcessExited?: (pid: number) => void;
|
|
73
|
+
/** Enable dev-only instrumentation (timing, verbose logging) */
|
|
74
|
+
debug?: boolean;
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
@@ -78,17 +81,19 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
78
81
|
backgroundTerminals: { [key: string]: BackgroundTerminal } = {};
|
|
79
82
|
clientCapabilities?: ClientCapabilities;
|
|
80
83
|
private logWriter?: SessionLogWriter;
|
|
81
|
-
private
|
|
84
|
+
private options?: ClaudeAcpAgentOptions;
|
|
82
85
|
private lastSentConfigOptions?: SessionConfigOption[];
|
|
86
|
+
private debug: boolean;
|
|
83
87
|
|
|
84
88
|
constructor(
|
|
85
89
|
client: AgentSideConnection,
|
|
86
90
|
logWriter?: SessionLogWriter,
|
|
87
|
-
|
|
91
|
+
options?: ClaudeAcpAgentOptions,
|
|
88
92
|
) {
|
|
89
93
|
super(client);
|
|
90
94
|
this.logWriter = logWriter;
|
|
91
|
-
this.
|
|
95
|
+
this.options = options;
|
|
96
|
+
this.debug = options?.debug ?? false;
|
|
92
97
|
this.toolUseCache = {};
|
|
93
98
|
this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
|
|
94
99
|
}
|
|
@@ -136,6 +141,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
136
141
|
async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
|
|
137
142
|
this.checkAuthStatus();
|
|
138
143
|
|
|
144
|
+
const tc = createTimingCollector(this.debug, (msg, data) =>
|
|
145
|
+
this.logger.info(msg, data),
|
|
146
|
+
);
|
|
147
|
+
|
|
139
148
|
const meta = params._meta as NewSessionMeta | undefined;
|
|
140
149
|
const sessionId = uuidv7();
|
|
141
150
|
const permissionMode: TwigExecutionMode =
|
|
@@ -144,26 +153,29 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
144
153
|
? (meta.permissionMode as TwigExecutionMode)
|
|
145
154
|
: "default";
|
|
146
155
|
|
|
147
|
-
const mcpServers = parseMcpServers(
|
|
148
|
-
|
|
156
|
+
const mcpServers = tc.timeSync("parseMcpServers", () =>
|
|
157
|
+
parseMcpServers(params),
|
|
158
|
+
);
|
|
149
159
|
|
|
150
|
-
const options = buildSessionOptions(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
const options = tc.timeSync("buildSessionOptions", () =>
|
|
161
|
+
buildSessionOptions({
|
|
162
|
+
cwd: params.cwd,
|
|
163
|
+
mcpServers,
|
|
164
|
+
permissionMode,
|
|
165
|
+
canUseTool: this.createCanUseTool(sessionId),
|
|
166
|
+
logger: this.logger,
|
|
167
|
+
systemPrompt: buildSystemPrompt(meta?.systemPrompt),
|
|
168
|
+
userProvidedOptions: meta?.claudeCode?.options,
|
|
169
|
+
sessionId,
|
|
170
|
+
isResume: false,
|
|
171
|
+
onModeChange: this.createOnModeChange(sessionId),
|
|
172
|
+
onProcessSpawned: this.options?.onProcessSpawned,
|
|
173
|
+
onProcessExited: this.options?.onProcessExited,
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
164
176
|
|
|
165
177
|
const input = new Pushable<SDKUserMessage>();
|
|
166
|
-
const q = query({ prompt: input, options });
|
|
178
|
+
const q = tc.timeSync("sdkQuery", () => query({ prompt: input, options }));
|
|
167
179
|
|
|
168
180
|
const session = this.createSession(
|
|
169
181
|
sessionId,
|
|
@@ -177,25 +189,36 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
177
189
|
this.registerPersistence(sessionId, meta as Record<string, unknown>);
|
|
178
190
|
|
|
179
191
|
if (meta?.taskRunId) {
|
|
180
|
-
await
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
192
|
+
await tc.time("extNotification", () =>
|
|
193
|
+
this.client.extNotification("_posthog/sdk_session", {
|
|
194
|
+
taskRunId: meta.taskRunId!,
|
|
195
|
+
sessionId,
|
|
196
|
+
adapter: "claude",
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
185
199
|
}
|
|
186
200
|
|
|
187
|
-
|
|
201
|
+
// Only await model config — slash commands and MCP metadata are deferred
|
|
202
|
+
// since they're not needed to return configOptions to the client.
|
|
203
|
+
const modelOptions = await tc.time("fetchModels", () =>
|
|
204
|
+
this.getModelConfigOptions(),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// Deferred: slash commands + MCP metadata (not needed to return configOptions)
|
|
208
|
+
this.deferBackgroundFetches(tc, q, sessionId, mcpServers);
|
|
209
|
+
|
|
188
210
|
session.modelId = modelOptions.currentModelId;
|
|
189
211
|
await this.trySetModel(q, modelOptions.currentModelId);
|
|
190
212
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
await getAvailableSlashCommands(q),
|
|
213
|
+
const configOptions = await tc.time("buildConfigOptions", () =>
|
|
214
|
+
this.buildConfigOptions(modelOptions),
|
|
194
215
|
);
|
|
195
216
|
|
|
217
|
+
tc.summarize("newSession");
|
|
218
|
+
|
|
196
219
|
return {
|
|
197
220
|
sessionId,
|
|
198
|
-
configOptions
|
|
221
|
+
configOptions,
|
|
199
222
|
};
|
|
200
223
|
}
|
|
201
224
|
|
|
@@ -206,6 +229,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
206
229
|
async resumeSession(
|
|
207
230
|
params: LoadSessionRequest,
|
|
208
231
|
): Promise<LoadSessionResponse> {
|
|
232
|
+
const tc = createTimingCollector(this.debug, (msg, data) =>
|
|
233
|
+
this.logger.info(msg, data),
|
|
234
|
+
);
|
|
235
|
+
|
|
209
236
|
const meta = params._meta as NewSessionMeta | undefined;
|
|
210
237
|
const sessionId = meta?.sessionId;
|
|
211
238
|
if (!sessionId) {
|
|
@@ -215,8 +242,9 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
215
242
|
return {};
|
|
216
243
|
}
|
|
217
244
|
|
|
218
|
-
const mcpServers = parseMcpServers(
|
|
219
|
-
|
|
245
|
+
const mcpServers = tc.timeSync("parseMcpServers", () =>
|
|
246
|
+
parseMcpServers(params),
|
|
247
|
+
);
|
|
220
248
|
|
|
221
249
|
const permissionMode: TwigExecutionMode =
|
|
222
250
|
meta?.permissionMode &&
|
|
@@ -224,28 +252,33 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
224
252
|
? (meta.permissionMode as TwigExecutionMode)
|
|
225
253
|
: "default";
|
|
226
254
|
|
|
227
|
-
const { query: q, session } = await
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
255
|
+
const { query: q, session } = await tc.time("initializeQuery", () =>
|
|
256
|
+
this.initializeQuery({
|
|
257
|
+
cwd: params.cwd,
|
|
258
|
+
permissionMode,
|
|
259
|
+
mcpServers,
|
|
260
|
+
systemPrompt: buildSystemPrompt(meta?.systemPrompt),
|
|
261
|
+
userProvidedOptions: meta?.claudeCode?.options,
|
|
262
|
+
sessionId,
|
|
263
|
+
isResume: true,
|
|
264
|
+
additionalDirectories: meta?.claudeCode?.options?.additionalDirectories,
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
237
267
|
|
|
238
268
|
session.taskRunId = meta?.taskRunId;
|
|
239
269
|
|
|
240
270
|
this.registerPersistence(sessionId, meta as Record<string, unknown>);
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
271
|
+
|
|
272
|
+
// Deferred: slash commands + MCP metadata (not needed to return configOptions)
|
|
273
|
+
this.deferBackgroundFetches(tc, q, sessionId, mcpServers);
|
|
274
|
+
|
|
275
|
+
const configOptions = await tc.time("buildConfigOptions", () =>
|
|
276
|
+
this.buildConfigOptions(),
|
|
244
277
|
);
|
|
245
278
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
};
|
|
279
|
+
tc.summarize("resumeSession");
|
|
280
|
+
|
|
281
|
+
return { configOptions };
|
|
249
282
|
}
|
|
250
283
|
|
|
251
284
|
async prompt(params: PromptRequest): Promise<PromptResponse> {
|
|
@@ -354,8 +387,8 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
354
387
|
isResume: config.isResume,
|
|
355
388
|
additionalDirectories: config.additionalDirectories,
|
|
356
389
|
onModeChange: this.createOnModeChange(config.sessionId),
|
|
357
|
-
onProcessSpawned: this.
|
|
358
|
-
onProcessExited: this.
|
|
390
|
+
onProcessSpawned: this.options?.onProcessSpawned,
|
|
391
|
+
onProcessExited: this.options?.onProcessExited,
|
|
359
392
|
});
|
|
360
393
|
|
|
361
394
|
const q = query({ prompt: input, options });
|
|
@@ -491,6 +524,30 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
491
524
|
}
|
|
492
525
|
}
|
|
493
526
|
|
|
527
|
+
/**
|
|
528
|
+
* Fire-and-forget: fetch slash commands and MCP tool metadata in parallel.
|
|
529
|
+
* Both populate caches used later — neither is needed to return configOptions.
|
|
530
|
+
*/
|
|
531
|
+
private deferBackgroundFetches(
|
|
532
|
+
tc: ReturnType<typeof createTimingCollector>,
|
|
533
|
+
q: Query,
|
|
534
|
+
sessionId: string,
|
|
535
|
+
mcpServers: ReturnType<typeof parseMcpServers>,
|
|
536
|
+
): void {
|
|
537
|
+
Promise.all([
|
|
538
|
+
tc.time("slashCommands", () => getAvailableSlashCommands(q)),
|
|
539
|
+
tc.time("mcpMetadata", () =>
|
|
540
|
+
fetchMcpToolMetadata(mcpServers, this.logger),
|
|
541
|
+
),
|
|
542
|
+
])
|
|
543
|
+
.then(([slashCommands]) => {
|
|
544
|
+
this.sendAvailableCommandsUpdate(sessionId, slashCommands);
|
|
545
|
+
})
|
|
546
|
+
.catch((err) => {
|
|
547
|
+
this.logger.warn("Failed to fetch deferred session data", { err });
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
494
551
|
private registerPersistence(
|
|
495
552
|
sessionId: string,
|
|
496
553
|
meta: Record<string, unknown> | undefined,
|
|
@@ -245,11 +245,7 @@ function clearStatsigCache(): void {
|
|
|
245
245
|
process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude"),
|
|
246
246
|
"statsig",
|
|
247
247
|
);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
} catch {
|
|
253
|
-
// Ignore errors - cache clearing is best-effort
|
|
254
|
-
}
|
|
248
|
+
fs.rm(statsigPath, { recursive: true, force: true }, () => {
|
|
249
|
+
// Best-effort, ignore errors
|
|
250
|
+
});
|
|
255
251
|
}
|
package/src/agent.ts
CHANGED
package/src/gateway-models.ts
CHANGED
|
@@ -26,6 +26,14 @@ type ArrayModelsResponse =
|
|
|
26
26
|
}
|
|
27
27
|
| Array<{ id?: string; owned_by?: string }>;
|
|
28
28
|
|
|
29
|
+
const CACHE_TTL = 10 * 60 * 1000; // 10 minutes
|
|
30
|
+
|
|
31
|
+
let gatewayModelsCache: {
|
|
32
|
+
models: GatewayModel[];
|
|
33
|
+
expiry: number;
|
|
34
|
+
url: string;
|
|
35
|
+
} | null = null;
|
|
36
|
+
|
|
29
37
|
export async function fetchGatewayModels(
|
|
30
38
|
options?: FetchGatewayModelsOptions,
|
|
31
39
|
): Promise<GatewayModel[]> {
|
|
@@ -34,6 +42,14 @@ export async function fetchGatewayModels(
|
|
|
34
42
|
return [];
|
|
35
43
|
}
|
|
36
44
|
|
|
45
|
+
if (
|
|
46
|
+
gatewayModelsCache &&
|
|
47
|
+
gatewayModelsCache.url === gatewayUrl &&
|
|
48
|
+
Date.now() < gatewayModelsCache.expiry
|
|
49
|
+
) {
|
|
50
|
+
return gatewayModelsCache.models;
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
const modelsUrl = `${gatewayUrl}/v1/models`;
|
|
38
54
|
|
|
39
55
|
try {
|
|
@@ -44,8 +60,13 @@ export async function fetchGatewayModels(
|
|
|
44
60
|
}
|
|
45
61
|
|
|
46
62
|
const data = (await response.json()) as GatewayModelsResponse;
|
|
47
|
-
const models = data.data ?? [];
|
|
48
|
-
|
|
63
|
+
const models = (data.data ?? []).filter((m) => !BLOCKED_MODELS.has(m.id));
|
|
64
|
+
gatewayModelsCache = {
|
|
65
|
+
models,
|
|
66
|
+
expiry: Date.now() + CACHE_TTL,
|
|
67
|
+
url: gatewayUrl,
|
|
68
|
+
};
|
|
69
|
+
return models;
|
|
49
70
|
} catch {
|
|
50
71
|
return [];
|
|
51
72
|
}
|
|
@@ -70,6 +91,12 @@ export interface ArrayModelInfo {
|
|
|
70
91
|
owned_by?: string;
|
|
71
92
|
}
|
|
72
93
|
|
|
94
|
+
let arrayModelsCache: {
|
|
95
|
+
models: ArrayModelInfo[];
|
|
96
|
+
expiry: number;
|
|
97
|
+
url: string;
|
|
98
|
+
} | null = null;
|
|
99
|
+
|
|
73
100
|
export async function fetchArrayModels(
|
|
74
101
|
options?: FetchGatewayModelsOptions,
|
|
75
102
|
): Promise<ArrayModelInfo[]> {
|
|
@@ -78,6 +105,14 @@ export async function fetchArrayModels(
|
|
|
78
105
|
return [];
|
|
79
106
|
}
|
|
80
107
|
|
|
108
|
+
if (
|
|
109
|
+
arrayModelsCache &&
|
|
110
|
+
arrayModelsCache.url === gatewayUrl &&
|
|
111
|
+
Date.now() < arrayModelsCache.expiry
|
|
112
|
+
) {
|
|
113
|
+
return arrayModelsCache.models;
|
|
114
|
+
}
|
|
115
|
+
|
|
81
116
|
try {
|
|
82
117
|
const base = new URL(gatewayUrl);
|
|
83
118
|
base.pathname = "/array/v1/models";
|
|
@@ -97,6 +132,11 @@ export async function fetchArrayModels(
|
|
|
97
132
|
if (!id) continue;
|
|
98
133
|
results.push({ id, owned_by: model?.owned_by });
|
|
99
134
|
}
|
|
135
|
+
arrayModelsCache = {
|
|
136
|
+
models: results,
|
|
137
|
+
expiry: Date.now() + CACHE_TTL,
|
|
138
|
+
url: gatewayUrl,
|
|
139
|
+
};
|
|
100
140
|
return results;
|
|
101
141
|
} catch {
|
|
102
142
|
return [];
|