@posthog/agent 2.1.2 → 2.1.8
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-kRbaLUfe.d.ts → agent-LrKyX9KN.d.ts} +1 -4
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +3 -86
- package/dist/agent.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +58 -86
- package/dist/index.js.map +1 -1
- package/dist/server/agent-server.js +4 -82
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +4 -79
- package/dist/server/bin.cjs.map +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
- package/src/agent.ts +1 -12
- package/src/server/agent-server.ts +2 -6
- package/src/session-log-writer.test.ts +72 -101
- package/src/session-log-writer.ts +3 -34
- package/src/types.ts +2 -0
package/dist/types.d.ts
CHANGED
|
@@ -101,6 +101,8 @@ interface AgentConfig {
|
|
|
101
101
|
posthog?: PostHogAPIConfig;
|
|
102
102
|
/** OTEL transport config for shipping logs to PostHog Logs */
|
|
103
103
|
otelTransport?: OtelTransportConfig;
|
|
104
|
+
/** Skip session log persistence (e.g. for preview sessions with no real task) */
|
|
105
|
+
skipLogPersistence?: boolean;
|
|
104
106
|
debug?: boolean;
|
|
105
107
|
onLog?: OnLogCallback;
|
|
106
108
|
}
|
package/package.json
CHANGED
package/src/agent.ts
CHANGED
|
@@ -32,18 +32,7 @@ export class Agent {
|
|
|
32
32
|
this.posthogAPI = new PostHogAPIClient(config.posthog);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
if (config.
|
|
36
|
-
// OTEL pipeline: use OtelLogWriter only (no S3 writer)
|
|
37
|
-
this.sessionLogWriter = new SessionLogWriter({
|
|
38
|
-
otelConfig: {
|
|
39
|
-
posthogHost: config.otelTransport.host,
|
|
40
|
-
apiKey: config.otelTransport.apiKey,
|
|
41
|
-
logsPath: config.otelTransport.logsPath,
|
|
42
|
-
},
|
|
43
|
-
logger: this.logger.child("SessionLogWriter"),
|
|
44
|
-
});
|
|
45
|
-
} else if (config.posthog) {
|
|
46
|
-
// Legacy: use S3 writer via PostHog API
|
|
35
|
+
if (config.posthog && !config.skipLogPersistence) {
|
|
47
36
|
this.sessionLogWriter = new SessionLogWriter({
|
|
48
37
|
posthogAPI: this.posthogAPI,
|
|
49
38
|
logger: this.logger.child("SessionLogWriter"),
|
|
@@ -464,18 +464,14 @@ export class AgentServer {
|
|
|
464
464
|
logger: new Logger({ debug: true, prefix: "[TreeTracker]" }),
|
|
465
465
|
});
|
|
466
466
|
|
|
467
|
-
const
|
|
467
|
+
const posthogAPI = new PostHogAPIClient({
|
|
468
468
|
apiUrl: this.config.apiUrl,
|
|
469
469
|
projectId: this.config.projectId,
|
|
470
470
|
getApiKey: () => this.config.apiKey,
|
|
471
471
|
});
|
|
472
472
|
|
|
473
473
|
const logWriter = new SessionLogWriter({
|
|
474
|
-
|
|
475
|
-
posthogHost: this.config.apiUrl,
|
|
476
|
-
apiKey: this.config.apiKey,
|
|
477
|
-
logsPath: "/i/v1/agent-logs",
|
|
478
|
-
},
|
|
474
|
+
posthogAPI,
|
|
479
475
|
logger: new Logger({ debug: true, prefix: "[SessionLogWriter]" }),
|
|
480
476
|
});
|
|
481
477
|
|
|
@@ -1,39 +1,31 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import type { PostHogAPIClient } from "./posthog-api.js";
|
|
3
3
|
import { SessionLogWriter } from "./session-log-writer.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
4
|
+
import type { StoredNotification } from "./types.js";
|
|
5
|
+
|
|
6
|
+
function makeSessionUpdate(
|
|
7
|
+
sessionUpdate: string,
|
|
8
|
+
extra: Record<string, unknown> = {},
|
|
9
|
+
): string {
|
|
10
|
+
return JSON.stringify({
|
|
11
|
+
jsonrpc: "2.0",
|
|
12
|
+
method: "session/update",
|
|
13
|
+
params: { update: { sessionUpdate, ...extra } },
|
|
14
|
+
});
|
|
15
|
+
}
|
|
9
16
|
|
|
10
17
|
describe("SessionLogWriter", () => {
|
|
11
18
|
let logWriter: SessionLogWriter;
|
|
12
|
-
let
|
|
13
|
-
let
|
|
14
|
-
let mockShutdown: ReturnType<typeof vi.fn>;
|
|
19
|
+
let mockAppendLog: ReturnType<typeof vi.fn>;
|
|
20
|
+
let mockPosthogAPI: PostHogAPIClient;
|
|
15
21
|
|
|
16
22
|
beforeEach(() => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
({
|
|
24
|
-
emit: mockEmit,
|
|
25
|
-
flush: mockFlush,
|
|
26
|
-
shutdown: mockShutdown,
|
|
27
|
-
}) as unknown as OtelLogWriter,
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
logWriter = new SessionLogWriter({
|
|
31
|
-
otelConfig: {
|
|
32
|
-
posthogHost: "http://localhost:8000",
|
|
33
|
-
apiKey: "test-api-key",
|
|
34
|
-
logsPath: "/i/v1/agent-logs",
|
|
35
|
-
},
|
|
36
|
-
});
|
|
23
|
+
mockAppendLog = vi.fn().mockResolvedValue(undefined);
|
|
24
|
+
mockPosthogAPI = {
|
|
25
|
+
appendTaskRunLog: mockAppendLog,
|
|
26
|
+
} as unknown as PostHogAPIClient;
|
|
27
|
+
|
|
28
|
+
logWriter = new SessionLogWriter({ posthogAPI: mockPosthogAPI });
|
|
37
29
|
});
|
|
38
30
|
|
|
39
31
|
afterEach(() => {
|
|
@@ -41,100 +33,79 @@ describe("SessionLogWriter", () => {
|
|
|
41
33
|
});
|
|
42
34
|
|
|
43
35
|
describe("appendRawLine", () => {
|
|
44
|
-
it("
|
|
45
|
-
const sessionId = "
|
|
46
|
-
logWriter.register(sessionId, { taskId: "
|
|
36
|
+
it("queues entries for flush", async () => {
|
|
37
|
+
const sessionId = "s1";
|
|
38
|
+
logWriter.register(sessionId, { taskId: "t1", runId: sessionId });
|
|
47
39
|
|
|
48
|
-
logWriter.appendRawLine(
|
|
49
|
-
|
|
50
|
-
JSON.stringify({ method: "test", params: {} }),
|
|
51
|
-
);
|
|
52
|
-
logWriter.appendRawLine(
|
|
53
|
-
sessionId,
|
|
54
|
-
JSON.stringify({ method: "test2", params: {} }),
|
|
55
|
-
);
|
|
40
|
+
logWriter.appendRawLine(sessionId, JSON.stringify({ method: "test" }));
|
|
41
|
+
logWriter.appendRawLine(sessionId, JSON.stringify({ method: "test2" }));
|
|
56
42
|
|
|
57
|
-
|
|
58
|
-
});
|
|
43
|
+
await logWriter.flush(sessionId);
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const message = {
|
|
65
|
-
jsonrpc: "2.0",
|
|
66
|
-
method: "session/update",
|
|
67
|
-
params: { foo: "bar" },
|
|
68
|
-
};
|
|
69
|
-
logWriter.appendRawLine(sessionId, JSON.stringify(message));
|
|
70
|
-
|
|
71
|
-
expect(mockEmit).toHaveBeenCalledTimes(1);
|
|
72
|
-
const emitArg = mockEmit.mock.calls[0][0];
|
|
73
|
-
expect(emitArg.notification.type).toBe("notification");
|
|
74
|
-
expect(emitArg.notification.timestamp).toBeDefined();
|
|
75
|
-
expect(emitArg.notification.notification).toEqual(message);
|
|
45
|
+
expect(mockAppendLog).toHaveBeenCalledTimes(1);
|
|
46
|
+
const entries: StoredNotification[] = mockAppendLog.mock.calls[0][2];
|
|
47
|
+
expect(entries).toHaveLength(2);
|
|
76
48
|
});
|
|
77
49
|
|
|
78
|
-
it("ignores unregistered sessions", () => {
|
|
79
|
-
logWriter.appendRawLine(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
expect(mockEmit).not.toHaveBeenCalled();
|
|
50
|
+
it("ignores unregistered sessions", async () => {
|
|
51
|
+
logWriter.appendRawLine("unknown", JSON.stringify({ method: "test" }));
|
|
52
|
+
await logWriter.flush("unknown");
|
|
53
|
+
expect(mockAppendLog).not.toHaveBeenCalled();
|
|
85
54
|
});
|
|
86
55
|
|
|
87
|
-
it("ignores invalid JSON", () => {
|
|
88
|
-
const sessionId = "
|
|
89
|
-
logWriter.register(sessionId, { taskId: "
|
|
56
|
+
it("ignores invalid JSON", async () => {
|
|
57
|
+
const sessionId = "s1";
|
|
58
|
+
logWriter.register(sessionId, { taskId: "t1", runId: sessionId });
|
|
90
59
|
|
|
91
60
|
logWriter.appendRawLine(sessionId, "not valid json {{{");
|
|
92
|
-
|
|
93
|
-
expect(
|
|
61
|
+
await logWriter.flush(sessionId);
|
|
62
|
+
expect(mockAppendLog).not.toHaveBeenCalled();
|
|
94
63
|
});
|
|
95
64
|
});
|
|
96
65
|
|
|
97
|
-
describe("
|
|
98
|
-
it("
|
|
99
|
-
const sessionId = "
|
|
100
|
-
logWriter.register(sessionId, { taskId: "
|
|
66
|
+
describe("agent_message_chunk coalescing", () => {
|
|
67
|
+
it("coalesces consecutive chunks into a single agent_message", async () => {
|
|
68
|
+
const sessionId = "s1";
|
|
69
|
+
logWriter.register(sessionId, { taskId: "t1", runId: sessionId });
|
|
101
70
|
|
|
102
|
-
logWriter.appendRawLine(
|
|
103
|
-
|
|
71
|
+
logWriter.appendRawLine(
|
|
72
|
+
sessionId,
|
|
73
|
+
makeSessionUpdate("agent_message_chunk", {
|
|
74
|
+
content: { type: "text", text: "Hello " },
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
logWriter.appendRawLine(
|
|
78
|
+
sessionId,
|
|
79
|
+
makeSessionUpdate("agent_message_chunk", {
|
|
80
|
+
content: { type: "text", text: "world" },
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
// Non-chunk event triggers flush of chunks
|
|
84
|
+
logWriter.appendRawLine(
|
|
85
|
+
sessionId,
|
|
86
|
+
makeSessionUpdate("tool_call", { toolCallId: "tc1" }),
|
|
87
|
+
);
|
|
104
88
|
|
|
105
|
-
|
|
106
|
-
});
|
|
89
|
+
await logWriter.flush(sessionId);
|
|
107
90
|
|
|
108
|
-
|
|
109
|
-
|
|
91
|
+
const entries: StoredNotification[] = mockAppendLog.mock.calls[0][2];
|
|
92
|
+
expect(entries).toHaveLength(2); // coalesced message + tool_call
|
|
110
93
|
|
|
111
|
-
|
|
94
|
+
const coalesced = entries[0].notification;
|
|
95
|
+
expect(coalesced.params?.update).toEqual({
|
|
96
|
+
sessionUpdate: "agent_message",
|
|
97
|
+
content: { type: "text", text: "Hello world" },
|
|
98
|
+
});
|
|
112
99
|
});
|
|
113
100
|
});
|
|
114
101
|
|
|
115
102
|
describe("register", () => {
|
|
116
|
-
it("creates OtelLogWriter with session context", () => {
|
|
117
|
-
const sessionId = "test-session";
|
|
118
|
-
const context = { taskId: "task-1", runId: sessionId };
|
|
119
|
-
|
|
120
|
-
logWriter.register(sessionId, context);
|
|
121
|
-
|
|
122
|
-
expect(OtelLogWriter).toHaveBeenCalledWith(
|
|
123
|
-
expect.objectContaining({
|
|
124
|
-
posthogHost: "http://localhost:8000",
|
|
125
|
-
apiKey: "test-api-key",
|
|
126
|
-
}),
|
|
127
|
-
context,
|
|
128
|
-
expect.anything(),
|
|
129
|
-
);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
103
|
it("does not re-register existing sessions", () => {
|
|
133
|
-
const sessionId = "
|
|
134
|
-
logWriter.register(sessionId, { taskId: "
|
|
135
|
-
logWriter.register(sessionId, { taskId: "
|
|
104
|
+
const sessionId = "s1";
|
|
105
|
+
logWriter.register(sessionId, { taskId: "t1", runId: sessionId });
|
|
106
|
+
logWriter.register(sessionId, { taskId: "t2", runId: sessionId });
|
|
136
107
|
|
|
137
|
-
expect(
|
|
108
|
+
expect(logWriter.isRegistered(sessionId)).toBe(true);
|
|
138
109
|
});
|
|
139
110
|
});
|
|
140
111
|
});
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type OtelLogConfig,
|
|
3
|
-
OtelLogWriter,
|
|
4
|
-
type SessionContext,
|
|
5
|
-
} from "./otel-log-writer.js";
|
|
1
|
+
import type { SessionContext } from "./otel-log-writer.js";
|
|
6
2
|
import type { PostHogAPIClient } from "./posthog-api.js";
|
|
7
3
|
import type { StoredNotification } from "./types.js";
|
|
8
4
|
import { Logger } from "./utils/logger.js";
|
|
9
5
|
|
|
10
6
|
export interface SessionLogWriterOptions {
|
|
11
|
-
/**
|
|
12
|
-
otelConfig?: OtelLogConfig;
|
|
13
|
-
/** PostHog API client for S3 log persistence */
|
|
7
|
+
/** PostHog API client for log persistence */
|
|
14
8
|
posthogAPI?: PostHogAPIClient;
|
|
15
9
|
/** Logger instance */
|
|
16
10
|
logger?: Logger;
|
|
@@ -23,13 +17,11 @@ interface ChunkBuffer {
|
|
|
23
17
|
|
|
24
18
|
interface SessionState {
|
|
25
19
|
context: SessionContext;
|
|
26
|
-
otelWriter?: OtelLogWriter;
|
|
27
20
|
chunkBuffer?: ChunkBuffer;
|
|
28
21
|
}
|
|
29
22
|
|
|
30
23
|
export class SessionLogWriter {
|
|
31
24
|
private posthogAPI?: PostHogAPIClient;
|
|
32
|
-
private otelConfig?: OtelLogConfig;
|
|
33
25
|
private pendingEntries: Map<string, StoredNotification[]> = new Map();
|
|
34
26
|
private flushTimeouts: Map<string, NodeJS.Timeout> = new Map();
|
|
35
27
|
private sessions: Map<string, SessionState> = new Map();
|
|
@@ -37,7 +29,6 @@ export class SessionLogWriter {
|
|
|
37
29
|
|
|
38
30
|
constructor(options: SessionLogWriterOptions = {}) {
|
|
39
31
|
this.posthogAPI = options.posthogAPI;
|
|
40
|
-
this.otelConfig = options.otelConfig;
|
|
41
32
|
this.logger =
|
|
42
33
|
options.logger ??
|
|
43
34
|
new Logger({ debug: false, prefix: "[SessionLogWriter]" });
|
|
@@ -56,17 +47,7 @@ export class SessionLogWriter {
|
|
|
56
47
|
return;
|
|
57
48
|
}
|
|
58
49
|
|
|
59
|
-
|
|
60
|
-
if (this.otelConfig) {
|
|
61
|
-
// Create a dedicated OtelLogWriter for this session with resource attributes
|
|
62
|
-
otelWriter = new OtelLogWriter(
|
|
63
|
-
this.otelConfig,
|
|
64
|
-
context,
|
|
65
|
-
this.logger.child(`OtelWriter:${sessionId}`),
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
this.sessions.set(sessionId, { context, otelWriter });
|
|
50
|
+
this.sessions.set(sessionId, { context });
|
|
70
51
|
}
|
|
71
52
|
|
|
72
53
|
isRegistered(sessionId: string): boolean {
|
|
@@ -106,10 +87,6 @@ export class SessionLogWriter {
|
|
|
106
87
|
notification: message,
|
|
107
88
|
};
|
|
108
89
|
|
|
109
|
-
if (session.otelWriter) {
|
|
110
|
-
session.otelWriter.emit({ notification: entry });
|
|
111
|
-
}
|
|
112
|
-
|
|
113
90
|
if (this.posthogAPI) {
|
|
114
91
|
const pending = this.pendingEntries.get(sessionId) ?? [];
|
|
115
92
|
pending.push(entry);
|
|
@@ -131,10 +108,6 @@ export class SessionLogWriter {
|
|
|
131
108
|
// Emit any buffered chunks before flushing
|
|
132
109
|
this.emitCoalescedMessage(sessionId, session);
|
|
133
110
|
|
|
134
|
-
if (session.otelWriter) {
|
|
135
|
-
await session.otelWriter.flush();
|
|
136
|
-
}
|
|
137
|
-
|
|
138
111
|
const pending = this.pendingEntries.get(sessionId);
|
|
139
112
|
if (!this.posthogAPI || !pending?.length) return;
|
|
140
113
|
|
|
@@ -196,10 +169,6 @@ export class SessionLogWriter {
|
|
|
196
169
|
},
|
|
197
170
|
};
|
|
198
171
|
|
|
199
|
-
if (session.otelWriter) {
|
|
200
|
-
session.otelWriter.emit({ notification: entry });
|
|
201
|
-
}
|
|
202
|
-
|
|
203
172
|
if (this.posthogAPI) {
|
|
204
173
|
const pending = this.pendingEntries.get(sessionId) ?? [];
|
|
205
174
|
pending.push(entry);
|
package/src/types.ts
CHANGED
|
@@ -141,6 +141,8 @@ export interface AgentConfig {
|
|
|
141
141
|
posthog?: PostHogAPIConfig;
|
|
142
142
|
/** OTEL transport config for shipping logs to PostHog Logs */
|
|
143
143
|
otelTransport?: OtelTransportConfig;
|
|
144
|
+
/** Skip session log persistence (e.g. for preview sessions with no real task) */
|
|
145
|
+
skipLogPersistence?: boolean;
|
|
144
146
|
debug?: boolean;
|
|
145
147
|
onLog?: OnLogCallback;
|
|
146
148
|
}
|