@pinta-ai/pinta-opencode 0.8.0 → 0.10.0
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/CHANGELOG.md +42 -0
- package/README.md +59 -2
- package/dist/index.mjs +385 -79
- package/dist/plugin.js +19 -19
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
- Emit exact scalar `opencode.model` with source/provider evidence from verified
|
|
6
|
+
host model shapes. Preserve original nested fields and conflicting top-level
|
|
7
|
+
model values; omit blanks, placeholders and every JSON-prefixed ID, including
|
|
8
|
+
malformed/truncated strings.
|
|
9
|
+
- Correlate tool models through the exact session + callID -> messageID join.
|
|
10
|
+
Request evidence is scoped to its user message and agent, not reused as a
|
|
11
|
+
session default. Bounded caches handle switches, subagents, concurrent
|
|
12
|
+
sessions, out-of-order updates, stale state and end/removal events without
|
|
13
|
+
borrowing an unrelated model.
|
|
14
|
+
- Report assistant metadata as host selection, not proof of actual provider
|
|
15
|
+
response identity. Span counts, guard behavior and redaction remain unchanged;
|
|
16
|
+
model resolution adds no file scans, host commands or network requests.
|
|
17
|
+
- Pinta Manager 0.1.11 or later remains the guard-payload compatibility floor,
|
|
18
|
+
with `@pinta-ai/core` still at `^0.8.0` and no new required envelope fields or
|
|
19
|
+
additional desktop upgrade.
|
|
20
|
+
|
|
21
|
+
Refs PTA-524.
|
|
22
|
+
|
|
23
|
+
## 0.9.0
|
|
24
|
+
|
|
25
|
+
- The guard is asked about the span, not about a summary of it. Until now the
|
|
26
|
+
`PreToolUse` gate assembled a five-field `GuardInput` (tool name, input, working
|
|
27
|
+
directory, hook, session) beside the span that carried the same facts under
|
|
28
|
+
`opencode.*`, and the two were free to drift — `cwd` (PTA-176) and the hook name
|
|
29
|
+
(PTA-207) were on the span and missing from the summary. Now the span is built
|
|
30
|
+
first, `POST /guard/evaluate` receives that OTLP payload unwrapped, the manager
|
|
31
|
+
projects it through the same AgentEvent assembly the backend stores it with,
|
|
32
|
+
and the verdict is attached to the same object before it is relayed — the span
|
|
33
|
+
the manager judged is the span the backend stores, `spanId` included.
|
|
34
|
+
Requires Pinta Manager 0.1.11 or later; an older manager answers `400` and the
|
|
35
|
+
gate fails open (`pinta.guard.fail_open_reason: error`), a newer manager that
|
|
36
|
+
refuses a body answers `410`, recorded as `refused`.
|
|
37
|
+
- `@pinta-ai/core` `^0.6.0` → `^0.8.0` (`evaluateGuard(payload)`, `attachGuard`). `Telemetry.toolBeforePayload` builds the
|
|
38
|
+
span the guard is asked about; `Telemetry.send` relays it once the verdict is
|
|
39
|
+
attached.
|
|
40
|
+
|
|
41
|
+
## 0.8.0
|
|
42
|
+
|
|
43
|
+
- Version derived from one literal (`src/core/version.ts`); the build fails on drift.
|
|
44
|
+
|
|
3
45
|
## 0.7.0
|
|
4
46
|
|
|
5
47
|
- Telemetry now names each tool span by the opencode hook it arrived on
|
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ opencode fires plugin hooks around every built-in **and** MCP tool. This adapter
|
|
|
13
13
|
| Hook | Adapter does | Verified payload |
|
|
14
14
|
|---|---|---|
|
|
15
15
|
| `chat.message` | start a new trace (turn boundary) | `{ sessionID, agent, model, messageID, variant }` |
|
|
16
|
+
| `chat.params` | remember exact-message request model evidence (no extra span) | `{ sessionID, agent, model, message }` |
|
|
16
17
|
| `event` | lifecycle span (Bronze flatten) + flush on `session.idle` | `{ event: { id, type, properties } }`, every event carries `properties.sessionID` |
|
|
17
18
|
| `tool.execute.before` | query guard → **`throw` on DENY** + emit span | input `{ tool, sessionID, callID }`, output `{ args }` (full tool args, mutable) |
|
|
18
19
|
| `tool.execute.after` | tool-result span (incl. exit code) | output `{ title, output, metadata{ output, exit, truncated, … } }` |
|
|
@@ -63,14 +64,17 @@ Telemetry and governance are independent — endpoint only, guard only, both, or
|
|
|
63
64
|
|
|
64
65
|
## Guard (allow / deny + reason)
|
|
65
66
|
|
|
66
|
-
On `tool.execute.before` the adapter POSTs to the guard endpoint (cc/codex/copilot contract —
|
|
67
|
+
On `tool.execute.before` the adapter POSTs the span it is about to relay to the guard endpoint (cc/codex/copilot contract — `@pinta-ai/core` ≥ 0.8.0):
|
|
67
68
|
|
|
68
69
|
```
|
|
69
70
|
POST {guard} header: x-pinta-relay-token: {PINTA_RELAY_TOKEN}
|
|
70
|
-
body: { "
|
|
71
|
+
body: the OTLP payload itself — { "resourceSpans": [ … one span, opencode.* attributes … ] }
|
|
71
72
|
200: { "decision": "ALLOW"|"DENY"|"REVIEW", "reason", "userMessage?", "durationMs?" }
|
|
73
|
+
410: the manager no longer accepts this body shape → recorded as fail-open `refused`
|
|
72
74
|
```
|
|
73
75
|
|
|
76
|
+
The verdict is then attached to that same span (`pinta.guard.*`) before it is sent, so the span the guard judged is the span the collector stores.
|
|
77
|
+
|
|
74
78
|
A `DENY` becomes `throw new Error(userMessage ?? reason ?? "guard_deny")`. Verified effect: **only that tool is blocked** (`tool.execute.after` does not fire), the reason shows as `✗ … failed` + `Error: <reason>` to the model/TUI, and the session stays alive. `ALLOW`/`REVIEW` pass through.
|
|
75
79
|
|
|
76
80
|
Guard is **fail-open** (no endpoint / `PINTA_GUARD_DISABLED=1` / non-200 / timeout / error → allow), so it never breaks a session. The 50ms inline call does not block tool execution.
|
|
@@ -88,6 +92,59 @@ Guard is **fail-open** (no endpoint / `PINTA_GUARD_DISABLED=1` / non-200 / timeo
|
|
|
88
92
|
|
|
89
93
|
Tool spans are built from `tool.execute.before/after` (richer: args, output, exit) rather than the event bus; `event` covers lifecycle and turn boundaries.
|
|
90
94
|
|
|
95
|
+
### Model evidence and limits
|
|
96
|
+
|
|
97
|
+
`opencode.model` is the exact scalar ID, never a serialized model object or an
|
|
98
|
+
`unknown` default. `opencode.model_source` records its provenance;
|
|
99
|
+
`opencode.provider` carries an explicitly supplied `providerID`.
|
|
100
|
+
|
|
101
|
+
| Evidence | Source / scope |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| `message.updated.info.modelID` (assistant) | `reported:message.updated.info.modelID`; exact session + assistant message |
|
|
104
|
+
| `message.updated.info.model.{modelID,id}` (user) | `requested:message.updated.info.model.…` |
|
|
105
|
+
| `chat.message` output message model, then input model | `requested:chat.message.…`; fallback only for that **user message and agent**, never its subagents |
|
|
106
|
+
| `chat.params.model.{modelID,id}` | `requested:chat.params.model.…`; same user-message + agent constraint |
|
|
107
|
+
| `session.updated.info.model` / `session.next.model.switched.model` | `selected:<event>.<field>` on that event **only**, not a default for tools |
|
|
108
|
+
| Explicit tool or other event `model` / `modelID` | `reported:tool.…` / `reported:event.…` |
|
|
109
|
+
|
|
110
|
+
The [plugin callbacks](https://github.com/anomalyco/opencode/blob/v1.15.3/packages/plugin/src/index.ts)
|
|
111
|
+
and [message/part schemas](https://github.com/anomalyco/opencode/blob/v1.15.3/packages/opencode/src/session/message-v2.ts)
|
|
112
|
+
agree with this repository's `phase1-e2e-events.jsonl`. OpenCode constructs an
|
|
113
|
+
assistant's `modelID` from the selected model **before** the provider replies
|
|
114
|
+
([prompt implementation](https://github.com/anomalyco/opencode/blob/v1.15.3/packages/opencode/src/session/prompt.ts)).
|
|
115
|
+
It is therefore *reported selection*, **not proof of the actual routed
|
|
116
|
+
response model** (notably for routers and fallback providers).
|
|
117
|
+
|
|
118
|
+
Tool hooks carry only `sessionID` + `callID`. They receive a model only when a
|
|
119
|
+
`message.part.updated` tool part joins that exact call to a message with model
|
|
120
|
+
evidence in the same session. Correlated `opencode.message_id` and
|
|
121
|
+
`opencode.agent` accompany it when available. No latest-message/session default,
|
|
122
|
+
parent-message model, or subtask's requested model is borrowed. Out-of-order
|
|
123
|
+
messages can enrich a later after-span, not retroactively invent a before-span.
|
|
124
|
+
Conflicting identities/models, missing joins and expired evidence are omitted.
|
|
125
|
+
The before model is pinned to its call; switches do not relabel in-flight tools.
|
|
126
|
+
Message-part updates/deltas likewise use only their own message's evidence.
|
|
127
|
+
Consumed calls cannot be reopened by delayed part events. Idle (`session.idle`
|
|
128
|
+
or `session.status` with `status.type=idle`), deleted and errored sessions clear
|
|
129
|
+
their state; a new chat/session start reopens them.
|
|
130
|
+
|
|
131
|
+
Memory is per plugin instance, capped globally at **1,024 message entries,
|
|
132
|
+
512 request entries, 2,048 call entries, 128 ended-session markers**. Model,
|
|
133
|
+
request and call entries expire **15 minutes after their last update**.
|
|
134
|
+
Identifiers over 1,024 characters are not cached or treated as model IDs.
|
|
135
|
+
Normal resolution uses fixed field checks and O(1)
|
|
136
|
+
map lookups; end/removal cleanup examines at most those bounded maps. **No
|
|
137
|
+
added file reads, host commands, network requests, timers or spans.**
|
|
138
|
+
|
|
139
|
+
Blank/non-string/placeholder IDs (`unknown`, `undefined`, `null`, `n/a`, `none`,
|
|
140
|
+
`-`, `auto`, `default`) are omitted. Every trimmed ID beginning with `{` or `[`
|
|
141
|
+
is also omitted, including malformed/truncated JSON-like strings without a
|
|
142
|
+
closing brace. Objects/conflicting top-level model fields
|
|
143
|
+
remain in `opencode.model_raw`; raw nested `info` / `part` payloads stay intact.
|
|
144
|
+
Existing redaction, tool guards, event counts and errors are unchanged. A host
|
|
145
|
+
that never exposes a model (or its exact call/message relationship) stays
|
|
146
|
+
unattributed rather than being assigned an unrelated model.
|
|
147
|
+
|
|
91
148
|
## Architecture
|
|
92
149
|
|
|
93
150
|
```
|
package/dist/index.mjs
CHANGED
|
@@ -16,9 +16,9 @@ var __commonJS = (cb, mod) => function __require2() {
|
|
|
16
16
|
};
|
|
17
17
|
var __copyProps = (to, from, except, desc) => {
|
|
18
18
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
|
-
for (let
|
|
20
|
-
if (!__hasOwnProp.call(to,
|
|
21
|
-
__defProp(to,
|
|
19
|
+
for (let key2 of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key2) && key2 !== except)
|
|
21
|
+
__defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
|
|
22
22
|
}
|
|
23
23
|
return to;
|
|
24
24
|
};
|
|
@@ -189,6 +189,7 @@ var require_otlp = __commonJS({
|
|
|
189
189
|
exports.clientAttrs = clientAttrs;
|
|
190
190
|
exports.enforceSpanBudget = enforceSpanBudget;
|
|
191
191
|
exports.buildPayload = buildPayload2;
|
|
192
|
+
exports.attachGuard = attachGuard3;
|
|
192
193
|
exports.mergeBatch = mergeBatch2;
|
|
193
194
|
var crypto_1 = __importDefault(__require("crypto"));
|
|
194
195
|
var redact_js_1 = require_redact();
|
|
@@ -215,19 +216,19 @@ var require_otlp = __commonJS({
|
|
|
215
216
|
return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toLowerCase();
|
|
216
217
|
}
|
|
217
218
|
var EMPTY_SET = /* @__PURE__ */ new Set();
|
|
218
|
-
function maybeRedactString(
|
|
219
|
+
function maybeRedactString(key2, raw, policy) {
|
|
219
220
|
const truncated = (0, redact_js_1.truncate)(raw);
|
|
220
|
-
if ((policy.skipRedactKeys ?? EMPTY_SET).has(
|
|
221
|
+
if ((policy.skipRedactKeys ?? EMPTY_SET).has(key2))
|
|
221
222
|
return truncated;
|
|
222
|
-
const context = (policy.bashContextKeys ?? EMPTY_SET).has(
|
|
223
|
+
const context = (policy.bashContextKeys ?? EMPTY_SET).has(key2) ? "bash" : void 0;
|
|
223
224
|
return (0, redact_js_1.redact)(truncated, { context });
|
|
224
225
|
}
|
|
225
|
-
function toOtlpValue(
|
|
226
|
+
function toOtlpValue(key2, v, policy = {}) {
|
|
226
227
|
if (v === null || v === void 0)
|
|
227
228
|
return null;
|
|
228
229
|
switch (typeof v) {
|
|
229
230
|
case "string":
|
|
230
|
-
return { stringValue: maybeRedactString(
|
|
231
|
+
return { stringValue: maybeRedactString(key2, v, policy) };
|
|
231
232
|
case "boolean":
|
|
232
233
|
return { boolValue: v };
|
|
233
234
|
case "number":
|
|
@@ -236,22 +237,22 @@ var require_otlp = __commonJS({
|
|
|
236
237
|
return { doubleValue: v };
|
|
237
238
|
case "object":
|
|
238
239
|
try {
|
|
239
|
-
return { stringValue: maybeRedactString(
|
|
240
|
+
return { stringValue: maybeRedactString(key2, JSON.stringify(v), policy) };
|
|
240
241
|
} catch {
|
|
241
|
-
return { stringValue: maybeRedactString(
|
|
242
|
+
return { stringValue: maybeRedactString(key2, String(v), policy) };
|
|
242
243
|
}
|
|
243
244
|
default:
|
|
244
|
-
return { stringValue: maybeRedactString(
|
|
245
|
+
return { stringValue: maybeRedactString(key2, String(v), policy) };
|
|
245
246
|
}
|
|
246
247
|
}
|
|
247
|
-
function attrsFromRecord2(
|
|
248
|
+
function attrsFromRecord2(record2, prefix, policy = {}) {
|
|
248
249
|
const out = [];
|
|
249
|
-
for (const [k, v] of Object.entries(
|
|
250
|
-
const
|
|
251
|
-
const value = toOtlpValue(
|
|
250
|
+
for (const [k, v] of Object.entries(record2)) {
|
|
251
|
+
const key2 = `${prefix}.${k}`;
|
|
252
|
+
const value = toOtlpValue(key2, v, policy);
|
|
252
253
|
if (value === null)
|
|
253
254
|
continue;
|
|
254
|
-
out.push({ key, value });
|
|
255
|
+
out.push({ key: key2, value });
|
|
255
256
|
}
|
|
256
257
|
return out;
|
|
257
258
|
}
|
|
@@ -310,11 +311,6 @@ var require_otlp = __commonJS({
|
|
|
310
311
|
const ts = args.now ?? Date.now();
|
|
311
312
|
const tsNano = (BigInt(ts) * 1000000n).toString();
|
|
312
313
|
const attrs = [...args.attributes];
|
|
313
|
-
if (args.guard)
|
|
314
|
-
attrs.push(...guardAttrs(args.guard));
|
|
315
|
-
const timing = args.clientTiming ?? (args.guard?.clientRttMs !== void 0 ? { op: "guard", rttMs: args.guard.clientRttMs } : null);
|
|
316
|
-
if (timing)
|
|
317
|
-
attrs.push(...clientAttrs(timing));
|
|
318
314
|
const span = {
|
|
319
315
|
traceId: ulidToTraceId2(args.traceId),
|
|
320
316
|
spanId: newSpanId(),
|
|
@@ -325,7 +321,7 @@ var require_otlp = __commonJS({
|
|
|
325
321
|
attributes: attrs
|
|
326
322
|
};
|
|
327
323
|
enforceSpanBudget(span);
|
|
328
|
-
|
|
324
|
+
const payload = {
|
|
329
325
|
resourceSpans: [
|
|
330
326
|
{
|
|
331
327
|
resource: { attributes: args.resource },
|
|
@@ -333,6 +329,18 @@ var require_otlp = __commonJS({
|
|
|
333
329
|
}
|
|
334
330
|
]
|
|
335
331
|
};
|
|
332
|
+
return attachGuard3(payload, args.guard, args.clientTiming);
|
|
333
|
+
}
|
|
334
|
+
function attachGuard3(payload, guard, clientTiming) {
|
|
335
|
+
const span = payload.resourceSpans[0]?.scopeSpans[0]?.spans[0];
|
|
336
|
+
if (!span)
|
|
337
|
+
return payload;
|
|
338
|
+
if (guard)
|
|
339
|
+
span.attributes.push(...guardAttrs(guard));
|
|
340
|
+
const timing = clientTiming ?? (guard?.clientRttMs !== void 0 ? { op: "guard", rttMs: guard.clientRttMs } : null);
|
|
341
|
+
if (timing)
|
|
342
|
+
span.attributes.push(...clientAttrs(timing));
|
|
343
|
+
return payload;
|
|
336
344
|
}
|
|
337
345
|
function mergeBatch2(payloads) {
|
|
338
346
|
const out = [];
|
|
@@ -348,6 +356,7 @@ var require_guard = __commonJS({
|
|
|
348
356
|
"node_modules/@pinta-ai/core/dist/guard.js"(exports) {
|
|
349
357
|
"use strict";
|
|
350
358
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
359
|
+
exports.GUARD_STATUS_REFUSED = void 0;
|
|
351
360
|
exports.evaluateGuard = evaluateGuard2;
|
|
352
361
|
var DEFAULT_TIMEOUT_MS2 = 1e4;
|
|
353
362
|
var DEFAULT_UA = "pinta-core";
|
|
@@ -362,7 +371,8 @@ var require_guard = __commonJS({
|
|
|
362
371
|
};
|
|
363
372
|
}
|
|
364
373
|
var VALID_DECISIONS = /* @__PURE__ */ new Set(["ALLOW", "DENY", "REVIEW"]);
|
|
365
|
-
|
|
374
|
+
exports.GUARD_STATUS_REFUSED = 410;
|
|
375
|
+
async function evaluateGuard2(payload, endpoint, opts = {}) {
|
|
366
376
|
if (!endpoint)
|
|
367
377
|
return null;
|
|
368
378
|
if (opts.disabled)
|
|
@@ -377,11 +387,18 @@ var require_guard = __commonJS({
|
|
|
377
387
|
headers: {
|
|
378
388
|
"content-type": "application/json",
|
|
379
389
|
"user-agent": opts.userAgent ?? DEFAULT_UA,
|
|
380
|
-
"x-pinta-relay-token": opts.token ?? ""
|
|
390
|
+
"x-pinta-relay-token": opts.token ?? "",
|
|
391
|
+
// Spread rather than defaulted: an empty string is not "unknown" to the
|
|
392
|
+
// manager, it is a claim that fails validation, and sending one would
|
|
393
|
+
// make every adapter that never sets this look like it tried.
|
|
394
|
+
...opts.agentType ? { "x-pinta-agent-type": opts.agentType } : {}
|
|
381
395
|
},
|
|
382
|
-
body: JSON.stringify(
|
|
396
|
+
body: JSON.stringify(payload),
|
|
383
397
|
signal: controller.signal
|
|
384
398
|
});
|
|
399
|
+
if (res.status === exports.GUARD_STATUS_REFUSED) {
|
|
400
|
+
return failOpen2(Date.now() - start, "refused");
|
|
401
|
+
}
|
|
385
402
|
if (res.status !== 200) {
|
|
386
403
|
return failOpen2(Date.now() - start, "error");
|
|
387
404
|
}
|
|
@@ -907,19 +924,19 @@ var require_session_trace = __commonJS({
|
|
|
907
924
|
}
|
|
908
925
|
}
|
|
909
926
|
newTrace(sessionId) {
|
|
910
|
-
const
|
|
927
|
+
const key2 = sessionId || "default";
|
|
911
928
|
const traceId = (0, trace_js_1.generateUlid)();
|
|
912
929
|
const map = this.read();
|
|
913
|
-
map[
|
|
930
|
+
map[key2] = traceId;
|
|
914
931
|
this.write(map);
|
|
915
932
|
return traceId;
|
|
916
933
|
}
|
|
917
934
|
currentTrace(sessionId) {
|
|
918
|
-
const
|
|
919
|
-
const existing = this.read()[
|
|
935
|
+
const key2 = sessionId || "default";
|
|
936
|
+
const existing = this.read()[key2];
|
|
920
937
|
if (existing)
|
|
921
938
|
return existing;
|
|
922
|
-
return this.newTrace(
|
|
939
|
+
return this.newTrace(key2);
|
|
923
940
|
}
|
|
924
941
|
};
|
|
925
942
|
exports.DiskSessionTraceManager = DiskSessionTraceManager;
|
|
@@ -930,9 +947,9 @@ var require_session_trace = __commonJS({
|
|
|
930
947
|
this.maxSessions = opts.maxSessions ?? DEFAULT_MAX_SESSIONS;
|
|
931
948
|
}
|
|
932
949
|
newTrace(sessionId) {
|
|
933
|
-
const
|
|
950
|
+
const key2 = sessionId || "default";
|
|
934
951
|
const traceId = (0, trace_js_1.generateUlid)();
|
|
935
|
-
this.map.set(
|
|
952
|
+
this.map.set(key2, traceId);
|
|
936
953
|
if (this.map.size > this.maxSessions) {
|
|
937
954
|
const oldest = this.map.keys().next().value;
|
|
938
955
|
if (oldest !== void 0)
|
|
@@ -941,8 +958,8 @@ var require_session_trace = __commonJS({
|
|
|
941
958
|
return traceId;
|
|
942
959
|
}
|
|
943
960
|
currentTrace(sessionId) {
|
|
944
|
-
const
|
|
945
|
-
return this.map.get(
|
|
961
|
+
const key2 = sessionId || "default";
|
|
962
|
+
return this.map.get(key2) ?? this.newTrace(key2);
|
|
946
963
|
}
|
|
947
964
|
};
|
|
948
965
|
exports.MemorySessionTraceManager = MemorySessionTraceManager2;
|
|
@@ -977,13 +994,13 @@ var require_env_file = __commonJS({
|
|
|
977
994
|
const idx = line.indexOf("=");
|
|
978
995
|
if (idx < 0)
|
|
979
996
|
continue;
|
|
980
|
-
const
|
|
997
|
+
const key2 = line.slice(0, idx).trim();
|
|
981
998
|
let value = line.slice(idx + 1).trim();
|
|
982
999
|
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
983
1000
|
value = value.slice(1, -1);
|
|
984
1001
|
}
|
|
985
|
-
if (
|
|
986
|
-
out[
|
|
1002
|
+
if (key2)
|
|
1003
|
+
out[key2] = value;
|
|
987
1004
|
}
|
|
988
1005
|
return out;
|
|
989
1006
|
}
|
|
@@ -995,9 +1012,9 @@ var require_env_file = __commonJS({
|
|
|
995
1012
|
return;
|
|
996
1013
|
}
|
|
997
1014
|
const parsed = parseEnvFile2(content);
|
|
998
|
-
for (const [
|
|
999
|
-
if (process.env[
|
|
1000
|
-
process.env[
|
|
1015
|
+
for (const [key2, value] of Object.entries(parsed)) {
|
|
1016
|
+
if (process.env[key2] === void 0) {
|
|
1017
|
+
process.env[key2] = value;
|
|
1001
1018
|
}
|
|
1002
1019
|
}
|
|
1003
1020
|
}
|
|
@@ -1009,7 +1026,7 @@ var require_dist = __commonJS({
|
|
|
1009
1026
|
"node_modules/@pinta-ai/core/dist/index.js"(exports) {
|
|
1010
1027
|
"use strict";
|
|
1011
1028
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1012
|
-
exports.loadEnvFile = exports.parseEnvFile = exports.envFilePath = exports.MemorySessionTraceManager = exports.DiskSessionTraceManager = exports.generateUlid = exports.TraceManager = exports.MAX_POST_BYTES = exports.parseHeadersEnv = exports.envOptionsResolver = exports.postOtlp = exports.MemoryTransport = exports.DiskTransport = exports.MemoryRetryQueue = exports.DiskRetryQueue = exports.evaluateGuard = exports.MAX_SPAN_BYTES = exports.enforceSpanBudget = exports.mergeBatch = exports.buildPayload = exports.clientAttrs = exports.guardAttrs = exports.attrsFromRecord = exports.toOtlpValue = exports.snakeCase = exports.newSpanId = exports.ulidToTraceId = exports.MAX_BYTES = exports.PATTERNS = exports.applyMatches = exports.resolveOverlaps = exports.collectMatches = exports.truncateTo = exports.truncate = exports.redact = void 0;
|
|
1029
|
+
exports.loadEnvFile = exports.parseEnvFile = exports.envFilePath = exports.MemorySessionTraceManager = exports.DiskSessionTraceManager = exports.generateUlid = exports.TraceManager = exports.MAX_POST_BYTES = exports.parseHeadersEnv = exports.envOptionsResolver = exports.postOtlp = exports.MemoryTransport = exports.DiskTransport = exports.MemoryRetryQueue = exports.DiskRetryQueue = exports.GUARD_STATUS_REFUSED = exports.evaluateGuard = exports.MAX_SPAN_BYTES = exports.enforceSpanBudget = exports.mergeBatch = exports.attachGuard = exports.buildPayload = exports.clientAttrs = exports.guardAttrs = exports.attrsFromRecord = exports.toOtlpValue = exports.snakeCase = exports.newSpanId = exports.ulidToTraceId = exports.MAX_BYTES = exports.PATTERNS = exports.applyMatches = exports.resolveOverlaps = exports.collectMatches = exports.truncateTo = exports.truncate = exports.redact = void 0;
|
|
1013
1030
|
var redact_js_1 = require_redact();
|
|
1014
1031
|
Object.defineProperty(exports, "redact", { enumerable: true, get: function() {
|
|
1015
1032
|
return redact_js_1.redact;
|
|
@@ -1060,6 +1077,9 @@ var require_dist = __commonJS({
|
|
|
1060
1077
|
Object.defineProperty(exports, "buildPayload", { enumerable: true, get: function() {
|
|
1061
1078
|
return otlp_js_1.buildPayload;
|
|
1062
1079
|
} });
|
|
1080
|
+
Object.defineProperty(exports, "attachGuard", { enumerable: true, get: function() {
|
|
1081
|
+
return otlp_js_1.attachGuard;
|
|
1082
|
+
} });
|
|
1063
1083
|
Object.defineProperty(exports, "mergeBatch", { enumerable: true, get: function() {
|
|
1064
1084
|
return otlp_js_1.mergeBatch;
|
|
1065
1085
|
} });
|
|
@@ -1073,6 +1093,9 @@ var require_dist = __commonJS({
|
|
|
1073
1093
|
Object.defineProperty(exports, "evaluateGuard", { enumerable: true, get: function() {
|
|
1074
1094
|
return guard_js_1.evaluateGuard;
|
|
1075
1095
|
} });
|
|
1096
|
+
Object.defineProperty(exports, "GUARD_STATUS_REFUSED", { enumerable: true, get: function() {
|
|
1097
|
+
return guard_js_1.GUARD_STATUS_REFUSED;
|
|
1098
|
+
} });
|
|
1076
1099
|
var retry_queue_js_1 = require_retry_queue();
|
|
1077
1100
|
Object.defineProperty(exports, "DiskRetryQueue", { enumerable: true, get: function() {
|
|
1078
1101
|
return retry_queue_js_1.DiskRetryQueue;
|
|
@@ -1212,12 +1235,20 @@ var Transport = class extends import_core3.MemoryTransport {
|
|
|
1212
1235
|
// src/core/trace.ts
|
|
1213
1236
|
var import_core4 = __toESM(require_dist(), 1);
|
|
1214
1237
|
|
|
1238
|
+
// src/plugin.ts
|
|
1239
|
+
var import_core8 = __toESM(require_dist(), 1);
|
|
1240
|
+
|
|
1215
1241
|
// src/core/guard.ts
|
|
1216
1242
|
var import_core5 = __toESM(require_dist(), 1);
|
|
1243
|
+
|
|
1244
|
+
// src/core/version.ts
|
|
1245
|
+
var ADAPTER_VERSION = "0.10.0";
|
|
1246
|
+
|
|
1247
|
+
// src/core/guard.ts
|
|
1217
1248
|
var DEFAULT_TIMEOUT_MS = 50;
|
|
1218
|
-
var GUARD_UA =
|
|
1219
|
-
function evaluateGuard(
|
|
1220
|
-
return (0, import_core5.evaluateGuard)(
|
|
1249
|
+
var GUARD_UA = `pinta-opencode/${ADAPTER_VERSION}`;
|
|
1250
|
+
function evaluateGuard(payload, endpoint, opts = {}) {
|
|
1251
|
+
return (0, import_core5.evaluateGuard)(payload, endpoint, {
|
|
1221
1252
|
timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
1222
1253
|
token: opts.token,
|
|
1223
1254
|
disabled: opts.disabled,
|
|
@@ -1225,9 +1256,12 @@ function evaluateGuard(input, endpoint, opts = {}) {
|
|
|
1225
1256
|
});
|
|
1226
1257
|
}
|
|
1227
1258
|
|
|
1259
|
+
// src/telemetry.ts
|
|
1260
|
+
var import_core7 = __toESM(require_dist(), 1);
|
|
1261
|
+
|
|
1228
1262
|
// src/core/otlp.ts
|
|
1229
|
-
var import_core6 = __toESM(require_dist(), 1);
|
|
1230
1263
|
import os from "os";
|
|
1264
|
+
var import_core6 = __toESM(require_dist(), 1);
|
|
1231
1265
|
var cachedProcessOwner;
|
|
1232
1266
|
function processOwner() {
|
|
1233
1267
|
if (cachedProcessOwner === void 0) {
|
|
@@ -1239,7 +1273,7 @@ function processOwner() {
|
|
|
1239
1273
|
}
|
|
1240
1274
|
return cachedProcessOwner;
|
|
1241
1275
|
}
|
|
1242
|
-
var SDK_VERSION =
|
|
1276
|
+
var SDK_VERSION = ADAPTER_VERSION;
|
|
1243
1277
|
var SKIP_REDACT_KEYS = /* @__PURE__ */ new Set([
|
|
1244
1278
|
"opencode.hook",
|
|
1245
1279
|
"opencode.event_type",
|
|
@@ -1289,11 +1323,262 @@ function buildOtlpPayload(args) {
|
|
|
1289
1323
|
attributes: flattenFields(args.fields),
|
|
1290
1324
|
resource: resourceAttrs(args.serviceVersion),
|
|
1291
1325
|
scope: { name: "pinta-opencode", version: SDK_VERSION },
|
|
1292
|
-
now: args.now
|
|
1293
|
-
guard: args.guard
|
|
1326
|
+
now: args.now
|
|
1294
1327
|
});
|
|
1295
1328
|
}
|
|
1296
1329
|
|
|
1330
|
+
// src/model.ts
|
|
1331
|
+
var MODEL_CACHE_LIMITS = {
|
|
1332
|
+
messages: 1024,
|
|
1333
|
+
requests: 512,
|
|
1334
|
+
calls: 2048,
|
|
1335
|
+
endedSessions: 128,
|
|
1336
|
+
ttlMs: 15 * 60 * 1e3
|
|
1337
|
+
};
|
|
1338
|
+
var PLACEHOLDERS = /* @__PURE__ */ new Set([
|
|
1339
|
+
"unknown",
|
|
1340
|
+
"undefined",
|
|
1341
|
+
"null",
|
|
1342
|
+
"n/a",
|
|
1343
|
+
"none",
|
|
1344
|
+
"-",
|
|
1345
|
+
"auto",
|
|
1346
|
+
"default"
|
|
1347
|
+
]);
|
|
1348
|
+
function identifier(value) {
|
|
1349
|
+
if (typeof value !== "string") return void 0;
|
|
1350
|
+
const text = value.trim();
|
|
1351
|
+
return text && text.length <= 1024 ? text : void 0;
|
|
1352
|
+
}
|
|
1353
|
+
function modelId(value) {
|
|
1354
|
+
const id = identifier(value);
|
|
1355
|
+
if (!id || id.startsWith("{") || id.startsWith("[") || PLACEHOLDERS.has(id.toLowerCase())) return void 0;
|
|
1356
|
+
return id;
|
|
1357
|
+
}
|
|
1358
|
+
function record(value) {
|
|
1359
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1360
|
+
}
|
|
1361
|
+
function fromModel(value, source, provider) {
|
|
1362
|
+
const obj = record(value);
|
|
1363
|
+
const model = obj ? modelId(obj.modelID) ?? modelId(obj.id) : modelId(value);
|
|
1364
|
+
if (!model) return void 0;
|
|
1365
|
+
return {
|
|
1366
|
+
model,
|
|
1367
|
+
source: obj ? `${source}.${modelId(obj.modelID) ? "modelID" : "id"}` : source,
|
|
1368
|
+
provider: modelId(obj?.providerID ?? provider)
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
function key(...parts) {
|
|
1372
|
+
return JSON.stringify(parts);
|
|
1373
|
+
}
|
|
1374
|
+
function agreed(...values) {
|
|
1375
|
+
const ids = values.map(identifier).filter((id) => id !== void 0);
|
|
1376
|
+
return ids.every((id) => id === ids[0]) ? ids[0] : void 0;
|
|
1377
|
+
}
|
|
1378
|
+
function eventSessionID(type, props) {
|
|
1379
|
+
const info = record(props.info);
|
|
1380
|
+
const part = record(props.part);
|
|
1381
|
+
return typeof type === "string" && type.startsWith("session.") ? agreed(props.sessionID, info?.id) : agreed(props.sessionID, info?.sessionID, part?.sessionID);
|
|
1382
|
+
}
|
|
1383
|
+
var BoundedCache = class {
|
|
1384
|
+
constructor(limit, now) {
|
|
1385
|
+
this.limit = limit;
|
|
1386
|
+
this.now = now;
|
|
1387
|
+
}
|
|
1388
|
+
entries = /* @__PURE__ */ new Map();
|
|
1389
|
+
get(id) {
|
|
1390
|
+
const entry = this.entries.get(id);
|
|
1391
|
+
if (entry && entry.expires > this.now()) return entry.value;
|
|
1392
|
+
this.entries.delete(id);
|
|
1393
|
+
return void 0;
|
|
1394
|
+
}
|
|
1395
|
+
set(id, value) {
|
|
1396
|
+
this.entries.delete(id);
|
|
1397
|
+
this.entries.set(id, { value, expires: this.now() + MODEL_CACHE_LIMITS.ttlMs });
|
|
1398
|
+
if (this.entries.size > this.limit) this.entries.delete(this.entries.keys().next().value);
|
|
1399
|
+
}
|
|
1400
|
+
removeWhere(predicate) {
|
|
1401
|
+
for (const [id, entry] of this.entries) {
|
|
1402
|
+
if (predicate(entry.value)) this.entries.delete(id);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
clearSession(sessionID) {
|
|
1406
|
+
this.removeWhere((entry) => entry.sessionID === sessionID);
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
var ModelTracker = class {
|
|
1410
|
+
constructor(now = Date.now) {
|
|
1411
|
+
this.now = now;
|
|
1412
|
+
this.messages = new BoundedCache(MODEL_CACHE_LIMITS.messages, now);
|
|
1413
|
+
this.requests = new BoundedCache(MODEL_CACHE_LIMITS.requests, now);
|
|
1414
|
+
this.calls = new BoundedCache(MODEL_CACHE_LIMITS.calls, now);
|
|
1415
|
+
}
|
|
1416
|
+
messages;
|
|
1417
|
+
requests;
|
|
1418
|
+
calls;
|
|
1419
|
+
ended = /* @__PURE__ */ new Map();
|
|
1420
|
+
chatMessage(input, output) {
|
|
1421
|
+
const sessionID = agreed(input.sessionID, output?.message?.sessionID);
|
|
1422
|
+
if (!sessionID) return;
|
|
1423
|
+
this.ended.delete(sessionID);
|
|
1424
|
+
const messageID = agreed(input.messageID, output?.message?.id);
|
|
1425
|
+
if (!messageID) return;
|
|
1426
|
+
const agent = identifier(output?.message?.agent ?? input.agent);
|
|
1427
|
+
const supplied = output?.message?.model !== void 0;
|
|
1428
|
+
this.requests.set(key(sessionID, messageID, agent ?? ""), {
|
|
1429
|
+
sessionID,
|
|
1430
|
+
messageID,
|
|
1431
|
+
evidence: fromModel(
|
|
1432
|
+
supplied ? output.message?.model : input.model,
|
|
1433
|
+
supplied ? "requested:chat.message.message.model" : "requested:chat.message.model"
|
|
1434
|
+
)
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
chatParams(input) {
|
|
1438
|
+
const sessionID = agreed(input.sessionID, input.message?.sessionID);
|
|
1439
|
+
const messageID = identifier(input.message?.id);
|
|
1440
|
+
const agent = identifier(input.agent);
|
|
1441
|
+
if (!sessionID || !messageID || !agent || this.ended.has(sessionID)) return;
|
|
1442
|
+
this.requests.set(key(sessionID, messageID, agent), {
|
|
1443
|
+
sessionID,
|
|
1444
|
+
messageID,
|
|
1445
|
+
evidence: fromModel(input.model, "requested:chat.params.model")
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
fresh(timestamp) {
|
|
1449
|
+
return timestamp === void 0 || typeof timestamp === "number" && Number.isFinite(timestamp) && timestamp >= this.now() - MODEL_CACHE_LIMITS.ttlMs;
|
|
1450
|
+
}
|
|
1451
|
+
clearSession(sessionID) {
|
|
1452
|
+
this.messages.clearSession(sessionID);
|
|
1453
|
+
this.requests.clearSession(sessionID);
|
|
1454
|
+
this.calls.clearSession(sessionID);
|
|
1455
|
+
}
|
|
1456
|
+
event(type, props) {
|
|
1457
|
+
const sessionID = eventSessionID(type, props);
|
|
1458
|
+
const info = record(props.info);
|
|
1459
|
+
const part = record(props.part);
|
|
1460
|
+
const ended = ["session.idle", "session.deleted", "session.error"].includes(type ?? "") || type === "session.status" && record(props.status)?.type === "idle";
|
|
1461
|
+
if (sessionID && ended) {
|
|
1462
|
+
this.clearSession(sessionID);
|
|
1463
|
+
this.ended.delete(sessionID);
|
|
1464
|
+
this.ended.set(sessionID, true);
|
|
1465
|
+
if (this.ended.size > MODEL_CACHE_LIMITS.endedSessions) this.ended.delete(this.ended.keys().next().value);
|
|
1466
|
+
} else if (sessionID && type === "session.created") {
|
|
1467
|
+
this.clearSession(sessionID);
|
|
1468
|
+
this.ended.delete(sessionID);
|
|
1469
|
+
}
|
|
1470
|
+
if (type === "message.updated" && info) {
|
|
1471
|
+
if (!agreed(props.sessionID, info.sessionID) && (props.sessionID || info.sessionID)) return void 0;
|
|
1472
|
+
const messageID = identifier(info.id);
|
|
1473
|
+
const agent = identifier(info.agent);
|
|
1474
|
+
const role = info.role;
|
|
1475
|
+
let evidence = role === "assistant" ? fromModel(modelId(info.modelID), "reported:message.updated.info.modelID", info.providerID) ?? fromModel(info.model, "reported:message.updated.info.model") : role === "user" ? fromModel(info.model, "requested:message.updated.info.model") : void 0;
|
|
1476
|
+
if (!evidence && role === "user" && !("model" in info) && sessionID && messageID) {
|
|
1477
|
+
evidence = this.requests.get(key(sessionID, messageID, agent ?? ""))?.evidence;
|
|
1478
|
+
}
|
|
1479
|
+
if (role === "user" && "model" in info && !evidence && sessionID && messageID) {
|
|
1480
|
+
this.requests.set(key(sessionID, messageID, agent ?? ""), { sessionID, messageID });
|
|
1481
|
+
}
|
|
1482
|
+
if (evidence) evidence = { ...evidence, agent, messageID };
|
|
1483
|
+
if (sessionID && messageID && !this.ended.has(sessionID) && this.fresh(record(info.time)?.created)) {
|
|
1484
|
+
const old = this.messages.get(key(sessionID, messageID));
|
|
1485
|
+
const ambiguous = old?.ambiguous || Boolean(role === "assistant" && old?.evidence && evidence && (old.evidence.model !== evidence.model || old.evidence.provider !== evidence.provider || old.evidence.agent !== evidence.agent));
|
|
1486
|
+
this.messages.set(key(sessionID, messageID), {
|
|
1487
|
+
sessionID,
|
|
1488
|
+
messageID,
|
|
1489
|
+
evidence: ambiguous ? void 0 : evidence,
|
|
1490
|
+
ambiguous
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
return evidence;
|
|
1494
|
+
}
|
|
1495
|
+
if (sessionID && type === "message.removed") {
|
|
1496
|
+
const messageID = identifier(props.messageID);
|
|
1497
|
+
this.messages.removeWhere((entry) => entry.sessionID === sessionID && entry.messageID === messageID);
|
|
1498
|
+
this.requests.removeWhere((entry) => entry.sessionID === sessionID && entry.messageID === messageID);
|
|
1499
|
+
this.calls.removeWhere((entry) => entry.sessionID === sessionID && entry.messageID === messageID);
|
|
1500
|
+
}
|
|
1501
|
+
if (sessionID && type === "message.part.removed") {
|
|
1502
|
+
const partID = identifier(props.partID);
|
|
1503
|
+
if (partID) this.calls.removeWhere((entry) => entry.sessionID === sessionID && entry.partID === partID);
|
|
1504
|
+
}
|
|
1505
|
+
if (type === "message.part.delta") {
|
|
1506
|
+
const messageID = identifier(props.messageID);
|
|
1507
|
+
return sessionID && messageID && !this.ended.has(sessionID) ? this.messages.get(key(sessionID, messageID))?.evidence : void 0;
|
|
1508
|
+
}
|
|
1509
|
+
if (type === "message.part.updated" && part) {
|
|
1510
|
+
if (!sessionID || this.ended.has(sessionID) || !this.fresh(props.time)) return void 0;
|
|
1511
|
+
const messageID = identifier(part.messageID);
|
|
1512
|
+
if (!messageID) return void 0;
|
|
1513
|
+
const callID = identifier(part.callID);
|
|
1514
|
+
if (part.type === "tool" && callID) {
|
|
1515
|
+
const callKey = key(sessionID, callID);
|
|
1516
|
+
const old = this.calls.get(callKey);
|
|
1517
|
+
if (!old?.closed) {
|
|
1518
|
+
const tool = identifier(part.tool);
|
|
1519
|
+
this.calls.set(callKey, {
|
|
1520
|
+
sessionID,
|
|
1521
|
+
messageID,
|
|
1522
|
+
partID: identifier(part.id),
|
|
1523
|
+
tool,
|
|
1524
|
+
pinned: old?.pinned,
|
|
1525
|
+
ambiguous: old?.ambiguous || Boolean(old && (old.messageID !== messageID || old.tool !== tool))
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
return this.messages.get(key(sessionID, messageID))?.evidence;
|
|
1530
|
+
}
|
|
1531
|
+
if (typeof type === "string" && type.startsWith("session.")) {
|
|
1532
|
+
return fromModel(info?.model, `selected:${type}.info.model`) ?? fromModel(props.model, `selected:${type}.model`);
|
|
1533
|
+
}
|
|
1534
|
+
return fromModel(props.model, "reported:event.model") ?? fromModel(modelId(props.modelID), "reported:event.modelID", props.providerID);
|
|
1535
|
+
}
|
|
1536
|
+
tool(input, after) {
|
|
1537
|
+
const direct = fromModel(modelId(input.modelID), "reported:tool.modelID", input.providerID) ?? fromModel(input.model, "reported:tool.model", input.providerID);
|
|
1538
|
+
const sessionID = identifier(input.sessionID);
|
|
1539
|
+
const callID = identifier(input.callID);
|
|
1540
|
+
if (!sessionID || !callID) return direct;
|
|
1541
|
+
const callKey = key(sessionID, callID);
|
|
1542
|
+
const call = this.calls.get(callKey);
|
|
1543
|
+
let evidence = direct;
|
|
1544
|
+
if (!("model" in input) && !("modelID" in input) && call && !call.closed && !call.ambiguous && !this.ended.has(sessionID) && (!call.tool || call.tool === input.tool)) {
|
|
1545
|
+
const message = this.messages.get(key(sessionID, call.messageID));
|
|
1546
|
+
if (!message?.ambiguous) evidence = call.pinned ?? message?.evidence;
|
|
1547
|
+
if (input.agent && evidence?.agent && input.agent !== evidence.agent || input.messageID && input.messageID !== call.messageID) evidence = void 0;
|
|
1548
|
+
}
|
|
1549
|
+
if (call && !call.closed) {
|
|
1550
|
+
this.calls.set(callKey, {
|
|
1551
|
+
...call,
|
|
1552
|
+
pinned: after ? void 0 : evidence,
|
|
1553
|
+
closed: after
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
return evidence;
|
|
1557
|
+
}
|
|
1558
|
+
beforeTool(input) {
|
|
1559
|
+
return this.tool(input, false);
|
|
1560
|
+
}
|
|
1561
|
+
afterTool(input) {
|
|
1562
|
+
return this.tool(input, true);
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
function modelFields(fields, evidence) {
|
|
1566
|
+
const { model: rawModel, model_source: rawSource, ...out } = fields;
|
|
1567
|
+
if ("model" in fields && rawModel !== evidence?.model) out.model_raw = rawModel;
|
|
1568
|
+
if ("model_source" in fields) out.model_source_raw = rawSource;
|
|
1569
|
+
if (evidence) {
|
|
1570
|
+
out.model = evidence.model;
|
|
1571
|
+
out.model_source = evidence.source;
|
|
1572
|
+
if (evidence.provider) {
|
|
1573
|
+
if ("provider" in out && out.provider !== evidence.provider) out.provider_raw = out.provider;
|
|
1574
|
+
out.provider = evidence.provider;
|
|
1575
|
+
}
|
|
1576
|
+
if (out.agent === void 0) out.agent = evidence.agent;
|
|
1577
|
+
if (out.message_id === void 0) out.message_id = evidence.messageID;
|
|
1578
|
+
}
|
|
1579
|
+
return out;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1297
1582
|
// src/telemetry.ts
|
|
1298
1583
|
var Telemetry = class {
|
|
1299
1584
|
constructor(transport, trace, config) {
|
|
@@ -1301,43 +1586,68 @@ var Telemetry = class {
|
|
|
1301
1586
|
this.trace = trace;
|
|
1302
1587
|
this.config = config;
|
|
1303
1588
|
}
|
|
1304
|
-
|
|
1589
|
+
models = new ModelTracker();
|
|
1590
|
+
build(name, sessionId, fields) {
|
|
1305
1591
|
const traceId = this.trace.currentTrace(sessionId);
|
|
1306
|
-
|
|
1592
|
+
return buildOtlpPayload({ name, traceId, fields, serviceVersion: this.config.serviceVersion });
|
|
1593
|
+
}
|
|
1594
|
+
/** Send a payload built by one of the `*Payload` methods (best-effort by the transport's contract). */
|
|
1595
|
+
async send(payload) {
|
|
1307
1596
|
await this.transport.send(payload);
|
|
1308
1597
|
}
|
|
1598
|
+
async emit(name, sessionId, fields) {
|
|
1599
|
+
await this.send(this.build(name, sessionId, fields));
|
|
1600
|
+
}
|
|
1601
|
+
chatMessage(input, output) {
|
|
1602
|
+
this.models.chatMessage(input, output);
|
|
1603
|
+
}
|
|
1604
|
+
chatParams(input) {
|
|
1605
|
+
this.models.chatParams(input);
|
|
1606
|
+
}
|
|
1309
1607
|
/** Lifecycle span from the `event` hook. Flushes the retry buffer on turn-END. */
|
|
1310
1608
|
async lifecycle(ev) {
|
|
1311
|
-
const props = ev.properties ?? {};
|
|
1312
|
-
const sessionId =
|
|
1313
|
-
|
|
1609
|
+
const props = record(ev.properties) ?? {};
|
|
1610
|
+
const sessionId = eventSessionID(ev.type, props);
|
|
1611
|
+
const model = this.models.event(ev.type, props);
|
|
1612
|
+
await this.emit(`opencode.event.${ev.type ?? "unknown"}`, sessionId, modelFields({
|
|
1314
1613
|
hook: "event",
|
|
1315
1614
|
event_type: ev.type,
|
|
1316
1615
|
session_id: sessionId,
|
|
1317
1616
|
cwd: process.cwd(),
|
|
1318
1617
|
...props
|
|
1319
|
-
});
|
|
1618
|
+
}, model));
|
|
1320
1619
|
if (ev.type === "session.idle") await this.transport.flush();
|
|
1321
1620
|
}
|
|
1322
|
-
/**
|
|
1323
|
-
|
|
1324
|
-
|
|
1621
|
+
/**
|
|
1622
|
+
* Tool span from `tool.execute.before`, before the guard has been asked. The
|
|
1623
|
+
* plugin asks the guard about this payload, attaches the verdict with
|
|
1624
|
+
* `attachGuard`, and sends the same object — so the span the manager judged
|
|
1625
|
+
* is the span the backend stores.
|
|
1626
|
+
*/
|
|
1627
|
+
toolBeforePayload(input, args) {
|
|
1628
|
+
return this.build(
|
|
1325
1629
|
"opencode.tool.before",
|
|
1326
1630
|
input.sessionID,
|
|
1327
|
-
|
|
1328
|
-
|
|
1631
|
+
modelFields(
|
|
1632
|
+
{ ...toolIdentity("tool.execute.before", input), tool_input: args },
|
|
1633
|
+
this.models.beforeTool(input)
|
|
1634
|
+
)
|
|
1329
1635
|
);
|
|
1330
1636
|
}
|
|
1637
|
+
/** Tool span from `tool.execute.before`, carrying an already-known guard decision. */
|
|
1638
|
+
async toolBefore(input, args, guard) {
|
|
1639
|
+
await this.send((0, import_core7.attachGuard)(this.toolBeforePayload(input, args), guard));
|
|
1640
|
+
}
|
|
1331
1641
|
/** Tool result span from `tool.execute.after`, incl. exit code / truncation. */
|
|
1332
1642
|
async toolAfter(input, output) {
|
|
1333
1643
|
const meta = output.metadata ?? {};
|
|
1334
|
-
await this.emit("opencode.tool.after", input.sessionID, {
|
|
1644
|
+
await this.emit("opencode.tool.after", input.sessionID, modelFields({
|
|
1335
1645
|
...toolIdentity("tool.execute.after", input),
|
|
1336
1646
|
title: output.title,
|
|
1337
1647
|
tool_response: output.output,
|
|
1338
1648
|
exit: meta.exit,
|
|
1339
1649
|
truncated: meta.truncated
|
|
1340
|
-
});
|
|
1650
|
+
}, this.models.afterTool(input)));
|
|
1341
1651
|
}
|
|
1342
1652
|
};
|
|
1343
1653
|
function toolIdentity(hook, input) {
|
|
@@ -1346,7 +1656,12 @@ function toolIdentity(hook, input) {
|
|
|
1346
1656
|
tool_name: input.tool,
|
|
1347
1657
|
session_id: input.sessionID,
|
|
1348
1658
|
tool_use_id: input.callID,
|
|
1349
|
-
cwd: process.cwd()
|
|
1659
|
+
cwd: process.cwd(),
|
|
1660
|
+
...input.model !== void 0 ? { model: input.model } : {},
|
|
1661
|
+
...input.modelID !== void 0 ? { modelID: input.modelID } : {},
|
|
1662
|
+
...input.providerID !== void 0 ? { providerID: input.providerID } : {},
|
|
1663
|
+
...input.agent !== void 0 ? { agent: input.agent } : {},
|
|
1664
|
+
...input.messageID !== void 0 ? { message_id: input.messageID } : {}
|
|
1350
1665
|
};
|
|
1351
1666
|
}
|
|
1352
1667
|
|
|
@@ -1372,19 +1687,13 @@ var PintaOpencode = async (_input, options) => {
|
|
|
1372
1687
|
async function guardAndTrace(input, args) {
|
|
1373
1688
|
let guard = null;
|
|
1374
1689
|
try {
|
|
1690
|
+
const payload = telemetry.toolBeforePayload(input, args);
|
|
1375
1691
|
guard = await evaluateGuard(
|
|
1376
|
-
|
|
1377
|
-
spanId: input.sessionID,
|
|
1378
|
-
toolName: input.tool,
|
|
1379
|
-
method: "tool.execute.before",
|
|
1380
|
-
cwd: process.cwd(),
|
|
1381
|
-
toolInput: args,
|
|
1382
|
-
rawTextFields: { toolInput: safeStringify(args) }
|
|
1383
|
-
},
|
|
1692
|
+
payload,
|
|
1384
1693
|
config.guardEndpoint,
|
|
1385
1694
|
{ timeoutMs: config.guardTimeoutMs, token: config.relayToken, disabled: config.guardDisabled }
|
|
1386
1695
|
);
|
|
1387
|
-
await telemetry.
|
|
1696
|
+
await telemetry.send((0, import_core8.attachGuard)(payload, guard));
|
|
1388
1697
|
} catch (err) {
|
|
1389
1698
|
warn("tool.execute.before", err);
|
|
1390
1699
|
}
|
|
@@ -1392,8 +1701,12 @@ var PintaOpencode = async (_input, options) => {
|
|
|
1392
1701
|
}
|
|
1393
1702
|
return {
|
|
1394
1703
|
// turn-START → rotate a new trace for this session.
|
|
1395
|
-
"chat.message": failOpen("chat.message", async (input) => {
|
|
1704
|
+
"chat.message": failOpen("chat.message", async (input, output) => {
|
|
1396
1705
|
trace.newTrace(input?.sessionID);
|
|
1706
|
+
telemetry.chatMessage(input, output);
|
|
1707
|
+
}),
|
|
1708
|
+
"chat.params": failOpen("chat.params", async (input, _output) => {
|
|
1709
|
+
telemetry.chatParams(input);
|
|
1397
1710
|
}),
|
|
1398
1711
|
// lifecycle telemetry; flushes the retry buffer on session.idle (turn-END).
|
|
1399
1712
|
event: failOpen("event", async (input) => {
|
|
@@ -1414,13 +1727,6 @@ var PintaOpencode = async (_input, options) => {
|
|
|
1414
1727
|
)
|
|
1415
1728
|
};
|
|
1416
1729
|
};
|
|
1417
|
-
function safeStringify(v) {
|
|
1418
|
-
try {
|
|
1419
|
-
return typeof v === "string" ? v : JSON.stringify(v) ?? "";
|
|
1420
|
-
} catch {
|
|
1421
|
-
return String(v);
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
1730
|
var plugin_default = PintaOpencode;
|
|
1425
1731
|
|
|
1426
1732
|
// src/lifecycle/scanner.ts
|
package/dist/plugin.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { createRequire as __pintaCreateRequire } from 'module'; const require = __pintaCreateRequire(import.meta.url);
|
|
2
|
-
var
|
|
3
|
-
`;
|
|
4
|
-
`)}}readAll(){try{let e=
|
|
5
|
-
`))if(n.trim())try{let
|
|
2
|
+
var _t=Object.create;var Pe=Object.defineProperty;var vt=Object.getOwnPropertyDescriptor;var Tt=Object.getOwnPropertyNames;var Ot=Object.getPrototypeOf,Pt=Object.prototype.hasOwnProperty;var y=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+r+'" is not supported')});var P=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports);var St=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Tt(e))!Pt.call(r,s)&&s!==t&&Pe(r,s,{get:()=>e[s],enumerable:!(n=vt(e,s))||n.enumerable});return r};var b=(r,e,t)=>(t=r!=null?_t(Ot(r)):{},St(e||!r||!r.__esModule?Pe(t,"default",{value:r,enumerable:!0}):t,r));var ee=P(h=>{"use strict";Object.defineProperty(h,"__esModule",{value:!0});h.PATTERNS=h.MAX_BYTES=void 0;h.truncateTo=Se;h.truncate=Et;h.collectMatches=Ee;h.resolveOverlaps=be;h.applyMatches=Ie;h.redact=bt;h.MAX_BYTES=102400;function Se(r,e){let t=Buffer.from(r,"utf-8");return t.length<=e?r:`${t.subarray(0,Math.max(0,e)).toString("utf-8")}\u2026[TRUNCATED:${t.length}]`}function Et(r){return Se(r,h.MAX_BYTES)}h.PATTERNS=[{type:"aws_access_key",regex:/AKIA[0-9A-Z]{16}/g},{type:"aws_secret_key",regex:/(?:aws[_-]?secret(?:[_-]?(?:access)?[_-]?key)?)\s*[:=]\s*["']?([A-Za-z0-9/+=]{40})(?![A-Za-z0-9/+=])/gi,captureGroup:1},{type:"gcp_service_account",regex:/\{[\s\S]{0,200}?"type"\s*:\s*"service_account"[\s\S]*?\}/g},{type:"github_token",regex:/gh[pousr]_[A-Za-z0-9]{36,}/g},{type:"gitlab_token",regex:/glpat-[A-Za-z0-9_-]{20}/g},{type:"slack_token",regex:/xox[abrsp]-[0-9A-Za-z-]{10,}/g},{type:"openai_key",regex:/sk-(?:proj-)?[A-Za-z0-9_-]{40,}/g},{type:"anthropic_key",regex:/sk-ant-[A-Za-z0-9_-]{50,}/g},{type:"stripe_key",regex:/(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{20,}/g},{type:"jwt",regex:/eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g},{type:"private_key_block",regex:/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g},{type:"bearer_token",regex:/bearer\s+([A-Za-z0-9._~+/=-]{12,})/gi,captureGroup:1},{type:"basic_auth",regex:/basic\s+([A-Za-z0-9+/=]{12,})/gi,captureGroup:1},{type:"db_url_password",regex:/\b(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|redis):\/\/[^:\s/]+:([^@\s]+)@/gi,captureGroup:1},{type:"cli_password_flag",regex:/(?:--password|--pass|--pwd)[=\s]([^\s'"]+)/g,captureGroup:1},{type:"cli_password_short",regex:/\s-p([^\s'"]+)/g,captureGroup:1,requireContext:"bash"},{type:"env_var_secret",regex:/^(?:export\s+)?([A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PWD|API_KEY)[A-Z0-9_]*)\s*=\s*["']?([^\s"'\n]+)/gm,captureGroup:2}];function Ee(r,e){let t=[];for(let n of h.PATTERNS){if(n.requireContext&&n.requireContext!==e.context)continue;let s=new RegExp(n.regex.source,n.regex.flags),i;for(;(i=s.exec(r))!==null;){let c=n.captureGroup??0,o=i[c];if(o===void 0){i.index===s.lastIndex&&s.lastIndex++;continue}let u=i.index,d=i.index+i[0].length,l=u+i[0].indexOf(o),O=l+o.length;t.push({start:u,end:d,replaceStart:l,replaceEnd:O,type:n.type}),i.index===s.lastIndex&&s.lastIndex++}}return t}function be(r){let e=[...r].sort((s,i)=>s.start!==i.start?s.start-i.start:i.end-i.start-(s.end-s.start)),t=[],n=-1;for(let s of e)s.start<n||(t.push(s),n=s.end);return t}function Ie(r,e){let t=[...e].sort((s,i)=>i.replaceStart-s.replaceStart),n=r;for(let s of t)n=n.slice(0,s.replaceStart)+`[REDACTED:${s.type}]`+n.slice(s.replaceEnd);return n}function bt(r,e={}){if(r.length===0)return r;let t=Ee(r,e);if(t.length===0)return r;let n=be(t);return Ie(r,n)}});var re=P(p=>{"use strict";var It=p&&p.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(p,"__esModule",{value:!0});p.MAX_SPAN_BYTES=void 0;p.ulidToTraceId=Ae;p.newSpanId=ke;p.snakeCase=wt;p.toOtlpValue=Re;p.attrsFromRecord=At;p.guardAttrs=xe;p.clientAttrs=je;p.enforceSpanBudget=Ne;p.buildPayload=kt;p.attachGuard=Ce;p.mergeBatch=Rt;var Dt=It(y("crypto")),te=ee(),Mt="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Ae(r){if(r.length!==26)throw new Error(`ulidToTraceId: expected 26 chars, got ${r.length}`);let e=0n;for(let n of r){let s=Mt.indexOf(n);if(s<0)throw new Error(`ulidToTraceId: invalid Crockford char "${n}"`);e=e<<5n|BigInt(s)}let t=(1n<<128n)-1n;return e&=t,e.toString(16).padStart(32,"0")}function ke(){return Dt.default.randomBytes(8).toString("hex")}function wt(r){return r.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}var De=new Set;function V(r,e,t){let n=(0,te.truncate)(e);if((t.skipRedactKeys??De).has(r))return n;let s=(t.bashContextKeys??De).has(r)?"bash":void 0;return(0,te.redact)(n,{context:s})}function Re(r,e,t={}){if(e==null)return null;switch(typeof e){case"string":return{stringValue:V(r,e,t)};case"boolean":return{boolValue:e};case"number":return Number.isInteger(e)?{intValue:e}:{doubleValue:e};case"object":try{return{stringValue:V(r,JSON.stringify(e),t)}}catch{return{stringValue:V(r,String(e),t)}}default:return{stringValue:V(r,String(e),t)}}}function At(r,e,t={}){let n=[];for(let[s,i]of Object.entries(r)){let c=`${e}.${s}`,o=Re(c,i,t);o!==null&&n.push({key:c,value:o})}return n}function xe(r){let e=[{key:"pinta.guard.decision",value:{stringValue:r.decision.toLowerCase()}},{key:"pinta.guard.duration_ms",value:{intValue:r.durationMs}}];return r.reason&&e.push({key:"pinta.guard.matched_rule",value:{stringValue:r.reason}}),r.failOpenReason&&e.push({key:"pinta.guard.fail_open_reason",value:{stringValue:r.failOpenReason}}),e}function je(r){return[{key:"pinta.client.rtt_ms",value:Number.isInteger(r.rttMs)?{intValue:r.rttMs}:{doubleValue:r.rttMs}},{key:"pinta.client.op",value:{stringValue:r.op}}]}p.MAX_SPAN_BYTES=800*1024;function Me(r){let e=JSON.stringify(r);return e===void 0?0:Buffer.byteLength(e,"utf-8")}var we=1024;function Ne(r,e=p.MAX_SPAN_BYTES){let t=Me(r);for(let n=0;n<100&&t>e;n++){let s=null,i=0;for(let u of r.attributes){if(!("stringValue"in u.value))continue;let d=Buffer.byteLength(u.value.stringValue,"utf-8");d>i&&(i=d,s=u.value)}if(s===null||i<=we)break;let c=t-e,o=Math.max(we,i-c-64);s.stringValue=(0,te.truncateTo)(s.stringValue,o),t=Me(r)}return r}function kt(r){let e=r.now??Date.now(),t=(BigInt(e)*1000000n).toString(),n=[...r.attributes],s={traceId:Ae(r.traceId),spanId:ke(),name:r.spanName,kind:r.spanKind??1,startTimeUnixNano:t,endTimeUnixNano:t,attributes:n};Ne(s);let i={resourceSpans:[{resource:{attributes:r.resource},scopeSpans:[{scope:r.scope,spans:[s]}]}]};return Ce(i,r.guard,r.clientTiming)}function Ce(r,e,t){let n=r.resourceSpans[0]?.scopeSpans[0]?.spans[0];if(!n)return r;e&&n.attributes.push(...xe(e));let s=t??(e?.clientRttMs!==void 0?{op:"guard",rttMs:e.clientRttMs}:null);return s&&n.attributes.push(...je(s)),r}function Rt(r){let e=[];for(let t of r)e.push(...t.resourceSpans);return{resourceSpans:e}}});var qe=P(R=>{"use strict";Object.defineProperty(R,"__esModule",{value:!0});R.GUARD_STATUS_REFUSED=void 0;R.evaluateGuard=Ct;var xt=1e4,jt="pinta-core";function G(r,e){return{decision:"ALLOW",reason:null,userMessage:null,durationMs:r,clientRttMs:r,failOpenReason:e}}var Nt=new Set(["ALLOW","DENY","REVIEW"]);R.GUARD_STATUS_REFUSED=410;async function Ct(r,e,t={}){if(!e||t.disabled)return null;let n=t.timeoutMs??xt,s=Date.now(),i=new AbortController,c=setTimeout(()=>i.abort(),n);try{let o=await fetch(e,{method:"POST",headers:{"content-type":"application/json","user-agent":t.userAgent??jt,"x-pinta-relay-token":t.token??"",...t.agentType?{"x-pinta-agent-type":t.agentType}:{}},body:JSON.stringify(r),signal:i.signal});if(o.status===R.GUARD_STATUS_REFUSED)return G(Date.now()-s,"refused");if(o.status!==200)return G(Date.now()-s,"error");let u=await o.json(),d=u?.durationMs===void 0||typeof u.durationMs=="number"&&Number.isFinite(u.durationMs);if(!u||!Nt.has(u.decision)||!d)return G(Date.now()-s,"error");let l=Date.now()-s;return{decision:u.decision,reason:u.reason??null,userMessage:u.userMessage??null,durationMs:u.durationMs??l,clientRttMs:l}}catch(o){let u=o.name,d=u==="AbortError"||u==="TimeoutError"?"timeout":"error";return G(Date.now()-s,d)}finally{clearTimeout(c)}}});var L=P(q=>{"use strict";var qt=q&&q.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(q,"__esModule",{value:!0});q.atomicWriteFileSync=Bt;var Be=qt(y("fs"));function Bt(r,e){let t=`${r}.${process.pid}.tmp`;Be.default.writeFileSync(t,e),Be.default.renameSync(t,r)}});var oe=P(I=>{"use strict";var Fe=I&&I.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(I,"__esModule",{value:!0});I.MemoryRetryQueue=I.DiskRetryQueue=void 0;var m=Fe(y("fs")),U=Fe(y("path")),Ft=L(),X=1e3,$t=50,Vt=5,ne=class{filePath;lockPath;logPrefix;constructor(e,t){this.filePath=U.default.join(e,"failed-spans.jsonl"),this.lockPath=this.filePath+".lock",this.logPrefix=t}enqueue(e){try{m.default.mkdirSync(U.default.dirname(this.filePath),{recursive:!0});let t=JSON.stringify({savedAt:new Date().toISOString(),payload:e})+`
|
|
3
|
+
`;m.default.appendFileSync(this.filePath,t),this.trim()}catch(t){process.stderr.write(`[${this.logPrefix}] retry-queue enqueue failed: ${t}
|
|
4
|
+
`)}}readAll(){try{let e=m.default.readFileSync(this.filePath,"utf-8"),t=[];for(let n of e.split(`
|
|
5
|
+
`))if(n.trim())try{let s=JSON.parse(n);Array.isArray(s?.payload?.resourceSpans)&&t.push(s)}catch{}return t}catch{return[]}}rewrite(e){try{if(e.length===0){m.default.existsSync(this.filePath)&&m.default.unlinkSync(this.filePath);return}m.default.mkdirSync(U.default.dirname(this.filePath),{recursive:!0}),(0,Ft.atomicWriteFileSync)(this.filePath,e.map(t=>JSON.stringify(t)).join(`
|
|
6
6
|
`)+`
|
|
7
|
-
`)}catch(
|
|
8
|
-
`)}}tryAcquireLock(){let e=Date.now();try{
|
|
9
|
-
`),!1}for(;Date.now()-e
|
|
10
|
-
`),!1;try{let i=
|
|
11
|
-
`),this.rewrite(e.slice(
|
|
12
|
-
`),!1}return!0}catch(i){return process.stderr.write(`[${
|
|
13
|
-
`),!1}finally{clearTimeout(
|
|
14
|
-
`);let i=[].concat(...n),
|
|
15
|
-
`)}finally{this.queue.release()}}dropIfOversized(e){let
|
|
16
|
-
`),!0)}};
|
|
17
|
-
`);return}await
|
|
18
|
-
`);let i=[].concat(...n),
|
|
19
|
-
`)}}};
|
|
20
|
-
`)){let n=r.trim();if(!n||n.startsWith("#"))continue;let o=n.indexOf("=");if(o<0)continue;let i=n.slice(0,o).trim(),u=n.slice(o+1).trim();(u.startsWith('"')&&u.endsWith('"')||u.startsWith("'")&&u.endsWith("'"))&&(u=u.slice(1,-1)),i&&(e[i]=u)}return e}function jt(t){let e;try{e=Rt.default.readFileSync(t,"utf-8")}catch{return}let r=Fe(e);for(let[n,o]of Object.entries(r))process.env[n]===void 0&&(process.env[n]=o)}});var b=_(s=>{"use strict";Object.defineProperty(s,"__esModule",{value:!0});s.loadEnvFile=s.parseEnvFile=s.envFilePath=s.MemorySessionTraceManager=s.DiskSessionTraceManager=s.generateUlid=s.TraceManager=s.MAX_POST_BYTES=s.parseHeadersEnv=s.envOptionsResolver=s.postOtlp=s.MemoryTransport=s.DiskTransport=s.MemoryRetryQueue=s.DiskRetryQueue=s.evaluateGuard=s.MAX_SPAN_BYTES=s.enforceSpanBudget=s.mergeBatch=s.buildPayload=s.clientAttrs=s.guardAttrs=s.attrsFromRecord=s.toOtlpValue=s.snakeCase=s.newSpanId=s.ulidToTraceId=s.MAX_BYTES=s.PATTERNS=s.applyMatches=s.resolveOverlaps=s.collectMatches=s.truncateTo=s.truncate=s.redact=void 0;var T=F();Object.defineProperty(s,"redact",{enumerable:!0,get:function(){return T.redact}});Object.defineProperty(s,"truncate",{enumerable:!0,get:function(){return T.truncate}});Object.defineProperty(s,"truncateTo",{enumerable:!0,get:function(){return T.truncateTo}});Object.defineProperty(s,"collectMatches",{enumerable:!0,get:function(){return T.collectMatches}});Object.defineProperty(s,"resolveOverlaps",{enumerable:!0,get:function(){return T.resolveOverlaps}});Object.defineProperty(s,"applyMatches",{enumerable:!0,get:function(){return T.applyMatches}});Object.defineProperty(s,"PATTERNS",{enumerable:!0,get:function(){return T.PATTERNS}});Object.defineProperty(s,"MAX_BYTES",{enumerable:!0,get:function(){return T.MAX_BYTES}});var y=V();Object.defineProperty(s,"ulidToTraceId",{enumerable:!0,get:function(){return y.ulidToTraceId}});Object.defineProperty(s,"newSpanId",{enumerable:!0,get:function(){return y.newSpanId}});Object.defineProperty(s,"snakeCase",{enumerable:!0,get:function(){return y.snakeCase}});Object.defineProperty(s,"toOtlpValue",{enumerable:!0,get:function(){return y.toOtlpValue}});Object.defineProperty(s,"attrsFromRecord",{enumerable:!0,get:function(){return y.attrsFromRecord}});Object.defineProperty(s,"guardAttrs",{enumerable:!0,get:function(){return y.guardAttrs}});Object.defineProperty(s,"clientAttrs",{enumerable:!0,get:function(){return y.clientAttrs}});Object.defineProperty(s,"buildPayload",{enumerable:!0,get:function(){return y.buildPayload}});Object.defineProperty(s,"mergeBatch",{enumerable:!0,get:function(){return y.mergeBatch}});Object.defineProperty(s,"enforceSpanBudget",{enumerable:!0,get:function(){return y.enforceSpanBudget}});Object.defineProperty(s,"MAX_SPAN_BYTES",{enumerable:!0,get:function(){return y.MAX_SPAN_BYTES}});var Nt=ve();Object.defineProperty(s,"evaluateGuard",{enumerable:!0,get:function(){return Nt.evaluateGuard}});var Ve=z();Object.defineProperty(s,"DiskRetryQueue",{enumerable:!0,get:function(){return Ve.DiskRetryQueue}});Object.defineProperty(s,"MemoryRetryQueue",{enumerable:!0,get:function(){return Ve.MemoryRetryQueue}});var A=xe();Object.defineProperty(s,"DiskTransport",{enumerable:!0,get:function(){return A.DiskTransport}});Object.defineProperty(s,"MemoryTransport",{enumerable:!0,get:function(){return A.MemoryTransport}});Object.defineProperty(s,"postOtlp",{enumerable:!0,get:function(){return A.postOtlp}});Object.defineProperty(s,"envOptionsResolver",{enumerable:!0,get:function(){return A.envOptionsResolver}});Object.defineProperty(s,"parseHeadersEnv",{enumerable:!0,get:function(){return A.parseHeadersEnv}});Object.defineProperty(s,"MAX_POST_BYTES",{enumerable:!0,get:function(){return A.MAX_POST_BYTES}});var Le=H();Object.defineProperty(s,"TraceManager",{enumerable:!0,get:function(){return Le.TraceManager}});Object.defineProperty(s,"generateUlid",{enumerable:!0,get:function(){return Le.generateUlid}});var Ge=qe();Object.defineProperty(s,"DiskSessionTraceManager",{enumerable:!0,get:function(){return Ge.DiskSessionTraceManager}});Object.defineProperty(s,"MemorySessionTraceManager",{enumerable:!0,get:function(){return Ge.MemorySessionTraceManager}});var re=$e();Object.defineProperty(s,"envFilePath",{enumerable:!0,get:function(){return re.envFilePath}});Object.defineProperty(s,"parseEnvFile",{enumerable:!0,get:function(){return re.parseEnvFile}});Object.defineProperty(s,"loadEnvFile",{enumerable:!0,get:function(){return re.loadEnvFile}})});var Ye=E(b(),1);import qt from"node:fs";import ne from"node:path";var x=E(b(),1);function Bt(){return(0,x.envFilePath)(".config/opencode","pinta-opencode.env","OPENCODE_CONFIG_DIR")}function Xe(t=Bt()){(0,x.loadEnvFile)(t)}function oe(...t){return t.find(e=>e)||void 0}function Ct(t){let e=oe(t.endpoint,process.env.PINTA_OPENCODE_ENDPOINT,process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT);if(e)return e.replace(/\/+$/,"");let r=process.env.OTEL_EXPORTER_OTLP_ENDPOINT;if(r)return r.replace(/\/+$/,"")+"/v1/traces"}var Ft="opencode-ai";function $t(){let t=ne.dirname(process.execPath);for(let e=0;e<5;e++){try{let n=JSON.parse(qt.readFileSync(ne.join(t,"package.json"),"utf8"));if(n.name===Ft&&typeof n.version=="string"&&n.version)return n.version}catch{}let r=ne.dirname(t);if(r===t)break;t=r}}function Vt(){let t=process.env.OPENCODE_VERSION?.trim();return t||$t()}function ze(t={}){Xe();let e=oe(t.token,process.env.PINTA_OPENCODE_TOKEN),r=(0,Ye.parseHeadersEnv)(t.headers??process.env.PINTA_OPENCODE_HEADERS??process.env.OTEL_EXPORTER_OTLP_HEADERS);e&&!Object.keys(r).some(o=>o.toLowerCase()==="x-pinta-relay-token")&&(r["x-pinta-relay-token"]=e);let n=t.guardTimeoutMs??(Number(process.env.PINTA_OPENCODE_GUARD_TIMEOUT_MS)||50);return{endpoint:Ct(t),headers:r,guardEndpoint:oe(t.guard,process.env.PINTA_OPENCODE_GUARD),relayToken:e,guardTimeoutMs:n,guardDisabled:process.env.PINTA_OPENCODE_GUARD_DISABLED==="1",serviceVersion:Vt()}}var Ue=E(b(),1),B=class extends Ue.MemoryTransport{constructor(e){super({logPrefix:"pinta-opencode",resolveOptions:()=>e.endpoint?{endpoint:e.endpoint,headers:e.headers}:null})}};var se=E(b(),1);var Ke=E(b(),1),Lt=50,Gt="pinta-opencode/0.7.0";function Ze(t,e,r={}){return(0,Ke.evaluateGuard)(t,e,{timeoutMs:r.timeoutMs??Lt,token:r.token,disabled:r.disabled,userAgent:Gt})}var k=E(b(),1);import ie from"os";var q;function Xt(){if(q===void 0)try{q=ie.userInfo().username}catch{q=process.env.USER??process.env.LOGNAME??(typeof process.getuid=="function"?String(process.getuid()):"unknown")}return q}var Je="0.7.0",Yt=new Set(["opencode.hook","opencode.event_type","opencode.tool_name","opencode.session_id","opencode.sessionID","opencode.tool_use_id","opencode.cwd","opencode.agent","opencode.model","opencode.exit","opencode.truncated","opencode.title"]),zt=new Set(["opencode.tool_input","opencode.tool_response"]),Ut={skipRedactKeys:Yt,bashContextKeys:zt};function Kt(t){let e=[{key:"ingest.type",value:{stringValue:"opencode"}}];return e.push(...(0,k.attrsFromRecord)(t,"opencode",Ut)),e}function Zt(t){return[{key:"service.name",value:{stringValue:"opencode"}},...t?[{key:"service.version",value:{stringValue:t}}]:[],{key:"telemetry.sdk.name",value:{stringValue:"pinta-opencode"}},{key:"telemetry.sdk.language",value:{stringValue:"nodejs"}},{key:"telemetry.sdk.version",value:{stringValue:Je}},{key:"process.pid",value:{intValue:process.pid}},{key:"process.owner",value:{stringValue:Xt()}},{key:"host.name",value:{stringValue:ie.hostname()}},{key:"host.arch",value:{stringValue:ie.arch()}}]}function We(t){return(0,k.buildPayload)({traceId:t.traceId,spanName:t.name,attributes:Kt(t.fields),resource:Zt(t.serviceVersion),scope:{name:"pinta-opencode",version:Je},now:t.now,guard:t.guard})}var C=class{constructor(e,r,n){this.transport=e;this.trace=r;this.config=n}async emit(e,r,n,o){let i=this.trace.currentTrace(r),u=We({name:e,traceId:i,fields:n,guard:o,serviceVersion:this.config.serviceVersion});await this.transport.send(u)}async lifecycle(e){let r=e.properties??{},n=typeof r.sessionID=="string"?r.sessionID:void 0;await this.emit(`opencode.event.${e.type??"unknown"}`,n,{hook:"event",event_type:e.type,session_id:n,cwd:process.cwd(),...r}),e.type==="session.idle"&&await this.transport.flush()}async toolBefore(e,r,n){await this.emit("opencode.tool.before",e.sessionID,{...He("tool.execute.before",e),tool_input:r},n)}async toolAfter(e,r){let n=r.metadata??{};await this.emit("opencode.tool.after",e.sessionID,{...He("tool.execute.after",e),title:r.title,tool_response:r.output,exit:n.exit,truncated:n.truncated})}};function He(t,e){return{hook:t,tool_name:e.tool,session_id:e.sessionID,tool_use_id:e.callID,cwd:process.cwd()}}function Qe(t,e){process.stderr.write(`[pinta-opencode] ${t}: ${e?.message??String(e)}
|
|
21
|
-
`)}function
|
|
7
|
+
`)}catch(t){process.stderr.write(`[${this.logPrefix}] retry-queue rewrite failed: ${t}
|
|
8
|
+
`)}}tryAcquireLock(){let e=Date.now();try{m.default.mkdirSync(U.default.dirname(this.lockPath),{recursive:!0})}catch(t){return process.stderr.write(`[${this.logPrefix}] retry-queue lock mkdir failed: ${t}
|
|
9
|
+
`),!1}for(;Date.now()-e<$t;)try{let t=m.default.openSync(this.lockPath,"wx");return m.default.writeSync(t,String(process.pid)),m.default.closeSync(t),!0}catch(t){if(t?.code!=="EEXIST")return process.stderr.write(`[${this.logPrefix}] retry-queue lock open failed: ${t}
|
|
10
|
+
`),!1;try{let i=m.default.statSync(this.lockPath);if(Date.now()-i.mtimeMs>3e4){m.default.unlinkSync(this.lockPath);continue}}catch{}let n=Vt,s=Date.now()+n;for(;Date.now()<s;);}return!1}release(){try{m.default.unlinkSync(this.lockPath)}catch{}}trim(){let e=this.readAll();if(e.length<=X)return;let t=e.length-X;process.stderr.write(`[${this.logPrefix}] retry-queue full, dropping ${t} oldest entries
|
|
11
|
+
`),this.rewrite(e.slice(t))}};I.DiskRetryQueue=ne;var se=class{entries=[];enqueue(e){this.entries.push(e),this.entries.length>X&&this.entries.splice(0,this.entries.length-X)}drain(){let e=this.entries;return this.entries=[],e}get size(){return this.entries.length}};I.MemoryRetryQueue=se});var Xe=P(f=>{"use strict";Object.defineProperty(f,"__esModule",{value:!0});f.MemoryTransport=f.DiskTransport=f.MAX_POST_BYTES=void 0;f.parseHeadersEnv=Le;f.envOptionsResolver=Ue;f.postOtlp=B;var $e=oe(),Ve=re(),Gt=5e3;f.MAX_POST_BYTES=900*1024;function Y(r){let e=JSON.stringify(r);return e===void 0?0:Buffer.byteLength(e,"utf-8")}function Ge(r,e,t){let n=[],s=[],i=[],c=0;for(let o of r){let u=e(o);if(u>t){s.push(o);continue}i.length>0&&c+u>t&&(n.push(i),i=[],c=0),i.push(o),c+=u}return i.length>0&&n.push(i),{chunks:n,oversize:s}}function Le(r){if(!r)return{};if(typeof r=="object")return{...r};let e={};for(let t of r.split(",")){let[n,...s]=t.split("=");n&&s.length>0&&(e[n.trim()]=s.join("=").trim())}return e}function Ue(){let r=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,e=process.env.OTEL_EXPORTER_OTLP_ENDPOINT,t;return r?t=r.replace(/\/+$/,""):e&&(t=e.replace(/\/+$/,"")+"/v1/traces"),t?{endpoint:t,headers:Le(process.env.OTEL_EXPORTER_OTLP_HEADERS)}:null}async function B(r,e,t){let n=new AbortController,s=setTimeout(()=>n.abort(),Gt);try{let i=await fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json",...e.headers},body:JSON.stringify(r),signal:n.signal});if(!i.ok){let c="";try{c=(await i.text()).slice(0,200)}catch{}let o=i.status===401||i.status===403?" \u2014 check OTEL_EXPORTER_OTLP_HEADERS (relay token)":i.status===404?" \u2014 check OTEL_EXPORTER_OTLP_TRACES_ENDPOINT path":i.status>=500?" \u2014 collector may be down":"";return process.stderr.write(`[${t}] OTLP POST ${i.status} ${e.endpoint}${o}${c?` body=${c}`:""}
|
|
12
|
+
`),!1}return!0}catch(i){return process.stderr.write(`[${t}] OTLP POST failed: ${i.message??String(i)}
|
|
13
|
+
`),!1}finally{clearTimeout(s)}}var ie=class{queue;logPrefix;resolveOptions;constructor(e){this.queue=new $e.DiskRetryQueue(e.pluginData,e.logPrefix),this.logPrefix=e.logPrefix,this.resolveOptions=e.resolveOptions??Ue}async send(e){let t=this.resolveOptions();if(!t||this.dropIfOversized(e))return;await B(e,t,this.logPrefix)||this.queue.enqueue(e)}async flush(){let e=this.resolveOptions();if(e&&this.queue.tryAcquireLock())try{let t=this.queue.readAll();if(t.length===0)return;let{chunks:n,oversize:s}=Ge(t,o=>Y(o.payload),f.MAX_POST_BYTES);s.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${s.length} oversized retry ${s.length===1?"entry":"entries"} (each > ${f.MAX_POST_BYTES} bytes \u2014 the backend caps the request body, so they can never be delivered)
|
|
14
|
+
`);let i=[].concat(...n),c=0;for(let o of n){if(!await B((0,Ve.mergeBatch)(o.map(d=>d.payload)),e,this.logPrefix))break;c+=o.length}this.queue.rewrite(i.slice(c))}catch(t){process.stderr.write(`[${this.logPrefix}] flush failed: ${t?.message??String(t)}
|
|
15
|
+
`)}finally{this.queue.release()}}dropIfOversized(e){let t=Y(e);return t<=f.MAX_POST_BYTES?!1:(process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (${t} > ${f.MAX_POST_BYTES} bytes) \u2014 undeliverable, not queued
|
|
16
|
+
`),!0)}};f.DiskTransport=ie;var ae=class{queue=new $e.MemoryRetryQueue;logPrefix;resolveOptions;constructor(e){this.logPrefix=e.logPrefix,this.resolveOptions=e.resolveOptions}async send(e){let t=this.resolveOptions();if(!t)return;if(Y(e)>f.MAX_POST_BYTES){process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (> ${f.MAX_POST_BYTES} bytes) \u2014 undeliverable, not buffered
|
|
17
|
+
`);return}await B(e,t,this.logPrefix)||this.queue.enqueue(e)}async flush(){let e=this.resolveOptions();if(!e)return;let t=this.queue.drain();if(t.length===0)return;let{chunks:n,oversize:s}=Ge(t,Y,f.MAX_POST_BYTES);s.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${s.length} oversized buffered ${s.length===1?"payload":"payloads"} (each > ${f.MAX_POST_BYTES} bytes \u2014 undeliverable)
|
|
18
|
+
`);let i=[].concat(...n),c=0;for(let o of n){if(!await B((0,Ve.mergeBatch)(o),e,this.logPrefix))break;c+=o.length}for(let o of i.slice(c))this.queue.enqueue(o)}};f.MemoryTransport=ae});var le=P(A=>{"use strict";var de=A&&A.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(A,"__esModule",{value:!0});A.TraceManager=void 0;A.generateUlid=ze;var Ye=de(y("fs")),Lt=de(y("path")),Ut=de(y("crypto")),Xt=L(),ue="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function ze(){let r=Date.now(),e="",t=r;for(let i=0;i<10;i++)e=ue[t&31]+e,t=Math.floor(t/32);let n=Ut.default.randomBytes(10),s="";for(let i=0;i<10;i++)s+=ue[n[i]&31];for(;s.length<16;)s+=ue[0];return e+s}var ce=class{tracePath;constructor(e){this.tracePath=e}newTrace(){let e=ze();return this.save(e),e}currentTrace(){try{let e=Ye.default.readFileSync(this.tracePath,"utf-8"),{traceId:t}=JSON.parse(e);if(t)return t}catch{}return this.newTrace()}save(e){try{Ye.default.mkdirSync(Lt.default.dirname(this.tracePath),{recursive:!0}),(0,Xt.atomicWriteFileSync)(this.tracePath,JSON.stringify({traceId:e}))}catch(t){process.stderr.write(`[pinta-core] trace persistence failed: ${t}
|
|
19
|
+
`)}}};A.TraceManager=ce});var He=P(D=>{"use strict";var Ke=D&&D.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(D,"__esModule",{value:!0});D.MemorySessionTraceManager=D.DiskSessionTraceManager=void 0;var We=Ke(y("fs")),Yt=Ke(y("path")),Ze=le(),zt=L(),Je=200,fe=class{tracePath;maxSessions;constructor(e,t={}){this.tracePath=e,this.maxSessions=t.maxSessions??Je}read(){try{let e=JSON.parse(We.default.readFileSync(this.tracePath,"utf-8"));if(e&&typeof e=="object"&&!("traceId"in e))return e}catch{}return{}}write(e){try{We.default.mkdirSync(Yt.default.dirname(this.tracePath),{recursive:!0});let t=Object.entries(e),n=t.length>this.maxSessions?Object.fromEntries(t.slice(-this.maxSessions)):e;(0,zt.atomicWriteFileSync)(this.tracePath,JSON.stringify(n))}catch{}}newTrace(e){let t=e||"default",n=(0,Ze.generateUlid)(),s=this.read();return s[t]=n,this.write(s),n}currentTrace(e){let t=e||"default",n=this.read()[t];return n||this.newTrace(t)}};D.DiskSessionTraceManager=fe;var pe=class{map=new Map;maxSessions;constructor(e={}){this.maxSessions=e.maxSessions??Je}newTrace(e){let t=e||"default",n=(0,Ze.generateUlid)();if(this.map.set(t,n),this.map.size>this.maxSessions){let s=this.map.keys().next().value;s!==void 0&&this.map.delete(s)}return n}currentTrace(e){let t=e||"default";return this.map.get(t)??this.newTrace(t)}};D.MemorySessionTraceManager=pe});var tt=P(k=>{"use strict";var ge=k&&k.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(k,"__esModule",{value:!0});k.envFilePath=Zt;k.parseEnvFile=et;k.loadEnvFile=Jt;var Wt=ge(y("node:fs")),Kt=ge(y("node:os")),Qe=ge(y("node:path"));function Zt(r,e,t){let n=t?process.env[t]:void 0,s=n&&n.length>0?n:Qe.default.join(Kt.default.homedir(),r);return Qe.default.join(s,e)}function et(r){let e={};for(let t of r.split(`
|
|
20
|
+
`)){let n=t.trim();if(!n||n.startsWith("#"))continue;let s=n.indexOf("=");if(s<0)continue;let i=n.slice(0,s).trim(),c=n.slice(s+1).trim();(c.startsWith('"')&&c.endsWith('"')||c.startsWith("'")&&c.endsWith("'"))&&(c=c.slice(1,-1)),i&&(e[i]=c)}return e}function Jt(r){let e;try{e=Wt.default.readFileSync(r,"utf-8")}catch{return}let t=et(e);for(let[n,s]of Object.entries(t))process.env[n]===void 0&&(process.env[n]=s)}});var S=P(a=>{"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.loadEnvFile=a.parseEnvFile=a.envFilePath=a.MemorySessionTraceManager=a.DiskSessionTraceManager=a.generateUlid=a.TraceManager=a.MAX_POST_BYTES=a.parseHeadersEnv=a.envOptionsResolver=a.postOtlp=a.MemoryTransport=a.DiskTransport=a.MemoryRetryQueue=a.DiskRetryQueue=a.GUARD_STATUS_REFUSED=a.evaluateGuard=a.MAX_SPAN_BYTES=a.enforceSpanBudget=a.mergeBatch=a.attachGuard=a.buildPayload=a.clientAttrs=a.guardAttrs=a.attrsFromRecord=a.toOtlpValue=a.snakeCase=a.newSpanId=a.ulidToTraceId=a.MAX_BYTES=a.PATTERNS=a.applyMatches=a.resolveOverlaps=a.collectMatches=a.truncateTo=a.truncate=a.redact=void 0;var M=ee();Object.defineProperty(a,"redact",{enumerable:!0,get:function(){return M.redact}});Object.defineProperty(a,"truncate",{enumerable:!0,get:function(){return M.truncate}});Object.defineProperty(a,"truncateTo",{enumerable:!0,get:function(){return M.truncateTo}});Object.defineProperty(a,"collectMatches",{enumerable:!0,get:function(){return M.collectMatches}});Object.defineProperty(a,"resolveOverlaps",{enumerable:!0,get:function(){return M.resolveOverlaps}});Object.defineProperty(a,"applyMatches",{enumerable:!0,get:function(){return M.applyMatches}});Object.defineProperty(a,"PATTERNS",{enumerable:!0,get:function(){return M.PATTERNS}});Object.defineProperty(a,"MAX_BYTES",{enumerable:!0,get:function(){return M.MAX_BYTES}});var _=re();Object.defineProperty(a,"ulidToTraceId",{enumerable:!0,get:function(){return _.ulidToTraceId}});Object.defineProperty(a,"newSpanId",{enumerable:!0,get:function(){return _.newSpanId}});Object.defineProperty(a,"snakeCase",{enumerable:!0,get:function(){return _.snakeCase}});Object.defineProperty(a,"toOtlpValue",{enumerable:!0,get:function(){return _.toOtlpValue}});Object.defineProperty(a,"attrsFromRecord",{enumerable:!0,get:function(){return _.attrsFromRecord}});Object.defineProperty(a,"guardAttrs",{enumerable:!0,get:function(){return _.guardAttrs}});Object.defineProperty(a,"clientAttrs",{enumerable:!0,get:function(){return _.clientAttrs}});Object.defineProperty(a,"buildPayload",{enumerable:!0,get:function(){return _.buildPayload}});Object.defineProperty(a,"attachGuard",{enumerable:!0,get:function(){return _.attachGuard}});Object.defineProperty(a,"mergeBatch",{enumerable:!0,get:function(){return _.mergeBatch}});Object.defineProperty(a,"enforceSpanBudget",{enumerable:!0,get:function(){return _.enforceSpanBudget}});Object.defineProperty(a,"MAX_SPAN_BYTES",{enumerable:!0,get:function(){return _.MAX_SPAN_BYTES}});var rt=qe();Object.defineProperty(a,"evaluateGuard",{enumerable:!0,get:function(){return rt.evaluateGuard}});Object.defineProperty(a,"GUARD_STATUS_REFUSED",{enumerable:!0,get:function(){return rt.GUARD_STATUS_REFUSED}});var nt=oe();Object.defineProperty(a,"DiskRetryQueue",{enumerable:!0,get:function(){return nt.DiskRetryQueue}});Object.defineProperty(a,"MemoryRetryQueue",{enumerable:!0,get:function(){return nt.MemoryRetryQueue}});var x=Xe();Object.defineProperty(a,"DiskTransport",{enumerable:!0,get:function(){return x.DiskTransport}});Object.defineProperty(a,"MemoryTransport",{enumerable:!0,get:function(){return x.MemoryTransport}});Object.defineProperty(a,"postOtlp",{enumerable:!0,get:function(){return x.postOtlp}});Object.defineProperty(a,"envOptionsResolver",{enumerable:!0,get:function(){return x.envOptionsResolver}});Object.defineProperty(a,"parseHeadersEnv",{enumerable:!0,get:function(){return x.parseHeadersEnv}});Object.defineProperty(a,"MAX_POST_BYTES",{enumerable:!0,get:function(){return x.MAX_POST_BYTES}});var st=le();Object.defineProperty(a,"TraceManager",{enumerable:!0,get:function(){return st.TraceManager}});Object.defineProperty(a,"generateUlid",{enumerable:!0,get:function(){return st.generateUlid}});var ot=He();Object.defineProperty(a,"DiskSessionTraceManager",{enumerable:!0,get:function(){return ot.DiskSessionTraceManager}});Object.defineProperty(a,"MemorySessionTraceManager",{enumerable:!0,get:function(){return ot.MemorySessionTraceManager}});var he=tt();Object.defineProperty(a,"envFilePath",{enumerable:!0,get:function(){return he.envFilePath}});Object.defineProperty(a,"parseEnvFile",{enumerable:!0,get:function(){return he.parseEnvFile}});Object.defineProperty(a,"loadEnvFile",{enumerable:!0,get:function(){return he.loadEnvFile}})});var at=b(S(),1);import Qt from"node:fs";import me from"node:path";var F=b(S(),1);function Ht(){return(0,F.envFilePath)(".config/opencode","pinta-opencode.env","OPENCODE_CONFIG_DIR")}function it(r=Ht()){(0,F.loadEnvFile)(r)}function ye(...r){return r.find(e=>e)||void 0}function er(r){let e=ye(r.endpoint,process.env.PINTA_OPENCODE_ENDPOINT,process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT);if(e)return e.replace(/\/+$/,"");let t=process.env.OTEL_EXPORTER_OTLP_ENDPOINT;if(t)return t.replace(/\/+$/,"")+"/v1/traces"}var tr="opencode-ai";function rr(){let r=me.dirname(process.execPath);for(let e=0;e<5;e++){try{let n=JSON.parse(Qt.readFileSync(me.join(r,"package.json"),"utf8"));if(n.name===tr&&typeof n.version=="string"&&n.version)return n.version}catch{}let t=me.dirname(r);if(t===r)break;r=t}}function nr(){let r=process.env.OPENCODE_VERSION?.trim();return r||rr()}function ut(r={}){it();let e=ye(r.token,process.env.PINTA_OPENCODE_TOKEN),t=(0,at.parseHeadersEnv)(r.headers??process.env.PINTA_OPENCODE_HEADERS??process.env.OTEL_EXPORTER_OTLP_HEADERS);e&&!Object.keys(t).some(s=>s.toLowerCase()==="x-pinta-relay-token")&&(t["x-pinta-relay-token"]=e);let n=r.guardTimeoutMs??(Number(process.env.PINTA_OPENCODE_GUARD_TIMEOUT_MS)||50);return{endpoint:er(r),headers:t,guardEndpoint:ye(r.guard,process.env.PINTA_OPENCODE_GUARD),relayToken:e,guardTimeoutMs:n,guardDisabled:process.env.PINTA_OPENCODE_GUARD_DISABLED==="1",serviceVersion:nr()}}var ct=b(S(),1),z=class extends ct.MemoryTransport{constructor(e){super({logPrefix:"pinta-opencode",resolveOptions:()=>e.endpoint?{endpoint:e.endpoint,headers:e.headers}:null})}};var _e=b(S(),1);var mt=b(S(),1);var dt=b(S(),1);var W="0.10.0";var sr=50,or=`pinta-opencode/${W}`;function lt(r,e,t={}){return(0,dt.evaluateGuard)(r,e,{timeoutMs:t.timeoutMs??sr,token:t.token,disabled:t.disabled,userAgent:or})}var ht=b(S(),1);import ve from"os";var j=b(S(),1);var K;function ir(){if(K===void 0)try{K=ve.userInfo().username}catch{K=process.env.USER??process.env.LOGNAME??(typeof process.getuid=="function"?String(process.getuid()):"unknown")}return K}var ft=W,ar=new Set(["opencode.hook","opencode.event_type","opencode.tool_name","opencode.session_id","opencode.sessionID","opencode.tool_use_id","opencode.cwd","opencode.agent","opencode.model","opencode.exit","opencode.truncated","opencode.title"]),ur=new Set(["opencode.tool_input","opencode.tool_response"]),cr={skipRedactKeys:ar,bashContextKeys:ur};function dr(r){let e=[{key:"ingest.type",value:{stringValue:"opencode"}}];return e.push(...(0,j.attrsFromRecord)(r,"opencode",cr)),e}function lr(r){return[{key:"service.name",value:{stringValue:"opencode"}},...r?[{key:"service.version",value:{stringValue:r}}]:[],{key:"telemetry.sdk.name",value:{stringValue:"pinta-opencode"}},{key:"telemetry.sdk.language",value:{stringValue:"nodejs"}},{key:"telemetry.sdk.version",value:{stringValue:ft}},{key:"process.pid",value:{intValue:process.pid}},{key:"process.owner",value:{stringValue:ir()}},{key:"host.name",value:{stringValue:ve.hostname()}},{key:"host.arch",value:{stringValue:ve.arch()}}]}function pt(r){return(0,j.buildPayload)({traceId:r.traceId,spanName:r.name,attributes:dr(r.fields),resource:lr(r.serviceVersion),scope:{name:"pinta-opencode",version:ft},now:r.now})}var N={messages:1024,requests:512,calls:2048,endedSessions:128,ttlMs:9e5},fr=new Set(["unknown","undefined","null","n/a","none","-","auto","default"]);function g(r){if(typeof r!="string")return;let e=r.trim();return e&&e.length<=1024?e:void 0}function w(r){let e=g(r);if(!(!e||e.startsWith("{")||e.startsWith("[")||fr.has(e.toLowerCase())))return e}function E(r){return r!==null&&typeof r=="object"&&!Array.isArray(r)?r:void 0}function v(r,e,t){let n=E(r),s=n?w(n.modelID)??w(n.id):w(r);if(s)return{model:s,source:n?`${e}.${w(n.modelID)?"modelID":"id"}`:e,provider:w(n?.providerID??t)}}function T(...r){return JSON.stringify(r)}function C(...r){let e=r.map(g).filter(t=>t!==void 0);return e.every(t=>t===e[0])?e[0]:void 0}function Te(r,e){let t=E(e.info),n=E(e.part);return typeof r=="string"&&r.startsWith("session.")?C(e.sessionID,t?.id):C(e.sessionID,t?.sessionID,n?.sessionID)}var $=class{constructor(e,t){this.limit=e;this.now=t}entries=new Map;get(e){let t=this.entries.get(e);if(t&&t.expires>this.now())return t.value;this.entries.delete(e)}set(e,t){this.entries.delete(e),this.entries.set(e,{value:t,expires:this.now()+N.ttlMs}),this.entries.size>this.limit&&this.entries.delete(this.entries.keys().next().value)}removeWhere(e){for(let[t,n]of this.entries)e(n.value)&&this.entries.delete(t)}clearSession(e){this.removeWhere(t=>t.sessionID===e)}},Z=class{constructor(e=Date.now){this.now=e;this.messages=new $(N.messages,e),this.requests=new $(N.requests,e),this.calls=new $(N.calls,e)}messages;requests;calls;ended=new Map;chatMessage(e,t){let n=C(e.sessionID,t?.message?.sessionID);if(!n)return;this.ended.delete(n);let s=C(e.messageID,t?.message?.id);if(!s)return;let i=g(t?.message?.agent??e.agent),c=t?.message?.model!==void 0;this.requests.set(T(n,s,i??""),{sessionID:n,messageID:s,evidence:v(c?t.message?.model:e.model,c?"requested:chat.message.message.model":"requested:chat.message.model")})}chatParams(e){let t=C(e.sessionID,e.message?.sessionID),n=g(e.message?.id),s=g(e.agent);!t||!n||!s||this.ended.has(t)||this.requests.set(T(t,n,s),{sessionID:t,messageID:n,evidence:v(e.model,"requested:chat.params.model")})}fresh(e){return e===void 0||typeof e=="number"&&Number.isFinite(e)&&e>=this.now()-N.ttlMs}clearSession(e){this.messages.clearSession(e),this.requests.clearSession(e),this.calls.clearSession(e)}event(e,t){let n=Te(e,t),s=E(t.info),i=E(t.part),c=["session.idle","session.deleted","session.error"].includes(e??"")||e==="session.status"&&E(t.status)?.type==="idle";if(n&&c?(this.clearSession(n),this.ended.delete(n),this.ended.set(n,!0),this.ended.size>N.endedSessions&&this.ended.delete(this.ended.keys().next().value)):n&&e==="session.created"&&(this.clearSession(n),this.ended.delete(n)),e==="message.updated"&&s){if(!C(t.sessionID,s.sessionID)&&(t.sessionID||s.sessionID))return;let o=g(s.id),u=g(s.agent),d=s.role,l=d==="assistant"?v(w(s.modelID),"reported:message.updated.info.modelID",s.providerID)??v(s.model,"reported:message.updated.info.model"):d==="user"?v(s.model,"requested:message.updated.info.model"):void 0;if(!l&&d==="user"&&!("model"in s)&&n&&o&&(l=this.requests.get(T(n,o,u??""))?.evidence),d==="user"&&"model"in s&&!l&&n&&o&&this.requests.set(T(n,o,u??""),{sessionID:n,messageID:o}),l&&(l={...l,agent:u,messageID:o}),n&&o&&!this.ended.has(n)&&this.fresh(E(s.time)?.created)){let O=this.messages.get(T(n,o)),Oe=O?.ambiguous||!!(d==="assistant"&&O?.evidence&&l&&(O.evidence.model!==l.model||O.evidence.provider!==l.provider||O.evidence.agent!==l.agent));this.messages.set(T(n,o),{sessionID:n,messageID:o,evidence:Oe?void 0:l,ambiguous:Oe})}return l}if(n&&e==="message.removed"){let o=g(t.messageID);this.messages.removeWhere(u=>u.sessionID===n&&u.messageID===o),this.requests.removeWhere(u=>u.sessionID===n&&u.messageID===o),this.calls.removeWhere(u=>u.sessionID===n&&u.messageID===o)}if(n&&e==="message.part.removed"){let o=g(t.partID);o&&this.calls.removeWhere(u=>u.sessionID===n&&u.partID===o)}if(e==="message.part.delta"){let o=g(t.messageID);return n&&o&&!this.ended.has(n)?this.messages.get(T(n,o))?.evidence:void 0}if(e==="message.part.updated"&&i){if(!n||this.ended.has(n)||!this.fresh(t.time))return;let o=g(i.messageID);if(!o)return;let u=g(i.callID);if(i.type==="tool"&&u){let d=T(n,u),l=this.calls.get(d);if(!l?.closed){let O=g(i.tool);this.calls.set(d,{sessionID:n,messageID:o,partID:g(i.id),tool:O,pinned:l?.pinned,ambiguous:l?.ambiguous||!!(l&&(l.messageID!==o||l.tool!==O))})}}return this.messages.get(T(n,o))?.evidence}return typeof e=="string"&&e.startsWith("session.")?v(s?.model,`selected:${e}.info.model`)??v(t.model,`selected:${e}.model`):v(t.model,"reported:event.model")??v(w(t.modelID),"reported:event.modelID",t.providerID)}tool(e,t){let n=v(w(e.modelID),"reported:tool.modelID",e.providerID)??v(e.model,"reported:tool.model",e.providerID),s=g(e.sessionID),i=g(e.callID);if(!s||!i)return n;let c=T(s,i),o=this.calls.get(c),u=n;if(!("model"in e)&&!("modelID"in e)&&o&&!o.closed&&!o.ambiguous&&!this.ended.has(s)&&(!o.tool||o.tool===e.tool)){let d=this.messages.get(T(s,o.messageID));d?.ambiguous||(u=o.pinned??d?.evidence),(e.agent&&u?.agent&&e.agent!==u.agent||e.messageID&&e.messageID!==o.messageID)&&(u=void 0)}return o&&!o.closed&&this.calls.set(c,{...o,pinned:t?void 0:u,closed:t}),u}beforeTool(e){return this.tool(e,!1)}afterTool(e){return this.tool(e,!0)}};function J(r,e){let{model:t,model_source:n,...s}=r;return"model"in r&&t!==e?.model&&(s.model_raw=t),"model_source"in r&&(s.model_source_raw=n),e&&(s.model=e.model,s.model_source=e.source,e.provider&&("provider"in s&&s.provider!==e.provider&&(s.provider_raw=s.provider),s.provider=e.provider),s.agent===void 0&&(s.agent=e.agent),s.message_id===void 0&&(s.message_id=e.messageID)),s}var H=class{constructor(e,t,n){this.transport=e;this.trace=t;this.config=n}models=new Z;build(e,t,n){let s=this.trace.currentTrace(t);return pt({name:e,traceId:s,fields:n,serviceVersion:this.config.serviceVersion})}async send(e){await this.transport.send(e)}async emit(e,t,n){await this.send(this.build(e,t,n))}chatMessage(e,t){this.models.chatMessage(e,t)}chatParams(e){this.models.chatParams(e)}async lifecycle(e){let t=E(e.properties)??{},n=Te(e.type,t),s=this.models.event(e.type,t);await this.emit(`opencode.event.${e.type??"unknown"}`,n,J({hook:"event",event_type:e.type,session_id:n,cwd:process.cwd(),...t},s)),e.type==="session.idle"&&await this.transport.flush()}toolBeforePayload(e,t){return this.build("opencode.tool.before",e.sessionID,J({...gt("tool.execute.before",e),tool_input:t},this.models.beforeTool(e)))}async toolBefore(e,t,n){await this.send((0,ht.attachGuard)(this.toolBeforePayload(e,t),n))}async toolAfter(e,t){let n=t.metadata??{};await this.emit("opencode.tool.after",e.sessionID,J({...gt("tool.execute.after",e),title:t.title,tool_response:t.output,exit:n.exit,truncated:n.truncated},this.models.afterTool(e)))}};function gt(r,e){return{hook:r,tool_name:e.tool,session_id:e.sessionID,tool_use_id:e.callID,cwd:process.cwd(),...e.model!==void 0?{model:e.model}:{},...e.modelID!==void 0?{modelID:e.modelID}:{},...e.providerID!==void 0?{providerID:e.providerID}:{},...e.agent!==void 0?{agent:e.agent}:{},...e.messageID!==void 0?{message_id:e.messageID}:{}}}function yt(r,e){process.stderr.write(`[pinta-opencode] ${r}: ${e?.message??String(e)}
|
|
21
|
+
`)}function Q(r,e){return async(...t)=>{try{await e(...t)}catch(n){yt(r,n)}}}var pr=async(r,e)=>{let t=ut(e??{}),n=new z({endpoint:t.endpoint,headers:t.headers}),s=new _e.MemorySessionTraceManager,i=new H(n,s,t);async function c(o,u){let d=null;try{let l=i.toolBeforePayload(o,u);d=await lt(l,t.guardEndpoint,{timeoutMs:t.guardTimeoutMs,token:t.relayToken,disabled:t.guardDisabled}),await i.send((0,mt.attachGuard)(l,d))}catch(l){yt("tool.execute.before",l)}return d}return{"chat.message":Q("chat.message",async(o,u)=>{s.newTrace(o?.sessionID),i.chatMessage(o,u)}),"chat.params":Q("chat.params",async(o,u)=>{i.chatParams(o)}),event:Q("event",async o=>{o?.event&&await i.lifecycle(o.event)}),"tool.execute.before":async(o,u)=>{let d=await c(o,u?.args);if(d?.decision==="DENY")throw new Error(d.userMessage??d.reason??"guard_deny")},"tool.execute.after":Q("tool.execute.after",(o,u)=>i.toolAfter(o,u))}},zr=pr;export{pr as PintaOpencode,zr as default};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pinta-ai/pinta-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "OTLP forwarder + guard for opencode (in-process plugin)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/plugin.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"prepublishOnly": "npm run build"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@pinta-ai/core": "^0.
|
|
39
|
+
"@pinta-ai/core": "^0.8.0",
|
|
40
40
|
"@types/node": "^22.0.0",
|
|
41
41
|
"esbuild": "0.24.2",
|
|
42
42
|
"typescript": "^5.7.0",
|