@raindrop-ai/pi-agent 0.0.2
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/README.md +48 -0
- package/dist/chunk-LZIGXU2D.js +922 -0
- package/dist/extension.cjs +1311 -0
- package/dist/extension.d.cts +60 -0
- package/dist/extension.d.ts +60 -0
- package/dist/extension.js +452 -0
- package/dist/index.cjs +1405 -0
- package/dist/index.d-npTVS9Mf.d.cts +216 -0
- package/dist/index.d-npTVS9Mf.d.ts +216 -0
- package/dist/index.d.cts +138 -0
- package/dist/index.d.ts +138 -0
- package/dist/index.js +498 -0
- package/package.json +91 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ExtensionAPI } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { E as EventShipper$1, T as TraceShipper$1, O as OtlpSpan } from './index.d-npTVS9Mf.cjs';
|
|
3
|
+
|
|
4
|
+
interface EventMetadata {
|
|
5
|
+
userId?: string;
|
|
6
|
+
eventName?: string;
|
|
7
|
+
properties?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
interface RaindropExtensionConfig {
|
|
10
|
+
writeKey: string;
|
|
11
|
+
endpoint: string;
|
|
12
|
+
eventName: string;
|
|
13
|
+
debug: boolean;
|
|
14
|
+
captureSystemPrompt: boolean;
|
|
15
|
+
eventMetadata?: EventMetadata;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare class EventShipper extends EventShipper$1 {
|
|
19
|
+
constructor(opts: ConstructorParameters<typeof EventShipper$1>[0]);
|
|
20
|
+
}
|
|
21
|
+
declare class TraceShipper extends TraceShipper$1 {
|
|
22
|
+
constructor(opts: ConstructorParameters<typeof TraceShipper$1>[0]);
|
|
23
|
+
enqueue(span: OtlpSpan): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Pi Coding Agent extension hooks for Raindrop observability.
|
|
28
|
+
*
|
|
29
|
+
* Adapted from KM's pi-extension implementation with enhancements:
|
|
30
|
+
* - 4-level trace hierarchy (session -> turn -> LLM -> tool)
|
|
31
|
+
* - LLM-to-tool parenting (tools nest under the LLM span that requested them)
|
|
32
|
+
* - Shared shipper/helpers with the subscriber entry point
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
declare function registerTracing(pi: ExtensionAPI, config: RaindropExtensionConfig, eventShipper: EventShipper, traceShipper: TraceShipper): void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Pi Coding Agent extension entry point.
|
|
39
|
+
*
|
|
40
|
+
* Usage:
|
|
41
|
+
* pi install npm:@raindrop-ai/pi-agent
|
|
42
|
+
*
|
|
43
|
+
* Or in ~/.pi/agent/extensions/:
|
|
44
|
+
* import extension from "@raindrop-ai/pi-agent/extension";
|
|
45
|
+
* export default extension;
|
|
46
|
+
*
|
|
47
|
+
* Configuration via env vars or JSON config files:
|
|
48
|
+
* RAINDROP_WRITE_KEY — required for telemetry
|
|
49
|
+
* RAINDROP_API_URL — custom endpoint (default: https://api.raindrop.ai/v1)
|
|
50
|
+
* RAINDROP_DEBUG — enable debug logging
|
|
51
|
+
* RAINDROP_CAPTURE_SYSTEM_PROMPT — capture system prompts in traces
|
|
52
|
+
*
|
|
53
|
+
* Config file locations:
|
|
54
|
+
* ~/.pi/agent/raindrop.json (global)
|
|
55
|
+
* .pi/raindrop.json (project)
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
declare function extension(pi: ExtensionAPI): void;
|
|
59
|
+
|
|
60
|
+
export { type EventMetadata, type RaindropExtensionConfig, extension as default, registerTracing };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ExtensionAPI } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { E as EventShipper$1, T as TraceShipper$1, O as OtlpSpan } from './index.d-npTVS9Mf.js';
|
|
3
|
+
|
|
4
|
+
interface EventMetadata {
|
|
5
|
+
userId?: string;
|
|
6
|
+
eventName?: string;
|
|
7
|
+
properties?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
interface RaindropExtensionConfig {
|
|
10
|
+
writeKey: string;
|
|
11
|
+
endpoint: string;
|
|
12
|
+
eventName: string;
|
|
13
|
+
debug: boolean;
|
|
14
|
+
captureSystemPrompt: boolean;
|
|
15
|
+
eventMetadata?: EventMetadata;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare class EventShipper extends EventShipper$1 {
|
|
19
|
+
constructor(opts: ConstructorParameters<typeof EventShipper$1>[0]);
|
|
20
|
+
}
|
|
21
|
+
declare class TraceShipper extends TraceShipper$1 {
|
|
22
|
+
constructor(opts: ConstructorParameters<typeof TraceShipper$1>[0]);
|
|
23
|
+
enqueue(span: OtlpSpan): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Pi Coding Agent extension hooks for Raindrop observability.
|
|
28
|
+
*
|
|
29
|
+
* Adapted from KM's pi-extension implementation with enhancements:
|
|
30
|
+
* - 4-level trace hierarchy (session -> turn -> LLM -> tool)
|
|
31
|
+
* - LLM-to-tool parenting (tools nest under the LLM span that requested them)
|
|
32
|
+
* - Shared shipper/helpers with the subscriber entry point
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
declare function registerTracing(pi: ExtensionAPI, config: RaindropExtensionConfig, eventShipper: EventShipper, traceShipper: TraceShipper): void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Pi Coding Agent extension entry point.
|
|
39
|
+
*
|
|
40
|
+
* Usage:
|
|
41
|
+
* pi install npm:@raindrop-ai/pi-agent
|
|
42
|
+
*
|
|
43
|
+
* Or in ~/.pi/agent/extensions/:
|
|
44
|
+
* import extension from "@raindrop-ai/pi-agent/extension";
|
|
45
|
+
* export default extension;
|
|
46
|
+
*
|
|
47
|
+
* Configuration via env vars or JSON config files:
|
|
48
|
+
* RAINDROP_WRITE_KEY — required for telemetry
|
|
49
|
+
* RAINDROP_API_URL — custom endpoint (default: https://api.raindrop.ai/v1)
|
|
50
|
+
* RAINDROP_DEBUG — enable debug logging
|
|
51
|
+
* RAINDROP_CAPTURE_SYSTEM_PROMPT — capture system prompts in traces
|
|
52
|
+
*
|
|
53
|
+
* Config file locations:
|
|
54
|
+
* ~/.pi/agent/raindrop.json (global)
|
|
55
|
+
* .pi/raindrop.json (project)
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
declare function extension(pi: ExtensionAPI): void;
|
|
59
|
+
|
|
60
|
+
export { type EventMetadata, type RaindropExtensionConfig, extension as default, registerTracing };
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EventShipper,
|
|
3
|
+
TraceShipper,
|
|
4
|
+
attrInt,
|
|
5
|
+
attrString,
|
|
6
|
+
extractAssistantText,
|
|
7
|
+
formatToolSpanName,
|
|
8
|
+
generateId,
|
|
9
|
+
getHostname,
|
|
10
|
+
getUsername,
|
|
11
|
+
libraryVersion,
|
|
12
|
+
nowUnixNanoString,
|
|
13
|
+
safeStringify,
|
|
14
|
+
truncate
|
|
15
|
+
} from "./chunk-LZIGXU2D.js";
|
|
16
|
+
|
|
17
|
+
// src/internal/config.ts
|
|
18
|
+
import { existsSync, readFileSync } from "fs";
|
|
19
|
+
import { homedir } from "os";
|
|
20
|
+
import { join } from "path";
|
|
21
|
+
function getPiAgentDirectory() {
|
|
22
|
+
var _a;
|
|
23
|
+
return (_a = process.env["PI_CODING_AGENT_DIR"]) != null ? _a : join(homedir(), ".pi", "agent");
|
|
24
|
+
}
|
|
25
|
+
function loadConfig(projectDirectory) {
|
|
26
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
27
|
+
let merged = {};
|
|
28
|
+
const configPaths = [
|
|
29
|
+
join(getPiAgentDirectory(), "raindrop.json"),
|
|
30
|
+
join(projectDirectory, ".pi", "raindrop.json")
|
|
31
|
+
];
|
|
32
|
+
for (const configPath of configPaths) {
|
|
33
|
+
try {
|
|
34
|
+
if (existsSync(configPath)) {
|
|
35
|
+
const content = readFileSync(configPath, "utf-8");
|
|
36
|
+
const parsed = JSON.parse(content);
|
|
37
|
+
merged = { ...merged, ...parsed };
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
let eventMetadata;
|
|
43
|
+
const envMeta = process.env["RAINDROP_EVENT_METADATA"];
|
|
44
|
+
if (envMeta) {
|
|
45
|
+
try {
|
|
46
|
+
eventMetadata = JSON.parse(envMeta);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
|
|
52
|
+
endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
|
|
53
|
+
eventName: (_e = merged.event_name) != null ? _e : "pi_session",
|
|
54
|
+
debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false,
|
|
55
|
+
captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_g = merged.capture_system_prompt) != null ? _g : false,
|
|
56
|
+
eventMetadata
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/internal/extension-tracing.ts
|
|
61
|
+
function safeParsArgs(argsStr) {
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(argsStr);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
var MAX_SYSTEM_PROMPT_LENGTH = 32768;
|
|
69
|
+
function createSessionState(sessionId) {
|
|
70
|
+
return {
|
|
71
|
+
sessionId,
|
|
72
|
+
currentInput: "",
|
|
73
|
+
turnNumber: 0,
|
|
74
|
+
totalInputTokens: 0,
|
|
75
|
+
totalOutputTokens: 0,
|
|
76
|
+
totalCacheReadTokens: 0,
|
|
77
|
+
toolSpanStarts: /* @__PURE__ */ new Map(),
|
|
78
|
+
toolCallToLlmParent: /* @__PURE__ */ new Map()
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function getUserId(state, metadata) {
|
|
82
|
+
var _a;
|
|
83
|
+
return (_a = metadata == null ? void 0 : metadata.userId) != null ? _a : state.sessionId;
|
|
84
|
+
}
|
|
85
|
+
function getEventName(config) {
|
|
86
|
+
var _a, _b;
|
|
87
|
+
return (_b = (_a = config.eventMetadata) == null ? void 0 : _a.eventName) != null ? _b : config.eventName;
|
|
88
|
+
}
|
|
89
|
+
function truncateSystemPrompt(systemPrompt) {
|
|
90
|
+
if (systemPrompt.length <= MAX_SYSTEM_PROMPT_LENGTH) return systemPrompt;
|
|
91
|
+
const suffix = "\n...[truncated]";
|
|
92
|
+
return systemPrompt.slice(0, MAX_SYSTEM_PROMPT_LENGTH - suffix.length) + suffix;
|
|
93
|
+
}
|
|
94
|
+
function getBaseProperties(config, ctx) {
|
|
95
|
+
var _a;
|
|
96
|
+
return {
|
|
97
|
+
workspace: ctx.cwd,
|
|
98
|
+
directory: ctx.cwd,
|
|
99
|
+
hostname: getHostname(),
|
|
100
|
+
os: process.platform,
|
|
101
|
+
username: getUsername(),
|
|
102
|
+
sdk_version: libraryVersion,
|
|
103
|
+
...(_a = config.eventMetadata) == null ? void 0 : _a.properties
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function getSystemPromptAttributes(systemPrompt) {
|
|
107
|
+
if (!systemPrompt) return [];
|
|
108
|
+
return [
|
|
109
|
+
attrString("gen_ai.prompt.0.role", "system"),
|
|
110
|
+
attrString("gen_ai.prompt.0.content", systemPrompt)
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
function getAssistantText(message) {
|
|
114
|
+
var _a;
|
|
115
|
+
return (_a = extractAssistantText(message)) != null ? _a : "";
|
|
116
|
+
}
|
|
117
|
+
function getAssistantError(message) {
|
|
118
|
+
var _a;
|
|
119
|
+
if (message.role !== "assistant") return void 0;
|
|
120
|
+
if (message.stopReason !== "error" && message.stopReason !== "aborted") return void 0;
|
|
121
|
+
return (_a = message.errorMessage) != null ? _a : `Assistant ${message.stopReason}`;
|
|
122
|
+
}
|
|
123
|
+
function getState(stateRef, ctx) {
|
|
124
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
125
|
+
if (!stateRef.current || stateRef.current.sessionId !== sessionId) {
|
|
126
|
+
stateRef.current = createSessionState(sessionId);
|
|
127
|
+
}
|
|
128
|
+
return stateRef.current;
|
|
129
|
+
}
|
|
130
|
+
function registerTracing(pi, config, eventShipper, traceShipper) {
|
|
131
|
+
const stateRef = {};
|
|
132
|
+
function logError(hook, err) {
|
|
133
|
+
console.log(
|
|
134
|
+
`[raindrop-ai/pi-agent] [error] Error in ${hook}: ${err instanceof Error ? err.message : String(err)}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
function startTurnSpan(state) {
|
|
138
|
+
if (!state.currentEventId || !state.currentRootSpan) return;
|
|
139
|
+
if (state.currentTurnSpan) {
|
|
140
|
+
traceShipper.endSpan(state.currentTurnSpan);
|
|
141
|
+
}
|
|
142
|
+
state.turnNumber += 1;
|
|
143
|
+
state.currentTurnSpan = traceShipper.startSpan({
|
|
144
|
+
name: `Turn ${state.turnNumber}`,
|
|
145
|
+
parent: state.currentRootSpan.ids,
|
|
146
|
+
eventId: state.currentEventId,
|
|
147
|
+
attributes: [
|
|
148
|
+
attrString("ai.operationId", "ai.turn"),
|
|
149
|
+
attrInt("ai.turn_number", state.turnNumber)
|
|
150
|
+
]
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function ensureTurnSpan(state) {
|
|
154
|
+
if (state.currentTurnSpan) return;
|
|
155
|
+
startTurnSpan(state);
|
|
156
|
+
}
|
|
157
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
158
|
+
try {
|
|
159
|
+
stateRef.current = createSessionState(ctx.sessionManager.getSessionId());
|
|
160
|
+
} catch (err) {
|
|
161
|
+
logError("session_start", err);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
165
|
+
var _a, _b, _c;
|
|
166
|
+
try {
|
|
167
|
+
const state = getState(stateRef, ctx);
|
|
168
|
+
if (state.currentTurnSpan) {
|
|
169
|
+
traceShipper.endSpan(state.currentTurnSpan);
|
|
170
|
+
state.currentTurnSpan = void 0;
|
|
171
|
+
}
|
|
172
|
+
if (state.currentRootSpan) {
|
|
173
|
+
traceShipper.endSpan(state.currentRootSpan, { error: "Previous run was not finalized" });
|
|
174
|
+
state.currentRootSpan = void 0;
|
|
175
|
+
}
|
|
176
|
+
if (state.currentEventId) {
|
|
177
|
+
await eventShipper.finish((_a = state.currentEventRequestId) != null ? _a : state.currentEventId, {
|
|
178
|
+
userId: getUserId(state, config.eventMetadata),
|
|
179
|
+
properties: { sdk_version: libraryVersion, incomplete: true }
|
|
180
|
+
});
|
|
181
|
+
state.currentEventRequestId = void 0;
|
|
182
|
+
state.currentEventId = void 0;
|
|
183
|
+
}
|
|
184
|
+
state.toolSpanStarts.clear();
|
|
185
|
+
state.toolCallToLlmParent.clear();
|
|
186
|
+
state.currentInput = event.prompt;
|
|
187
|
+
state.turnNumber = 0;
|
|
188
|
+
state.currentSystemPrompt = config.captureSystemPrompt ? truncateSystemPrompt(event.systemPrompt) : void 0;
|
|
189
|
+
state.currentEventRequestId = generateId();
|
|
190
|
+
state.totalInputTokens = 0;
|
|
191
|
+
state.totalOutputTokens = 0;
|
|
192
|
+
state.totalCacheReadTokens = 0;
|
|
193
|
+
const attachments = (_c = (_b = event.images) == null ? void 0 : _b.map((image, index) => {
|
|
194
|
+
var _a2, _b2;
|
|
195
|
+
return {
|
|
196
|
+
type: "image",
|
|
197
|
+
role: "input",
|
|
198
|
+
name: `image-${index + 1}.${(_b2 = (_a2 = image.mimeType.split("/")[1]) == null ? void 0 : _a2.split("+")[0]) != null ? _b2 : "png"}`,
|
|
199
|
+
value: `data:${image.mimeType};base64,${image.data}`
|
|
200
|
+
};
|
|
201
|
+
})) != null ? _c : [];
|
|
202
|
+
await eventShipper.patch(state.currentEventRequestId, {
|
|
203
|
+
isPending: true,
|
|
204
|
+
userId: getUserId(state, config.eventMetadata),
|
|
205
|
+
convoId: state.sessionId,
|
|
206
|
+
eventName: getEventName(config),
|
|
207
|
+
input: event.prompt,
|
|
208
|
+
...attachments.length > 0 ? { attachments } : {},
|
|
209
|
+
properties: getBaseProperties(config, ctx)
|
|
210
|
+
});
|
|
211
|
+
await eventShipper.flush();
|
|
212
|
+
state.currentEventId = state.currentEventRequestId;
|
|
213
|
+
state.currentRootSpan = traceShipper.startSpan({
|
|
214
|
+
name: "ai.event",
|
|
215
|
+
eventId: state.currentEventId,
|
|
216
|
+
attributes: [
|
|
217
|
+
attrString("ai.prompt", truncate(state.currentInput)),
|
|
218
|
+
attrString("workspace", ctx.cwd),
|
|
219
|
+
attrString("hostname", getHostname()),
|
|
220
|
+
attrString("os", process.platform),
|
|
221
|
+
attrString("username", getUsername())
|
|
222
|
+
]
|
|
223
|
+
});
|
|
224
|
+
} catch (err) {
|
|
225
|
+
logError("before_agent_start", err);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
pi.on("turn_start", async (_event, ctx) => {
|
|
229
|
+
try {
|
|
230
|
+
const state = getState(stateRef, ctx);
|
|
231
|
+
startTurnSpan(state);
|
|
232
|
+
} catch (err) {
|
|
233
|
+
logError("turn_start", err);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
pi.on("turn_end", async (_event, ctx) => {
|
|
237
|
+
try {
|
|
238
|
+
const state = getState(stateRef, ctx);
|
|
239
|
+
if (!state.currentTurnSpan) return;
|
|
240
|
+
traceShipper.endSpan(state.currentTurnSpan);
|
|
241
|
+
state.currentTurnSpan = void 0;
|
|
242
|
+
} catch (err) {
|
|
243
|
+
logError("turn_end", err);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
pi.on("message_end", async (event, ctx) => {
|
|
247
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
248
|
+
try {
|
|
249
|
+
if (((_a = event.message) == null ? void 0 : _a.role) !== "assistant") return;
|
|
250
|
+
const state = getState(stateRef, ctx);
|
|
251
|
+
if (!state.currentEventId || !state.currentRootSpan) return;
|
|
252
|
+
ensureTurnSpan(state);
|
|
253
|
+
const message = event.message;
|
|
254
|
+
const provider = (_b = message.provider) != null ? _b : "";
|
|
255
|
+
const modelId = (_c = message.model) != null ? _c : "";
|
|
256
|
+
const modelName = provider && modelId ? `${provider}/${modelId}` : modelId || "llm";
|
|
257
|
+
const errorForSpan = getAssistantError(message);
|
|
258
|
+
const outputText = getAssistantText(message);
|
|
259
|
+
const inputTokens = (_d = message.usage) == null ? void 0 : _d.input;
|
|
260
|
+
const outputTokens = (_e = message.usage) == null ? void 0 : _e.output;
|
|
261
|
+
const cacheReadTokens = (_f = message.usage) == null ? void 0 : _f.cacheRead;
|
|
262
|
+
if (typeof inputTokens === "number") state.totalInputTokens += inputTokens;
|
|
263
|
+
if (typeof outputTokens === "number") state.totalOutputTokens += outputTokens;
|
|
264
|
+
if (typeof cacheReadTokens === "number" && cacheReadTokens > 0) state.totalCacheReadTokens += cacheReadTokens;
|
|
265
|
+
const turnParent = (_g = state.currentTurnSpan) != null ? _g : state.currentRootSpan;
|
|
266
|
+
const llmAttrs = [
|
|
267
|
+
attrString("ai.operationId", "generateText"),
|
|
268
|
+
provider ? attrString("gen_ai.system", provider) : void 0,
|
|
269
|
+
modelId ? attrString("gen_ai.request.model", modelId) : void 0,
|
|
270
|
+
modelId ? attrString("gen_ai.response.model", modelId) : void 0,
|
|
271
|
+
typeof inputTokens === "number" ? attrInt("gen_ai.usage.input_tokens", inputTokens) : void 0,
|
|
272
|
+
typeof outputTokens === "number" ? attrInt("gen_ai.usage.output_tokens", outputTokens) : void 0,
|
|
273
|
+
typeof cacheReadTokens === "number" && cacheReadTokens > 0 ? attrInt("gen_ai.usage.cache_read_tokens", cacheReadTokens) : void 0,
|
|
274
|
+
outputText ? attrString("ai.response.text", truncate(outputText)) : void 0,
|
|
275
|
+
state.currentInput ? attrString("ai.prompt", truncate(state.currentInput)) : void 0,
|
|
276
|
+
message.stopReason ? attrString("ai.stop_reason", message.stopReason) : void 0,
|
|
277
|
+
...getSystemPromptAttributes(state.currentSystemPrompt)
|
|
278
|
+
].filter(Boolean);
|
|
279
|
+
const llmSpan = traceShipper.startSpan({
|
|
280
|
+
name: modelName,
|
|
281
|
+
parent: turnParent.ids,
|
|
282
|
+
eventId: state.currentEventId,
|
|
283
|
+
attributes: llmAttrs
|
|
284
|
+
});
|
|
285
|
+
traceShipper.endSpan(llmSpan, errorForSpan ? { error: errorForSpan } : void 0);
|
|
286
|
+
if (Array.isArray(message.content)) {
|
|
287
|
+
for (const part of message.content) {
|
|
288
|
+
if (part && typeof part === "object" && part.type === "toolCall" && typeof part.id === "string") {
|
|
289
|
+
state.toolCallToLlmParent.set(part.id, llmSpan.ids);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (message.stopReason === "toolUse") {
|
|
294
|
+
await traceShipper.flush();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (state.currentTurnSpan) {
|
|
298
|
+
traceShipper.endSpan(state.currentTurnSpan);
|
|
299
|
+
state.currentTurnSpan = void 0;
|
|
300
|
+
}
|
|
301
|
+
state.currentRootSpan.name = modelName;
|
|
302
|
+
traceShipper.endSpan(state.currentRootSpan, {
|
|
303
|
+
attributes: [
|
|
304
|
+
...modelId ? [attrString("gen_ai.response.model", modelId)] : [],
|
|
305
|
+
...outputText ? [attrString("ai.response.text", truncate(outputText))] : [],
|
|
306
|
+
attrInt("ai.total_turns", state.turnNumber)
|
|
307
|
+
],
|
|
308
|
+
...errorForSpan ? { error: errorForSpan } : {}
|
|
309
|
+
});
|
|
310
|
+
state.currentRootSpan = void 0;
|
|
311
|
+
await eventShipper.finish((_h = state.currentEventRequestId) != null ? _h : state.currentEventId, {
|
|
312
|
+
userId: getUserId(state, config.eventMetadata),
|
|
313
|
+
model: modelName,
|
|
314
|
+
...outputText.trim() ? { output: outputText } : {},
|
|
315
|
+
properties: {
|
|
316
|
+
sdk_version: libraryVersion,
|
|
317
|
+
stop_reason: message.stopReason,
|
|
318
|
+
...state.totalInputTokens > 0 ? { "ai.usage.prompt_tokens": state.totalInputTokens } : {},
|
|
319
|
+
...state.totalOutputTokens > 0 ? { "ai.usage.completion_tokens": state.totalOutputTokens } : {},
|
|
320
|
+
...state.totalCacheReadTokens > 0 ? { "ai.usage.cache_read_tokens": state.totalCacheReadTokens } : {}
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
state.currentEventRequestId = void 0;
|
|
324
|
+
state.currentEventId = void 0;
|
|
325
|
+
state.toolSpanStarts.clear();
|
|
326
|
+
state.toolCallToLlmParent.clear();
|
|
327
|
+
await traceShipper.flush();
|
|
328
|
+
} catch (err) {
|
|
329
|
+
logError("message_end", err);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
pi.on("tool_execution_start", async (event, ctx) => {
|
|
333
|
+
var _a, _b, _c;
|
|
334
|
+
try {
|
|
335
|
+
const state = getState(stateRef, ctx);
|
|
336
|
+
if (!state.currentEventId || !state.currentRootSpan) return;
|
|
337
|
+
const llmParent = state.toolCallToLlmParent.get(event.toolCallId);
|
|
338
|
+
const parent = (_b = llmParent != null ? llmParent : (_a = state.currentTurnSpan) == null ? void 0 : _a.ids) != null ? _b : state.currentRootSpan.ids;
|
|
339
|
+
state.toolSpanStarts.set(event.toolCallId, {
|
|
340
|
+
startTimeUnixNano: nowUnixNanoString(),
|
|
341
|
+
parent,
|
|
342
|
+
eventId: state.currentEventId,
|
|
343
|
+
name: event.toolName,
|
|
344
|
+
args: (_c = safeStringify(event.args)) != null ? _c : "{}"
|
|
345
|
+
});
|
|
346
|
+
} catch (err) {
|
|
347
|
+
logError("tool_execution_start", err);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
pi.on("tool_execution_end", async (event, ctx) => {
|
|
351
|
+
try {
|
|
352
|
+
const state = getState(stateRef, ctx);
|
|
353
|
+
const start = state.toolSpanStarts.get(event.toolCallId);
|
|
354
|
+
if (!start) return;
|
|
355
|
+
state.toolSpanStarts.delete(event.toolCallId);
|
|
356
|
+
state.toolCallToLlmParent.delete(event.toolCallId);
|
|
357
|
+
const resultStr = safeStringify(event.result);
|
|
358
|
+
traceShipper.createSpan({
|
|
359
|
+
name: formatToolSpanName(start.name, safeParsArgs(start.args)),
|
|
360
|
+
parent: start.parent,
|
|
361
|
+
eventId: start.eventId,
|
|
362
|
+
startTimeUnixNano: start.startTimeUnixNano,
|
|
363
|
+
endTimeUnixNano: nowUnixNanoString(),
|
|
364
|
+
attributes: [
|
|
365
|
+
attrString("ai.operationId", "ai.toolCall"),
|
|
366
|
+
attrString("ai.toolCall.name", start.name),
|
|
367
|
+
attrString("ai.toolCall.id", event.toolCallId),
|
|
368
|
+
attrString("ai.toolCall.args", truncate(start.args)),
|
|
369
|
+
...resultStr ? [attrString("ai.toolCall.result", truncate(resultStr))] : []
|
|
370
|
+
],
|
|
371
|
+
...event.isError ? { status: { code: 2, message: `Tool "${start.name}" failed` } } : {}
|
|
372
|
+
});
|
|
373
|
+
} catch (err) {
|
|
374
|
+
logError("tool_execution_end", err);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
378
|
+
var _a;
|
|
379
|
+
try {
|
|
380
|
+
const state = getState(stateRef, ctx);
|
|
381
|
+
if (state.currentTurnSpan) {
|
|
382
|
+
traceShipper.endSpan(state.currentTurnSpan);
|
|
383
|
+
state.currentTurnSpan = void 0;
|
|
384
|
+
}
|
|
385
|
+
if (state.currentRootSpan) {
|
|
386
|
+
traceShipper.endSpan(state.currentRootSpan, {
|
|
387
|
+
error: "Agent ended before a final assistant response was recorded"
|
|
388
|
+
});
|
|
389
|
+
state.currentRootSpan = void 0;
|
|
390
|
+
}
|
|
391
|
+
if (state.currentEventId) {
|
|
392
|
+
await eventShipper.finish((_a = state.currentEventRequestId) != null ? _a : state.currentEventId, {
|
|
393
|
+
userId: getUserId(state, config.eventMetadata),
|
|
394
|
+
properties: {
|
|
395
|
+
sdk_version: libraryVersion,
|
|
396
|
+
incomplete: true
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
state.currentEventRequestId = void 0;
|
|
400
|
+
state.currentEventId = void 0;
|
|
401
|
+
}
|
|
402
|
+
state.toolSpanStarts.clear();
|
|
403
|
+
state.toolCallToLlmParent.clear();
|
|
404
|
+
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
405
|
+
} catch (err) {
|
|
406
|
+
logError("agent_end", err);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
410
|
+
try {
|
|
411
|
+
stateRef.current = void 0;
|
|
412
|
+
await Promise.all([eventShipper.shutdown(), traceShipper.shutdown()]);
|
|
413
|
+
} catch (err) {
|
|
414
|
+
logError("session_shutdown", err);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/extension.ts
|
|
420
|
+
var PLUGIN_NAME = "@raindrop-ai/pi-agent";
|
|
421
|
+
function extension(pi) {
|
|
422
|
+
const config = loadConfig(process.cwd());
|
|
423
|
+
function appLog(level, message) {
|
|
424
|
+
console.log(`[raindrop-ai/pi-agent] [${level}] ${message}`);
|
|
425
|
+
}
|
|
426
|
+
appLog("info", `Loading ${PLUGIN_NAME} v${libraryVersion}`);
|
|
427
|
+
if (!config.writeKey) {
|
|
428
|
+
appLog(
|
|
429
|
+
"warn",
|
|
430
|
+
"RAINDROP_WRITE_KEY not set \u2014 Raindrop tracing disabled. Set it in your environment or in ~/.pi/agent/raindrop.json."
|
|
431
|
+
);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (config.debug) {
|
|
435
|
+
appLog("info", `Raindrop tracing enabled (endpoint: ${config.endpoint})`);
|
|
436
|
+
}
|
|
437
|
+
const eventShipper = new EventShipper({
|
|
438
|
+
writeKey: config.writeKey,
|
|
439
|
+
endpoint: config.endpoint,
|
|
440
|
+
debug: config.debug
|
|
441
|
+
});
|
|
442
|
+
const traceShipper = new TraceShipper({
|
|
443
|
+
writeKey: config.writeKey,
|
|
444
|
+
endpoint: config.endpoint,
|
|
445
|
+
debug: config.debug
|
|
446
|
+
});
|
|
447
|
+
registerTracing(pi, config, eventShipper, traceShipper);
|
|
448
|
+
}
|
|
449
|
+
export {
|
|
450
|
+
extension as default,
|
|
451
|
+
registerTracing
|
|
452
|
+
};
|