@raindrop-ai/opencode-plugin 0.0.11 → 0.0.13
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 +11 -0
- package/dist/index.cjs +795 -232
- package/dist/index.js +795 -232
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5,124 +5,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
5
5
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
import { existsSync, readFileSync } from "fs";
|
|
10
|
-
import { homedir } from "os";
|
|
11
|
-
import { join } from "path";
|
|
12
|
-
function loadConfig(projectDirectory) {
|
|
13
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
14
|
-
let merged = {};
|
|
15
|
-
const configPaths = [
|
|
16
|
-
join(homedir(), ".config", "opencode", "raindrop.json"),
|
|
17
|
-
join(projectDirectory, ".opencode", "raindrop.json")
|
|
18
|
-
];
|
|
19
|
-
for (const configPath of configPaths) {
|
|
20
|
-
try {
|
|
21
|
-
if (existsSync(configPath)) {
|
|
22
|
-
const content = readFileSync(configPath, "utf-8");
|
|
23
|
-
const parsed = JSON.parse(content);
|
|
24
|
-
merged = { ...merged, ...parsed };
|
|
25
|
-
}
|
|
26
|
-
} catch (e) {
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
let eventMetadata;
|
|
30
|
-
const envMeta = process.env["RAINDROP_EVENT_METADATA"];
|
|
31
|
-
if (envMeta) {
|
|
32
|
-
try {
|
|
33
|
-
eventMetadata = JSON.parse(envMeta);
|
|
34
|
-
} catch (e) {
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return {
|
|
38
|
-
writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
|
|
39
|
-
endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
|
|
40
|
-
eventName: (_e = merged.event_name) != null ? _e : "opencode_session",
|
|
41
|
-
debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false,
|
|
42
|
-
captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_g = merged.capture_system_prompt) != null ? _g : false,
|
|
43
|
-
eventMetadata
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// package.json
|
|
48
|
-
var package_default = {
|
|
49
|
-
name: "@raindrop-ai/opencode-plugin",
|
|
50
|
-
version: "0.0.11",
|
|
51
|
-
description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
|
|
52
|
-
type: "module",
|
|
53
|
-
main: "dist/index.js",
|
|
54
|
-
module: "dist/index.js",
|
|
55
|
-
types: "dist/index.d.ts",
|
|
56
|
-
license: "MIT",
|
|
57
|
-
repository: {
|
|
58
|
-
type: "git",
|
|
59
|
-
url: "git+https://github.com/raindrop-ai/raindrop-js.git",
|
|
60
|
-
directory: "packages/opencode-plugin"
|
|
61
|
-
},
|
|
62
|
-
homepage: "https://github.com/raindrop-ai/raindrop-js/tree/main/packages/opencode-plugin#readme",
|
|
63
|
-
bugs: {
|
|
64
|
-
url: "https://github.com/raindrop-ai/raindrop-js/issues"
|
|
65
|
-
},
|
|
66
|
-
exports: {
|
|
67
|
-
".": {
|
|
68
|
-
types: "./dist/index.d.ts",
|
|
69
|
-
import: "./dist/index.js",
|
|
70
|
-
require: "./dist/index.cjs"
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
sideEffects: false,
|
|
74
|
-
files: [
|
|
75
|
-
"dist/**"
|
|
76
|
-
],
|
|
77
|
-
scripts: {
|
|
78
|
-
build: "tsup",
|
|
79
|
-
dev: "tsup --watch",
|
|
80
|
-
clean: "rm -rf dist",
|
|
81
|
-
test: "cd tests && pnpm test"
|
|
82
|
-
},
|
|
83
|
-
peerDependencies: {
|
|
84
|
-
"@opencode-ai/plugin": ">=1.3.0",
|
|
85
|
-
"@opencode-ai/sdk": ">=1.3.0"
|
|
86
|
-
},
|
|
87
|
-
peerDependenciesMeta: {
|
|
88
|
-
"@opencode-ai/sdk": {
|
|
89
|
-
optional: true
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
devDependencies: {
|
|
93
|
-
"@raindrop-ai/core": "workspace:*",
|
|
94
|
-
"@opencode-ai/plugin": "^1.3.3",
|
|
95
|
-
"@opencode-ai/sdk": "^1.3.3",
|
|
96
|
-
"@types/node": "^20.11.17",
|
|
97
|
-
tsup: "^8.4.0",
|
|
98
|
-
typescript: "^5.3.3"
|
|
99
|
-
},
|
|
100
|
-
tsup: {
|
|
101
|
-
entry: [
|
|
102
|
-
"src/index.ts"
|
|
103
|
-
],
|
|
104
|
-
format: [
|
|
105
|
-
"cjs",
|
|
106
|
-
"esm"
|
|
107
|
-
],
|
|
108
|
-
dts: {
|
|
109
|
-
resolve: true
|
|
110
|
-
},
|
|
111
|
-
clean: true,
|
|
112
|
-
noExternal: [
|
|
113
|
-
"@raindrop-ai/core"
|
|
114
|
-
]
|
|
115
|
-
},
|
|
116
|
-
publishConfig: {
|
|
117
|
-
access: "public"
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// src/package-info.ts
|
|
122
|
-
var PLUGIN_NAME = package_default.name;
|
|
123
|
-
var PLUGIN_VERSION = package_default.version;
|
|
124
|
-
|
|
125
|
-
// ../core/dist/chunk-4UCYIEH4.js
|
|
8
|
+
// ../core/dist/chunk-U5HUTMR5.js
|
|
126
9
|
function getCrypto() {
|
|
127
10
|
const c = globalThis.crypto;
|
|
128
11
|
return c;
|
|
@@ -169,6 +52,8 @@ function base64Encode(bytes) {
|
|
|
169
52
|
function generateId() {
|
|
170
53
|
return base64Encode(randomBytes(12)).replace(/[+/=]/g, "").slice(0, 16);
|
|
171
54
|
}
|
|
55
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
56
|
+
var MAX_RETRY_DELAY_MS = 3e4;
|
|
172
57
|
function wait(ms) {
|
|
173
58
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
174
59
|
}
|
|
@@ -176,6 +61,46 @@ function formatEndpoint(endpoint) {
|
|
|
176
61
|
if (!endpoint) return void 0;
|
|
177
62
|
return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
|
|
178
63
|
}
|
|
64
|
+
function redactUrlForLog(url) {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = new URL(url);
|
|
67
|
+
parsed.username = "";
|
|
68
|
+
parsed.password = "";
|
|
69
|
+
parsed.search = "";
|
|
70
|
+
parsed.hash = "";
|
|
71
|
+
return parsed.toString();
|
|
72
|
+
} catch (e) {
|
|
73
|
+
return "<unparseable-url>";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
|
|
77
|
+
var rateLimitedLogLast = /* @__PURE__ */ new Map();
|
|
78
|
+
function rateLimitedLog(key, log) {
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
const last = rateLimitedLogLast.get(key);
|
|
81
|
+
if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
rateLimitedLogLast.set(key, now);
|
|
85
|
+
log();
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
async function raceWithTimeout(promise, timeoutMs) {
|
|
89
|
+
let timer;
|
|
90
|
+
const settledInTime = await Promise.race([
|
|
91
|
+
promise.then(
|
|
92
|
+
() => true,
|
|
93
|
+
() => true
|
|
94
|
+
),
|
|
95
|
+
new Promise((resolve) => {
|
|
96
|
+
var _a;
|
|
97
|
+
timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
|
|
98
|
+
(_a = timer.unref) == null ? void 0 : _a.call(timer);
|
|
99
|
+
})
|
|
100
|
+
]);
|
|
101
|
+
if (timer) clearTimeout(timer);
|
|
102
|
+
return settledInTime;
|
|
103
|
+
}
|
|
179
104
|
function parseRetryAfter(headers) {
|
|
180
105
|
var _a;
|
|
181
106
|
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
@@ -192,12 +117,12 @@ function parseRetryAfter(headers) {
|
|
|
192
117
|
function getRetryDelayMs(attemptNumber, previousError) {
|
|
193
118
|
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
194
119
|
const v = previousError.retryAfterMs;
|
|
195
|
-
if (typeof v === "number") return Math.max(0, v);
|
|
120
|
+
if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
|
|
196
121
|
}
|
|
197
122
|
if (attemptNumber <= 1) return 0;
|
|
198
123
|
const base = 500;
|
|
199
124
|
const factor = Math.pow(2, attemptNumber - 2);
|
|
200
|
-
return base * factor;
|
|
125
|
+
return Math.min(base * factor, MAX_RETRY_DELAY_MS);
|
|
201
126
|
}
|
|
202
127
|
async function withRetry(operation, opName, opts) {
|
|
203
128
|
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
@@ -232,7 +157,9 @@ async function withRetry(operation, opName, opts) {
|
|
|
232
157
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
233
158
|
}
|
|
234
159
|
async function postJson(url, body, headers, opts) {
|
|
235
|
-
|
|
160
|
+
var _a;
|
|
161
|
+
const opName = `POST ${redactUrlForLog(url)}`;
|
|
162
|
+
const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
236
163
|
await withRetry(
|
|
237
164
|
async () => {
|
|
238
165
|
const resp = await fetch(url, {
|
|
@@ -241,7 +168,8 @@ async function postJson(url, body, headers, opts) {
|
|
|
241
168
|
"Content-Type": "application/json",
|
|
242
169
|
...headers
|
|
243
170
|
},
|
|
244
|
-
body: JSON.stringify(body)
|
|
171
|
+
body: JSON.stringify(body),
|
|
172
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
245
173
|
});
|
|
246
174
|
if (!resp.ok) {
|
|
247
175
|
const text = await resp.text().catch(() => "");
|
|
@@ -258,6 +186,27 @@ async function postJson(url, body, headers, opts) {
|
|
|
258
186
|
opts
|
|
259
187
|
);
|
|
260
188
|
}
|
|
189
|
+
var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
|
|
190
|
+
var TRUNCATION_MARKER = "...[truncated by raindrop]";
|
|
191
|
+
var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
|
|
192
|
+
function resolveMaxTextFieldChars(value) {
|
|
193
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
194
|
+
return Math.floor(value);
|
|
195
|
+
}
|
|
196
|
+
return currentDefaultMaxTextFieldChars;
|
|
197
|
+
}
|
|
198
|
+
function truncateToLimit(text, limit) {
|
|
199
|
+
if (limit > TRUNCATION_MARKER.length) {
|
|
200
|
+
return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
201
|
+
}
|
|
202
|
+
return text.slice(0, Math.max(0, limit));
|
|
203
|
+
}
|
|
204
|
+
function capText(value, limit) {
|
|
205
|
+
if (typeof value !== "string") return value;
|
|
206
|
+
const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
|
|
207
|
+
if (value.length <= max) return value;
|
|
208
|
+
return truncateToLimit(value, max);
|
|
209
|
+
}
|
|
261
210
|
var SpanStatusCode = {
|
|
262
211
|
UNSET: 0,
|
|
263
212
|
OK: 1,
|
|
@@ -315,6 +264,93 @@ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", se
|
|
|
315
264
|
]
|
|
316
265
|
};
|
|
317
266
|
}
|
|
267
|
+
var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
|
|
268
|
+
var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
|
|
269
|
+
var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
|
|
270
|
+
function readEnvVar(name) {
|
|
271
|
+
var _a;
|
|
272
|
+
try {
|
|
273
|
+
const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
|
|
274
|
+
if (env && typeof env[name] === "string" && env[name].length > 0) {
|
|
275
|
+
return env[name];
|
|
276
|
+
}
|
|
277
|
+
} catch (e) {
|
|
278
|
+
}
|
|
279
|
+
return void 0;
|
|
280
|
+
}
|
|
281
|
+
function readWorkshopEnv() {
|
|
282
|
+
const raw = readEnvVar(WORKSHOP_ENV_VAR);
|
|
283
|
+
if (raw === void 0) return void 0;
|
|
284
|
+
const trimmed = raw.trim();
|
|
285
|
+
if (trimmed.length === 0) return void 0;
|
|
286
|
+
if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
|
|
287
|
+
if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
|
|
288
|
+
if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
|
|
289
|
+
return void 0;
|
|
290
|
+
}
|
|
291
|
+
function isLocalDevHost(hostname) {
|
|
292
|
+
if (!hostname) return false;
|
|
293
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
if (hostname.endsWith(".localhost")) return true;
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
function readRuntimeHostname() {
|
|
300
|
+
try {
|
|
301
|
+
const loc = globalThis == null ? void 0 : globalThis.location;
|
|
302
|
+
if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
|
|
303
|
+
return loc.hostname;
|
|
304
|
+
}
|
|
305
|
+
} catch (e) {
|
|
306
|
+
}
|
|
307
|
+
return void 0;
|
|
308
|
+
}
|
|
309
|
+
function shouldAutoEnableLocalWorkshop() {
|
|
310
|
+
if (isLocalDevHost(readRuntimeHostname())) return true;
|
|
311
|
+
if (readEnvVar("NODE_ENV") === "development") return true;
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
function resolveLocalDebuggerBaseUrl(baseUrl) {
|
|
315
|
+
var _a, _b, _c;
|
|
316
|
+
if (baseUrl === null) return null;
|
|
317
|
+
if (typeof baseUrl === "string" && baseUrl.length > 0) {
|
|
318
|
+
return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
|
|
319
|
+
}
|
|
320
|
+
const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
|
|
321
|
+
if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
|
|
322
|
+
const workshopEnv = readWorkshopEnv();
|
|
323
|
+
if (workshopEnv === "disable") return null;
|
|
324
|
+
if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
325
|
+
if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
|
|
326
|
+
if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
function mirrorTraceExportToLocalDebugger(body, options = {}) {
|
|
330
|
+
var _a;
|
|
331
|
+
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
332
|
+
if (!baseUrl) return;
|
|
333
|
+
void postJson(`${baseUrl}traces`, body, {}, {
|
|
334
|
+
maxAttempts: 1,
|
|
335
|
+
debug: (_a = options.debug) != null ? _a : false,
|
|
336
|
+
sdkName: options.sdkName
|
|
337
|
+
}).catch(() => {
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function mirrorPartialEventToLocalDebugger(event, options = {}) {
|
|
341
|
+
var _a;
|
|
342
|
+
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
343
|
+
if (!baseUrl) return;
|
|
344
|
+
const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
|
|
345
|
+
void postJson(`${baseUrl}events/track_partial`, event, headers, {
|
|
346
|
+
maxAttempts: 1,
|
|
347
|
+
debug: (_a = options.debug) != null ? _a : false,
|
|
348
|
+
sdkName: options.sdkName
|
|
349
|
+
}).catch(() => {
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
353
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
318
354
|
function mergePatches(target, source) {
|
|
319
355
|
var _a, _b, _c, _d;
|
|
320
356
|
const out = { ...target, ...source };
|
|
@@ -332,7 +368,8 @@ var EventShipper = class {
|
|
|
332
368
|
this.sticky = /* @__PURE__ */ new Map();
|
|
333
369
|
this.timers = /* @__PURE__ */ new Map();
|
|
334
370
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
335
|
-
|
|
371
|
+
this.hasShutdown = false;
|
|
372
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
336
373
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
337
374
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
338
375
|
this.enabled = opts.enabled !== false;
|
|
@@ -341,11 +378,16 @@ var EventShipper = class {
|
|
|
341
378
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
342
379
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
343
380
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
381
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
382
|
+
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
383
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
384
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
385
|
+
}
|
|
344
386
|
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
345
387
|
this.context = {
|
|
346
388
|
library: {
|
|
347
|
-
name: (
|
|
348
|
-
version: (
|
|
389
|
+
name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
|
|
390
|
+
version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
|
|
349
391
|
},
|
|
350
392
|
metadata: {
|
|
351
393
|
jsRuntime: isNode ? "node" : "web",
|
|
@@ -359,10 +401,52 @@ var EventShipper = class {
|
|
|
359
401
|
authHeaders() {
|
|
360
402
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
361
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
406
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
407
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
408
|
+
* of issuing a request that could outlive process exit.
|
|
409
|
+
*
|
|
410
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
411
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
412
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
413
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
414
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
415
|
+
* run as a single short attempt rather than regaining the full retry
|
|
416
|
+
* schedule.
|
|
417
|
+
*/
|
|
418
|
+
requestOpts() {
|
|
419
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
420
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
421
|
+
if (remainingMs <= 0) return null;
|
|
422
|
+
return {
|
|
423
|
+
maxAttempts: 1,
|
|
424
|
+
debug: this.debug,
|
|
425
|
+
sdkName: this.sdkName,
|
|
426
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
if (this.hasShutdown) {
|
|
430
|
+
return {
|
|
431
|
+
maxAttempts: 1,
|
|
432
|
+
debug: this.debug,
|
|
433
|
+
sdkName: this.sdkName,
|
|
434
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
438
|
+
}
|
|
362
439
|
async patch(eventId, patch) {
|
|
363
440
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
364
441
|
if (!this.enabled) return;
|
|
365
442
|
if (!eventId || !eventId.trim()) return;
|
|
443
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
444
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
445
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
446
|
+
}
|
|
447
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
448
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
449
|
+
}
|
|
366
450
|
if (this.debug) {
|
|
367
451
|
console.log(`${this.prefix} queue patch`, {
|
|
368
452
|
eventId,
|
|
@@ -409,9 +493,16 @@ var EventShipper = class {
|
|
|
409
493
|
})));
|
|
410
494
|
}
|
|
411
495
|
async shutdown() {
|
|
412
|
-
|
|
413
|
-
this.
|
|
414
|
-
|
|
496
|
+
this.hasShutdown = true;
|
|
497
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
498
|
+
try {
|
|
499
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
500
|
+
this.timers.clear();
|
|
501
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
502
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
503
|
+
} finally {
|
|
504
|
+
this.shutdownDeadlineAt = void 0;
|
|
505
|
+
}
|
|
415
506
|
}
|
|
416
507
|
async trackSignal(signal) {
|
|
417
508
|
var _a, _b;
|
|
@@ -431,16 +522,21 @@ var EventShipper = class {
|
|
|
431
522
|
}
|
|
432
523
|
}
|
|
433
524
|
];
|
|
525
|
+
if (!this.writeKey) return;
|
|
434
526
|
const url = `${this.baseUrl}signals/track`;
|
|
527
|
+
const opts = this.requestOpts();
|
|
528
|
+
if (!opts) {
|
|
529
|
+
this.warnShutdownDrop("signal");
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
435
532
|
try {
|
|
436
|
-
await postJson(url, body, this.authHeaders(),
|
|
437
|
-
maxAttempts: 3,
|
|
438
|
-
debug: this.debug,
|
|
439
|
-
sdkName: this.sdkName
|
|
440
|
-
});
|
|
533
|
+
await postJson(url, body, this.authHeaders(), opts);
|
|
441
534
|
} catch (err) {
|
|
442
535
|
const msg = err instanceof Error ? err.message : String(err);
|
|
443
|
-
|
|
536
|
+
rateLimitedLog(
|
|
537
|
+
`${this.prefix}.send_signal_failed`,
|
|
538
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
539
|
+
);
|
|
444
540
|
}
|
|
445
541
|
}
|
|
446
542
|
async identify(users) {
|
|
@@ -461,19 +557,30 @@ var EventShipper = class {
|
|
|
461
557
|
traits: (_a = user.traits) != null ? _a : {}
|
|
462
558
|
};
|
|
463
559
|
});
|
|
560
|
+
if (!this.writeKey) return;
|
|
464
561
|
if (body.length === 0) return;
|
|
465
562
|
const url = `${this.baseUrl}users/identify`;
|
|
563
|
+
const opts = this.requestOpts();
|
|
564
|
+
if (!opts) {
|
|
565
|
+
this.warnShutdownDrop("identify");
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
466
568
|
try {
|
|
467
|
-
await postJson(url, body, this.authHeaders(),
|
|
468
|
-
maxAttempts: 3,
|
|
469
|
-
debug: this.debug,
|
|
470
|
-
sdkName: this.sdkName
|
|
471
|
-
});
|
|
569
|
+
await postJson(url, body, this.authHeaders(), opts);
|
|
472
570
|
} catch (err) {
|
|
473
571
|
const msg = err instanceof Error ? err.message : String(err);
|
|
474
|
-
|
|
572
|
+
rateLimitedLog(
|
|
573
|
+
`${this.prefix}.send_identify_failed`,
|
|
574
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
575
|
+
);
|
|
475
576
|
}
|
|
476
577
|
}
|
|
578
|
+
warnShutdownDrop(what) {
|
|
579
|
+
rateLimitedLog(
|
|
580
|
+
`${this.prefix}.shutdown_deadline`,
|
|
581
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
582
|
+
);
|
|
583
|
+
}
|
|
477
584
|
async flushOne(eventId) {
|
|
478
585
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
479
586
|
if (!this.enabled) return;
|
|
@@ -537,11 +644,25 @@ var EventShipper = class {
|
|
|
537
644
|
endpoint: url
|
|
538
645
|
});
|
|
539
646
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
647
|
+
if (this.localDebuggerUrl) {
|
|
648
|
+
mirrorPartialEventToLocalDebugger(payload, {
|
|
649
|
+
baseUrl: this.localDebuggerUrl,
|
|
650
|
+
writeKey: this.writeKey,
|
|
651
|
+
debug: this.debug,
|
|
652
|
+
sdkName: this.sdkName
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
if (!this.writeKey) {
|
|
656
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const opts = this.requestOpts();
|
|
660
|
+
if (!opts) {
|
|
661
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
662
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const p = postJson(url, payload, this.authHeaders(), opts);
|
|
545
666
|
this.inFlight.add(p);
|
|
546
667
|
try {
|
|
547
668
|
try {
|
|
@@ -551,7 +672,10 @@ var EventShipper = class {
|
|
|
551
672
|
}
|
|
552
673
|
} catch (err) {
|
|
553
674
|
const msg = err instanceof Error ? err.message : String(err);
|
|
554
|
-
|
|
675
|
+
rateLimitedLog(
|
|
676
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
677
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
678
|
+
);
|
|
555
679
|
}
|
|
556
680
|
} finally {
|
|
557
681
|
this.inFlight.delete(p);
|
|
@@ -561,28 +685,114 @@ var EventShipper = class {
|
|
|
561
685
|
}
|
|
562
686
|
}
|
|
563
687
|
};
|
|
564
|
-
var
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
688
|
+
var DEFAULT_SECRET_KEY_NAMES = [
|
|
689
|
+
"apikey",
|
|
690
|
+
"apisecret",
|
|
691
|
+
"apitoken",
|
|
692
|
+
"secretaccesskey",
|
|
693
|
+
"sessiontoken",
|
|
694
|
+
"privatekey",
|
|
695
|
+
"privatekeyid",
|
|
696
|
+
"clientsecret",
|
|
697
|
+
"accesstoken",
|
|
698
|
+
"refreshtoken",
|
|
699
|
+
"oauthtoken",
|
|
700
|
+
"bearertoken",
|
|
701
|
+
"authorization",
|
|
702
|
+
"password",
|
|
703
|
+
"passphrase"
|
|
704
|
+
];
|
|
705
|
+
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
706
|
+
function normalizeKeyName(name) {
|
|
707
|
+
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
569
708
|
}
|
|
570
|
-
function
|
|
571
|
-
var _a;
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
709
|
+
function redactSecretsInObject(value, options) {
|
|
710
|
+
var _a, _b;
|
|
711
|
+
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
712
|
+
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
713
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
714
|
+
const walk = (node) => {
|
|
715
|
+
if (node === null || typeof node !== "object") return node;
|
|
716
|
+
if (seen.has(node)) return "[CIRCULAR]";
|
|
717
|
+
seen.add(node);
|
|
718
|
+
if (Array.isArray(node)) {
|
|
719
|
+
return node.map((item) => walk(item));
|
|
720
|
+
}
|
|
721
|
+
const out = {};
|
|
722
|
+
for (const [k, v] of Object.entries(node)) {
|
|
723
|
+
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
724
|
+
out[k] = placeholder;
|
|
725
|
+
} else {
|
|
726
|
+
out[k] = walk(v);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return out;
|
|
730
|
+
};
|
|
731
|
+
return walk(value);
|
|
732
|
+
}
|
|
733
|
+
function buildSecretSet(names) {
|
|
734
|
+
const set = /* @__PURE__ */ new Set();
|
|
735
|
+
for (const name of names) set.add(normalizeKeyName(name));
|
|
736
|
+
return set;
|
|
737
|
+
}
|
|
738
|
+
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
739
|
+
"ai.request.providerOptions",
|
|
740
|
+
"ai.response.providerMetadata"
|
|
741
|
+
];
|
|
742
|
+
function defaultTransformSpan(span) {
|
|
743
|
+
const attrs = span.attributes;
|
|
744
|
+
if (!attrs || attrs.length === 0) return span;
|
|
745
|
+
let nextAttrs;
|
|
746
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
747
|
+
const attr = attrs[i];
|
|
748
|
+
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
749
|
+
if (redacted === void 0) continue;
|
|
750
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
751
|
+
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
752
|
+
}
|
|
753
|
+
if (!nextAttrs) return span;
|
|
754
|
+
return { ...span, attributes: nextAttrs };
|
|
755
|
+
}
|
|
756
|
+
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
757
|
+
function redactJsonAttributeValue(key, value) {
|
|
758
|
+
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
759
|
+
const json = value.stringValue;
|
|
760
|
+
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
761
|
+
let parsed;
|
|
762
|
+
try {
|
|
763
|
+
parsed = JSON.parse(json);
|
|
764
|
+
} catch (e) {
|
|
765
|
+
return void 0;
|
|
766
|
+
}
|
|
767
|
+
const scrubbed = redactSecretsInObject(parsed);
|
|
768
|
+
let scrubbedJson;
|
|
769
|
+
try {
|
|
770
|
+
scrubbedJson = JSON.stringify(scrubbed);
|
|
771
|
+
} catch (e) {
|
|
772
|
+
return void 0;
|
|
773
|
+
}
|
|
774
|
+
if (scrubbedJson === json) return void 0;
|
|
775
|
+
return { stringValue: scrubbedJson };
|
|
776
|
+
}
|
|
777
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
778
|
+
var _a, _b;
|
|
779
|
+
try {
|
|
780
|
+
const raw = (_b = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env) == null ? void 0 : _b.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
|
|
781
|
+
if (!raw) return limit;
|
|
782
|
+
const parsed = Number.parseInt(raw, 10);
|
|
783
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
784
|
+
return Math.min(limit, parsed);
|
|
785
|
+
}
|
|
786
|
+
} catch (e) {
|
|
787
|
+
}
|
|
788
|
+
return limit;
|
|
580
789
|
}
|
|
581
790
|
var TraceShipper = class {
|
|
582
791
|
constructor(opts) {
|
|
583
792
|
this.queue = [];
|
|
584
793
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
585
|
-
|
|
794
|
+
this.hasShutdown = false;
|
|
795
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
586
796
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
587
797
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
588
798
|
this.enabled = opts.enabled !== false;
|
|
@@ -595,13 +805,79 @@ var TraceShipper = class {
|
|
|
595
805
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
596
806
|
this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
|
|
597
807
|
this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
|
|
598
|
-
|
|
599
|
-
if (
|
|
600
|
-
this.
|
|
601
|
-
|
|
602
|
-
|
|
808
|
+
this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
|
|
809
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
810
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
811
|
+
}
|
|
812
|
+
this.transformSpanHook = opts.transformSpan;
|
|
813
|
+
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
814
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
818
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
819
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
820
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
821
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
822
|
+
* in the surviving prefix).
|
|
823
|
+
*
|
|
824
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
825
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
826
|
+
*/
|
|
827
|
+
capSpanAttributes(span) {
|
|
828
|
+
var _a;
|
|
829
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
830
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
831
|
+
);
|
|
832
|
+
const attrs = span.attributes;
|
|
833
|
+
if (!attrs || attrs.length === 0) return span;
|
|
834
|
+
let nextAttrs;
|
|
835
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
836
|
+
const attr = attrs[i];
|
|
837
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
838
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
839
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
840
|
+
nextAttrs[i] = {
|
|
841
|
+
key: attr.key,
|
|
842
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
if (!nextAttrs) return span;
|
|
846
|
+
return { ...span, attributes: nextAttrs };
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
850
|
+
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
851
|
+
* ship, or `null` to drop the span entirely.
|
|
852
|
+
*
|
|
853
|
+
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
854
|
+
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
855
|
+
* about). The default redactor then runs on whatever the user produced,
|
|
856
|
+
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
857
|
+
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
858
|
+
*
|
|
859
|
+
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
860
|
+
* hook can never accidentally ship raw, un-redacted spans.
|
|
861
|
+
*/
|
|
862
|
+
redactSpan(span) {
|
|
863
|
+
let current = span;
|
|
864
|
+
if (this.transformSpanHook) {
|
|
865
|
+
try {
|
|
866
|
+
const result = this.transformSpanHook(current);
|
|
867
|
+
if (result === null) return null;
|
|
868
|
+
if (result !== void 0) current = result;
|
|
869
|
+
} catch (err) {
|
|
870
|
+
if (this.debug) {
|
|
871
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
872
|
+
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
873
|
+
}
|
|
874
|
+
return null;
|
|
603
875
|
}
|
|
604
876
|
}
|
|
877
|
+
if (!this.disableDefaultRedaction) {
|
|
878
|
+
current = defaultTransformSpan(current);
|
|
879
|
+
}
|
|
880
|
+
return this.capSpanAttributes(current);
|
|
605
881
|
}
|
|
606
882
|
isDebugEnabled() {
|
|
607
883
|
return this.debug;
|
|
@@ -619,8 +895,8 @@ var TraceShipper = class {
|
|
|
619
895
|
];
|
|
620
896
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
621
897
|
const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
622
|
-
|
|
623
|
-
|
|
898
|
+
this.mirrorToLocalDebugger(
|
|
899
|
+
buildOtlpSpan({
|
|
624
900
|
ids: span.ids,
|
|
625
901
|
name: span.name,
|
|
626
902
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
@@ -628,16 +904,21 @@ var TraceShipper = class {
|
|
|
628
904
|
// placeholder — will be updated on endSpan
|
|
629
905
|
attributes: span.attributes,
|
|
630
906
|
status: { code: SpanStatusCode.UNSET }
|
|
631
|
-
})
|
|
632
|
-
|
|
633
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
634
|
-
baseUrl: this.localDebuggerUrl,
|
|
635
|
-
debug: false,
|
|
636
|
-
sdkName: this.sdkName
|
|
637
|
-
});
|
|
638
|
-
}
|
|
907
|
+
})
|
|
908
|
+
);
|
|
639
909
|
return span;
|
|
640
910
|
}
|
|
911
|
+
mirrorToLocalDebugger(span) {
|
|
912
|
+
if (!this.localDebuggerUrl) return;
|
|
913
|
+
const redacted = this.redactSpan(span);
|
|
914
|
+
if (redacted === null) return;
|
|
915
|
+
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
916
|
+
mirrorTraceExportToLocalDebugger(body, {
|
|
917
|
+
baseUrl: this.localDebuggerUrl,
|
|
918
|
+
debug: false,
|
|
919
|
+
sdkName: this.sdkName
|
|
920
|
+
});
|
|
921
|
+
}
|
|
641
922
|
endSpan(span, extra) {
|
|
642
923
|
var _a, _b;
|
|
643
924
|
if (span.endTimeUnixNano) return;
|
|
@@ -659,14 +940,7 @@ var TraceShipper = class {
|
|
|
659
940
|
status
|
|
660
941
|
});
|
|
661
942
|
this.enqueue(otlp);
|
|
662
|
-
|
|
663
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
664
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
665
|
-
baseUrl: this.localDebuggerUrl,
|
|
666
|
-
debug: false,
|
|
667
|
-
sdkName: this.sdkName
|
|
668
|
-
});
|
|
669
|
-
}
|
|
943
|
+
this.mirrorToLocalDebugger(otlp);
|
|
670
944
|
}
|
|
671
945
|
createSpan(args) {
|
|
672
946
|
var _a;
|
|
@@ -684,14 +958,7 @@ var TraceShipper = class {
|
|
|
684
958
|
status: args.status
|
|
685
959
|
});
|
|
686
960
|
this.enqueue(otlp);
|
|
687
|
-
|
|
688
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
689
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
690
|
-
baseUrl: this.localDebuggerUrl,
|
|
691
|
-
debug: false,
|
|
692
|
-
sdkName: this.sdkName
|
|
693
|
-
});
|
|
694
|
-
}
|
|
961
|
+
this.mirrorToLocalDebugger(otlp);
|
|
695
962
|
}
|
|
696
963
|
enqueue(span) {
|
|
697
964
|
if (!this.enabled) return;
|
|
@@ -703,10 +970,12 @@ var TraceShipper = class {
|
|
|
703
970
|
)}`
|
|
704
971
|
);
|
|
705
972
|
}
|
|
973
|
+
const redacted = this.redactSpan(span);
|
|
974
|
+
if (redacted === null) return;
|
|
706
975
|
if (this.queue.length >= this.maxQueueSize) {
|
|
707
976
|
this.queue.shift();
|
|
708
977
|
}
|
|
709
|
-
this.queue.push(
|
|
978
|
+
this.queue.push(redacted);
|
|
710
979
|
if (this.queue.length >= this.maxBatchSize) {
|
|
711
980
|
void this.flush().catch(() => {
|
|
712
981
|
});
|
|
@@ -728,6 +997,17 @@ var TraceShipper = class {
|
|
|
728
997
|
}
|
|
729
998
|
while (this.queue.length > 0) {
|
|
730
999
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
1000
|
+
if (!this.writeKey) continue;
|
|
1001
|
+
const opts = this.requestOpts();
|
|
1002
|
+
if (!opts) {
|
|
1003
|
+
rateLimitedLog(
|
|
1004
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1005
|
+
() => console.warn(
|
|
1006
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1007
|
+
)
|
|
1008
|
+
);
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
731
1011
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
732
1012
|
const url = `${this.baseUrl}traces`;
|
|
733
1013
|
if (this.debug) {
|
|
@@ -736,11 +1016,7 @@ var TraceShipper = class {
|
|
|
736
1016
|
endpoint: url
|
|
737
1017
|
});
|
|
738
1018
|
}
|
|
739
|
-
const p = postJson(url, body, this.authHeaders(),
|
|
740
|
-
maxAttempts: 3,
|
|
741
|
-
debug: this.debug,
|
|
742
|
-
sdkName: this.sdkName
|
|
743
|
-
});
|
|
1019
|
+
const p = postJson(url, body, this.authHeaders(), opts);
|
|
744
1020
|
this.inFlight.add(p);
|
|
745
1021
|
try {
|
|
746
1022
|
try {
|
|
@@ -748,21 +1024,61 @@ var TraceShipper = class {
|
|
|
748
1024
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
749
1025
|
} catch (err) {
|
|
750
1026
|
const msg = err instanceof Error ? err.message : String(err);
|
|
751
|
-
|
|
1027
|
+
rateLimitedLog(
|
|
1028
|
+
`${this.prefix}.send_spans_failed`,
|
|
1029
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1030
|
+
);
|
|
752
1031
|
}
|
|
753
1032
|
} finally {
|
|
754
1033
|
this.inFlight.delete(p);
|
|
755
1034
|
}
|
|
756
1035
|
}
|
|
757
1036
|
}
|
|
1037
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1038
|
+
requestOpts() {
|
|
1039
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1040
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1041
|
+
if (remainingMs <= 0) return null;
|
|
1042
|
+
return {
|
|
1043
|
+
maxAttempts: 1,
|
|
1044
|
+
debug: this.debug,
|
|
1045
|
+
sdkName: this.sdkName,
|
|
1046
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
if (this.hasShutdown) {
|
|
1050
|
+
return {
|
|
1051
|
+
maxAttempts: 1,
|
|
1052
|
+
debug: this.debug,
|
|
1053
|
+
sdkName: this.sdkName,
|
|
1054
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1058
|
+
}
|
|
758
1059
|
async shutdown() {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1060
|
+
this.hasShutdown = true;
|
|
1061
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1062
|
+
try {
|
|
1063
|
+
if (this.timer) {
|
|
1064
|
+
clearTimeout(this.timer);
|
|
1065
|
+
this.timer = void 0;
|
|
1066
|
+
}
|
|
1067
|
+
const drain = async () => {
|
|
1068
|
+
await this.flush();
|
|
1069
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1070
|
+
})));
|
|
1071
|
+
};
|
|
1072
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1073
|
+
if (!settled) {
|
|
1074
|
+
rateLimitedLog(
|
|
1075
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1076
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
} finally {
|
|
1080
|
+
this.shutdownDeadlineAt = void 0;
|
|
762
1081
|
}
|
|
763
|
-
await this.flush();
|
|
764
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
765
|
-
})));
|
|
766
1082
|
}
|
|
767
1083
|
};
|
|
768
1084
|
|
|
@@ -770,6 +1086,134 @@ var TraceShipper = class {
|
|
|
770
1086
|
import { AsyncLocalStorage } from "async_hooks";
|
|
771
1087
|
globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
|
|
772
1088
|
|
|
1089
|
+
// src/config.ts
|
|
1090
|
+
import { existsSync, readFileSync } from "fs";
|
|
1091
|
+
import { homedir } from "os";
|
|
1092
|
+
import { join } from "path";
|
|
1093
|
+
function loadConfig(projectDirectory) {
|
|
1094
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1095
|
+
let merged = {};
|
|
1096
|
+
const configPaths = [
|
|
1097
|
+
join(homedir(), ".config", "opencode", "raindrop.json"),
|
|
1098
|
+
join(projectDirectory, ".opencode", "raindrop.json")
|
|
1099
|
+
];
|
|
1100
|
+
for (const configPath of configPaths) {
|
|
1101
|
+
try {
|
|
1102
|
+
if (existsSync(configPath)) {
|
|
1103
|
+
const content = readFileSync(configPath, "utf-8");
|
|
1104
|
+
const parsed = JSON.parse(content);
|
|
1105
|
+
merged = { ...merged, ...parsed };
|
|
1106
|
+
}
|
|
1107
|
+
} catch (e) {
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
let eventMetadata;
|
|
1111
|
+
const envMeta = process.env["RAINDROP_EVENT_METADATA"];
|
|
1112
|
+
if (envMeta) {
|
|
1113
|
+
try {
|
|
1114
|
+
eventMetadata = JSON.parse(envMeta);
|
|
1115
|
+
} catch (e) {
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return {
|
|
1119
|
+
writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
|
|
1120
|
+
endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
|
|
1121
|
+
eventName: (_e = merged.event_name) != null ? _e : "opencode_session",
|
|
1122
|
+
debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false,
|
|
1123
|
+
captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_g = merged.capture_system_prompt) != null ? _g : false,
|
|
1124
|
+
eventMetadata,
|
|
1125
|
+
localWorkshopUrl: resolveLocalWorkshopUrl(merged.local_workshop_url)
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function resolveLocalWorkshopUrl(fileValue) {
|
|
1129
|
+
const envValue = process.env["RAINDROP_LOCAL_WORKSHOP_URL"];
|
|
1130
|
+
if (envValue !== void 0) {
|
|
1131
|
+
if (envValue === "" || envValue.toLowerCase() === "null" || envValue.toLowerCase() === "false") {
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
return envValue;
|
|
1135
|
+
}
|
|
1136
|
+
return fileValue;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// package.json
|
|
1140
|
+
var package_default = {
|
|
1141
|
+
name: "@raindrop-ai/opencode-plugin",
|
|
1142
|
+
version: "0.0.13",
|
|
1143
|
+
description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
|
|
1144
|
+
type: "module",
|
|
1145
|
+
main: "dist/index.js",
|
|
1146
|
+
module: "dist/index.js",
|
|
1147
|
+
types: "dist/index.d.ts",
|
|
1148
|
+
license: "MIT",
|
|
1149
|
+
repository: {
|
|
1150
|
+
type: "git",
|
|
1151
|
+
url: "git+https://github.com/raindrop-ai/raindrop-js.git",
|
|
1152
|
+
directory: "packages/opencode-plugin"
|
|
1153
|
+
},
|
|
1154
|
+
homepage: "https://github.com/raindrop-ai/raindrop-js/tree/main/packages/opencode-plugin#readme",
|
|
1155
|
+
bugs: {
|
|
1156
|
+
url: "https://github.com/raindrop-ai/raindrop-js/issues"
|
|
1157
|
+
},
|
|
1158
|
+
exports: {
|
|
1159
|
+
".": {
|
|
1160
|
+
types: "./dist/index.d.ts",
|
|
1161
|
+
import: "./dist/index.js",
|
|
1162
|
+
require: "./dist/index.cjs"
|
|
1163
|
+
}
|
|
1164
|
+
},
|
|
1165
|
+
sideEffects: false,
|
|
1166
|
+
files: [
|
|
1167
|
+
"dist/**"
|
|
1168
|
+
],
|
|
1169
|
+
scripts: {
|
|
1170
|
+
build: "tsup",
|
|
1171
|
+
dev: "tsup --watch",
|
|
1172
|
+
clean: "rm -rf dist",
|
|
1173
|
+
test: "cd tests && pnpm test"
|
|
1174
|
+
},
|
|
1175
|
+
peerDependencies: {
|
|
1176
|
+
"@opencode-ai/plugin": ">=1.3.0",
|
|
1177
|
+
"@opencode-ai/sdk": ">=1.3.0"
|
|
1178
|
+
},
|
|
1179
|
+
peerDependenciesMeta: {
|
|
1180
|
+
"@opencode-ai/sdk": {
|
|
1181
|
+
optional: true
|
|
1182
|
+
}
|
|
1183
|
+
},
|
|
1184
|
+
devDependencies: {
|
|
1185
|
+
"@raindrop-ai/core": "workspace:*",
|
|
1186
|
+
"@opencode-ai/plugin": "^1.3.3",
|
|
1187
|
+
"@opencode-ai/sdk": "^1.3.3",
|
|
1188
|
+
"@types/node": "^20.11.17",
|
|
1189
|
+
tsup: "^8.4.0",
|
|
1190
|
+
typescript: "^5.3.3"
|
|
1191
|
+
},
|
|
1192
|
+
tsup: {
|
|
1193
|
+
entry: [
|
|
1194
|
+
"src/index.ts"
|
|
1195
|
+
],
|
|
1196
|
+
format: [
|
|
1197
|
+
"cjs",
|
|
1198
|
+
"esm"
|
|
1199
|
+
],
|
|
1200
|
+
dts: {
|
|
1201
|
+
resolve: true
|
|
1202
|
+
},
|
|
1203
|
+
clean: true,
|
|
1204
|
+
noExternal: [
|
|
1205
|
+
"@raindrop-ai/core"
|
|
1206
|
+
]
|
|
1207
|
+
},
|
|
1208
|
+
publishConfig: {
|
|
1209
|
+
access: "public"
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
// src/package-info.ts
|
|
1214
|
+
var PLUGIN_NAME = package_default.name;
|
|
1215
|
+
var PLUGIN_VERSION = package_default.version;
|
|
1216
|
+
|
|
773
1217
|
// src/shipper.ts
|
|
774
1218
|
var EventShipper2 = class extends EventShipper {
|
|
775
1219
|
constructor(opts) {
|
|
@@ -805,6 +1249,86 @@ var TraceShipper2 = class extends TraceShipper {
|
|
|
805
1249
|
}
|
|
806
1250
|
};
|
|
807
1251
|
|
|
1252
|
+
// src/bounded.ts
|
|
1253
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1254
|
+
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
1255
|
+
var MAX_BOUNDED_DEPTH = 12;
|
|
1256
|
+
function truncateToLimit2(text, limit) {
|
|
1257
|
+
if (text.length <= limit) return text;
|
|
1258
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1259
|
+
return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1260
|
+
}
|
|
1261
|
+
return text.slice(0, limit);
|
|
1262
|
+
}
|
|
1263
|
+
function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1264
|
+
if (value.length <= limit) return value;
|
|
1265
|
+
return truncateToLimit2(value, limit);
|
|
1266
|
+
}
|
|
1267
|
+
function boundedClone2(value, budget, depth) {
|
|
1268
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1269
|
+
if (typeof value === "string") {
|
|
1270
|
+
if (value.length > budget.remaining) {
|
|
1271
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1272
|
+
budget.remaining = 0;
|
|
1273
|
+
return taken;
|
|
1274
|
+
}
|
|
1275
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1276
|
+
return value;
|
|
1277
|
+
}
|
|
1278
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1279
|
+
budget.remaining -= 8;
|
|
1280
|
+
return value;
|
|
1281
|
+
}
|
|
1282
|
+
if (typeof value !== "object") {
|
|
1283
|
+
budget.remaining -= 8;
|
|
1284
|
+
return value;
|
|
1285
|
+
}
|
|
1286
|
+
if (depth >= MAX_BOUNDED_DEPTH) {
|
|
1287
|
+
budget.remaining -= 16;
|
|
1288
|
+
return `<max depth: ${TRUNCATION_MARKER2}>`;
|
|
1289
|
+
}
|
|
1290
|
+
const withToJson = value;
|
|
1291
|
+
if (typeof withToJson.toJSON === "function") {
|
|
1292
|
+
try {
|
|
1293
|
+
return boundedClone2(withToJson.toJSON(), budget, depth + 1);
|
|
1294
|
+
} catch (e) {
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (Array.isArray(value)) {
|
|
1298
|
+
const out2 = [];
|
|
1299
|
+
for (const item of value) {
|
|
1300
|
+
if (budget.remaining <= 0) {
|
|
1301
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1302
|
+
break;
|
|
1303
|
+
}
|
|
1304
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1305
|
+
}
|
|
1306
|
+
return out2;
|
|
1307
|
+
}
|
|
1308
|
+
const out = {};
|
|
1309
|
+
for (const key of Object.keys(value)) {
|
|
1310
|
+
if (budget.remaining <= 0) {
|
|
1311
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1312
|
+
break;
|
|
1313
|
+
}
|
|
1314
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1315
|
+
out[key] = boundedClone2(value[key], budget, depth + 1);
|
|
1316
|
+
}
|
|
1317
|
+
return out;
|
|
1318
|
+
}
|
|
1319
|
+
function boundedStringify(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1320
|
+
var _a;
|
|
1321
|
+
try {
|
|
1322
|
+
if (typeof value === "string") {
|
|
1323
|
+
return truncateToLimit2(JSON.stringify(capText2(value, limit)), limit);
|
|
1324
|
+
}
|
|
1325
|
+
const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
|
|
1326
|
+
return truncateToLimit2((_a = JSON.stringify(pruned)) != null ? _a : "", limit);
|
|
1327
|
+
} catch (e) {
|
|
1328
|
+
return capText2(String(value), limit);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
|
|
808
1332
|
// src/tracing-linkage-helpers.ts
|
|
809
1333
|
function createSessionParentMapHelpers({
|
|
810
1334
|
sessions: sessions2,
|
|
@@ -882,6 +1406,15 @@ function createSessionParentMapHelpers({
|
|
|
882
1406
|
}
|
|
883
1407
|
|
|
884
1408
|
// src/tracing.ts
|
|
1409
|
+
var ERROR_LOG_INTERVAL_MS = 3e4;
|
|
1410
|
+
var lastErrorLogAt = /* @__PURE__ */ new Map();
|
|
1411
|
+
function rateLimitedErrorLog(key, message) {
|
|
1412
|
+
const now = Date.now();
|
|
1413
|
+
const last = lastErrorLogAt.get(key);
|
|
1414
|
+
if (last !== void 0 && now - last < ERROR_LOG_INTERVAL_MS) return;
|
|
1415
|
+
lastErrorLogAt.set(key, now);
|
|
1416
|
+
console.log(message);
|
|
1417
|
+
}
|
|
885
1418
|
var sessions = /* @__PURE__ */ new Map();
|
|
886
1419
|
var taskContexts = /* @__PURE__ */ new Map();
|
|
887
1420
|
var runningTaskCallsBySession = /* @__PURE__ */ new Map();
|
|
@@ -940,6 +1473,19 @@ function endPendingToolSpanWithError(state, callID, tool, toolCallArgs, errorMes
|
|
|
940
1473
|
}
|
|
941
1474
|
return true;
|
|
942
1475
|
}
|
|
1476
|
+
function unwrapQuotes(s) {
|
|
1477
|
+
if (s.length < 2) return s;
|
|
1478
|
+
const first = s[0];
|
|
1479
|
+
const last = s[s.length - 1];
|
|
1480
|
+
if ((first === '"' || first === "'") && first === last) return s.slice(1, -1);
|
|
1481
|
+
return s;
|
|
1482
|
+
}
|
|
1483
|
+
function buildPromptMessages(systemPrompt, userInput) {
|
|
1484
|
+
const messages = [];
|
|
1485
|
+
if (systemPrompt) messages.push({ role: "system", content: systemPrompt });
|
|
1486
|
+
messages.push({ role: "user", content: unwrapQuotes(userInput) });
|
|
1487
|
+
return JSON.stringify(messages);
|
|
1488
|
+
}
|
|
943
1489
|
function getHostname() {
|
|
944
1490
|
var _a;
|
|
945
1491
|
try {
|
|
@@ -1021,7 +1567,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1021
1567
|
const state = sessions.get(partSessionID);
|
|
1022
1568
|
if (!state) return;
|
|
1023
1569
|
if (partObj["type"] === "text" && typeof partObj["text"] === "string") {
|
|
1024
|
-
state.outputParts.set(messageId, partObj["text"]);
|
|
1570
|
+
state.outputParts.set(messageId, capText2(partObj["text"]));
|
|
1025
1571
|
} else if (partObj["type"] === "tool" && messageId) {
|
|
1026
1572
|
const tool = partObj["tool"];
|
|
1027
1573
|
const callID = partObj["callID"];
|
|
@@ -1051,7 +1597,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1051
1597
|
if (typeof errorMessage !== "string" || errorMessage === "")
|
|
1052
1598
|
throw new Error("message.part.updated: error tool state must have error string");
|
|
1053
1599
|
const input = toolState["input"];
|
|
1054
|
-
const toolCallArgs = input === void 0 ? void 0 : typeof input === "string" ? input :
|
|
1600
|
+
const toolCallArgs = input === void 0 ? void 0 : typeof input === "string" ? capText2(input) : boundedStringify(input);
|
|
1055
1601
|
if (endPendingToolSpanWithError(
|
|
1056
1602
|
state,
|
|
1057
1603
|
callID,
|
|
@@ -1064,7 +1610,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1064
1610
|
}
|
|
1065
1611
|
}
|
|
1066
1612
|
} else if (partObj["type"] === "reasoning" && typeof partObj["text"] === "string") {
|
|
1067
|
-
state.reasoningParts.set(messageId, partObj["text"]);
|
|
1613
|
+
state.reasoningParts.set(messageId, capText2(partObj["text"]));
|
|
1068
1614
|
}
|
|
1069
1615
|
} else if (event.type === "message.updated") {
|
|
1070
1616
|
const msgInfo = props["info"];
|
|
@@ -1165,9 +1711,14 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1165
1711
|
const rootSpan = state.currentRootSpan;
|
|
1166
1712
|
const isCompaction = state.isCompacting === true;
|
|
1167
1713
|
if (state.parentId) {
|
|
1714
|
+
const userInput = unwrapQuotes(state.currentInput);
|
|
1168
1715
|
const llmAttrs = [
|
|
1169
1716
|
attrString("ai.operationId", "generateText"),
|
|
1170
|
-
attrString("ai.prompt",
|
|
1717
|
+
attrString("ai.prompt", userInput),
|
|
1718
|
+
attrString(
|
|
1719
|
+
"ai.prompt.messages",
|
|
1720
|
+
buildPromptMessages(state.currentSystemPrompt, state.currentInput)
|
|
1721
|
+
),
|
|
1171
1722
|
attrString("ai.response.text", outputText),
|
|
1172
1723
|
attrString("gen_ai.system", providerID),
|
|
1173
1724
|
attrString("gen_ai.request.model", modelID),
|
|
@@ -1212,9 +1763,14 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1212
1763
|
});
|
|
1213
1764
|
} else {
|
|
1214
1765
|
rootSpan.name = isCompaction ? "ai.compaction" : modelName;
|
|
1766
|
+
const userInput = unwrapQuotes(state.currentInput);
|
|
1215
1767
|
const rootAttrs = [
|
|
1216
1768
|
attrString("ai.operationId", "generateText"),
|
|
1217
|
-
attrString("ai.prompt",
|
|
1769
|
+
attrString("ai.prompt", userInput),
|
|
1770
|
+
attrString(
|
|
1771
|
+
"ai.prompt.messages",
|
|
1772
|
+
buildPromptMessages(state.currentSystemPrompt, state.currentInput)
|
|
1773
|
+
),
|
|
1218
1774
|
attrString("ai.response.text", outputText),
|
|
1219
1775
|
attrString("gen_ai.system", providerID),
|
|
1220
1776
|
attrString("gen_ai.request.model", modelID),
|
|
@@ -1321,7 +1877,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1321
1877
|
sessions.delete(String(errorSessionID));
|
|
1322
1878
|
}
|
|
1323
1879
|
} catch (err) {
|
|
1324
|
-
|
|
1880
|
+
rateLimitedErrorLog(
|
|
1881
|
+
"event",
|
|
1325
1882
|
`[raindrop-ai/opencode-plugin] [error] Error in event hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1326
1883
|
);
|
|
1327
1884
|
}
|
|
@@ -1377,7 +1934,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1377
1934
|
...promptMeta,
|
|
1378
1935
|
properties: baseMeta.properties || promptMeta.properties ? { ...(_a = baseMeta.properties) != null ? _a : {}, ...(_b = promptMeta.properties) != null ? _b : {} } : void 0
|
|
1379
1936
|
};
|
|
1380
|
-
const userText = textParts.join("\n");
|
|
1937
|
+
const userText = capText2(textParts.join("\n"));
|
|
1381
1938
|
state.currentInput = userText;
|
|
1382
1939
|
if (state.parentId) {
|
|
1383
1940
|
if (!state.parentTaskSpanIds || !state.parentEventContext) {
|
|
@@ -1486,7 +2043,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1486
2043
|
});
|
|
1487
2044
|
}
|
|
1488
2045
|
} catch (err) {
|
|
1489
|
-
|
|
2046
|
+
rateLimitedErrorLog(
|
|
2047
|
+
"tool.execute.before",
|
|
1490
2048
|
`[raindrop-ai/opencode-plugin] [error] Error in tool.execute.before hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1491
2049
|
);
|
|
1492
2050
|
}
|
|
@@ -1501,7 +2059,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1501
2059
|
if (!state || !state.currentEventId) return;
|
|
1502
2060
|
if (args === void 0 || args === null)
|
|
1503
2061
|
throw new Error("tool.execute.after: args is required");
|
|
1504
|
-
const toolCallArgs = typeof args === "string" ? args :
|
|
2062
|
+
const toolCallArgs = typeof args === "string" ? capText2(args) : boundedStringify(args);
|
|
1505
2063
|
const startInfo = state.toolSpanStarts.get(callID);
|
|
1506
2064
|
state.toolSpanStarts.delete(callID);
|
|
1507
2065
|
const resultMetadata = result.metadata;
|
|
@@ -1514,13 +2072,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1514
2072
|
const endTimeUnixNano = nowUnixNanoString();
|
|
1515
2073
|
if (!("output" in result) || result.output === void 0)
|
|
1516
2074
|
throw new Error("tool.execute.after: result.output is required");
|
|
1517
|
-
|
|
1518
|
-
try {
|
|
1519
|
-
toolResult = JSON.stringify(result.output);
|
|
1520
|
-
} catch (err) {
|
|
1521
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1522
|
-
throw new Error(`tool.execute.after: result.output must be JSON-serializable: ${msg}`);
|
|
1523
|
-
}
|
|
2075
|
+
const toolResult = boundedStringify(result.output);
|
|
1524
2076
|
if (startInfo.liveSpan) {
|
|
1525
2077
|
traceShipper.endSpan(startInfo.liveSpan, {
|
|
1526
2078
|
attributes: [
|
|
@@ -1549,7 +2101,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1549
2101
|
markTaskCallFinished(sessionID, callID);
|
|
1550
2102
|
}
|
|
1551
2103
|
} catch (err) {
|
|
1552
|
-
|
|
2104
|
+
rateLimitedErrorLog(
|
|
2105
|
+
"tool.execute.after",
|
|
1553
2106
|
`[raindrop-ai/opencode-plugin] [error] Error in tool.execute.after hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1554
2107
|
);
|
|
1555
2108
|
}
|
|
@@ -1566,7 +2119,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1566
2119
|
state.isCompacting = true;
|
|
1567
2120
|
}
|
|
1568
2121
|
} catch (err) {
|
|
1569
|
-
|
|
2122
|
+
rateLimitedErrorLog(
|
|
2123
|
+
"experimental.session.compacting",
|
|
1570
2124
|
`[raindrop-ai/opencode-plugin] [error] Error in experimental.session.compacting hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1571
2125
|
);
|
|
1572
2126
|
}
|
|
@@ -1585,11 +2139,12 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1585
2139
|
sessions.set(sessionID, state);
|
|
1586
2140
|
}
|
|
1587
2141
|
const MAX_SYSTEM_PROMPT_LENGTH = 32768;
|
|
1588
|
-
const
|
|
2142
|
+
const TRUNCATION_MARKER3 = "\n...[truncated]";
|
|
1589
2143
|
const joined = output.system.join("\n\n");
|
|
1590
|
-
state.currentSystemPrompt = joined.length > MAX_SYSTEM_PROMPT_LENGTH ? joined.slice(0, MAX_SYSTEM_PROMPT_LENGTH -
|
|
2144
|
+
state.currentSystemPrompt = joined.length > MAX_SYSTEM_PROMPT_LENGTH ? joined.slice(0, MAX_SYSTEM_PROMPT_LENGTH - TRUNCATION_MARKER3.length) + TRUNCATION_MARKER3 : joined;
|
|
1591
2145
|
} catch (err) {
|
|
1592
|
-
|
|
2146
|
+
rateLimitedErrorLog(
|
|
2147
|
+
"experimental.chat.system.transform",
|
|
1593
2148
|
`[raindrop-ai/opencode-plugin] [error] Error in experimental.chat.system.transform hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1594
2149
|
);
|
|
1595
2150
|
}
|
|
@@ -1605,25 +2160,33 @@ async function plugin(input) {
|
|
|
1605
2160
|
console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
|
|
1606
2161
|
}
|
|
1607
2162
|
appLog("info", `Loading ${PLUGIN_NAME} v${PLUGIN_VERSION}`);
|
|
1608
|
-
|
|
2163
|
+
const resolvedLocalUrl = resolveLocalDebuggerBaseUrl(config.localWorkshopUrl);
|
|
2164
|
+
const hasLocalDestination = resolvedLocalUrl !== null;
|
|
2165
|
+
if (!config.writeKey && !hasLocalDestination) {
|
|
1609
2166
|
appLog(
|
|
1610
2167
|
"warn",
|
|
1611
|
-
"RAINDROP_WRITE_KEY not set \u2014 Raindrop tracing disabled. Set
|
|
2168
|
+
"RAINDROP_WRITE_KEY not set and no local Workshop daemon detected \u2014 Raindrop tracing disabled. Set RAINDROP_WRITE_KEY for cloud, or RAINDROP_LOCAL_WORKSHOP_URL / RAINDROP_LOCAL_DEBUGGER for local-only mode."
|
|
1612
2169
|
);
|
|
1613
2170
|
return {};
|
|
1614
2171
|
}
|
|
1615
2172
|
if (config.debug) {
|
|
1616
|
-
|
|
2173
|
+
const destinations = [
|
|
2174
|
+
config.writeKey ? `cloud (${config.endpoint})` : null,
|
|
2175
|
+
resolvedLocalUrl ? `local Workshop (${resolvedLocalUrl})` : null
|
|
2176
|
+
].filter(Boolean);
|
|
2177
|
+
appLog("info", `Raindrop tracing enabled \u2014 destinations: ${destinations.join(", ")}`);
|
|
1617
2178
|
}
|
|
1618
2179
|
const eventShipper = new EventShipper2({
|
|
1619
2180
|
writeKey: config.writeKey,
|
|
1620
2181
|
endpoint: config.endpoint,
|
|
1621
|
-
debug: config.debug
|
|
2182
|
+
debug: config.debug,
|
|
2183
|
+
localDebuggerUrl: config.localWorkshopUrl
|
|
1622
2184
|
});
|
|
1623
2185
|
const traceShipper = new TraceShipper2({
|
|
1624
2186
|
writeKey: config.writeKey,
|
|
1625
2187
|
endpoint: config.endpoint,
|
|
1626
|
-
debug: config.debug
|
|
2188
|
+
debug: config.debug,
|
|
2189
|
+
localDebuggerUrl: config.localWorkshopUrl
|
|
1627
2190
|
});
|
|
1628
2191
|
const worktree = (_a = input.worktree) != null ? _a : input.directory;
|
|
1629
2192
|
const hooks = createHooks(
|