@posthog/agent 2.3.22 → 2.3.31

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/agent",
3
- "version": "2.3.22",
3
+ "version": "2.3.31",
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": {
@@ -59,7 +59,12 @@ import { fetchMcpToolMetadata } from "./mcp/tool-metadata";
59
59
  import { canUseTool } from "./permissions/permission-handlers";
60
60
  import { getAvailableSlashCommands } from "./session/commands";
61
61
  import { parseMcpServers } from "./session/mcp-config";
62
- import { DEFAULT_MODEL, toSdkModelId } from "./session/models";
62
+ import {
63
+ DEFAULT_MODEL,
64
+ getDefaultContextWindow,
65
+ getEffortOptions,
66
+ toSdkModelId,
67
+ } from "./session/models";
63
68
  import {
64
69
  buildSessionOptions,
65
70
  buildSystemPrompt,
@@ -337,7 +342,9 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
337
342
  (m) => m.contextWindow,
338
343
  );
339
344
  const contextWindowSize =
340
- contextWindows.length > 0 ? Math.min(...contextWindows) : 200000;
345
+ contextWindows.length > 0
346
+ ? Math.min(...contextWindows)
347
+ : getDefaultContextWindow(this.session.modelId ?? "");
341
348
 
342
349
  // Send usage_update notification
343
350
  if (lastAssistantTotalUsage !== null) {
@@ -509,6 +516,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
509
516
  const sdkModelId = toSdkModelId(params.modelId);
510
517
  await this.session.query.setModel(sdkModelId);
511
518
  this.session.modelId = params.modelId;
519
+ this.rebuildEffortConfigOption(params.modelId);
512
520
  await this.updateConfigOption("model", params.modelId);
513
521
  return {};
514
522
  }
@@ -559,6 +567,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
559
567
  const sdkModelId = toSdkModelId(params.value);
560
568
  await this.session.query.setModel(sdkModelId);
561
569
  this.session.modelId = params.value;
570
+ this.rebuildEffortConfigOption(params.value);
562
571
  } else if (params.configId === "effort") {
563
572
  const newEffort = params.value as EffortLevel;
564
573
  this.session.effort = newEffort;
@@ -865,7 +874,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
865
874
  description: mode.description ?? undefined,
866
875
  }));
867
876
 
868
- return [
877
+ const configOptions: SessionConfigOption[] = [
869
878
  {
870
879
  id: "mode",
871
880
  name: "Approval Preset",
@@ -885,21 +894,67 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
885
894
  category: "model" as SessionConfigOptionCategory,
886
895
  description: "Choose which model Claude should use",
887
896
  },
888
- {
897
+ ];
898
+
899
+ const effortOptions = getEffortOptions(modelOptions.currentModelId);
900
+ if (effortOptions) {
901
+ configOptions.push({
889
902
  id: "effort",
890
903
  name: "Effort",
891
904
  type: "select",
892
905
  currentValue: currentEffort,
893
- options: [
894
- { value: "low", name: "Low" },
895
- { value: "medium", name: "Medium" },
896
- { value: "high", name: "High" },
897
- { value: "max", name: "Max" },
898
- ],
906
+ options: effortOptions,
899
907
  category: "thought_level" as SessionConfigOptionCategory,
900
908
  description: "Controls how much effort Claude puts into its response",
901
- },
902
- ];
909
+ });
910
+ }
911
+
912
+ return configOptions;
913
+ }
914
+
915
+ private rebuildEffortConfigOption(modelId: string): void {
916
+ const effortOptions = getEffortOptions(modelId);
917
+ const existingEffort = this.session.configOptions.find(
918
+ (o) => o.id === "effort",
919
+ );
920
+
921
+ if (!effortOptions) {
922
+ this.session.configOptions = this.session.configOptions.filter(
923
+ (o) => o.id !== "effort",
924
+ );
925
+ if (this.session.effort) {
926
+ this.session.effort = undefined;
927
+ this.session.queryOptions.effort = undefined;
928
+ }
929
+ return;
930
+ }
931
+
932
+ const currentValue = existingEffort?.currentValue ?? "high";
933
+ const isValidValue = effortOptions.some((o) => o.value === currentValue);
934
+ const resolvedValue = isValidValue ? currentValue : "high";
935
+
936
+ if (resolvedValue !== currentValue && this.session.effort) {
937
+ this.session.effort = resolvedValue as EffortLevel;
938
+ this.session.queryOptions.effort = resolvedValue as EffortLevel;
939
+ }
940
+
941
+ const effortConfig: SessionConfigOption = {
942
+ id: "effort",
943
+ name: "Effort",
944
+ type: "select",
945
+ currentValue: resolvedValue,
946
+ options: effortOptions,
947
+ category: "thought_level" as SessionConfigOptionCategory,
948
+ description: "Controls how much effort Claude puts into its response",
949
+ };
950
+
951
+ if (existingEffort) {
952
+ this.session.configOptions = this.session.configOptions.map((o) =>
953
+ o.id === "effort" ? effortConfig : o,
954
+ );
955
+ } else {
956
+ this.session.configOptions.push(effortConfig);
957
+ }
903
958
  }
