@posthog/agent 2.3.619 → 2.3.643

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.619",
3
+ "version": "2.3.643",
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": {
@@ -106,9 +106,9 @@
106
106
  "tsx": "^4.20.6",
107
107
  "typescript": "^5.5.0",
108
108
  "vitest": "^2.1.8",
109
+ "@posthog/git": "1.0.0",
109
110
  "@posthog/shared": "1.0.0",
110
- "@posthog/enricher": "1.0.0",
111
- "@posthog/git": "1.0.0"
111
+ "@posthog/enricher": "1.0.0"
112
112
  },
113
113
  "dependencies": {
114
114
  "@agentclientprotocol/sdk": "0.19.0",
@@ -1,8 +1,34 @@
1
+ import type { PromptRequest } from "@agentclientprotocol/sdk";
1
2
  import { describe, expect, it } from "vitest";
2
- import { promptToClaude } from "./acp-to-sdk";
3
+ import {
4
+ promptToClaude,
5
+ readToolGuidanceForPath,
6
+ workspacePromptFromFileUri,
7
+ } from "./acp-to-sdk";
8
+
9
+ describe("readToolGuidanceForPath", () => {
10
+ it.each([
11
+ ["/docs/x.pdf", ["pages"]],
12
+ ["/proj/app.ts", ["offset", "limit"]],
13
+ ["/assets/logo.png", ["Binary", "file_path"]],
14
+ ])("guides reads for %s", (filePath, keywords) => {
15
+ const guidance = readToolGuidanceForPath(filePath);
16
+ for (const keyword of keywords) {
17
+ expect(guidance).toContain(keyword);
18
+ }
19
+ });
20
+ });
21
+
22
+ describe("workspacePromptFromFileUri", () => {
23
+ it("includes file_path and Read-oriented chunking hints", () => {
24
+ const s = workspacePromptFromFileUri("file:///tmp/x.pdf");
25
+ expect(s).toContain("Read");
26
+ expect(s).toContain("file_path: /tmp/x.pdf");
27
+ });
28
+ });
3
29
 
4
30
  describe("promptToClaude", () => {
5
- it("renders file resource links as explicit workspace attachments", () => {
31
+ it("maps file resource_link to workspace path + Read guidance", () => {
6
32
  const result = promptToClaude({
7
33
  sessionId: "session-1",
8
34
  prompt: [
@@ -14,17 +40,87 @@ describe("promptToClaude", () => {
14
40
  ],
15
41
  });
16
42
 
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
- ]);
43
+ expect(result.message.content.length).toBe(1);
44
+ expect(result.message.content[0]).toMatchObject({
45
+ type: "text",
46
+ text: expect.any(String),
47
+ });
48
+ expect(
49
+ (result.message.content[0] as { type: string; text: string }).text,
50
+ ).toContain("file_path:");
51
+ expect(
52
+ (result.message.content[0] as { type: string; text: string }).text,
53
+ ).toContain("/tmp/workspace/.posthog/attachments/run-1/report.pdf");
54
+ const text = (result.message.content[0] as { text: string }).text;
55
+ expect(text.toLowerCase()).toContain("read");
56
+ expect(text).toContain("pages");
57
+ });
58
+
59
+ it("drops embedded body for file:// resource but keeps attachment:// payload", () => {
60
+ const hugeInline = `${"y".repeat(30_000)}KEEP_ATTACH${"y".repeat(30_000)}`;
61
+ const fileRes = promptToClaude({
62
+ sessionId: "x",
63
+ prompt: [
64
+ {
65
+ type: "resource",
66
+ resource: {
67
+ uri: "file:///tmp/note.txt",
68
+ text: `${"x".repeat(50_000)}DROP_THIS${"x".repeat(50_000)}`,
69
+ mimeType: "text/plain",
70
+ },
71
+ },
72
+ ],
73
+ });
74
+ expect(fileRes.message.content.length).toBe(1);
75
+ expect(JSON.stringify(fileRes)).not.toContain("DROP_THIS");
76
+ expect(fileRes.message.content[0]).toMatchObject({
77
+ type: "text",
78
+ text: expect.stringContaining("file_path: /tmp/note.txt"),
79
+ });
80
+
81
+ const attachRes = promptToClaude({
82
+ sessionId: "y",
83
+ prompt: [
84
+ {
85
+ type: "resource",
86
+ resource: {
87
+ uri: "attachment://z?label=f.txt",
88
+ text: hugeInline,
89
+ mimeType: "text/plain",
90
+ },
91
+ },
92
+ ],
93
+ });
94
+ expect(attachRes.message.content.length).toBe(2);
95
+ expect(JSON.stringify(attachRes)).toContain("KEEP_ATTACH");
96
+ });
97
+
98
+ it("maps file URI-only image blocks to workspace Read prompt text", () => {
99
+ const req: PromptRequest = {
100
+ sessionId: "session-1",
101
+ prompt: [
102
+ {
103
+ type: "image",
104
+ uri: "file:///tmp/ui/screenshot.png",
105
+ mimeType: "image/png",
106
+ } as PromptRequest["prompt"][number],
107
+ ],
108
+ };
109
+ const result = promptToClaude(req);
110
+
111
+ expect(result.message.content).toHaveLength(1);
112
+ expect(result.message.content[0]).toMatchObject({
113
+ type: "text",
114
+ text: expect.any(String),
115
+ });
116
+ expect(
117
+ (result.message.content[0] as { type: string; text: string }).text,
118
+ ).toContain("/tmp/ui/screenshot.png");
119
+ expect(
120
+ (
121
+ result.message.content[0] as { type: string; text: string }
122
+ ).text.toLowerCase(),
123
+ ).toContain("read");
28
124
  });
29
125
 
30
126
  it("preserves non-file resource links as links", () => {
@@ -6,6 +6,31 @@ import type { ContentBlockParam } from "@anthropic-ai/sdk/resources";
6
6
 
7
7
  type ImageMimeType = "image/jpeg" | "image/png" | "image/gif" | "image/webp";
8
8
 
9
+ const PDF_EXTENSIONS = new Set(["pdf"]);
10
+
11
+ const COMMON_IMAGE_EXTENSIONS = new Set([
12
+ "png",
13
+ "jpg",
14
+ "jpeg",
15
+ "gif",
16
+ "webp",
17
+ "bmp",
18
+ "svg",
19
+ "heic",
20
+ "tif",
21
+ "tiff",
22
+ ]);
23
+
24
+ const VIDEO_EXTENSIONS = new Set([
25
+ "mp4",
26
+ "mov",
27
+ "webm",
28
+ "mkv",
29
+ "avi",
30
+ "mpeg",
31
+ "mpg",
32
+ ]);
33
+
9
34
  function sdkText(value: string): ContentBlockParam {
10
35
  return { type: "text", text: value };
11
36
  }
@@ -22,21 +47,42 @@ function formatUriAsLink(uri: string): string {
22
47
  }
23
48
  }
24
49
 
25
- function formatFileAttachment(uri: string): string {
50
+ /** Chunking hints for Claude Code `Read` (`file_path`, optional `pages` / `offset` / `limit`). */
51
+ export function readToolGuidanceForPath(filePath: string): string {
52
+ const ext = path.extname(filePath).slice(1).toLowerCase();
53
+ if (PDF_EXTENSIONS.has(ext)) {
54
+ return 'Optional `pages` string (e.g. "1-5") per Read call instead of loading the entire PDF.';
55
+ }
56
+ if (COMMON_IMAGE_EXTENSIONS.has(ext) || VIDEO_EXTENSIONS.has(ext)) {
57
+ return "Binary file — use Read with `file_path`; prefer bounded reads where supported.";
58
+ }
59
+ return "Large text — use multiple Read calls with optional `offset` and `limit`.";
60
+ }
61
+
62
+ /** Path-only workspace attach text (never embed `resource.text` from disk). */
63
+ export function workspacePromptFromFileUri(uri: string): string {
26
64
  try {
27
65
  const filePath = fileURLToPath(uri);
28
66
  const name = path.basename(filePath) || filePath;
29
67
  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.",
68
+ "Attached workspace file use Read with required `file_path`:",
69
+ `- file_path: ${filePath}`,
70
+ `- name (context): ${name}`,
71
+ readToolGuidanceForPath(filePath),
34
72
  ].join("\n");
35
73
  } catch {
36
- return `Attached file available at ${uri}`;
74
+ return [
75
+ "Attached file — decode path from URI, call Read with that path as `file_path`:",
76
+ uri,
77
+ 'Chunk PDFs with `pages` (e.g. "1-5"); long text with `offset`/`limit`.',
78
+ ].join("\n");
37
79
  }
38
80
  }
39
81
 
82
+ function isFileSchemeUri(uri: string | undefined | null): boolean {
83
+ return Boolean(uri?.startsWith("file://"));
84
+ }
85
+
40
86
  function transformMcpCommand(text: string): string {
41
87
  const mcpMatch = text.match(/^\/mcp:([^:\s]+):(\S+)(\s+.*)?$/);
42
88
  if (mcpMatch) {
@@ -60,7 +106,7 @@ function processPromptChunk(
60
106
  content.push(
61
107
  sdkText(
62
108
  chunk.uri.startsWith("file://")
63
- ? formatFileAttachment(chunk.uri)
109
+ ? workspacePromptFromFileUri(chunk.uri)
64
110
  : formatUriAsLink(chunk.uri),
65
111
  ),
66
112
  );
@@ -68,10 +114,16 @@ function processPromptChunk(
68
114
 
69
115
  case "resource":
70
116
  if ("text" in chunk.resource) {
71
- content.push(sdkText(formatUriAsLink(chunk.resource.uri)));
117
+ const uri = chunk.resource.uri;
118
+ if (uri != null && isFileSchemeUri(uri)) {
119
+ content.push(sdkText(workspacePromptFromFileUri(uri)));
120
+ break;
121
+ }
122
+
123
+ content.push(sdkText(formatUriAsLink(uri ?? "")));
72
124
  context.push(
73
125
  sdkText(
74
- `\n<context ref="${chunk.resource.uri}">\n${chunk.resource.text}\n</context>`,
126
+ `\n<context ref="${uri ?? ""}">\n${chunk.resource.text}\n</context>`,
75
127
  ),
76
128
  );
77
129
  }
@@ -92,6 +144,8 @@ function processPromptChunk(
92
144
  type: "image",
93
145
  source: { type: "url", url: chunk.uri },
94
146
  });
147
+ } else if (chunk.uri != null && isFileSchemeUri(chunk.uri)) {
148
+ content.push(sdkText(workspacePromptFromFileUri(chunk.uri)));
95
149
  }
96
150
  break;
97
151