@raindrop-ai/opencode-plugin 0.0.12 → 0.0.14
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 +562 -89
- package/dist/index.js +562 -89
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5,7 +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
|
-
// ../core/dist/chunk-
|
|
8
|
+
// ../core/dist/chunk-SK6EJEO7.js
|
|
9
9
|
function getCrypto() {
|
|
10
10
|
const c = globalThis.crypto;
|
|
11
11
|
return c;
|
|
@@ -52,6 +52,8 @@ function base64Encode(bytes) {
|
|
|
52
52
|
function generateId() {
|
|
53
53
|
return base64Encode(randomBytes(12)).replace(/[+/=]/g, "").slice(0, 16);
|
|
54
54
|
}
|
|
55
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
56
|
+
var MAX_RETRY_DELAY_MS = 3e4;
|
|
55
57
|
function wait(ms) {
|
|
56
58
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
57
59
|
}
|
|
@@ -59,6 +61,46 @@ function formatEndpoint(endpoint) {
|
|
|
59
61
|
if (!endpoint) return void 0;
|
|
60
62
|
return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
|
|
61
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
|
+
}
|
|
62
104
|
function parseRetryAfter(headers) {
|
|
63
105
|
var _a;
|
|
64
106
|
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
@@ -75,12 +117,12 @@ function parseRetryAfter(headers) {
|
|
|
75
117
|
function getRetryDelayMs(attemptNumber, previousError) {
|
|
76
118
|
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
77
119
|
const v = previousError.retryAfterMs;
|
|
78
|
-
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);
|
|
79
121
|
}
|
|
80
122
|
if (attemptNumber <= 1) return 0;
|
|
81
123
|
const base = 500;
|
|
82
124
|
const factor = Math.pow(2, attemptNumber - 2);
|
|
83
|
-
return base * factor;
|
|
125
|
+
return Math.min(base * factor, MAX_RETRY_DELAY_MS);
|
|
84
126
|
}
|
|
85
127
|
async function withRetry(operation, opName, opts) {
|
|
86
128
|
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
@@ -115,7 +157,9 @@ async function withRetry(operation, opName, opts) {
|
|
|
115
157
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
116
158
|
}
|
|
117
159
|
async function postJson(url, body, headers, opts) {
|
|
118
|
-
|
|
160
|
+
var _a;
|
|
161
|
+
const opName = `POST ${redactUrlForLog(url)}`;
|
|
162
|
+
const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
119
163
|
await withRetry(
|
|
120
164
|
async () => {
|
|
121
165
|
const resp = await fetch(url, {
|
|
@@ -124,7 +168,8 @@ async function postJson(url, body, headers, opts) {
|
|
|
124
168
|
"Content-Type": "application/json",
|
|
125
169
|
...headers
|
|
126
170
|
},
|
|
127
|
-
body: JSON.stringify(body)
|
|
171
|
+
body: JSON.stringify(body),
|
|
172
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
128
173
|
});
|
|
129
174
|
if (!resp.ok) {
|
|
130
175
|
const text = await resp.text().catch(() => "");
|
|
@@ -141,6 +186,27 @@ async function postJson(url, body, headers, opts) {
|
|
|
141
186
|
opts
|
|
142
187
|
);
|
|
143
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
|
+
}
|
|
144
210
|
var SpanStatusCode = {
|
|
145
211
|
UNSET: 0,
|
|
146
212
|
OK: 1,
|
|
@@ -283,6 +349,27 @@ function mirrorPartialEventToLocalDebugger(event, options = {}) {
|
|
|
283
349
|
}).catch(() => {
|
|
284
350
|
});
|
|
285
351
|
}
|
|
352
|
+
var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
|
|
353
|
+
var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
354
|
+
function isValidProjectIdSlug(value) {
|
|
355
|
+
return PROJECT_ID_SLUG_PATTERN.test(value);
|
|
356
|
+
}
|
|
357
|
+
function normalizeProjectId(raw, opts) {
|
|
358
|
+
if (typeof raw !== "string") return void 0;
|
|
359
|
+
const trimmed = raw.trim();
|
|
360
|
+
if (!trimmed) return void 0;
|
|
361
|
+
if (!isValidProjectIdSlug(trimmed) && opts.debug) {
|
|
362
|
+
console.warn(
|
|
363
|
+
`${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
return trimmed;
|
|
367
|
+
}
|
|
368
|
+
function projectIdHeaders(projectId) {
|
|
369
|
+
return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
|
|
370
|
+
}
|
|
371
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
372
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
286
373
|
function mergePatches(target, source) {
|
|
287
374
|
var _a, _b, _c, _d;
|
|
288
375
|
const out = { ...target, ...source };
|
|
@@ -300,6 +387,7 @@ var EventShipper = class {
|
|
|
300
387
|
this.sticky = /* @__PURE__ */ new Map();
|
|
301
388
|
this.timers = /* @__PURE__ */ new Map();
|
|
302
389
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
390
|
+
this.hasShutdown = false;
|
|
303
391
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
304
392
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
305
393
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
@@ -309,10 +397,15 @@ var EventShipper = class {
|
|
|
309
397
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
310
398
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
311
399
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
400
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
312
401
|
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
313
402
|
if (this.debug && this.localDebuggerUrl) {
|
|
314
403
|
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
315
404
|
}
|
|
405
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
406
|
+
debug: this.debug,
|
|
407
|
+
prefix: this.prefix
|
|
408
|
+
});
|
|
316
409
|
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
317
410
|
this.context = {
|
|
318
411
|
library: {
|
|
@@ -331,10 +424,55 @@ var EventShipper = class {
|
|
|
331
424
|
authHeaders() {
|
|
332
425
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
333
426
|
}
|
|
427
|
+
requestHeaders() {
|
|
428
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
432
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
433
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
434
|
+
* of issuing a request that could outlive process exit.
|
|
435
|
+
*
|
|
436
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
437
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
438
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
439
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
440
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
441
|
+
* run as a single short attempt rather than regaining the full retry
|
|
442
|
+
* schedule.
|
|
443
|
+
*/
|
|
444
|
+
requestOpts() {
|
|
445
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
446
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
447
|
+
if (remainingMs <= 0) return null;
|
|
448
|
+
return {
|
|
449
|
+
maxAttempts: 1,
|
|
450
|
+
debug: this.debug,
|
|
451
|
+
sdkName: this.sdkName,
|
|
452
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
if (this.hasShutdown) {
|
|
456
|
+
return {
|
|
457
|
+
maxAttempts: 1,
|
|
458
|
+
debug: this.debug,
|
|
459
|
+
sdkName: this.sdkName,
|
|
460
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
464
|
+
}
|
|
334
465
|
async patch(eventId, patch) {
|
|
335
466
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
336
467
|
if (!this.enabled) return;
|
|
337
468
|
if (!eventId || !eventId.trim()) return;
|
|
469
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
470
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
471
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
472
|
+
}
|
|
473
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
474
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
475
|
+
}
|
|
338
476
|
if (this.debug) {
|
|
339
477
|
console.log(`${this.prefix} queue patch`, {
|
|
340
478
|
eventId,
|
|
@@ -381,9 +519,16 @@ var EventShipper = class {
|
|
|
381
519
|
})));
|
|
382
520
|
}
|
|
383
521
|
async shutdown() {
|
|
384
|
-
|
|
385
|
-
this.
|
|
386
|
-
|
|
522
|
+
this.hasShutdown = true;
|
|
523
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
524
|
+
try {
|
|
525
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
526
|
+
this.timers.clear();
|
|
527
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
528
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
529
|
+
} finally {
|
|
530
|
+
this.shutdownDeadlineAt = void 0;
|
|
531
|
+
}
|
|
387
532
|
}
|
|
388
533
|
async trackSignal(signal) {
|
|
389
534
|
var _a, _b;
|
|
@@ -405,15 +550,19 @@ var EventShipper = class {
|
|
|
405
550
|
];
|
|
406
551
|
if (!this.writeKey) return;
|
|
407
552
|
const url = `${this.baseUrl}signals/track`;
|
|
553
|
+
const opts = this.requestOpts();
|
|
554
|
+
if (!opts) {
|
|
555
|
+
this.warnShutdownDrop("signal");
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
408
558
|
try {
|
|
409
|
-
await postJson(url, body, this.
|
|
410
|
-
maxAttempts: 3,
|
|
411
|
-
debug: this.debug,
|
|
412
|
-
sdkName: this.sdkName
|
|
413
|
-
});
|
|
559
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
414
560
|
} catch (err) {
|
|
415
561
|
const msg = err instanceof Error ? err.message : String(err);
|
|
416
|
-
|
|
562
|
+
rateLimitedLog(
|
|
563
|
+
`${this.prefix}.send_signal_failed`,
|
|
564
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
565
|
+
);
|
|
417
566
|
}
|
|
418
567
|
}
|
|
419
568
|
async identify(users) {
|
|
@@ -437,17 +586,27 @@ var EventShipper = class {
|
|
|
437
586
|
if (!this.writeKey) return;
|
|
438
587
|
if (body.length === 0) return;
|
|
439
588
|
const url = `${this.baseUrl}users/identify`;
|
|
589
|
+
const opts = this.requestOpts();
|
|
590
|
+
if (!opts) {
|
|
591
|
+
this.warnShutdownDrop("identify");
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
440
594
|
try {
|
|
441
|
-
await postJson(url, body, this.
|
|
442
|
-
maxAttempts: 3,
|
|
443
|
-
debug: this.debug,
|
|
444
|
-
sdkName: this.sdkName
|
|
445
|
-
});
|
|
595
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
446
596
|
} catch (err) {
|
|
447
597
|
const msg = err instanceof Error ? err.message : String(err);
|
|
448
|
-
|
|
598
|
+
rateLimitedLog(
|
|
599
|
+
`${this.prefix}.send_identify_failed`,
|
|
600
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
601
|
+
);
|
|
449
602
|
}
|
|
450
603
|
}
|
|
604
|
+
warnShutdownDrop(what) {
|
|
605
|
+
rateLimitedLog(
|
|
606
|
+
`${this.prefix}.shutdown_deadline`,
|
|
607
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
608
|
+
);
|
|
609
|
+
}
|
|
451
610
|
async flushOne(eventId) {
|
|
452
611
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
453
612
|
if (!this.enabled) return;
|
|
@@ -523,11 +682,13 @@ var EventShipper = class {
|
|
|
523
682
|
if (!isPending) this.sticky.delete(eventId);
|
|
524
683
|
return;
|
|
525
684
|
}
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
685
|
+
const opts = this.requestOpts();
|
|
686
|
+
if (!opts) {
|
|
687
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
688
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
const p = postJson(url, payload, this.requestHeaders(), opts);
|
|
531
692
|
this.inFlight.add(p);
|
|
532
693
|
try {
|
|
533
694
|
try {
|
|
@@ -537,7 +698,10 @@ var EventShipper = class {
|
|
|
537
698
|
}
|
|
538
699
|
} catch (err) {
|
|
539
700
|
const msg = err instanceof Error ? err.message : String(err);
|
|
540
|
-
|
|
701
|
+
rateLimitedLog(
|
|
702
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
703
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
704
|
+
);
|
|
541
705
|
}
|
|
542
706
|
} finally {
|
|
543
707
|
this.inFlight.delete(p);
|
|
@@ -547,10 +711,113 @@ var EventShipper = class {
|
|
|
547
711
|
}
|
|
548
712
|
}
|
|
549
713
|
};
|
|
714
|
+
var DEFAULT_SECRET_KEY_NAMES = [
|
|
715
|
+
"apikey",
|
|
716
|
+
"apisecret",
|
|
717
|
+
"apitoken",
|
|
718
|
+
"secretaccesskey",
|
|
719
|
+
"sessiontoken",
|
|
720
|
+
"privatekey",
|
|
721
|
+
"privatekeyid",
|
|
722
|
+
"clientsecret",
|
|
723
|
+
"accesstoken",
|
|
724
|
+
"refreshtoken",
|
|
725
|
+
"oauthtoken",
|
|
726
|
+
"bearertoken",
|
|
727
|
+
"authorization",
|
|
728
|
+
"password",
|
|
729
|
+
"passphrase"
|
|
730
|
+
];
|
|
731
|
+
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
732
|
+
function normalizeKeyName(name) {
|
|
733
|
+
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
734
|
+
}
|
|
735
|
+
function redactSecretsInObject(value, options) {
|
|
736
|
+
var _a, _b;
|
|
737
|
+
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
738
|
+
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
739
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
740
|
+
const walk = (node) => {
|
|
741
|
+
if (node === null || typeof node !== "object") return node;
|
|
742
|
+
if (seen.has(node)) return "[CIRCULAR]";
|
|
743
|
+
seen.add(node);
|
|
744
|
+
if (Array.isArray(node)) {
|
|
745
|
+
return node.map((item) => walk(item));
|
|
746
|
+
}
|
|
747
|
+
const out = {};
|
|
748
|
+
for (const [k, v] of Object.entries(node)) {
|
|
749
|
+
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
750
|
+
out[k] = placeholder;
|
|
751
|
+
} else {
|
|
752
|
+
out[k] = walk(v);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return out;
|
|
756
|
+
};
|
|
757
|
+
return walk(value);
|
|
758
|
+
}
|
|
759
|
+
function buildSecretSet(names) {
|
|
760
|
+
const set = /* @__PURE__ */ new Set();
|
|
761
|
+
for (const name of names) set.add(normalizeKeyName(name));
|
|
762
|
+
return set;
|
|
763
|
+
}
|
|
764
|
+
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
765
|
+
"ai.request.providerOptions",
|
|
766
|
+
"ai.response.providerMetadata"
|
|
767
|
+
];
|
|
768
|
+
function defaultTransformSpan(span) {
|
|
769
|
+
const attrs = span.attributes;
|
|
770
|
+
if (!attrs || attrs.length === 0) return span;
|
|
771
|
+
let nextAttrs;
|
|
772
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
773
|
+
const attr = attrs[i];
|
|
774
|
+
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
775
|
+
if (redacted === void 0) continue;
|
|
776
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
777
|
+
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
778
|
+
}
|
|
779
|
+
if (!nextAttrs) return span;
|
|
780
|
+
return { ...span, attributes: nextAttrs };
|
|
781
|
+
}
|
|
782
|
+
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
783
|
+
function redactJsonAttributeValue(key, value) {
|
|
784
|
+
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
785
|
+
const json = value.stringValue;
|
|
786
|
+
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
787
|
+
let parsed;
|
|
788
|
+
try {
|
|
789
|
+
parsed = JSON.parse(json);
|
|
790
|
+
} catch (e) {
|
|
791
|
+
return void 0;
|
|
792
|
+
}
|
|
793
|
+
const scrubbed = redactSecretsInObject(parsed);
|
|
794
|
+
let scrubbedJson;
|
|
795
|
+
try {
|
|
796
|
+
scrubbedJson = JSON.stringify(scrubbed);
|
|
797
|
+
} catch (e) {
|
|
798
|
+
return void 0;
|
|
799
|
+
}
|
|
800
|
+
if (scrubbedJson === json) return void 0;
|
|
801
|
+
return { stringValue: scrubbedJson };
|
|
802
|
+
}
|
|
803
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
804
|
+
var _a, _b;
|
|
805
|
+
try {
|
|
806
|
+
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;
|
|
807
|
+
if (!raw) return limit;
|
|
808
|
+
const parsed = Number.parseInt(raw, 10);
|
|
809
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
810
|
+
return Math.min(limit, parsed);
|
|
811
|
+
}
|
|
812
|
+
} catch (e) {
|
|
813
|
+
}
|
|
814
|
+
return limit;
|
|
815
|
+
}
|
|
550
816
|
var TraceShipper = class {
|
|
551
817
|
constructor(opts) {
|
|
552
818
|
this.queue = [];
|
|
553
819
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
820
|
+
this.hasShutdown = false;
|
|
554
821
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
555
822
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
556
823
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
@@ -568,6 +835,79 @@ var TraceShipper = class {
|
|
|
568
835
|
if (this.debug && this.localDebuggerUrl) {
|
|
569
836
|
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
570
837
|
}
|
|
838
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
839
|
+
debug: this.debug,
|
|
840
|
+
prefix: this.prefix
|
|
841
|
+
});
|
|
842
|
+
this.transformSpanHook = opts.transformSpan;
|
|
843
|
+
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
844
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
848
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
849
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
850
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
851
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
852
|
+
* in the surviving prefix).
|
|
853
|
+
*
|
|
854
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
855
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
856
|
+
*/
|
|
857
|
+
capSpanAttributes(span) {
|
|
858
|
+
var _a;
|
|
859
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
860
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
861
|
+
);
|
|
862
|
+
const attrs = span.attributes;
|
|
863
|
+
if (!attrs || attrs.length === 0) return span;
|
|
864
|
+
let nextAttrs;
|
|
865
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
866
|
+
const attr = attrs[i];
|
|
867
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
868
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
869
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
870
|
+
nextAttrs[i] = {
|
|
871
|
+
key: attr.key,
|
|
872
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
if (!nextAttrs) return span;
|
|
876
|
+
return { ...span, attributes: nextAttrs };
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
880
|
+
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
881
|
+
* ship, or `null` to drop the span entirely.
|
|
882
|
+
*
|
|
883
|
+
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
884
|
+
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
885
|
+
* about). The default redactor then runs on whatever the user produced,
|
|
886
|
+
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
887
|
+
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
888
|
+
*
|
|
889
|
+
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
890
|
+
* hook can never accidentally ship raw, un-redacted spans.
|
|
891
|
+
*/
|
|
892
|
+
redactSpan(span) {
|
|
893
|
+
let current = span;
|
|
894
|
+
if (this.transformSpanHook) {
|
|
895
|
+
try {
|
|
896
|
+
const result = this.transformSpanHook(current);
|
|
897
|
+
if (result === null) return null;
|
|
898
|
+
if (result !== void 0) current = result;
|
|
899
|
+
} catch (err) {
|
|
900
|
+
if (this.debug) {
|
|
901
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
902
|
+
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
903
|
+
}
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (!this.disableDefaultRedaction) {
|
|
908
|
+
current = defaultTransformSpan(current);
|
|
909
|
+
}
|
|
910
|
+
return this.capSpanAttributes(current);
|
|
571
911
|
}
|
|
572
912
|
isDebugEnabled() {
|
|
573
913
|
return this.debug;
|
|
@@ -575,6 +915,9 @@ var TraceShipper = class {
|
|
|
575
915
|
authHeaders() {
|
|
576
916
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
577
917
|
}
|
|
918
|
+
requestHeaders() {
|
|
919
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
920
|
+
}
|
|
578
921
|
startSpan(args) {
|
|
579
922
|
var _a, _b;
|
|
580
923
|
const ids = createSpanIds(args.parent);
|
|
@@ -585,8 +928,8 @@ var TraceShipper = class {
|
|
|
585
928
|
];
|
|
586
929
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
587
930
|
const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
588
|
-
|
|
589
|
-
|
|
931
|
+
this.mirrorToLocalDebugger(
|
|
932
|
+
buildOtlpSpan({
|
|
590
933
|
ids: span.ids,
|
|
591
934
|
name: span.name,
|
|
592
935
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
@@ -594,16 +937,21 @@ var TraceShipper = class {
|
|
|
594
937
|
// placeholder — will be updated on endSpan
|
|
595
938
|
attributes: span.attributes,
|
|
596
939
|
status: { code: SpanStatusCode.UNSET }
|
|
597
|
-
})
|
|
598
|
-
|
|
599
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
600
|
-
baseUrl: this.localDebuggerUrl,
|
|
601
|
-
debug: false,
|
|
602
|
-
sdkName: this.sdkName
|
|
603
|
-
});
|
|
604
|
-
}
|
|
940
|
+
})
|
|
941
|
+
);
|
|
605
942
|
return span;
|
|
606
943
|
}
|
|
944
|
+
mirrorToLocalDebugger(span) {
|
|
945
|
+
if (!this.localDebuggerUrl) return;
|
|
946
|
+
const redacted = this.redactSpan(span);
|
|
947
|
+
if (redacted === null) return;
|
|
948
|
+
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
949
|
+
mirrorTraceExportToLocalDebugger(body, {
|
|
950
|
+
baseUrl: this.localDebuggerUrl,
|
|
951
|
+
debug: false,
|
|
952
|
+
sdkName: this.sdkName
|
|
953
|
+
});
|
|
954
|
+
}
|
|
607
955
|
endSpan(span, extra) {
|
|
608
956
|
var _a, _b;
|
|
609
957
|
if (span.endTimeUnixNano) return;
|
|
@@ -625,14 +973,7 @@ var TraceShipper = class {
|
|
|
625
973
|
status
|
|
626
974
|
});
|
|
627
975
|
this.enqueue(otlp);
|
|
628
|
-
|
|
629
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
630
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
631
|
-
baseUrl: this.localDebuggerUrl,
|
|
632
|
-
debug: false,
|
|
633
|
-
sdkName: this.sdkName
|
|
634
|
-
});
|
|
635
|
-
}
|
|
976
|
+
this.mirrorToLocalDebugger(otlp);
|
|
636
977
|
}
|
|
637
978
|
createSpan(args) {
|
|
638
979
|
var _a;
|
|
@@ -650,14 +991,7 @@ var TraceShipper = class {
|
|
|
650
991
|
status: args.status
|
|
651
992
|
});
|
|
652
993
|
this.enqueue(otlp);
|
|
653
|
-
|
|
654
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
655
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
656
|
-
baseUrl: this.localDebuggerUrl,
|
|
657
|
-
debug: false,
|
|
658
|
-
sdkName: this.sdkName
|
|
659
|
-
});
|
|
660
|
-
}
|
|
994
|
+
this.mirrorToLocalDebugger(otlp);
|
|
661
995
|
}
|
|
662
996
|
enqueue(span) {
|
|
663
997
|
if (!this.enabled) return;
|
|
@@ -669,10 +1003,12 @@ var TraceShipper = class {
|
|
|
669
1003
|
)}`
|
|
670
1004
|
);
|
|
671
1005
|
}
|
|
1006
|
+
const redacted = this.redactSpan(span);
|
|
1007
|
+
if (redacted === null) return;
|
|
672
1008
|
if (this.queue.length >= this.maxQueueSize) {
|
|
673
1009
|
this.queue.shift();
|
|
674
1010
|
}
|
|
675
|
-
this.queue.push(
|
|
1011
|
+
this.queue.push(redacted);
|
|
676
1012
|
if (this.queue.length >= this.maxBatchSize) {
|
|
677
1013
|
void this.flush().catch(() => {
|
|
678
1014
|
});
|
|
@@ -695,6 +1031,16 @@ var TraceShipper = class {
|
|
|
695
1031
|
while (this.queue.length > 0) {
|
|
696
1032
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
697
1033
|
if (!this.writeKey) continue;
|
|
1034
|
+
const opts = this.requestOpts();
|
|
1035
|
+
if (!opts) {
|
|
1036
|
+
rateLimitedLog(
|
|
1037
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1038
|
+
() => console.warn(
|
|
1039
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1040
|
+
)
|
|
1041
|
+
);
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
698
1044
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
699
1045
|
const url = `${this.baseUrl}traces`;
|
|
700
1046
|
if (this.debug) {
|
|
@@ -703,11 +1049,7 @@ var TraceShipper = class {
|
|
|
703
1049
|
endpoint: url
|
|
704
1050
|
});
|
|
705
1051
|
}
|
|
706
|
-
const p = postJson(url, body, this.
|
|
707
|
-
maxAttempts: 3,
|
|
708
|
-
debug: this.debug,
|
|
709
|
-
sdkName: this.sdkName
|
|
710
|
-
});
|
|
1052
|
+
const p = postJson(url, body, this.requestHeaders(), opts);
|
|
711
1053
|
this.inFlight.add(p);
|
|
712
1054
|
try {
|
|
713
1055
|
try {
|
|
@@ -715,21 +1057,61 @@ var TraceShipper = class {
|
|
|
715
1057
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
716
1058
|
} catch (err) {
|
|
717
1059
|
const msg = err instanceof Error ? err.message : String(err);
|
|
718
|
-
|
|
1060
|
+
rateLimitedLog(
|
|
1061
|
+
`${this.prefix}.send_spans_failed`,
|
|
1062
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1063
|
+
);
|
|
719
1064
|
}
|
|
720
1065
|
} finally {
|
|
721
1066
|
this.inFlight.delete(p);
|
|
722
1067
|
}
|
|
723
1068
|
}
|
|
724
1069
|
}
|
|
1070
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1071
|
+
requestOpts() {
|
|
1072
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1073
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1074
|
+
if (remainingMs <= 0) return null;
|
|
1075
|
+
return {
|
|
1076
|
+
maxAttempts: 1,
|
|
1077
|
+
debug: this.debug,
|
|
1078
|
+
sdkName: this.sdkName,
|
|
1079
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
if (this.hasShutdown) {
|
|
1083
|
+
return {
|
|
1084
|
+
maxAttempts: 1,
|
|
1085
|
+
debug: this.debug,
|
|
1086
|
+
sdkName: this.sdkName,
|
|
1087
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1091
|
+
}
|
|
725
1092
|
async shutdown() {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1093
|
+
this.hasShutdown = true;
|
|
1094
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1095
|
+
try {
|
|
1096
|
+
if (this.timer) {
|
|
1097
|
+
clearTimeout(this.timer);
|
|
1098
|
+
this.timer = void 0;
|
|
1099
|
+
}
|
|
1100
|
+
const drain = async () => {
|
|
1101
|
+
await this.flush();
|
|
1102
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1103
|
+
})));
|
|
1104
|
+
};
|
|
1105
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1106
|
+
if (!settled) {
|
|
1107
|
+
rateLimitedLog(
|
|
1108
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1109
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
} finally {
|
|
1113
|
+
this.shutdownDeadlineAt = void 0;
|
|
729
1114
|
}
|
|
730
|
-
await this.flush();
|
|
731
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
732
|
-
})));
|
|
733
1115
|
}
|
|
734
1116
|
};
|
|
735
1117
|
|
|
@@ -742,7 +1124,7 @@ import { existsSync, readFileSync } from "fs";
|
|
|
742
1124
|
import { homedir } from "os";
|
|
743
1125
|
import { join } from "path";
|
|
744
1126
|
function loadConfig(projectDirectory) {
|
|
745
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1127
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
746
1128
|
let merged = {};
|
|
747
1129
|
const configPaths = [
|
|
748
1130
|
join(homedir(), ".config", "opencode", "raindrop.json"),
|
|
@@ -769,9 +1151,10 @@ function loadConfig(projectDirectory) {
|
|
|
769
1151
|
return {
|
|
770
1152
|
writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
|
|
771
1153
|
endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1154
|
+
projectId: (_e = process.env["RAINDROP_PROJECT_ID"]) != null ? _e : merged.project_id,
|
|
1155
|
+
eventName: (_f = merged.event_name) != null ? _f : "opencode_session",
|
|
1156
|
+
debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_g = merged.debug) != null ? _g : false,
|
|
1157
|
+
captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_h = merged.capture_system_prompt) != null ? _h : false,
|
|
775
1158
|
eventMetadata,
|
|
776
1159
|
localWorkshopUrl: resolveLocalWorkshopUrl(merged.local_workshop_url)
|
|
777
1160
|
};
|
|
@@ -790,7 +1173,7 @@ function resolveLocalWorkshopUrl(fileValue) {
|
|
|
790
1173
|
// package.json
|
|
791
1174
|
var package_default = {
|
|
792
1175
|
name: "@raindrop-ai/opencode-plugin",
|
|
793
|
-
version: "0.0.
|
|
1176
|
+
version: "0.0.14",
|
|
794
1177
|
description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
|
|
795
1178
|
type: "module",
|
|
796
1179
|
main: "dist/index.js",
|
|
@@ -900,6 +1283,86 @@ var TraceShipper2 = class extends TraceShipper {
|
|
|
900
1283
|
}
|
|
901
1284
|
};
|
|
902
1285
|
|
|
1286
|
+
// src/bounded.ts
|
|
1287
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1288
|
+
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
1289
|
+
var MAX_BOUNDED_DEPTH = 12;
|
|
1290
|
+
function truncateToLimit2(text, limit) {
|
|
1291
|
+
if (text.length <= limit) return text;
|
|
1292
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1293
|
+
return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1294
|
+
}
|
|
1295
|
+
return text.slice(0, limit);
|
|
1296
|
+
}
|
|
1297
|
+
function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1298
|
+
if (value.length <= limit) return value;
|
|
1299
|
+
return truncateToLimit2(value, limit);
|
|
1300
|
+
}
|
|
1301
|
+
function boundedClone2(value, budget, depth) {
|
|
1302
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1303
|
+
if (typeof value === "string") {
|
|
1304
|
+
if (value.length > budget.remaining) {
|
|
1305
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1306
|
+
budget.remaining = 0;
|
|
1307
|
+
return taken;
|
|
1308
|
+
}
|
|
1309
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1310
|
+
return value;
|
|
1311
|
+
}
|
|
1312
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1313
|
+
budget.remaining -= 8;
|
|
1314
|
+
return value;
|
|
1315
|
+
}
|
|
1316
|
+
if (typeof value !== "object") {
|
|
1317
|
+
budget.remaining -= 8;
|
|
1318
|
+
return value;
|
|
1319
|
+
}
|
|
1320
|
+
if (depth >= MAX_BOUNDED_DEPTH) {
|
|
1321
|
+
budget.remaining -= 16;
|
|
1322
|
+
return `<max depth: ${TRUNCATION_MARKER2}>`;
|
|
1323
|
+
}
|
|
1324
|
+
const withToJson = value;
|
|
1325
|
+
if (typeof withToJson.toJSON === "function") {
|
|
1326
|
+
try {
|
|
1327
|
+
return boundedClone2(withToJson.toJSON(), budget, depth + 1);
|
|
1328
|
+
} catch (e) {
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
if (Array.isArray(value)) {
|
|
1332
|
+
const out2 = [];
|
|
1333
|
+
for (const item of value) {
|
|
1334
|
+
if (budget.remaining <= 0) {
|
|
1335
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1336
|
+
break;
|
|
1337
|
+
}
|
|
1338
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1339
|
+
}
|
|
1340
|
+
return out2;
|
|
1341
|
+
}
|
|
1342
|
+
const out = {};
|
|
1343
|
+
for (const key of Object.keys(value)) {
|
|
1344
|
+
if (budget.remaining <= 0) {
|
|
1345
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1346
|
+
break;
|
|
1347
|
+
}
|
|
1348
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1349
|
+
out[key] = boundedClone2(value[key], budget, depth + 1);
|
|
1350
|
+
}
|
|
1351
|
+
return out;
|
|
1352
|
+
}
|
|
1353
|
+
function boundedStringify(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1354
|
+
var _a;
|
|
1355
|
+
try {
|
|
1356
|
+
if (typeof value === "string") {
|
|
1357
|
+
return truncateToLimit2(JSON.stringify(capText2(value, limit)), limit);
|
|
1358
|
+
}
|
|
1359
|
+
const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
|
|
1360
|
+
return truncateToLimit2((_a = JSON.stringify(pruned)) != null ? _a : "", limit);
|
|
1361
|
+
} catch (e) {
|
|
1362
|
+
return capText2(String(value), limit);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
903
1366
|
// src/tracing-linkage-helpers.ts
|
|
904
1367
|
function createSessionParentMapHelpers({
|
|
905
1368
|
sessions: sessions2,
|
|
@@ -977,6 +1440,15 @@ function createSessionParentMapHelpers({
|
|
|
977
1440
|
}
|
|
978
1441
|
|
|
979
1442
|
// src/tracing.ts
|
|
1443
|
+
var ERROR_LOG_INTERVAL_MS = 3e4;
|
|
1444
|
+
var lastErrorLogAt = /* @__PURE__ */ new Map();
|
|
1445
|
+
function rateLimitedErrorLog(key, message) {
|
|
1446
|
+
const now = Date.now();
|
|
1447
|
+
const last = lastErrorLogAt.get(key);
|
|
1448
|
+
if (last !== void 0 && now - last < ERROR_LOG_INTERVAL_MS) return;
|
|
1449
|
+
lastErrorLogAt.set(key, now);
|
|
1450
|
+
console.log(message);
|
|
1451
|
+
}
|
|
980
1452
|
var sessions = /* @__PURE__ */ new Map();
|
|
981
1453
|
var taskContexts = /* @__PURE__ */ new Map();
|
|
982
1454
|
var runningTaskCallsBySession = /* @__PURE__ */ new Map();
|
|
@@ -1129,7 +1601,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1129
1601
|
const state = sessions.get(partSessionID);
|
|
1130
1602
|
if (!state) return;
|
|
1131
1603
|
if (partObj["type"] === "text" && typeof partObj["text"] === "string") {
|
|
1132
|
-
state.outputParts.set(messageId, partObj["text"]);
|
|
1604
|
+
state.outputParts.set(messageId, capText2(partObj["text"]));
|
|
1133
1605
|
} else if (partObj["type"] === "tool" && messageId) {
|
|
1134
1606
|
const tool = partObj["tool"];
|
|
1135
1607
|
const callID = partObj["callID"];
|
|
@@ -1159,7 +1631,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1159
1631
|
if (typeof errorMessage !== "string" || errorMessage === "")
|
|
1160
1632
|
throw new Error("message.part.updated: error tool state must have error string");
|
|
1161
1633
|
const input = toolState["input"];
|
|
1162
|
-
const toolCallArgs = input === void 0 ? void 0 : typeof input === "string" ? input :
|
|
1634
|
+
const toolCallArgs = input === void 0 ? void 0 : typeof input === "string" ? capText2(input) : boundedStringify(input);
|
|
1163
1635
|
if (endPendingToolSpanWithError(
|
|
1164
1636
|
state,
|
|
1165
1637
|
callID,
|
|
@@ -1172,7 +1644,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1172
1644
|
}
|
|
1173
1645
|
}
|
|
1174
1646
|
} else if (partObj["type"] === "reasoning" && typeof partObj["text"] === "string") {
|
|
1175
|
-
state.reasoningParts.set(messageId, partObj["text"]);
|
|
1647
|
+
state.reasoningParts.set(messageId, capText2(partObj["text"]));
|
|
1176
1648
|
}
|
|
1177
1649
|
} else if (event.type === "message.updated") {
|
|
1178
1650
|
const msgInfo = props["info"];
|
|
@@ -1439,7 +1911,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1439
1911
|
sessions.delete(String(errorSessionID));
|
|
1440
1912
|
}
|
|
1441
1913
|
} catch (err) {
|
|
1442
|
-
|
|
1914
|
+
rateLimitedErrorLog(
|
|
1915
|
+
"event",
|
|
1443
1916
|
`[raindrop-ai/opencode-plugin] [error] Error in event hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1444
1917
|
);
|
|
1445
1918
|
}
|
|
@@ -1495,7 +1968,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1495
1968
|
...promptMeta,
|
|
1496
1969
|
properties: baseMeta.properties || promptMeta.properties ? { ...(_a = baseMeta.properties) != null ? _a : {}, ...(_b = promptMeta.properties) != null ? _b : {} } : void 0
|
|
1497
1970
|
};
|
|
1498
|
-
const userText = textParts.join("\n");
|
|
1971
|
+
const userText = capText2(textParts.join("\n"));
|
|
1499
1972
|
state.currentInput = userText;
|
|
1500
1973
|
if (state.parentId) {
|
|
1501
1974
|
if (!state.parentTaskSpanIds || !state.parentEventContext) {
|
|
@@ -1604,7 +2077,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1604
2077
|
});
|
|
1605
2078
|
}
|
|
1606
2079
|
} catch (err) {
|
|
1607
|
-
|
|
2080
|
+
rateLimitedErrorLog(
|
|
2081
|
+
"tool.execute.before",
|
|
1608
2082
|
`[raindrop-ai/opencode-plugin] [error] Error in tool.execute.before hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1609
2083
|
);
|
|
1610
2084
|
}
|
|
@@ -1619,7 +2093,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1619
2093
|
if (!state || !state.currentEventId) return;
|
|
1620
2094
|
if (args === void 0 || args === null)
|
|
1621
2095
|
throw new Error("tool.execute.after: args is required");
|
|
1622
|
-
const toolCallArgs = typeof args === "string" ? args :
|
|
2096
|
+
const toolCallArgs = typeof args === "string" ? capText2(args) : boundedStringify(args);
|
|
1623
2097
|
const startInfo = state.toolSpanStarts.get(callID);
|
|
1624
2098
|
state.toolSpanStarts.delete(callID);
|
|
1625
2099
|
const resultMetadata = result.metadata;
|
|
@@ -1632,13 +2106,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1632
2106
|
const endTimeUnixNano = nowUnixNanoString();
|
|
1633
2107
|
if (!("output" in result) || result.output === void 0)
|
|
1634
2108
|
throw new Error("tool.execute.after: result.output is required");
|
|
1635
|
-
|
|
1636
|
-
try {
|
|
1637
|
-
toolResult = JSON.stringify(result.output);
|
|
1638
|
-
} catch (err) {
|
|
1639
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1640
|
-
throw new Error(`tool.execute.after: result.output must be JSON-serializable: ${msg}`);
|
|
1641
|
-
}
|
|
2109
|
+
const toolResult = boundedStringify(result.output);
|
|
1642
2110
|
if (startInfo.liveSpan) {
|
|
1643
2111
|
traceShipper.endSpan(startInfo.liveSpan, {
|
|
1644
2112
|
attributes: [
|
|
@@ -1667,7 +2135,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1667
2135
|
markTaskCallFinished(sessionID, callID);
|
|
1668
2136
|
}
|
|
1669
2137
|
} catch (err) {
|
|
1670
|
-
|
|
2138
|
+
rateLimitedErrorLog(
|
|
2139
|
+
"tool.execute.after",
|
|
1671
2140
|
`[raindrop-ai/opencode-plugin] [error] Error in tool.execute.after hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1672
2141
|
);
|
|
1673
2142
|
}
|
|
@@ -1684,7 +2153,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1684
2153
|
state.isCompacting = true;
|
|
1685
2154
|
}
|
|
1686
2155
|
} catch (err) {
|
|
1687
|
-
|
|
2156
|
+
rateLimitedErrorLog(
|
|
2157
|
+
"experimental.session.compacting",
|
|
1688
2158
|
`[raindrop-ai/opencode-plugin] [error] Error in experimental.session.compacting hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1689
2159
|
);
|
|
1690
2160
|
}
|
|
@@ -1703,11 +2173,12 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1703
2173
|
sessions.set(sessionID, state);
|
|
1704
2174
|
}
|
|
1705
2175
|
const MAX_SYSTEM_PROMPT_LENGTH = 32768;
|
|
1706
|
-
const
|
|
2176
|
+
const TRUNCATION_MARKER3 = "\n...[truncated]";
|
|
1707
2177
|
const joined = output.system.join("\n\n");
|
|
1708
|
-
state.currentSystemPrompt = joined.length > MAX_SYSTEM_PROMPT_LENGTH ? joined.slice(0, MAX_SYSTEM_PROMPT_LENGTH -
|
|
2178
|
+
state.currentSystemPrompt = joined.length > MAX_SYSTEM_PROMPT_LENGTH ? joined.slice(0, MAX_SYSTEM_PROMPT_LENGTH - TRUNCATION_MARKER3.length) + TRUNCATION_MARKER3 : joined;
|
|
1709
2179
|
} catch (err) {
|
|
1710
|
-
|
|
2180
|
+
rateLimitedErrorLog(
|
|
2181
|
+
"experimental.chat.system.transform",
|
|
1711
2182
|
`[raindrop-ai/opencode-plugin] [error] Error in experimental.chat.system.transform hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1712
2183
|
);
|
|
1713
2184
|
}
|
|
@@ -1743,12 +2214,14 @@ async function plugin(input) {
|
|
|
1743
2214
|
writeKey: config.writeKey,
|
|
1744
2215
|
endpoint: config.endpoint,
|
|
1745
2216
|
debug: config.debug,
|
|
2217
|
+
projectId: config.projectId,
|
|
1746
2218
|
localDebuggerUrl: config.localWorkshopUrl
|
|
1747
2219
|
});
|
|
1748
2220
|
const traceShipper = new TraceShipper2({
|
|
1749
2221
|
writeKey: config.writeKey,
|
|
1750
2222
|
endpoint: config.endpoint,
|
|
1751
2223
|
debug: config.debug,
|
|
2224
|
+
projectId: config.projectId,
|
|
1752
2225
|
localDebuggerUrl: config.localWorkshopUrl
|
|
1753
2226
|
});
|
|
1754
2227
|
const worktree = (_a = input.worktree) != null ? _a : input.directory;
|