904
959
 
905
960
  private async sendAvailableCommandsUpdate(): Promise<void> {
@@ -5,6 +5,7 @@ import * as path from "node:path";
5
5
  import type { ContentBlock } from "@agentclientprotocol/sdk";
6
6
  import type { PostHogAPIClient } from "../../../posthog-api";
7
7
  import type { StoredEntry } from "../../../types";
8
+ import { supports1MContext } from "./models";
8
9
 
9
10
  interface ConversationTurn {
10
11
  role: "user" | "assistant";
@@ -188,6 +189,7 @@ export function rebuildConversation(
188
189
 
189
190
  const CHARS_PER_TOKEN = 4;
190
191
  const DEFAULT_MAX_TOKENS = 150_000;
192
+ const LARGE_CONTEXT_MAX_TOKENS = 800_000;
191
193
 
192
194
  function estimateTurnTokens(turn: ConversationTurn): number {
193
195
  let chars = 0;
@@ -546,7 +548,10 @@ export async function hydrateSessionJsonl(params: {
546
548
  return;
547
549
  }
548
550
 
549
- const conversation = selectRecentTurns(allTurns);
551
+ const maxTokens = supports1MContext(params.model ?? "")
552
+ ? LARGE_CONTEXT_MAX_TOKENS
553
+ : DEFAULT_MAX_TOKENS;
554
+ const conversation = selectRecentTurns(allTurns, maxTokens);
550
555
  log.info("Selected recent turns for hydration", {
551
556
  totalTurns: allTurns.length,
552
557
  selectedTurns: conversation.length,
@@ -11,3 +11,53 @@ const GATEWAY_TO_SDK_MODEL: Record<string, string> = {
11
11
  export function toSdkModelId(modelId: string): string {
12
12
  return GATEWAY_TO_SDK_MODEL[modelId] ?? modelId;
13
13
  }
14
+
15
+ const MODELS_WITH_1M_CONTEXT = new Set([
16
+ "claude-opus-4-6",
17
+ "claude-sonnet-4-6",
18
+ ]);
19
+
20
+ export function supports1MContext(modelId: string): boolean {
21
+ return MODELS_WITH_1M_CONTEXT.has(modelId);
22
+ }
23
+
24
+ export function getDefaultContextWindow(modelId: string): number {
25
+ return supports1MContext(modelId) ? 1_000_000 : 200_000;
26
+ }
27
+
28
+ const MODELS_WITH_EFFORT = new Set([
29
+ "claude-opus-4-5",
30
+ "claude-opus-4-6",
31
+ "claude-sonnet-4-6",
32
+ ]);
33
+
34
+ const MODELS_WITH_MAX_EFFORT = new Set(["claude-opus-4-6"]);
35
+
36
+ export function supportsEffort(modelId: string): boolean {
37
+ return MODELS_WITH_EFFORT.has(modelId);
38
+ }
39
+
40
+ export function supportsMaxEffort(modelId: string): boolean {
41
+ return MODELS_WITH_MAX_EFFORT.has(modelId);
42
+ }
43
+
44
+ interface EffortOption {
45
+ value: string;
46
+ name: string;
47
+ }
48
+
49
+ export function getEffortOptions(modelId: string): EffortOption[] | null {
50
+ if (!supportsEffort(modelId)) return null;
51
+
52
+ const options: EffortOption[] = [
53
+ { value: "low", name: "Low" },
54
+ { value: "medium", name: "Medium" },
55
+ { value: "high", name: "High" },
56
+ ];
57
+
58
+ if (supportsMaxEffort(modelId)) {
59
+ options.push({ value: "max", name: "Max" });
60
+ }
61
+
62
+ return options;
63
+ }
@@ -639,7 +639,14 @@ export class AgentServer {
639
639
  _meta: {
640
640
  sessionId: payload.run_id,
641
641
  taskRunId: payload.run_id,
642
- systemPrompt: { append: this.buildCloudSystemPrompt(prUrl) },
642
+ systemPrompt: this.buildSessionSystemPrompt(prUrl),
643
+ ...(this.config.claudeCode?.plugins?.length && {
644
+ claudeCode: {
645
+ options: {
646
+ plugins: this.config.claudeCode.plugins,
647
+ },
648
+ },
649
+ }),
643
650
  },
644
651
  });
645
652
 
@@ -944,6 +951,28 @@ export class AgentServer {
944
951
  : null;
945
952
  }
946
953
 
954
+ private buildSessionSystemPrompt(
955
+ prUrl?: string | null,
956
+ ): string | { append: string } {
957
+ const cloudAppend = this.buildCloudSystemPrompt(prUrl);
958
+ const userPrompt = this.config.claudeCode?.systemPrompt;
959
+
960
+ // String override: combine user prompt with cloud instructions
961
+ if (typeof userPrompt === "string") {
962
+ return [userPrompt, cloudAppend].join("\n\n");
963
+ }
964
+
965
+ // Preset with append: merge user append with cloud instructions
966
+ if (typeof userPrompt === "object") {
967
+ return {
968
+ append: [userPrompt.append, cloudAppend].filter(Boolean).join("\n\n"),
969
+ };
970
+ }
971
+
972
+ // Default: just cloud instructions
973
+ return { append: cloudAppend };
974
+ }
975
+
947
976
  private buildCloudSystemPrompt(prUrl?: string | null): string {
948
977
  if (prUrl) {
949
978
  return `
package/src/server/bin.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  import { Command } from "commander";
3
3
  import { z } from "zod";
4
4
  import { AgentServer } from "./agent-server";
5
- import { mcpServersSchema } from "./schemas";
5
+ import { claudeCodeConfigSchema, mcpServersSchema } from "./schemas";
6
6
 
7
7
  const envSchema = z.object({
8
8
  JWT_PUBLIC_KEY: z
@@ -34,6 +34,30 @@ const envSchema = z.object({
34
34
 
35
35
  const program = new Command();
36
36
 
37
+ function parseJsonOption<S extends z.ZodTypeAny>(
38
+ raw: string | undefined,
39
+ schema: S,
40
+ flag: string,
41
+ ): z.output<S> | undefined {
42
+ if (!raw) return undefined;
43
+
44
+ let parsed: unknown;
45
+ try {
46
+ parsed = JSON.parse(raw);
47
+ } catch {
48
+ program.error(`${flag} must be valid JSON`);
49
+ }
50
+
51
+ const result = schema.safeParse(parsed);
52
+ if (!result.success) {
53
+ const errors = result.error.issues
54
+ .map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`)
55
+ .join("\n");
56
+ program.error(`${flag} validation failed:\n${errors}`);
57
+ }
58
+ return result.data;
59
+ }
60
+
37
61
  program
38
62
  .name("agent-server")
39
63
  .description("PostHog cloud agent server - runs in sandbox environments")
@@ -51,6 +75,10 @@ program
51
75
  "MCP servers config as JSON array (ACP McpServer[] format)",
52
76
  )
53
77
  .option("--baseBranch <branch>", "Base branch for PR creation")
78
+ .option(
79
+ "--claudeCodeConfig <json>",
80
+ "Claude Code config as JSON (systemPrompt, systemPromptAppend, plugins)",
81
+ )
54
82
  .action(async (options) => {
55
83
  const envResult = envSchema.safeParse(process.env);
56
84
 
@@ -66,28 +94,16 @@ program
66
94
 
67
95
  const mode = options.mode === "background" ? "background" : "interactive";
68
96
 
69
- let mcpServers: z.infer<typeof mcpServersSchema> | undefined;
70
- if (options.mcpServers) {
71
- let parsed: unknown;
72
- try {
73
- parsed = JSON.parse(options.mcpServers);
74
- } catch {
75
- program.error("--mcpServers must be valid JSON");
76
- return;
77
- }
78
-
79
- const result = mcpServersSchema.safeParse(parsed);
80
- if (!result.success) {
81
- const errors = result.error.issues
82
- .map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`)
83
- .join("\n");
84
- program.error(
85
- `--mcpServers validation failed (only remote http/sse servers are supported):\n${errors}`,
86
- );
87
- return;
88
- }
89
- mcpServers = result.data;
90
- }
97
+ const mcpServers = parseJsonOption(
98
+ options.mcpServers,
99
+ mcpServersSchema,
100
+ "--mcpServers",
101
+ );
102
+ const claudeCode = parseJsonOption(
103
+ options.claudeCodeConfig,
104
+ claudeCodeConfigSchema,
105
+ "--claudeCodeConfig",
106
+ );
91
107
 
92
108
  const server = new AgentServer({
93
109
  port: parseInt(options.port, 10),
@@ -101,6 +117,7 @@ program
101
117
  runId: options.runId,
102
118
  mcpServers,
103
119
  baseBranch: options.baseBranch,
120
+ claudeCode,
104
121
  });
105
122
 
106
123
  process.on("SIGINT", async () => {
@@ -16,6 +16,22 @@ export const mcpServersSchema = z.array(remoteMcpServerSchema);
16
16
 
17
17
  export type RemoteMcpServer = z.infer<typeof remoteMcpServerSchema>;
18
18
 
19
+ export const claudeCodeConfigSchema = z.object({
20
+ systemPrompt: z
21
+ .union([
22
+ z.string(),
23
+ z.object({
24
+ type: z.literal("preset"),
25
+ preset: z.literal("claude_code"),
26
+ append: z.string().optional(),
27
+ }),
28
+ ])
29
+ .optional(),
30
+ plugins: z
31
+ .array(z.object({ type: z.literal("local"), path: z.string() }))
32
+ .optional(),
33
+ });
34
+
19
35
  export const jsonRpcRequestSchema = z.object({
20
36
  jsonrpc: z.literal("2.0"),
21
37
  method: z.string(),
@@ -1,6 +1,13 @@
1
1
  import type { AgentMode } from "../types";
2
2
  import type { RemoteMcpServer } from "./schemas";
3
3
 
4
+ export interface ClaudeCodeConfig {
5
+ systemPrompt?:
6
+ | string
7
+ | { type: "preset"; preset: "claude_code"; append?: string };
8
+ plugins?: { type: "local"; path: string }[];
9
+ }
10
+
4
11
  export interface AgentServerConfig {
5
12
  port: number;
6
13
  repositoryPath?: string;
@@ -14,4 +21,5 @@ export interface AgentServerConfig {
14
21
  version?: string;
15
22
  mcpServers?: RemoteMcpServer[];
16
23
  baseBranch?: string;
24
+ claudeCode?: ClaudeCodeConfig;
17
25
  }