@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.cjs
CHANGED
|
@@ -24,7 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
|
|
27
|
-
// ../core/dist/chunk-
|
|
27
|
+
// ../core/dist/chunk-SK6EJEO7.js
|
|
28
28
|
function getCrypto() {
|
|
29
29
|
const c = globalThis.crypto;
|
|
30
30
|
return c;
|
|
@@ -71,6 +71,8 @@ function base64Encode(bytes) {
|
|
|
71
71
|
function generateId() {
|
|
72
72
|
return base64Encode(randomBytes(12)).replace(/[+/=]/g, "").slice(0, 16);
|
|
73
73
|
}
|
|
74
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
75
|
+
var MAX_RETRY_DELAY_MS = 3e4;
|
|
74
76
|
function wait(ms) {
|
|
75
77
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
76
78
|
}
|
|
@@ -78,6 +80,46 @@ function formatEndpoint(endpoint) {
|
|
|
78
80
|
if (!endpoint) return void 0;
|
|
79
81
|
return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
|
|
80
82
|
}
|
|
83
|
+
function redactUrlForLog(url) {
|
|
84
|
+
try {
|
|
85
|
+
const parsed = new URL(url);
|
|
86
|
+
parsed.username = "";
|
|
87
|
+
parsed.password = "";
|
|
88
|
+
parsed.search = "";
|
|
89
|
+
parsed.hash = "";
|
|
90
|
+
return parsed.toString();
|
|
91
|
+
} catch (e) {
|
|
92
|
+
return "<unparseable-url>";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
|
|
96
|
+
var rateLimitedLogLast = /* @__PURE__ */ new Map();
|
|
97
|
+
function rateLimitedLog(key, log) {
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
const last = rateLimitedLogLast.get(key);
|
|
100
|
+
if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
rateLimitedLogLast.set(key, now);
|
|
104
|
+
log();
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
async function raceWithTimeout(promise, timeoutMs) {
|
|
108
|
+
let timer;
|
|
109
|
+
const settledInTime = await Promise.race([
|
|
110
|
+
promise.then(
|
|
111
|
+
() => true,
|
|
112
|
+
() => true
|
|
113
|
+
),
|
|
114
|
+
new Promise((resolve) => {
|
|
115
|
+
var _a;
|
|
116
|
+
timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
|
|
117
|
+
(_a = timer.unref) == null ? void 0 : _a.call(timer);
|
|
118
|
+
})
|
|
119
|
+
]);
|
|
120
|
+
if (timer) clearTimeout(timer);
|
|
121
|
+
return settledInTime;
|
|
122
|
+
}
|
|
81
123
|
function parseRetryAfter(headers) {
|
|
82
124
|
var _a;
|
|
83
125
|
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
@@ -94,12 +136,12 @@ function parseRetryAfter(headers) {
|
|
|
94
136
|
function getRetryDelayMs(attemptNumber, previousError) {
|
|
95
137
|
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
96
138
|
const v = previousError.retryAfterMs;
|
|
97
|
-
if (typeof v === "number") return Math.max(0, v);
|
|
139
|
+
if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
|
|
98
140
|
}
|
|
99
141
|
if (attemptNumber <= 1) return 0;
|
|
100
142
|
const base = 500;
|
|
101
143
|
const factor = Math.pow(2, attemptNumber - 2);
|
|
102
|
-
return base * factor;
|
|
144
|
+
return Math.min(base * factor, MAX_RETRY_DELAY_MS);
|
|
103
145
|
}
|
|
104
146
|
async function withRetry(operation, opName, opts) {
|
|
105
147
|
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
@@ -134,7 +176,9 @@ async function withRetry(operation, opName, opts) {
|
|
|
134
176
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
135
177
|
}
|
|
136
178
|
async function postJson(url, body, headers, opts) {
|
|
137
|
-
|
|
179
|
+
var _a;
|
|
180
|
+
const opName = `POST ${redactUrlForLog(url)}`;
|
|
181
|
+
const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
138
182
|
await withRetry(
|
|
139
183
|
async () => {
|
|
140
184
|
const resp = await fetch(url, {
|
|
@@ -143,7 +187,8 @@ async function postJson(url, body, headers, opts) {
|
|
|
143
187
|
"Content-Type": "application/json",
|
|
144
188
|
...headers
|
|
145
189
|
},
|
|
146
|
-
body: JSON.stringify(body)
|
|
190
|
+
body: JSON.stringify(body),
|
|
191
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
147
192
|
});
|
|
148
193
|
if (!resp.ok) {
|
|
149
194
|
const text = await resp.text().catch(() => "");
|
|
@@ -160,6 +205,27 @@ async function postJson(url, body, headers, opts) {
|
|
|
160
205
|
opts
|
|
161
206
|
);
|
|
162
207
|
}
|
|
208
|
+
var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
|
|
209
|
+
var TRUNCATION_MARKER = "...[truncated by raindrop]";
|
|
210
|
+
var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
|
|
211
|
+
function resolveMaxTextFieldChars(value) {
|
|
212
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
213
|
+
return Math.floor(value);
|
|
214
|
+
}
|
|
215
|
+
return currentDefaultMaxTextFieldChars;
|
|
216
|
+
}
|
|
217
|
+
function truncateToLimit(text, limit) {
|
|
218
|
+
if (limit > TRUNCATION_MARKER.length) {
|
|
219
|
+
return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
220
|
+
}
|
|
221
|
+
return text.slice(0, Math.max(0, limit));
|
|
222
|
+
}
|
|
223
|
+
function capText(value, limit) {
|
|
224
|
+
if (typeof value !== "string") return value;
|
|
225
|
+
const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
|
|
226
|
+
if (value.length <= max) return value;
|
|
227
|
+
return truncateToLimit(value, max);
|
|
228
|
+
}
|
|
163
229
|
var SpanStatusCode = {
|
|
164
230
|
UNSET: 0,
|
|
165
231
|
OK: 1,
|
|
@@ -302,6 +368,27 @@ function mirrorPartialEventToLocalDebugger(event, options = {}) {
|
|
|
302
368
|
}).catch(() => {
|
|
303
369
|
});
|
|
304
370
|
}
|
|
371
|
+
var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
|
|
372
|
+
var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
373
|
+
function isValidProjectIdSlug(value) {
|
|
374
|
+
return PROJECT_ID_SLUG_PATTERN.test(value);
|
|
375
|
+
}
|
|
376
|
+
function normalizeProjectId(raw, opts) {
|
|
377
|
+
if (typeof raw !== "string") return void 0;
|
|
378
|
+
const trimmed = raw.trim();
|
|
379
|
+
if (!trimmed) return void 0;
|
|
380
|
+
if (!isValidProjectIdSlug(trimmed) && opts.debug) {
|
|
381
|
+
console.warn(
|
|
382
|
+
`${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return trimmed;
|
|
386
|
+
}
|
|
387
|
+
function projectIdHeaders(projectId) {
|
|
388
|
+
return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
|
|
389
|
+
}
|
|
390
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
391
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
305
392
|
function mergePatches(target, source) {
|
|
306
393
|
var _a, _b, _c, _d;
|
|
307
394
|
const out = { ...target, ...source };
|
|
@@ -319,6 +406,7 @@ var EventShipper = class {
|
|
|
319
406
|
this.sticky = /* @__PURE__ */ new Map();
|
|
320
407
|
this.timers = /* @__PURE__ */ new Map();
|
|
321
408
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
409
|
+
this.hasShutdown = false;
|
|
322
410
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
323
411
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
324
412
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
@@ -328,10 +416,15 @@ var EventShipper = class {
|
|
|
328
416
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
329
417
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
330
418
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
419
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
331
420
|
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
332
421
|
if (this.debug && this.localDebuggerUrl) {
|
|
333
422
|
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
334
423
|
}
|
|
424
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
425
|
+
debug: this.debug,
|
|
426
|
+
prefix: this.prefix
|
|
427
|
+
});
|
|
335
428
|
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
336
429
|
this.context = {
|
|
337
430
|
library: {
|
|
@@ -350,10 +443,55 @@ var EventShipper = class {
|
|
|
350
443
|
authHeaders() {
|
|
351
444
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
352
445
|
}
|
|
446
|
+
requestHeaders() {
|
|
447
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
451
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
452
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
453
|
+
* of issuing a request that could outlive process exit.
|
|
454
|
+
*
|
|
455
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
456
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
457
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
458
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
459
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
460
|
+
* run as a single short attempt rather than regaining the full retry
|
|
461
|
+
* schedule.
|
|
462
|
+
*/
|
|
463
|
+
requestOpts() {
|
|
464
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
465
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
466
|
+
if (remainingMs <= 0) return null;
|
|
467
|
+
return {
|
|
468
|
+
maxAttempts: 1,
|
|
469
|
+
debug: this.debug,
|
|
470
|
+
sdkName: this.sdkName,
|
|
471
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
if (this.hasShutdown) {
|
|
475
|
+
return {
|
|
476
|
+
maxAttempts: 1,
|
|
477
|
+
debug: this.debug,
|
|
478
|
+
sdkName: this.sdkName,
|
|
479
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
483
|
+
}
|
|
353
484
|
async patch(eventId, patch) {
|
|
354
485
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
355
486
|
if (!this.enabled) return;
|
|
356
487
|
if (!eventId || !eventId.trim()) return;
|
|
488
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
489
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
490
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
491
|
+
}
|
|
492
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
493
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
494
|
+
}
|
|
357
495
|
if (this.debug) {
|
|
358
496
|
console.log(`${this.prefix} queue patch`, {
|
|
359
497
|
eventId,
|
|
@@ -400,9 +538,16 @@ var EventShipper = class {
|
|
|
400
538
|
})));
|
|
401
539
|
}
|
|
402
540
|
async shutdown() {
|
|
403
|
-
|
|
404
|
-
this.
|
|
405
|
-
|
|
541
|
+
this.hasShutdown = true;
|
|
542
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
543
|
+
try {
|
|
544
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
545
|
+
this.timers.clear();
|
|
546
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
547
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
548
|
+
} finally {
|
|
549
|
+
this.shutdownDeadlineAt = void 0;
|
|
550
|
+
}
|
|
406
551
|
}
|
|
407
552
|
async trackSignal(signal) {
|
|
408
553
|
var _a, _b;
|
|
@@ -424,15 +569,19 @@ var EventShipper = class {
|
|
|
424
569
|
];
|
|
425
570
|
if (!this.writeKey) return;
|
|
426
571
|
const url = `${this.baseUrl}signals/track`;
|
|
572
|
+
const opts = this.requestOpts();
|
|
573
|
+
if (!opts) {
|
|
574
|
+
this.warnShutdownDrop("signal");
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
427
577
|
try {
|
|
428
|
-
await postJson(url, body, this.
|
|
429
|
-
maxAttempts: 3,
|
|
430
|
-
debug: this.debug,
|
|
431
|
-
sdkName: this.sdkName
|
|
432
|
-
});
|
|
578
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
433
579
|
} catch (err) {
|
|
434
580
|
const msg = err instanceof Error ? err.message : String(err);
|
|
435
|
-
|
|
581
|
+
rateLimitedLog(
|
|
582
|
+
`${this.prefix}.send_signal_failed`,
|
|
583
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
584
|
+
);
|
|
436
585
|
}
|
|
437
586
|
}
|
|
438
587
|
async identify(users) {
|
|
@@ -456,17 +605,27 @@ var EventShipper = class {
|
|
|
456
605
|
if (!this.writeKey) return;
|
|
457
606
|
if (body.length === 0) return;
|
|
458
607
|
const url = `${this.baseUrl}users/identify`;
|
|
608
|
+
const opts = this.requestOpts();
|
|
609
|
+
if (!opts) {
|
|
610
|
+
this.warnShutdownDrop("identify");
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
459
613
|
try {
|
|
460
|
-
await postJson(url, body, this.
|
|
461
|
-
maxAttempts: 3,
|
|
462
|
-
debug: this.debug,
|
|
463
|
-
sdkName: this.sdkName
|
|
464
|
-
});
|
|
614
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
465
615
|
} catch (err) {
|
|
466
616
|
const msg = err instanceof Error ? err.message : String(err);
|
|
467
|
-
|
|
617
|
+
rateLimitedLog(
|
|
618
|
+
`${this.prefix}.send_identify_failed`,
|
|
619
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
620
|
+
);
|
|
468
621
|
}
|
|
469
622
|
}
|
|
623
|
+
warnShutdownDrop(what) {
|
|
624
|
+
rateLimitedLog(
|
|
625
|
+
`${this.prefix}.shutdown_deadline`,
|
|
626
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
627
|
+
);
|
|
628
|
+
}
|
|
470
629
|
async flushOne(eventId) {
|
|
471
630
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
472
631
|
if (!this.enabled) return;
|
|
@@ -542,11 +701,13 @@ var EventShipper = class {
|
|
|
542
701
|
if (!isPending) this.sticky.delete(eventId);
|
|
543
702
|
return;
|
|
544
703
|
}
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
704
|
+
const opts = this.requestOpts();
|
|
705
|
+
if (!opts) {
|
|
706
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
707
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const p = postJson(url, payload, this.requestHeaders(), opts);
|
|
550
711
|
this.inFlight.add(p);
|
|
551
712
|
try {
|
|
552
713
|
try {
|
|
@@ -556,7 +717,10 @@ var EventShipper = class {
|
|
|
556
717
|
}
|
|
557
718
|
} catch (err) {
|
|
558
719
|
const msg = err instanceof Error ? err.message : String(err);
|
|
559
|
-
|
|
720
|
+
rateLimitedLog(
|
|
721
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
722
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
723
|
+
);
|
|
560
724
|
}
|
|
561
725
|
} finally {
|
|
562
726
|
this.inFlight.delete(p);
|
|
@@ -566,10 +730,113 @@ var EventShipper = class {
|
|
|
566
730
|
}
|
|
567
731
|
}
|
|
568
732
|
};
|
|
733
|
+
var DEFAULT_SECRET_KEY_NAMES = [
|
|
734
|
+
"apikey",
|
|
735
|
+
"apisecret",
|
|
736
|
+
"apitoken",
|
|
737
|
+
"secretaccesskey",
|
|
738
|
+
"sessiontoken",
|
|
739
|
+
"privatekey",
|
|
740
|
+
"privatekeyid",
|
|
741
|
+
"clientsecret",
|
|
742
|
+
"accesstoken",
|
|
743
|
+
"refreshtoken",
|
|
744
|
+
"oauthtoken",
|
|
745
|
+
"bearertoken",
|
|
746
|
+
"authorization",
|
|
747
|
+
"password",
|
|
748
|
+
"passphrase"
|
|
749
|
+
];
|
|
750
|
+
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
751
|
+
function normalizeKeyName(name) {
|
|
752
|
+
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
753
|
+
}
|
|
754
|
+
function redactSecretsInObject(value, options) {
|
|
755
|
+
var _a, _b;
|
|
756
|
+
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
757
|
+
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
758
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
759
|
+
const walk = (node) => {
|
|
760
|
+
if (node === null || typeof node !== "object") return node;
|
|
761
|
+
if (seen.has(node)) return "[CIRCULAR]";
|
|
762
|
+
seen.add(node);
|
|
763
|
+
if (Array.isArray(node)) {
|
|
764
|
+
return node.map((item) => walk(item));
|
|
765
|
+
}
|
|
766
|
+
const out = {};
|
|
767
|
+
for (const [k, v] of Object.entries(node)) {
|
|
768
|
+
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
769
|
+
out[k] = placeholder;
|
|
770
|
+
} else {
|
|
771
|
+
out[k] = walk(v);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return out;
|
|
775
|
+
};
|
|
776
|
+
return walk(value);
|
|
777
|
+
}
|
|
778
|
+
function buildSecretSet(names) {
|
|
779
|
+
const set = /* @__PURE__ */ new Set();
|
|
780
|
+
for (const name of names) set.add(normalizeKeyName(name));
|
|
781
|
+
return set;
|
|
782
|
+
}
|
|
783
|
+
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
784
|
+
"ai.request.providerOptions",
|
|
785
|
+
"ai.response.providerMetadata"
|
|
786
|
+
];
|
|
787
|
+
function defaultTransformSpan(span) {
|
|
788
|
+
const attrs = span.attributes;
|
|
789
|
+
if (!attrs || attrs.length === 0) return span;
|
|
790
|
+
let nextAttrs;
|
|
791
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
792
|
+
const attr = attrs[i];
|
|
793
|
+
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
794
|
+
if (redacted === void 0) continue;
|
|
795
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
796
|
+
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
797
|
+
}
|
|
798
|
+
if (!nextAttrs) return span;
|
|
799
|
+
return { ...span, attributes: nextAttrs };
|
|
800
|
+
}
|
|
801
|
+
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
802
|
+
function redactJsonAttributeValue(key, value) {
|
|
803
|
+
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
804
|
+
const json = value.stringValue;
|
|
805
|
+
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
806
|
+
let parsed;
|
|
807
|
+
try {
|
|
808
|
+
parsed = JSON.parse(json);
|
|
809
|
+
} catch (e) {
|
|
810
|
+
return void 0;
|
|
811
|
+
}
|
|
812
|
+
const scrubbed = redactSecretsInObject(parsed);
|
|
813
|
+
let scrubbedJson;
|
|
814
|
+
try {
|
|
815
|
+
scrubbedJson = JSON.stringify(scrubbed);
|
|
816
|
+
} catch (e) {
|
|
817
|
+
return void 0;
|
|
818
|
+
}
|
|
819
|
+
if (scrubbedJson === json) return void 0;
|
|
820
|
+
return { stringValue: scrubbedJson };
|
|
821
|
+
}
|
|
822
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
823
|
+
var _a, _b;
|
|
824
|
+
try {
|
|
825
|
+
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;
|
|
826
|
+
if (!raw) return limit;
|
|
827
|
+
const parsed = Number.parseInt(raw, 10);
|
|
828
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
829
|
+
return Math.min(limit, parsed);
|
|
830
|
+
}
|
|
831
|
+
} catch (e) {
|
|
832
|
+
}
|
|
833
|
+
return limit;
|
|
834
|
+
}
|
|
569
835
|
var TraceShipper = class {
|
|
570
836
|
constructor(opts) {
|
|
571
837
|
this.queue = [];
|
|
572
838
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
839
|
+
this.hasShutdown = false;
|
|
573
840
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
574
841
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
575
842
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
@@ -587,6 +854,79 @@ var TraceShipper = class {
|
|
|
587
854
|
if (this.debug && this.localDebuggerUrl) {
|
|
588
855
|
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
589
856
|
}
|
|
857
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
858
|
+
debug: this.debug,
|
|
859
|
+
prefix: this.prefix
|
|
860
|
+
});
|
|
861
|
+
this.transformSpanHook = opts.transformSpan;
|
|
862
|
+
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
863
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
867
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
868
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
869
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
870
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
871
|
+
* in the surviving prefix).
|
|
872
|
+
*
|
|
873
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
874
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
875
|
+
*/
|
|
876
|
+
capSpanAttributes(span) {
|
|
877
|
+
var _a;
|
|
878
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
879
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
880
|
+
);
|
|
881
|
+
const attrs = span.attributes;
|
|
882
|
+
if (!attrs || attrs.length === 0) return span;
|
|
883
|
+
let nextAttrs;
|
|
884
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
885
|
+
const attr = attrs[i];
|
|
886
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
887
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
888
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
889
|
+
nextAttrs[i] = {
|
|
890
|
+
key: attr.key,
|
|
891
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
if (!nextAttrs) return span;
|
|
895
|
+
return { ...span, attributes: nextAttrs };
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
899
|
+
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
900
|
+
* ship, or `null` to drop the span entirely.
|
|
901
|
+
*
|
|
902
|
+
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
903
|
+
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
904
|
+
* about). The default redactor then runs on whatever the user produced,
|
|
905
|
+
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
906
|
+
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
907
|
+
*
|
|
908
|
+
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
909
|
+
* hook can never accidentally ship raw, un-redacted spans.
|
|
910
|
+
*/
|
|
911
|
+
redactSpan(span) {
|
|
912
|
+
let current = span;
|
|
913
|
+
if (this.transformSpanHook) {
|
|
914
|
+
try {
|
|
915
|
+
const result = this.transformSpanHook(current);
|
|
916
|
+
if (result === null) return null;
|
|
917
|
+
if (result !== void 0) current = result;
|
|
918
|
+
} catch (err) {
|
|
919
|
+
if (this.debug) {
|
|
920
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
921
|
+
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
922
|
+
}
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (!this.disableDefaultRedaction) {
|
|
927
|
+
current = defaultTransformSpan(current);
|
|
928
|
+
}
|
|
929
|
+
return this.capSpanAttributes(current);
|
|
590
930
|
}
|
|
591
931
|
isDebugEnabled() {
|
|
592
932
|
return this.debug;
|
|
@@ -594,6 +934,9 @@ var TraceShipper = class {
|
|
|
594
934
|
authHeaders() {
|
|
595
935
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
596
936
|
}
|
|
937
|
+
requestHeaders() {
|
|
938
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
939
|
+
}
|
|
597
940
|
startSpan(args) {
|
|
598
941
|
var _a, _b;
|
|
599
942
|
const ids = createSpanIds(args.parent);
|
|
@@ -604,8 +947,8 @@ var TraceShipper = class {
|
|
|
604
947
|
];
|
|
605
948
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
606
949
|
const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
607
|
-
|
|
608
|
-
|
|
950
|
+
this.mirrorToLocalDebugger(
|
|
951
|
+
buildOtlpSpan({
|
|
609
952
|
ids: span.ids,
|
|
610
953
|
name: span.name,
|
|
611
954
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
@@ -613,16 +956,21 @@ var TraceShipper = class {
|
|
|
613
956
|
// placeholder — will be updated on endSpan
|
|
614
957
|
attributes: span.attributes,
|
|
615
958
|
status: { code: SpanStatusCode.UNSET }
|
|
616
|
-
})
|
|
617
|
-
|
|
618
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
619
|
-
baseUrl: this.localDebuggerUrl,
|
|
620
|
-
debug: false,
|
|
621
|
-
sdkName: this.sdkName
|
|
622
|
-
});
|
|
623
|
-
}
|
|
959
|
+
})
|
|
960
|
+
);
|
|
624
961
|
return span;
|
|
625
962
|
}
|
|
963
|
+
mirrorToLocalDebugger(span) {
|
|
964
|
+
if (!this.localDebuggerUrl) return;
|
|
965
|
+
const redacted = this.redactSpan(span);
|
|
966
|
+
if (redacted === null) return;
|
|
967
|
+
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
968
|
+
mirrorTraceExportToLocalDebugger(body, {
|
|
969
|
+
baseUrl: this.localDebuggerUrl,
|
|
970
|
+
debug: false,
|
|
971
|
+
sdkName: this.sdkName
|
|
972
|
+
});
|
|
973
|
+
}
|
|
626
974
|
endSpan(span, extra) {
|
|
627
975
|
var _a, _b;
|
|
628
976
|
if (span.endTimeUnixNano) return;
|
|
@@ -644,14 +992,7 @@ var TraceShipper = class {
|
|
|
644
992
|
status
|
|
645
993
|
});
|
|
646
994
|
this.enqueue(otlp);
|
|
647
|
-
|
|
648
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
649
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
650
|
-
baseUrl: this.localDebuggerUrl,
|
|
651
|
-
debug: false,
|
|
652
|
-
sdkName: this.sdkName
|
|
653
|
-
});
|
|
654
|
-
}
|
|
995
|
+
this.mirrorToLocalDebugger(otlp);
|
|
655
996
|
}
|
|
656
997
|
createSpan(args) {
|
|
657
998
|
var _a;
|
|
@@ -669,14 +1010,7 @@ var TraceShipper = class {
|
|
|
669
1010
|
status: args.status
|
|
670
1011
|
});
|
|
671
1012
|
this.enqueue(otlp);
|
|
672
|
-
|
|
673
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
674
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
675
|
-
baseUrl: this.localDebuggerUrl,
|
|
676
|
-
debug: false,
|
|
677
|
-
sdkName: this.sdkName
|
|
678
|
-
});
|
|
679
|
-
}
|
|
1013
|
+
this.mirrorToLocalDebugger(otlp);
|
|
680
1014
|
}
|
|
681
1015
|
enqueue(span) {
|
|
682
1016
|
if (!this.enabled) return;
|
|
@@ -688,10 +1022,12 @@ var TraceShipper = class {
|
|
|
688
1022
|
)}`
|
|
689
1023
|
);
|
|
690
1024
|
}
|
|
1025
|
+
const redacted = this.redactSpan(span);
|
|
1026
|
+
if (redacted === null) return;
|
|
691
1027
|
if (this.queue.length >= this.maxQueueSize) {
|
|
692
1028
|
this.queue.shift();
|
|
693
1029
|
}
|
|
694
|
-
this.queue.push(
|
|
1030
|
+
this.queue.push(redacted);
|
|
695
1031
|
if (this.queue.length >= this.maxBatchSize) {
|
|
696
1032
|
void this.flush().catch(() => {
|
|
697
1033
|
});
|
|
@@ -714,6 +1050,16 @@ var TraceShipper = class {
|
|
|
714
1050
|
while (this.queue.length > 0) {
|
|
715
1051
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
716
1052
|
if (!this.writeKey) continue;
|
|
1053
|
+
const opts = this.requestOpts();
|
|
1054
|
+
if (!opts) {
|
|
1055
|
+
rateLimitedLog(
|
|
1056
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1057
|
+
() => console.warn(
|
|
1058
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1059
|
+
)
|
|
1060
|
+
);
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
717
1063
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
718
1064
|
const url = `${this.baseUrl}traces`;
|
|
719
1065
|
if (this.debug) {
|
|
@@ -722,11 +1068,7 @@ var TraceShipper = class {
|
|
|
722
1068
|
endpoint: url
|
|
723
1069
|
});
|
|
724
1070
|
}
|
|
725
|
-
const p = postJson(url, body, this.
|
|
726
|
-
maxAttempts: 3,
|
|
727
|
-
debug: this.debug,
|
|
728
|
-
sdkName: this.sdkName
|
|
729
|
-
});
|
|
1071
|
+
const p = postJson(url, body, this.requestHeaders(), opts);
|
|
730
1072
|
this.inFlight.add(p);
|
|
731
1073
|
try {
|
|
732
1074
|
try {
|
|
@@ -734,21 +1076,61 @@ var TraceShipper = class {
|
|
|
734
1076
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
735
1077
|
} catch (err) {
|
|
736
1078
|
const msg = err instanceof Error ? err.message : String(err);
|
|
737
|
-
|
|
1079
|
+
rateLimitedLog(
|
|
1080
|
+
`${this.prefix}.send_spans_failed`,
|
|
1081
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1082
|
+
);
|
|
738
1083
|
}
|
|
739
1084
|
} finally {
|
|
740
1085
|
this.inFlight.delete(p);
|
|
741
1086
|
}
|
|
742
1087
|
}
|
|
743
1088
|
}
|
|
1089
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1090
|
+
requestOpts() {
|
|
1091
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1092
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1093
|
+
if (remainingMs <= 0) return null;
|
|
1094
|
+
return {
|
|
1095
|
+
maxAttempts: 1,
|
|
1096
|
+
debug: this.debug,
|
|
1097
|
+
sdkName: this.sdkName,
|
|
1098
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
if (this.hasShutdown) {
|
|
1102
|
+
return {
|
|
1103
|
+
maxAttempts: 1,
|
|
1104
|
+
debug: this.debug,
|
|
1105
|
+
sdkName: this.sdkName,
|
|
1106
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1110
|
+
}
|
|
744
1111
|
async shutdown() {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
1112
|
+
this.hasShutdown = true;
|
|
1113
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1114
|
+
try {
|
|
1115
|
+
if (this.timer) {
|
|
1116
|
+
clearTimeout(this.timer);
|
|
1117
|
+
this.timer = void 0;
|
|
1118
|
+
}
|
|
1119
|
+
const drain = async () => {
|
|
1120
|
+
await this.flush();
|
|
1121
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1122
|
+
})));
|
|
1123
|
+
};
|
|
1124
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1125
|
+
if (!settled) {
|
|
1126
|
+
rateLimitedLog(
|
|
1127
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1128
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
} finally {
|
|
1132
|
+
this.shutdownDeadlineAt = void 0;
|
|
748
1133
|
}
|
|
749
|
-
await this.flush();
|
|
750
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
751
|
-
})));
|
|
752
1134
|
}
|
|
753
1135
|
};
|
|
754
1136
|
|
|
@@ -761,7 +1143,7 @@ var import_node_fs = require("fs");
|
|
|
761
1143
|
var import_node_os = require("os");
|
|
762
1144
|
var import_node_path = require("path");
|
|
763
1145
|
function loadConfig(projectDirectory) {
|
|
764
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1146
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
765
1147
|
let merged = {};
|
|
766
1148
|
const configPaths = [
|
|
767
1149
|
(0, import_node_path.join)((0, import_node_os.homedir)(), ".config", "opencode", "raindrop.json"),
|
|
@@ -788,9 +1170,10 @@ function loadConfig(projectDirectory) {
|
|
|
788
1170
|
return {
|
|
789
1171
|
writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
|
|
790
1172
|
endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1173
|
+
projectId: (_e = process.env["RAINDROP_PROJECT_ID"]) != null ? _e : merged.project_id,
|
|
1174
|
+
eventName: (_f = merged.event_name) != null ? _f : "opencode_session",
|
|
1175
|
+
debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_g = merged.debug) != null ? _g : false,
|
|
1176
|
+
captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_h = merged.capture_system_prompt) != null ? _h : false,
|
|
794
1177
|
eventMetadata,
|
|
795
1178
|
localWorkshopUrl: resolveLocalWorkshopUrl(merged.local_workshop_url)
|
|
796
1179
|
};
|
|
@@ -809,7 +1192,7 @@ function resolveLocalWorkshopUrl(fileValue) {
|
|
|
809
1192
|
// package.json
|
|
810
1193
|
var package_default = {
|
|
811
1194
|
name: "@raindrop-ai/opencode-plugin",
|
|
812
|
-
version: "0.0.
|
|
1195
|
+
version: "0.0.14",
|
|
813
1196
|
description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
|
|
814
1197
|
type: "module",
|
|
815
1198
|
main: "dist/index.js",
|
|
@@ -919,6 +1302,86 @@ var TraceShipper2 = class extends TraceShipper {
|
|
|
919
1302
|
}
|
|
920
1303
|
};
|
|
921
1304
|
|
|
1305
|
+
// src/bounded.ts
|
|
1306
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1307
|
+
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
1308
|
+
var MAX_BOUNDED_DEPTH = 12;
|
|
1309
|
+
function truncateToLimit2(text, limit) {
|
|
1310
|
+
if (text.length <= limit) return text;
|
|
1311
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1312
|
+
return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1313
|
+
}
|
|
1314
|
+
return text.slice(0, limit);
|
|
1315
|
+
}
|
|
1316
|
+
function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1317
|
+
if (value.length <= limit) return value;
|
|
1318
|
+
return truncateToLimit2(value, limit);
|
|
1319
|
+
}
|
|
1320
|
+
function boundedClone2(value, budget, depth) {
|
|
1321
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1322
|
+
if (typeof value === "string") {
|
|
1323
|
+
if (value.length > budget.remaining) {
|
|
1324
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1325
|
+
budget.remaining = 0;
|
|
1326
|
+
return taken;
|
|
1327
|
+
}
|
|
1328
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1329
|
+
return value;
|
|
1330
|
+
}
|
|
1331
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1332
|
+
budget.remaining -= 8;
|
|
1333
|
+
return value;
|
|
1334
|
+
}
|
|
1335
|
+
if (typeof value !== "object") {
|
|
1336
|
+
budget.remaining -= 8;
|
|
1337
|
+
return value;
|
|
1338
|
+
}
|
|
1339
|
+
if (depth >= MAX_BOUNDED_DEPTH) {
|
|
1340
|
+
budget.remaining -= 16;
|
|
1341
|
+
return `<max depth: ${TRUNCATION_MARKER2}>`;
|
|
1342
|
+
}
|
|
1343
|
+
const withToJson = value;
|
|
1344
|
+
if (typeof withToJson.toJSON === "function") {
|
|
1345
|
+
try {
|
|
1346
|
+
return boundedClone2(withToJson.toJSON(), budget, depth + 1);
|
|
1347
|
+
} catch (e) {
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
if (Array.isArray(value)) {
|
|
1351
|
+
const out2 = [];
|
|
1352
|
+
for (const item of value) {
|
|
1353
|
+
if (budget.remaining <= 0) {
|
|
1354
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1358
|
+
}
|
|
1359
|
+
return out2;
|
|
1360
|
+
}
|
|
1361
|
+
const out = {};
|
|
1362
|
+
for (const key of Object.keys(value)) {
|
|
1363
|
+
if (budget.remaining <= 0) {
|
|
1364
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1365
|
+
break;
|
|
1366
|
+
}
|
|
1367
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1368
|
+
out[key] = boundedClone2(value[key], budget, depth + 1);
|
|
1369
|
+
}
|
|
1370
|
+
return out;
|
|
1371
|
+
}
|
|
1372
|
+
function boundedStringify(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1373
|
+
var _a;
|
|
1374
|
+
try {
|
|
1375
|
+
if (typeof value === "string") {
|
|
1376
|
+
return truncateToLimit2(JSON.stringify(capText2(value, limit)), limit);
|
|
1377
|
+
}
|
|
1378
|
+
const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
|
|
1379
|
+
return truncateToLimit2((_a = JSON.stringify(pruned)) != null ? _a : "", limit);
|
|
1380
|
+
} catch (e) {
|
|
1381
|
+
return capText2(String(value), limit);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
922
1385
|
// src/tracing-linkage-helpers.ts
|
|
923
1386
|
function createSessionParentMapHelpers({
|
|
924
1387
|
sessions: sessions2,
|
|
@@ -996,6 +1459,15 @@ function createSessionParentMapHelpers({
|
|
|
996
1459
|
}
|
|
997
1460
|
|
|
998
1461
|
// src/tracing.ts
|
|
1462
|
+
var ERROR_LOG_INTERVAL_MS = 3e4;
|
|
1463
|
+
var lastErrorLogAt = /* @__PURE__ */ new Map();
|
|
1464
|
+
function rateLimitedErrorLog(key, message) {
|
|
1465
|
+
const now = Date.now();
|
|
1466
|
+
const last = lastErrorLogAt.get(key);
|
|
1467
|
+
if (last !== void 0 && now - last < ERROR_LOG_INTERVAL_MS) return;
|
|
1468
|
+
lastErrorLogAt.set(key, now);
|
|
1469
|
+
console.log(message);
|
|
1470
|
+
}
|
|
999
1471
|
var sessions = /* @__PURE__ */ new Map();
|
|
1000
1472
|
var taskContexts = /* @__PURE__ */ new Map();
|
|
1001
1473
|
var runningTaskCallsBySession = /* @__PURE__ */ new Map();
|
|
@@ -1148,7 +1620,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1148
1620
|
const state = sessions.get(partSessionID);
|
|
1149
1621
|
if (!state) return;
|
|
1150
1622
|
if (partObj["type"] === "text" && typeof partObj["text"] === "string") {
|
|
1151
|
-
state.outputParts.set(messageId, partObj["text"]);
|
|
1623
|
+
state.outputParts.set(messageId, capText2(partObj["text"]));
|
|
1152
1624
|
} else if (partObj["type"] === "tool" && messageId) {
|
|
1153
1625
|
const tool = partObj["tool"];
|
|
1154
1626
|
const callID = partObj["callID"];
|
|
@@ -1178,7 +1650,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1178
1650
|
if (typeof errorMessage !== "string" || errorMessage === "")
|
|
1179
1651
|
throw new Error("message.part.updated: error tool state must have error string");
|
|
1180
1652
|
const input = toolState["input"];
|
|
1181
|
-
const toolCallArgs = input === void 0 ? void 0 : typeof input === "string" ? input :
|
|
1653
|
+
const toolCallArgs = input === void 0 ? void 0 : typeof input === "string" ? capText2(input) : boundedStringify(input);
|
|
1182
1654
|
if (endPendingToolSpanWithError(
|
|
1183
1655
|
state,
|
|
1184
1656
|
callID,
|
|
@@ -1191,7 +1663,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1191
1663
|
}
|
|
1192
1664
|
}
|
|
1193
1665
|
} else if (partObj["type"] === "reasoning" && typeof partObj["text"] === "string") {
|
|
1194
|
-
state.reasoningParts.set(messageId, partObj["text"]);
|
|
1666
|
+
state.reasoningParts.set(messageId, capText2(partObj["text"]));
|
|
1195
1667
|
}
|
|
1196
1668
|
} else if (event.type === "message.updated") {
|
|
1197
1669
|
const msgInfo = props["info"];
|
|
@@ -1458,7 +1930,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1458
1930
|
sessions.delete(String(errorSessionID));
|
|
1459
1931
|
}
|
|
1460
1932
|
} catch (err) {
|
|
1461
|
-
|
|
1933
|
+
rateLimitedErrorLog(
|
|
1934
|
+
"event",
|
|
1462
1935
|
`[raindrop-ai/opencode-plugin] [error] Error in event hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1463
1936
|
);
|
|
1464
1937
|
}
|
|
@@ -1514,7 +1987,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1514
1987
|
...promptMeta,
|
|
1515
1988
|
properties: baseMeta.properties || promptMeta.properties ? { ...(_a = baseMeta.properties) != null ? _a : {}, ...(_b = promptMeta.properties) != null ? _b : {} } : void 0
|
|
1516
1989
|
};
|
|
1517
|
-
const userText = textParts.join("\n");
|
|
1990
|
+
const userText = capText2(textParts.join("\n"));
|
|
1518
1991
|
state.currentInput = userText;
|
|
1519
1992
|
if (state.parentId) {
|
|
1520
1993
|
if (!state.parentTaskSpanIds || !state.parentEventContext) {
|
|
@@ -1623,7 +2096,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1623
2096
|
});
|
|
1624
2097
|
}
|
|
1625
2098
|
} catch (err) {
|
|
1626
|
-
|
|
2099
|
+
rateLimitedErrorLog(
|
|
2100
|
+
"tool.execute.before",
|
|
1627
2101
|
`[raindrop-ai/opencode-plugin] [error] Error in tool.execute.before hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1628
2102
|
);
|
|
1629
2103
|
}
|
|
@@ -1638,7 +2112,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1638
2112
|
if (!state || !state.currentEventId) return;
|
|
1639
2113
|
if (args === void 0 || args === null)
|
|
1640
2114
|
throw new Error("tool.execute.after: args is required");
|
|
1641
|
-
const toolCallArgs = typeof args === "string" ? args :
|
|
2115
|
+
const toolCallArgs = typeof args === "string" ? capText2(args) : boundedStringify(args);
|
|
1642
2116
|
const startInfo = state.toolSpanStarts.get(callID);
|
|
1643
2117
|
state.toolSpanStarts.delete(callID);
|
|
1644
2118
|
const resultMetadata = result.metadata;
|
|
@@ -1651,13 +2125,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1651
2125
|
const endTimeUnixNano = nowUnixNanoString();
|
|
1652
2126
|
if (!("output" in result) || result.output === void 0)
|
|
1653
2127
|
throw new Error("tool.execute.after: result.output is required");
|
|
1654
|
-
|
|
1655
|
-
try {
|
|
1656
|
-
toolResult = JSON.stringify(result.output);
|
|
1657
|
-
} catch (err) {
|
|
1658
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1659
|
-
throw new Error(`tool.execute.after: result.output must be JSON-serializable: ${msg}`);
|
|
1660
|
-
}
|
|
2128
|
+
const toolResult = boundedStringify(result.output);
|
|
1661
2129
|
if (startInfo.liveSpan) {
|
|
1662
2130
|
traceShipper.endSpan(startInfo.liveSpan, {
|
|
1663
2131
|
attributes: [
|
|
@@ -1686,7 +2154,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1686
2154
|
markTaskCallFinished(sessionID, callID);
|
|
1687
2155
|
}
|
|
1688
2156
|
} catch (err) {
|
|
1689
|
-
|
|
2157
|
+
rateLimitedErrorLog(
|
|
2158
|
+
"tool.execute.after",
|
|
1690
2159
|
`[raindrop-ai/opencode-plugin] [error] Error in tool.execute.after hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1691
2160
|
);
|
|
1692
2161
|
}
|
|
@@ -1703,7 +2172,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1703
2172
|
state.isCompacting = true;
|
|
1704
2173
|
}
|
|
1705
2174
|
} catch (err) {
|
|
1706
|
-
|
|
2175
|
+
rateLimitedErrorLog(
|
|
2176
|
+
"experimental.session.compacting",
|
|
1707
2177
|
`[raindrop-ai/opencode-plugin] [error] Error in experimental.session.compacting hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1708
2178
|
);
|
|
1709
2179
|
}
|
|
@@ -1722,11 +2192,12 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
1722
2192
|
sessions.set(sessionID, state);
|
|
1723
2193
|
}
|
|
1724
2194
|
const MAX_SYSTEM_PROMPT_LENGTH = 32768;
|
|
1725
|
-
const
|
|
2195
|
+
const TRUNCATION_MARKER3 = "\n...[truncated]";
|
|
1726
2196
|
const joined = output.system.join("\n\n");
|
|
1727
|
-
state.currentSystemPrompt = joined.length > MAX_SYSTEM_PROMPT_LENGTH ? joined.slice(0, MAX_SYSTEM_PROMPT_LENGTH -
|
|
2197
|
+
state.currentSystemPrompt = joined.length > MAX_SYSTEM_PROMPT_LENGTH ? joined.slice(0, MAX_SYSTEM_PROMPT_LENGTH - TRUNCATION_MARKER3.length) + TRUNCATION_MARKER3 : joined;
|
|
1728
2198
|
} catch (err) {
|
|
1729
|
-
|
|
2199
|
+
rateLimitedErrorLog(
|
|
2200
|
+
"experimental.chat.system.transform",
|
|
1730
2201
|
`[raindrop-ai/opencode-plugin] [error] Error in experimental.chat.system.transform hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1731
2202
|
);
|
|
1732
2203
|
}
|
|
@@ -1762,12 +2233,14 @@ async function plugin(input) {
|
|
|
1762
2233
|
writeKey: config.writeKey,
|
|
1763
2234
|
endpoint: config.endpoint,
|
|
1764
2235
|
debug: config.debug,
|
|
2236
|
+
projectId: config.projectId,
|
|
1765
2237
|
localDebuggerUrl: config.localWorkshopUrl
|
|
1766
2238
|
});
|
|
1767
2239
|
const traceShipper = new TraceShipper2({
|
|
1768
2240
|
writeKey: config.writeKey,
|
|
1769
2241
|
endpoint: config.endpoint,
|
|
1770
2242
|
debug: config.debug,
|
|
2243
|
+
projectId: config.projectId,
|
|
1771
2244
|
localDebuggerUrl: config.localWorkshopUrl
|
|
1772
2245
|
});
|
|
1773
2246
|
const worktree = (_a = input.worktree) != null ? _a : input.directory;
|