@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/dist/agent.js +56 -10
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.js +1 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.js +56 -10
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +56 -10
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +3 -3
- package/src/adapters/claude/conversion/acp-to-sdk.test.ts +109 -13
- package/src/adapters/claude/conversion/acp-to-sdk.ts +63 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/agent",
|
|
3
|
-
"version": "2.3.
|
|
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 {
|
|
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("
|
|
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).
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
|
31
|
-
`-
|
|
32
|
-
`-
|
|
33
|
-
|
|
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
|
|
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
|
-
?
|
|
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
|
-
|
|
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="${
|
|
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
|
|