@pinta-ai/pinta-opencode 0.9.0 → 0.11.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 CHANGED
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.0
4
+
5
+ - Guard default timeout 50ms -> 100ms. At 50ms a manager answering in its
6
+ normal range still lost the race often enough to show up as timeout
7
+ fail-opens. The plugin awaits the verdict in-process before the tool runs
8
+ (`tool.execute.before`), so a tool call can now stall on the guard for up to
9
+ 100ms instead of 50ms. `PINTA_OPENCODE_GUARD_TIMEOUT_MS` / `guardTimeoutMs`
10
+ still override it.
11
+ - `@pinta-ai/core` `^0.8.0` -> `^0.9.0`: the effective timeout (default or
12
+ override) is declared to the manager as `x-pinta-guard-budget-ms`. Manager
13
+ 0.1.10+ plans its own work around it instead of the 50ms it had copied for
14
+ this adaptor, so the backend package check gets 80ms rather than 40ms. Older
15
+ managers ignore the header.
16
+ - README no longer says the guard never blocks tool execution; it does, for up
17
+ to the timeout.
18
+ - Pinta Manager 0.1.11 or later remains the guard-payload compatibility floor.
19
+
20
+ Refs PTA-579.
21
+
22
+ ## 0.10.0
23
+
24
+ - Emit exact scalar `opencode.model` with source/provider evidence from verified
25
+ host model shapes. Preserve original nested fields and conflicting top-level
26
+ model values; omit blanks, placeholders and every JSON-prefixed ID, including
27
+ malformed/truncated strings.
28
+ - Correlate tool models through the exact session + callID -> messageID join.
29
+ Request evidence is scoped to its user message and agent, not reused as a
30
+ session default. Bounded caches handle switches, subagents, concurrent
31
+ sessions, out-of-order updates, stale state and end/removal events without
32
+ borrowing an unrelated model.
33
+ - Report assistant metadata as host selection, not proof of actual provider
34
+ response identity. Span counts, guard behavior and redaction remain unchanged;
35
+ model resolution adds no file scans, host commands or network requests.
36
+ - Pinta Manager 0.1.11 or later remains the guard-payload compatibility floor,
37
+ with `@pinta-ai/core` still at `^0.8.0` and no new required envelope fields or
38
+ additional desktop upgrade.
39
+
40
+ Refs PTA-524.
41
+
3
42
  ## 0.9.0
4
43
 
5
44
  - The guard is asked about the span, not about a summary of it. Until now the
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, … } }` |
@@ -56,7 +57,7 @@ PINTA_OPENCODE_GUARD=https://your-relay.example.com/guard
56
57
  | `headers` / `PINTA_OPENCODE_HEADERS` | `key=val,key=val` request headers (auth). Falls back to `OTEL_EXPORTER_OTLP_HEADERS`. |
57
58
  | `guard` / `PINTA_OPENCODE_GUARD` | Optional. POST'd on `tool.execute.before`; a `DENY` blocks the tool. No endpoint → governance disabled. |
58
59
  | `token` / `PINTA_OPENCODE_TOKEN` | Sent as `x-pinta-relay-token` on guard + OTLP. |
59
- | `PINTA_OPENCODE_GUARD_TIMEOUT_MS` | Guard client timeout (default `50`; `300` recommended in production for cold-start). |
60
+ | `PINTA_OPENCODE_GUARD_TIMEOUT_MS` | Guard client timeout (default `100`). Declared to the Pinta Manager as its budget, so raising it also lets the manager wait longer. |
60
61
  | `PINTA_OPENCODE_GUARD_DISABLED=1` | Force-disable the guard. |
61
62
 
62
63
  Telemetry and governance are independent — endpoint only, guard only, both, or neither all work.
@@ -76,7 +77,7 @@ The verdict is then attached to that same span (`pinta.guard.*`) before it is se
76
77
 
77
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.
78
79
 
79
- 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.
80
+ Guard is **fail-open** (no endpoint / `PINTA_GUARD_DISABLED=1` / non-200 / timeout / error → allow), so it never breaks a session. The call is awaited before the tool runs, so a tool call stalls on it for at most the guard timeout (100ms by default).
80
81
 
81
82
  ## Span conventions
82
83
 
@@ -91,6 +92,59 @@ Guard is **fail-open** (no endpoint / `PINTA_GUARD_DISABLED=1` / non-200 / timeo
91
92
 
92
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.
93
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
+
94
148
  ## Architecture
95
149
 
96
150
  ```
@@ -103,7 +157,7 @@ src/
103
157
  │ ├── trace.ts # ULID trace, keyed by sessionID, rotated on chat.message
104
158
  │ ├── transport.ts # POST OTLP/HTTP traces (5s), in-memory retry queue
105
159
  │ ├── retry-queue.ts # batched flush on next event
106
- │ ├── guard.ts # POST PINTA_GUARD_ENDPOINT (50ms), fail-open
160
+ │ ├── guard.ts # POST PINTA_GUARD_ENDPOINT (100ms), fail-open
107
161
  │ └── redact.ts # Tier-1 redaction + Tier-3 truncation
108
162
  ```
109
163
 
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 key of __getOwnPropNames(from))
20
- if (!__hasOwnProp.call(to, key) && key !== except)
21
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
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
  };
@@ -216,19 +216,19 @@ var require_otlp = __commonJS({
216
216
  return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toLowerCase();
217
217
  }
218
218
  var EMPTY_SET = /* @__PURE__ */ new Set();
219
- function maybeRedactString(key, raw, policy) {
219
+ function maybeRedactString(key2, raw, policy) {
220
220
  const truncated = (0, redact_js_1.truncate)(raw);
221
- if ((policy.skipRedactKeys ?? EMPTY_SET).has(key))
221
+ if ((policy.skipRedactKeys ?? EMPTY_SET).has(key2))
222
222
  return truncated;
223
- const context = (policy.bashContextKeys ?? EMPTY_SET).has(key) ? "bash" : void 0;
223
+ const context = (policy.bashContextKeys ?? EMPTY_SET).has(key2) ? "bash" : void 0;
224
224
  return (0, redact_js_1.redact)(truncated, { context });
225
225
  }
226
- function toOtlpValue(key, v, policy = {}) {
226
+ function toOtlpValue(key2, v, policy = {}) {
227
227
  if (v === null || v === void 0)
228
228
  return null;
229
229
  switch (typeof v) {
230
230
  case "string":
231
- return { stringValue: maybeRedactString(key, v, policy) };
231
+ return { stringValue: maybeRedactString(key2, v, policy) };
232
232
  case "boolean":
233
233
  return { boolValue: v };
234
234
  case "number":
@@ -237,22 +237,22 @@ var require_otlp = __commonJS({
237
237
  return { doubleValue: v };
238
238
  case "object":
239
239
  try {
240
- return { stringValue: maybeRedactString(key, JSON.stringify(v), policy) };
240
+ return { stringValue: maybeRedactString(key2, JSON.stringify(v), policy) };
241
241
  } catch {
242
- return { stringValue: maybeRedactString(key, String(v), policy) };
242
+ return { stringValue: maybeRedactString(key2, String(v), policy) };
243
243
  }
244
244
  default:
245
- return { stringValue: maybeRedactString(key, String(v), policy) };
245
+ return { stringValue: maybeRedactString(key2, String(v), policy) };
246
246
  }
247
247
  }
248
- function attrsFromRecord2(record, prefix, policy = {}) {
248
+ function attrsFromRecord2(record2, prefix, policy = {}) {
249
249
  const out = [];
250
- for (const [k, v] of Object.entries(record)) {
251
- const key = `${prefix}.${k}`;
252
- const value = toOtlpValue(key, v, policy);
250
+ for (const [k, v] of Object.entries(record2)) {
251
+ const key2 = `${prefix}.${k}`;
252
+ const value = toOtlpValue(key2, v, policy);
253
253
  if (value === null)
254
254
  continue;
255
- out.push({ key, value });
255
+ out.push({ key: key2, value });
256
256
  }
257
257
  return out;
258
258
  }
@@ -356,10 +356,11 @@ var require_guard = __commonJS({
356
356
  "node_modules/@pinta-ai/core/dist/guard.js"(exports) {
357
357
  "use strict";
358
358
  Object.defineProperty(exports, "__esModule", { value: true });
359
- exports.GUARD_STATUS_REFUSED = void 0;
359
+ exports.GUARD_STATUS_REFUSED = exports.GUARD_BUDGET_HEADER = void 0;
360
360
  exports.evaluateGuard = evaluateGuard2;
361
361
  var DEFAULT_TIMEOUT_MS2 = 1e4;
362
362
  var DEFAULT_UA = "pinta-core";
363
+ exports.GUARD_BUDGET_HEADER = "x-pinta-guard-budget-ms";
363
364
  function failOpen2(clientRttMs, failOpenReason) {
364
365
  return {
365
366
  decision: "ALLOW",
@@ -391,7 +392,10 @@ var require_guard = __commonJS({
391
392
  // Spread rather than defaulted: an empty string is not "unknown" to the
392
393
  // manager, it is a claim that fails validation, and sending one would
393
394
  // make every adapter that never sets this look like it tried.
394
- ...opts.agentType ? { "x-pinta-agent-type": opts.agentType } : {}
395
+ ...opts.agentType ? { "x-pinta-agent-type": opts.agentType } : {},
396
+ // Same rule: the manager discards a non-positive or non-numeric budget
397
+ // and falls back to its table, so such a value is not sent at all.
398
+ ...Number.isFinite(timeoutMs) && timeoutMs > 0 ? { [exports.GUARD_BUDGET_HEADER]: String(timeoutMs) } : {}
395
399
  },
396
400
  body: JSON.stringify(payload),
397
401
  signal: controller.signal
@@ -924,19 +928,19 @@ var require_session_trace = __commonJS({
924
928
  }
925
929
  }
926
930
  newTrace(sessionId) {
927
- const key = sessionId || "default";
931
+ const key2 = sessionId || "default";
928
932
  const traceId = (0, trace_js_1.generateUlid)();
929
933
  const map = this.read();
930
- map[key] = traceId;
934
+ map[key2] = traceId;
931
935
  this.write(map);
932
936
  return traceId;
933
937
  }
934
938
  currentTrace(sessionId) {
935
- const key = sessionId || "default";
936
- const existing = this.read()[key];
939
+ const key2 = sessionId || "default";
940
+ const existing = this.read()[key2];
937
941
  if (existing)
938
942
  return existing;
939
- return this.newTrace(key);
943
+ return this.newTrace(key2);
940
944
  }
941
945
  };
942
946
  exports.DiskSessionTraceManager = DiskSessionTraceManager;
@@ -947,9 +951,9 @@ var require_session_trace = __commonJS({
947
951
  this.maxSessions = opts.maxSessions ?? DEFAULT_MAX_SESSIONS;
948
952
  }
949
953
  newTrace(sessionId) {
950
- const key = sessionId || "default";
954
+ const key2 = sessionId || "default";
951
955
  const traceId = (0, trace_js_1.generateUlid)();
952
- this.map.set(key, traceId);
956
+ this.map.set(key2, traceId);
953
957
  if (this.map.size > this.maxSessions) {
954
958
  const oldest = this.map.keys().next().value;
955
959
  if (oldest !== void 0)
@@ -958,8 +962,8 @@ var require_session_trace = __commonJS({
958
962
  return traceId;
959
963
  }
960
964
  currentTrace(sessionId) {
961
- const key = sessionId || "default";
962
- return this.map.get(key) ?? this.newTrace(key);
965
+ const key2 = sessionId || "default";
966
+ return this.map.get(key2) ?? this.newTrace(key2);
963
967
  }
964
968
  };
965
969
  exports.MemorySessionTraceManager = MemorySessionTraceManager2;
@@ -994,13 +998,13 @@ var require_env_file = __commonJS({
994
998
  const idx = line.indexOf("=");
995
999
  if (idx < 0)
996
1000
  continue;
997
- const key = line.slice(0, idx).trim();
1001
+ const key2 = line.slice(0, idx).trim();
998
1002
  let value = line.slice(idx + 1).trim();
999
1003
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1000
1004
  value = value.slice(1, -1);
1001
1005
  }
1002
- if (key)
1003
- out[key] = value;
1006
+ if (key2)
1007
+ out[key2] = value;
1004
1008
  }
1005
1009
  return out;
1006
1010
  }
@@ -1012,9 +1016,9 @@ var require_env_file = __commonJS({
1012
1016
  return;
1013
1017
  }
1014
1018
  const parsed = parseEnvFile2(content);
1015
- for (const [key, value] of Object.entries(parsed)) {
1016
- if (process.env[key] === void 0) {
1017
- process.env[key] = value;
1019
+ for (const [key2, value] of Object.entries(parsed)) {
1020
+ if (process.env[key2] === void 0) {
1021
+ process.env[key2] = value;
1018
1022
  }
1019
1023
  }
1020
1024
  }
@@ -1026,7 +1030,7 @@ var require_dist = __commonJS({
1026
1030
  "node_modules/@pinta-ai/core/dist/index.js"(exports) {
1027
1031
  "use strict";
1028
1032
  Object.defineProperty(exports, "__esModule", { value: true });
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;
1033
+ 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_BUDGET_HEADER = 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;
1030
1034
  var redact_js_1 = require_redact();
1031
1035
  Object.defineProperty(exports, "redact", { enumerable: true, get: function() {
1032
1036
  return redact_js_1.redact;
@@ -1096,6 +1100,9 @@ var require_dist = __commonJS({
1096
1100
  Object.defineProperty(exports, "GUARD_STATUS_REFUSED", { enumerable: true, get: function() {
1097
1101
  return guard_js_1.GUARD_STATUS_REFUSED;
1098
1102
  } });
1103
+ Object.defineProperty(exports, "GUARD_BUDGET_HEADER", { enumerable: true, get: function() {
1104
+ return guard_js_1.GUARD_BUDGET_HEADER;
1105
+ } });
1099
1106
  var retry_queue_js_1 = require_retry_queue();
1100
1107
  Object.defineProperty(exports, "DiskRetryQueue", { enumerable: true, get: function() {
1101
1108
  return retry_queue_js_1.DiskRetryQueue;
@@ -1209,7 +1216,7 @@ function resolveConfig(options = {}) {
1209
1216
  if (relayToken && !Object.keys(headers).some((k) => k.toLowerCase() === "x-pinta-relay-token")) {
1210
1217
  headers["x-pinta-relay-token"] = relayToken;
1211
1218
  }
1212
- const guardTimeoutMs = options.guardTimeoutMs ?? (Number(process.env.PINTA_OPENCODE_GUARD_TIMEOUT_MS) || 50);
1219
+ const guardTimeoutMs = options.guardTimeoutMs ?? (Number(process.env.PINTA_OPENCODE_GUARD_TIMEOUT_MS) || 100);
1213
1220
  return {
1214
1221
  endpoint: resolveEndpoint(options),
1215
1222
  headers,
@@ -1242,10 +1249,10 @@ var import_core8 = __toESM(require_dist(), 1);
1242
1249
  var import_core5 = __toESM(require_dist(), 1);
1243
1250
 
1244
1251
  // src/core/version.ts
1245
- var ADAPTER_VERSION = "0.9.0";
1252
+ var ADAPTER_VERSION = "0.11.0";
1246
1253
 
1247
1254
  // src/core/guard.ts
1248
- var DEFAULT_TIMEOUT_MS = 50;
1255
+ var DEFAULT_TIMEOUT_MS = 100;
1249
1256
  var GUARD_UA = `pinta-opencode/${ADAPTER_VERSION}`;
1250
1257
  function evaluateGuard(payload, endpoint, opts = {}) {
1251
1258
  return (0, import_core5.evaluateGuard)(payload, endpoint, {
@@ -1327,6 +1334,258 @@ function buildOtlpPayload(args) {
1327
1334
  });
1328
1335
  }
1329
1336
 
1337
+ // src/model.ts
1338
+ var MODEL_CACHE_LIMITS = {
1339
+ messages: 1024,
1340
+ requests: 512,
1341
+ calls: 2048,
1342
+ endedSessions: 128,
1343
+ ttlMs: 15 * 60 * 1e3
1344
+ };
1345
+ var PLACEHOLDERS = /* @__PURE__ */ new Set([
1346
+ "unknown",
1347
+ "undefined",
1348
+ "null",
1349
+ "n/a",
1350
+ "none",
1351
+ "-",
1352
+ "auto",
1353
+ "default"
1354
+ ]);
1355
+ function identifier(value) {
1356
+ if (typeof value !== "string") return void 0;
1357
+ const text = value.trim();
1358
+ return text && text.length <= 1024 ? text : void 0;
1359
+ }
1360
+ function modelId(value) {
1361
+ const id = identifier(value);
1362
+ if (!id || id.startsWith("{") || id.startsWith("[") || PLACEHOLDERS.has(id.toLowerCase())) return void 0;
1363
+ return id;
1364
+ }
1365
+ function record(value) {
1366
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1367
+ }
1368
+ function fromModel(value, source, provider) {
1369
+ const obj = record(value);
1370
+ const model = obj ? modelId(obj.modelID) ?? modelId(obj.id) : modelId(value);
1371
+ if (!model) return void 0;
1372
+ return {
1373
+ model,
1374
+ source: obj ? `${source}.${modelId(obj.modelID) ? "modelID" : "id"}` : source,
1375
+ provider: modelId(obj?.providerID ?? provider)
1376
+ };
1377
+ }
1378
+ function key(...parts) {
1379
+ return JSON.stringify(parts);
1380
+ }
1381
+ function agreed(...values) {
1382
+ const ids = values.map(identifier).filter((id) => id !== void 0);
1383
+ return ids.every((id) => id === ids[0]) ? ids[0] : void 0;
1384
+ }
1385
+ function eventSessionID(type, props) {
1386
+ const info = record(props.info);
1387
+ const part = record(props.part);
1388
+ return typeof type === "string" && type.startsWith("session.") ? agreed(props.sessionID, info?.id) : agreed(props.sessionID, info?.sessionID, part?.sessionID);
1389
+ }
1390
+ var BoundedCache = class {
1391
+ constructor(limit, now) {
1392
+ this.limit = limit;
1393
+ this.now = now;
1394
+ }
1395
+ entries = /* @__PURE__ */ new Map();
1396
+ get(id) {
1397
+ const entry = this.entries.get(id);
1398
+ if (entry && entry.expires > this.now()) return entry.value;
1399
+ this.entries.delete(id);
1400
+ return void 0;
1401
+ }
1402
+ set(id, value) {
1403
+ this.entries.delete(id);
1404
+ this.entries.set(id, { value, expires: this.now() + MODEL_CACHE_LIMITS.ttlMs });
1405
+ if (this.entries.size > this.limit) this.entries.delete(this.entries.keys().next().value);
1406
+ }
1407
+ removeWhere(predicate) {
1408
+ for (const [id, entry] of this.entries) {
1409
+ if (predicate(entry.value)) this.entries.delete(id);
1410
+ }
1411
+ }
1412
+ clearSession(sessionID) {
1413
+ this.removeWhere((entry) => entry.sessionID === sessionID);
1414
+ }
1415
+ };
1416
+ var ModelTracker = class {
1417
+ constructor(now = Date.now) {
1418
+ this.now = now;
1419
+ this.messages = new BoundedCache(MODEL_CACHE_LIMITS.messages, now);
1420
+ this.requests = new BoundedCache(MODEL_CACHE_LIMITS.requests, now);
1421
+ this.calls = new BoundedCache(MODEL_CACHE_LIMITS.calls, now);
1422
+ }
1423
+ messages;
1424
+ requests;
1425
+ calls;
1426
+ ended = /* @__PURE__ */ new Map();
1427
+ chatMessage(input, output) {
1428
+ const sessionID = agreed(input.sessionID, output?.message?.sessionID);
1429
+ if (!sessionID) return;
1430
+ this.ended.delete(sessionID);
1431
+ const messageID = agreed(input.messageID, output?.message?.id);
1432
+ if (!messageID) return;
1433
+ const agent = identifier(output?.message?.agent ?? input.agent);
1434
+ const supplied = output?.message?.model !== void 0;
1435
+ this.requests.set(key(sessionID, messageID, agent ?? ""), {
1436
+ sessionID,
1437
+ messageID,
1438
+ evidence: fromModel(
1439
+ supplied ? output.message?.model : input.model,
1440
+ supplied ? "requested:chat.message.message.model" : "requested:chat.message.model"
1441
+ )
1442
+ });
1443
+ }
1444
+ chatParams(input) {
1445
+ const sessionID = agreed(input.sessionID, input.message?.sessionID);
1446
+ const messageID = identifier(input.message?.id);
1447
+ const agent = identifier(input.agent);
1448
+ if (!sessionID || !messageID || !agent || this.ended.has(sessionID)) return;
1449
+ this.requests.set(key(sessionID, messageID, agent), {
1450
+ sessionID,
1451
+ messageID,
1452
+ evidence: fromModel(input.model, "requested:chat.params.model")
1453
+ });
1454
+ }
1455
+ fresh(timestamp) {
1456
+ return timestamp === void 0 || typeof timestamp === "number" && Number.isFinite(timestamp) && timestamp >= this.now() - MODEL_CACHE_LIMITS.ttlMs;
1457
+ }
1458
+ clearSession(sessionID) {
1459
+ this.messages.clearSession(sessionID);
1460
+ this.requests.clearSession(sessionID);
1461
+ this.calls.clearSession(sessionID);
1462
+ }
1463
+ event(type, props) {
1464
+ const sessionID = eventSessionID(type, props);
1465
+ const info = record(props.info);
1466
+ const part = record(props.part);
1467
+ const ended = ["session.idle", "session.deleted", "session.error"].includes(type ?? "") || type === "session.status" && record(props.status)?.type === "idle";
1468
+ if (sessionID && ended) {
1469
+ this.clearSession(sessionID);
1470
+ this.ended.delete(sessionID);
1471
+ this.ended.set(sessionID, true);
1472
+ if (this.ended.size > MODEL_CACHE_LIMITS.endedSessions) this.ended.delete(this.ended.keys().next().value);
1473
+ } else if (sessionID && type === "session.created") {
1474
+ this.clearSession(sessionID);
1475
+ this.ended.delete(sessionID);
1476
+ }
1477
+ if (type === "message.updated" && info) {
1478
+ if (!agreed(props.sessionID, info.sessionID) && (props.sessionID || info.sessionID)) return void 0;
1479
+ const messageID = identifier(info.id);
1480
+ const agent = identifier(info.agent);
1481
+ const role = info.role;
1482
+ 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;
1483
+ if (!evidence && role === "user" && !("model" in info) && sessionID && messageID) {
1484
+ evidence = this.requests.get(key(sessionID, messageID, agent ?? ""))?.evidence;
1485
+ }
1486
+ if (role === "user" && "model" in info && !evidence && sessionID && messageID) {
1487
+ this.requests.set(key(sessionID, messageID, agent ?? ""), { sessionID, messageID });
1488
+ }
1489
+ if (evidence) evidence = { ...evidence, agent, messageID };
1490
+ if (sessionID && messageID && !this.ended.has(sessionID) && this.fresh(record(info.time)?.created)) {
1491
+ const old = this.messages.get(key(sessionID, messageID));
1492
+ 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));
1493
+ this.messages.set(key(sessionID, messageID), {
1494
+ sessionID,
1495
+ messageID,
1496
+ evidence: ambiguous ? void 0 : evidence,
1497
+ ambiguous
1498
+ });
1499
+ }
1500
+ return evidence;
1501
+ }
1502
+ if (sessionID && type === "message.removed") {
1503
+ const messageID = identifier(props.messageID);
1504
+ this.messages.removeWhere((entry) => entry.sessionID === sessionID && entry.messageID === messageID);
1505
+ this.requests.removeWhere((entry) => entry.sessionID === sessionID && entry.messageID === messageID);
1506
+ this.calls.removeWhere((entry) => entry.sessionID === sessionID && entry.messageID === messageID);
1507
+ }
1508
+ if (sessionID && type === "message.part.removed") {
1509
+ const partID = identifier(props.partID);
1510
+ if (partID) this.calls.removeWhere((entry) => entry.sessionID === sessionID && entry.partID === partID);
1511
+ }
1512
+ if (type === "message.part.delta") {
1513
+ const messageID = identifier(props.messageID);
1514
+ return sessionID && messageID && !this.ended.has(sessionID) ? this.messages.get(key(sessionID, messageID))?.evidence : void 0;
1515
+ }
1516
+ if (type === "message.part.updated" && part) {
1517
+ if (!sessionID || this.ended.has(sessionID) || !this.fresh(props.time)) return void 0;
1518
+ const messageID = identifier(part.messageID);
1519
+ if (!messageID) return void 0;
1520
+ const callID = identifier(part.callID);
1521
+ if (part.type === "tool" && callID) {
1522
+ const callKey = key(sessionID, callID);
1523
+ const old = this.calls.get(callKey);
1524
+ if (!old?.closed) {
1525
+ const tool = identifier(part.tool);
1526
+ this.calls.set(callKey, {
1527
+ sessionID,
1528
+ messageID,
1529
+ partID: identifier(part.id),
1530
+ tool,
1531
+ pinned: old?.pinned,
1532
+ ambiguous: old?.ambiguous || Boolean(old && (old.messageID !== messageID || old.tool !== tool))
1533
+ });
1534
+ }
1535
+ }
1536
+ return this.messages.get(key(sessionID, messageID))?.evidence;
1537
+ }
1538
+ if (typeof type === "string" && type.startsWith("session.")) {
1539
+ return fromModel(info?.model, `selected:${type}.info.model`) ?? fromModel(props.model, `selected:${type}.model`);
1540
+ }
1541
+ return fromModel(props.model, "reported:event.model") ?? fromModel(modelId(props.modelID), "reported:event.modelID", props.providerID);
1542
+ }
1543
+ tool(input, after) {
1544
+ const direct = fromModel(modelId(input.modelID), "reported:tool.modelID", input.providerID) ?? fromModel(input.model, "reported:tool.model", input.providerID);
1545
+ const sessionID = identifier(input.sessionID);
1546
+ const callID = identifier(input.callID);
1547
+ if (!sessionID || !callID) return direct;
1548
+ const callKey = key(sessionID, callID);
1549
+ const call = this.calls.get(callKey);
1550
+ let evidence = direct;
1551
+ if (!("model" in input) && !("modelID" in input) && call && !call.closed && !call.ambiguous && !this.ended.has(sessionID) && (!call.tool || call.tool === input.tool)) {
1552
+ const message = this.messages.get(key(sessionID, call.messageID));
1553
+ if (!message?.ambiguous) evidence = call.pinned ?? message?.evidence;
1554
+ if (input.agent && evidence?.agent && input.agent !== evidence.agent || input.messageID && input.messageID !== call.messageID) evidence = void 0;
1555
+ }
1556
+ if (call && !call.closed) {
1557
+ this.calls.set(callKey, {
1558
+ ...call,
1559
+ pinned: after ? void 0 : evidence,
1560
+ closed: after
1561
+ });
1562
+ }
1563
+ return evidence;
1564
+ }
1565
+ beforeTool(input) {
1566
+ return this.tool(input, false);
1567
+ }
1568
+ afterTool(input) {
1569
+ return this.tool(input, true);
1570
+ }
1571
+ };
1572
+ function modelFields(fields, evidence) {
1573
+ const { model: rawModel, model_source: rawSource, ...out } = fields;
1574
+ if ("model" in fields && rawModel !== evidence?.model) out.model_raw = rawModel;
1575
+ if ("model_source" in fields) out.model_source_raw = rawSource;
1576
+ if (evidence) {
1577
+ out.model = evidence.model;
1578
+ out.model_source = evidence.source;
1579
+ if (evidence.provider) {
1580
+ if ("provider" in out && out.provider !== evidence.provider) out.provider_raw = out.provider;
1581
+ out.provider = evidence.provider;
1582
+ }
1583
+ if (out.agent === void 0) out.agent = evidence.agent;
1584
+ if (out.message_id === void 0) out.message_id = evidence.messageID;
1585
+ }
1586
+ return out;
1587
+ }
1588
+
1330
1589
  // src/telemetry.ts
1331
1590
  var Telemetry = class {
1332
1591
  constructor(transport, trace, config) {
@@ -1334,6 +1593,7 @@ var Telemetry = class {
1334
1593
  this.trace = trace;
1335
1594
  this.config = config;
1336
1595
  }
1596
+ models = new ModelTracker();
1337
1597
  build(name, sessionId, fields) {
1338
1598
  const traceId = this.trace.currentTrace(sessionId);
1339
1599
  return buildOtlpPayload({ name, traceId, fields, serviceVersion: this.config.serviceVersion });
@@ -1345,17 +1605,24 @@ var Telemetry = class {
1345
1605
  async emit(name, sessionId, fields) {
1346
1606
  await this.send(this.build(name, sessionId, fields));
1347
1607
  }
1608
+ chatMessage(input, output) {
1609
+ this.models.chatMessage(input, output);
1610
+ }
1611
+ chatParams(input) {
1612
+ this.models.chatParams(input);
1613
+ }
1348
1614
  /** Lifecycle span from the `event` hook. Flushes the retry buffer on turn-END. */
1349
1615
  async lifecycle(ev) {
1350
- const props = ev.properties ?? {};
1351
- const sessionId = typeof props.sessionID === "string" ? props.sessionID : void 0;
1352
- await this.emit(`opencode.event.${ev.type ?? "unknown"}`, sessionId, {
1616
+ const props = record(ev.properties) ?? {};
1617
+ const sessionId = eventSessionID(ev.type, props);
1618
+ const model = this.models.event(ev.type, props);
1619
+ await this.emit(`opencode.event.${ev.type ?? "unknown"}`, sessionId, modelFields({
1353
1620
  hook: "event",
1354
1621
  event_type: ev.type,
1355
1622
  session_id: sessionId,
1356
1623
  cwd: process.cwd(),
1357
1624
  ...props
1358
- });
1625
+ }, model));
1359
1626
  if (ev.type === "session.idle") await this.transport.flush();
1360
1627
  }
1361
1628
  /**
@@ -1368,7 +1635,10 @@ var Telemetry = class {
1368
1635
  return this.build(
1369
1636
  "opencode.tool.before",
1370
1637
  input.sessionID,
1371
- { ...toolIdentity("tool.execute.before", input), tool_input: args }
1638
+ modelFields(
1639
+ { ...toolIdentity("tool.execute.before", input), tool_input: args },
1640
+ this.models.beforeTool(input)
1641
+ )
1372
1642
  );
1373
1643
  }
1374
1644
  /** Tool span from `tool.execute.before`, carrying an already-known guard decision. */
@@ -1378,13 +1648,13 @@ var Telemetry = class {
1378
1648
  /** Tool result span from `tool.execute.after`, incl. exit code / truncation. */
1379
1649
  async toolAfter(input, output) {
1380
1650
  const meta = output.metadata ?? {};
1381
- await this.emit("opencode.tool.after", input.sessionID, {
1651
+ await this.emit("opencode.tool.after", input.sessionID, modelFields({
1382
1652
  ...toolIdentity("tool.execute.after", input),
1383
1653
  title: output.title,
1384
1654
  tool_response: output.output,
1385
1655
  exit: meta.exit,
1386
1656
  truncated: meta.truncated
1387
- });
1657
+ }, this.models.afterTool(input)));
1388
1658
  }
1389
1659
  };
1390
1660
  function toolIdentity(hook, input) {
@@ -1393,7 +1663,12 @@ function toolIdentity(hook, input) {
1393
1663
  tool_name: input.tool,
1394
1664
  session_id: input.sessionID,
1395
1665
  tool_use_id: input.callID,
1396
- cwd: process.cwd()
1666
+ cwd: process.cwd(),
1667
+ ...input.model !== void 0 ? { model: input.model } : {},
1668
+ ...input.modelID !== void 0 ? { modelID: input.modelID } : {},
1669
+ ...input.providerID !== void 0 ? { providerID: input.providerID } : {},
1670
+ ...input.agent !== void 0 ? { agent: input.agent } : {},
1671
+ ...input.messageID !== void 0 ? { message_id: input.messageID } : {}
1397
1672
  };
1398
1673
  }
1399
1674
 
@@ -1433,8 +1708,12 @@ var PintaOpencode = async (_input, options) => {
1433
1708
  }
1434
1709
  return {
1435
1710
  // turn-START → rotate a new trace for this session.
1436
- "chat.message": failOpen("chat.message", async (input) => {
1711
+ "chat.message": failOpen("chat.message", async (input, output) => {
1437
1712
  trace.newTrace(input?.sessionID);
1713
+ telemetry.chatMessage(input, output);
1714
+ }),
1715
+ "chat.params": failOpen("chat.params", async (input, _output) => {
1716
+ telemetry.chatParams(input);
1438
1717
  }),
1439
1718
  // lifecycle telemetry; flushes the retry buffer on session.idle (turn-END).
1440
1719
  event: failOpen("event", async (input) => {
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 it=Object.create;var ce=Object.defineProperty;var at=Object.getOwnPropertyDescriptor;var ut=Object.getOwnPropertyNames;var ct=Object.getPrototypeOf,lt=Object.prototype.hasOwnProperty;var h=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var m=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var dt=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of ut(e))!lt.call(t,o)&&o!==r&&ce(t,o,{get:()=>e[o],enumerable:!(n=at(e,o))||n.enumerable});return t};var O=(t,e,r)=>(r=t!=null?it(ct(t)):{},dt(e||!t||!t.__esModule?ce(r,"default",{value:t,enumerable:!0}):r,t));var V=m(f=>{"use strict";Object.defineProperty(f,"__esModule",{value:!0});f.PATTERNS=f.MAX_BYTES=void 0;f.truncateTo=le;f.truncate=pt;f.collectMatches=de;f.resolveOverlaps=pe;f.applyMatches=fe;f.redact=ft;f.MAX_BYTES=102400;function le(t,e){let r=Buffer.from(t,"utf-8");return r.length<=e?t:`${r.subarray(0,Math.max(0,e)).toString("utf-8")}\u2026[TRUNCATED:${r.length}]`}function pt(t){return le(t,f.MAX_BYTES)}f.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 de(t,e){let r=[];for(let n of f.PATTERNS){if(n.requireContext&&n.requireContext!==e.context)continue;let o=new RegExp(n.regex.source,n.regex.flags),i;for(;(i=o.exec(t))!==null;){let u=n.captureGroup??0,a=i[u];if(a===void 0){i.index===o.lastIndex&&o.lastIndex++;continue}let c=i.index,d=i.index+i[0].length,_=c+i[0].indexOf(a),st=_+a.length;r.push({start:c,end:d,replaceStart:_,replaceEnd:st,type:n.type}),i.index===o.lastIndex&&o.lastIndex++}}return r}function pe(t){let e=[...t].sort((o,i)=>o.start!==i.start?o.start-i.start:i.end-i.start-(o.end-o.start)),r=[],n=-1;for(let o of e)o.start<n||(r.push(o),n=o.end);return r}function fe(t,e){let r=[...e].sort((o,i)=>i.replaceStart-o.replaceStart),n=t;for(let o of r)n=n.slice(0,o.replaceStart)+`[REDACTED:${o.type}]`+n.slice(o.replaceEnd);return n}function ft(t,e={}){if(t.length===0)return t;let r=de(t,e);if(r.length===0)return t;let n=pe(r);return fe(t,n)}});var U=m(p=>{"use strict";var gt=p&&p.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(p,"__esModule",{value:!0});p.MAX_SPAN_BYTES=void 0;p.ulidToTraceId=_e;p.newSpanId=me;p.snakeCase=_t;p.toOtlpValue=Te;p.attrsFromRecord=mt;p.guardAttrs=Oe;p.clientAttrs=Pe;p.enforceSpanBudget=Se;p.buildPayload=Tt;p.attachGuard=ve;p.mergeBatch=Ot;var ht=gt(h("crypto")),L=V(),yt="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function _e(t){if(t.length!==26)throw new Error(`ulidToTraceId: expected 26 chars, got ${t.length}`);let e=0n;for(let n of t){let o=yt.indexOf(n);if(o<0)throw new Error(`ulidToTraceId: invalid Crockford char "${n}"`);e=e<<5n|BigInt(o)}let r=(1n<<128n)-1n;return e&=r,e.toString(16).padStart(32,"0")}function me(){return ht.default.randomBytes(8).toString("hex")}function _t(t){return t.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}var ge=new Set;function x(t,e,r){let n=(0,L.truncate)(e);if((r.skipRedactKeys??ge).has(t))return n;let o=(r.bashContextKeys??ge).has(t)?"bash":void 0;return(0,L.redact)(n,{context:o})}function Te(t,e,r={}){if(e==null)return null;switch(typeof e){case"string":return{stringValue:x(t,e,r)};case"boolean":return{boolValue:e};case"number":return Number.isInteger(e)?{intValue:e}:{doubleValue:e};case"object":try{return{stringValue:x(t,JSON.stringify(e),r)}}catch{return{stringValue:x(t,String(e),r)}}default:return{stringValue:x(t,String(e),r)}}}function mt(t,e,r={}){let n=[];for(let[o,i]of Object.entries(t)){let u=`${e}.${o}`,a=Te(u,i,r);a!==null&&n.push({key:u,value:a})}return n}function Oe(t){let e=[{key:"pinta.guard.decision",value:{stringValue:t.decision.toLowerCase()}},{key:"pinta.guard.duration_ms",value:{intValue:t.durationMs}}];return t.reason&&e.push({key:"pinta.guard.matched_rule",value:{stringValue:t.reason}}),t.failOpenReason&&e.push({key:"pinta.guard.fail_open_reason",value:{stringValue:t.failOpenReason}}),e}function Pe(t){return[{key:"pinta.client.rtt_ms",value:Number.isInteger(t.rttMs)?{intValue:t.rttMs}:{doubleValue:t.rttMs}},{key:"pinta.client.op",value:{stringValue:t.op}}]}p.MAX_SPAN_BYTES=800*1024;function he(t){let e=JSON.stringify(t);return e===void 0?0:Buffer.byteLength(e,"utf-8")}var ye=1024;function Se(t,e=p.MAX_SPAN_BYTES){let r=he(t);for(let n=0;n<100&&r>e;n++){let o=null,i=0;for(let c of t.attributes){if(!("stringValue"in c.value))continue;let d=Buffer.byteLength(c.value.stringValue,"utf-8");d>i&&(i=d,o=c.value)}if(o===null||i<=ye)break;let u=r-e,a=Math.max(ye,i-u-64);o.stringValue=(0,L.truncateTo)(o.stringValue,a),r=he(t)}return t}function Tt(t){let e=t.now??Date.now(),r=(BigInt(e)*1000000n).toString(),n=[...t.attributes],o={traceId:_e(t.traceId),spanId:me(),name:t.spanName,kind:t.spanKind??1,startTimeUnixNano:r,endTimeUnixNano:r,attributes:n};Se(o);let i={resourceSpans:[{resource:{attributes:t.resource},scopeSpans:[{scope:t.scope,spans:[o]}]}]};return ve(i,t.guard,t.clientTiming)}function ve(t,e,r){let n=t.resourceSpans[0]?.scopeSpans[0]?.spans[0];if(!n)return t;e&&n.attributes.push(...Oe(e));let o=r??(e?.clientRttMs!==void 0?{op:"guard",rttMs:e.clientRttMs}:null);return o&&n.attributes.push(...Pe(o)),t}function Ot(t){let e=[];for(let r of t)e.push(...r.resourceSpans);return{resourceSpans:e}}});var Ee=m(A=>{"use strict";Object.defineProperty(A,"__esModule",{value:!0});A.GUARD_STATUS_REFUSED=void 0;A.evaluateGuard=Et;var Pt=1e4,St="pinta-core";function I(t,e){return{decision:"ALLOW",reason:null,userMessage:null,durationMs:t,clientRttMs:t,failOpenReason:e}}var vt=new Set(["ALLOW","DENY","REVIEW"]);A.GUARD_STATUS_REFUSED=410;async function Et(t,e,r={}){if(!e||r.disabled)return null;let n=r.timeoutMs??Pt,o=Date.now(),i=new AbortController,u=setTimeout(()=>i.abort(),n);try{let a=await fetch(e,{method:"POST",headers:{"content-type":"application/json","user-agent":r.userAgent??St,"x-pinta-relay-token":r.token??"",...r.agentType?{"x-pinta-agent-type":r.agentType}:{}},body:JSON.stringify(t),signal:i.signal});if(a.status===A.GUARD_STATUS_REFUSED)return I(Date.now()-o,"refused");if(a.status!==200)return I(Date.now()-o,"error");let c=await a.json(),d=c?.durationMs===void 0||typeof c.durationMs=="number"&&Number.isFinite(c.durationMs);if(!c||!vt.has(c.decision)||!d)return I(Date.now()-o,"error");let _=Date.now()-o;return{decision:c.decision,reason:c.reason??null,userMessage:c.userMessage??null,durationMs:c.durationMs??_,clientRttMs:_}}catch(a){let c=a.name,d=c==="AbortError"||c==="TimeoutError"?"timeout":"error";return I(Date.now()-o,d)}finally{clearTimeout(u)}}});var j=m(M=>{"use strict";var bt=M&&M.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(M,"__esModule",{value:!0});M.atomicWriteFileSync=At;var be=bt(h("fs"));function At(t,e){let r=`${t}.${process.pid}.tmp`;be.default.writeFileSync(r,e),be.default.renameSync(r,t)}});var z=m(P=>{"use strict";var Ae=P&&P.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(P,"__esModule",{value:!0});P.MemoryRetryQueue=P.DiskRetryQueue=void 0;var g=Ae(h("fs")),N=Ae(h("path")),kt=j(),B=1e3,wt=50,Mt=5,X=class{filePath;lockPath;logPrefix;constructor(e,r){this.filePath=N.default.join(e,"failed-spans.jsonl"),this.lockPath=this.filePath+".lock",this.logPrefix=r}enqueue(e){try{g.default.mkdirSync(N.default.dirname(this.filePath),{recursive:!0});let r=JSON.stringify({savedAt:new Date().toISOString(),payload:e})+`
3
- `;g.default.appendFileSync(this.filePath,r),this.trim()}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue enqueue failed: ${r}
4
- `)}}readAll(){try{let e=g.default.readFileSync(this.filePath,"utf-8"),r=[];for(let n of e.split(`
5
- `))if(n.trim())try{let o=JSON.parse(n);Array.isArray(o?.payload?.resourceSpans)&&r.push(o)}catch{}return r}catch{return[]}}rewrite(e){try{if(e.length===0){g.default.existsSync(this.filePath)&&g.default.unlinkSync(this.filePath);return}g.default.mkdirSync(N.default.dirname(this.filePath),{recursive:!0}),(0,kt.atomicWriteFileSync)(this.filePath,e.map(r=>JSON.stringify(r)).join(`
2
+ var _t=Object.create;var Pe=Object.defineProperty;var vt=Object.getOwnPropertyDescriptor;var Tt=Object.getOwnPropertyNames;var Ot=Object.getPrototypeOf,Et=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 E=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports);var Pt=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Tt(e))!Et.call(r,s)&&s!==t&&Pe(r,s,{get:()=>e[s],enumerable:!(n=vt(e,s))||n.enumerable});return r};var D=(r,e,t)=>(t=r!=null?_t(Ot(r)):{},Pt(e||!r||!r.__esModule?Pe(t,"default",{value:r,enumerable:!0}):t,r));var ee=E(h=>{"use strict";Object.defineProperty(h,"__esModule",{value:!0});h.PATTERNS=h.MAX_BYTES=void 0;h.truncateTo=Se;h.truncate=St;h.collectMatches=be;h.resolveOverlaps=De;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 St(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 be(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 De(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=be(r,e);if(t.length===0)return r;let n=De(t);return Ie(r,n)}});var re=E(p=>{"use strict";var Dt=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=ke;p.newSpanId=Re;p.snakeCase=At;p.toOtlpValue=xe;p.attrsFromRecord=wt;p.guardAttrs=je;p.clientAttrs=Ne;p.enforceSpanBudget=Ce;p.buildPayload=kt;p.attachGuard=qe;p.mergeBatch=Rt;var It=Dt(y("crypto")),te=ee(),Mt="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function ke(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 Re(){return It.default.randomBytes(8).toString("hex")}function At(r){return r.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}var Me=new Set;function $(r,e,t){let n=(0,te.truncate)(e);if((t.skipRedactKeys??Me).has(r))return n;let s=(t.bashContextKeys??Me).has(r)?"bash":void 0;return(0,te.redact)(n,{context:s})}function xe(r,e,t={}){if(e==null)return null;switch(typeof e){case"string":return{stringValue:$(r,e,t)};case"boolean":return{boolValue:e};case"number":return Number.isInteger(e)?{intValue:e}:{doubleValue:e};case"object":try{return{stringValue:$(r,JSON.stringify(e),t)}}catch{return{stringValue:$(r,String(e),t)}}default:return{stringValue:$(r,String(e),t)}}}function wt(r,e,t={}){let n=[];for(let[s,i]of Object.entries(r)){let c=`${e}.${s}`,o=xe(c,i,t);o!==null&&n.push({key:c,value:o})}return n}function je(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 Ne(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 Ae(r){let e=JSON.stringify(r);return e===void 0?0:Buffer.byteLength(e,"utf-8")}var we=1024;function Ce(r,e=p.MAX_SPAN_BYTES){let t=Ae(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=Ae(r)}return r}function kt(r){let e=r.now??Date.now(),t=(BigInt(e)*1000000n).toString(),n=[...r.attributes],s={traceId:ke(r.traceId),spanId:Re(),name:r.spanName,kind:r.spanKind??1,startTimeUnixNano:t,endTimeUnixNano:t,attributes:n};Ce(s);let i={resourceSpans:[{resource:{attributes:r.resource},scopeSpans:[{scope:r.scope,spans:[s]}]}]};return qe(i,r.guard,r.clientTiming)}function qe(r,e,t){let n=r.resourceSpans[0]?.scopeSpans[0]?.spans[0];if(!n)return r;e&&n.attributes.push(...je(e));let s=t??(e?.clientRttMs!==void 0?{op:"guard",rttMs:e.clientRttMs}:null);return s&&n.attributes.push(...Ne(s)),r}function Rt(r){let e=[];for(let t of r)e.push(...t.resourceSpans);return{resourceSpans:e}}});var Be=E(P=>{"use strict";Object.defineProperty(P,"__esModule",{value:!0});P.GUARD_STATUS_REFUSED=P.GUARD_BUDGET_HEADER=void 0;P.evaluateGuard=Ct;var xt=1e4,jt="pinta-core";P.GUARD_BUDGET_HEADER="x-pinta-guard-budget-ms";function U(r,e){return{decision:"ALLOW",reason:null,userMessage:null,durationMs:r,clientRttMs:r,failOpenReason:e}}var Nt=new Set(["ALLOW","DENY","REVIEW"]);P.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}:{},...Number.isFinite(n)&&n>0?{[P.GUARD_BUDGET_HEADER]:String(n)}:{}},body:JSON.stringify(r),signal:i.signal});if(o.status===P.GUARD_STATUS_REFUSED)return U(Date.now()-s,"refused");if(o.status!==200)return U(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 U(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 U(Date.now()-s,d)}finally{clearTimeout(c)}}});var V=E(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 Ge=qt(y("fs"));function Bt(r,e){let t=`${r}.${process.pid}.tmp`;Ge.default.writeFileSync(t,e),Ge.default.renameSync(t,r)}});var oe=E(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")),L=Fe(y("path")),Gt=V(),X=1e3,Ft=50,$t=5,ne=class{filePath;lockPath;logPrefix;constructor(e,t){this.filePath=L.default.join(e,"failed-spans.jsonl"),this.lockPath=this.filePath+".lock",this.logPrefix=t}enqueue(e){try{m.default.mkdirSync(L.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(L.default.dirname(this.filePath),{recursive:!0}),(0,Gt.atomicWriteFileSync)(this.filePath,e.map(t=>JSON.stringify(t)).join(`
6
6
  `)+`
7
- `)}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue rewrite failed: ${r}
8
- `)}}tryAcquireLock(){let e=Date.now();try{g.default.mkdirSync(N.default.dirname(this.lockPath),{recursive:!0})}catch(r){return process.stderr.write(`[${this.logPrefix}] retry-queue lock mkdir failed: ${r}
9
- `),!1}for(;Date.now()-e<wt;)try{let r=g.default.openSync(this.lockPath,"wx");return g.default.writeSync(r,String(process.pid)),g.default.closeSync(r),!0}catch(r){if(r?.code!=="EEXIST")return process.stderr.write(`[${this.logPrefix}] retry-queue lock open failed: ${r}
10
- `),!1;try{let i=g.default.statSync(this.lockPath);if(Date.now()-i.mtimeMs>3e4){g.default.unlinkSync(this.lockPath);continue}}catch{}let n=Mt,o=Date.now()+n;for(;Date.now()<o;);}return!1}release(){try{g.default.unlinkSync(this.lockPath)}catch{}}trim(){let e=this.readAll();if(e.length<=B)return;let r=e.length-B;process.stderr.write(`[${this.logPrefix}] retry-queue full, dropping ${r} oldest entries
11
- `),this.rewrite(e.slice(r))}};P.DiskRetryQueue=X;var Y=class{entries=[];enqueue(e){this.entries.push(e),this.entries.length>B&&this.entries.splice(0,this.entries.length-B)}drain(){let e=this.entries;return this.entries=[],e}get size(){return this.entries.length}};P.MemoryRetryQueue=Y});var xe=m(l=>{"use strict";Object.defineProperty(l,"__esModule",{value:!0});l.MemoryTransport=l.DiskTransport=l.MAX_POST_BYTES=void 0;l.parseHeadersEnv=Re;l.envOptionsResolver=De;l.postOtlp=R;var ke=z(),we=U(),Rt=5e3;l.MAX_POST_BYTES=900*1024;function q(t){let e=JSON.stringify(t);return e===void 0?0:Buffer.byteLength(e,"utf-8")}function Me(t,e,r){let n=[],o=[],i=[],u=0;for(let a of t){let c=e(a);if(c>r){o.push(a);continue}i.length>0&&u+c>r&&(n.push(i),i=[],u=0),i.push(a),u+=c}return i.length>0&&n.push(i),{chunks:n,oversize:o}}function Re(t){if(!t)return{};if(typeof t=="object")return{...t};let e={};for(let r of t.split(",")){let[n,...o]=r.split("=");n&&o.length>0&&(e[n.trim()]=o.join("=").trim())}return e}function De(){let t=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,e=process.env.OTEL_EXPORTER_OTLP_ENDPOINT,r;return t?r=t.replace(/\/+$/,""):e&&(r=e.replace(/\/+$/,"")+"/v1/traces"),r?{endpoint:r,headers:Re(process.env.OTEL_EXPORTER_OTLP_HEADERS)}:null}async function R(t,e,r){let n=new AbortController,o=setTimeout(()=>n.abort(),Rt);try{let i=await fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json",...e.headers},body:JSON.stringify(t),signal:n.signal});if(!i.ok){let u="";try{u=(await i.text()).slice(0,200)}catch{}let a=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(`[${r}] OTLP POST ${i.status} ${e.endpoint}${a}${u?` body=${u}`:""}
12
- `),!1}return!0}catch(i){return process.stderr.write(`[${r}] OTLP POST failed: ${i.message??String(i)}
13
- `),!1}finally{clearTimeout(o)}}var K=class{queue;logPrefix;resolveOptions;constructor(e){this.queue=new ke.DiskRetryQueue(e.pluginData,e.logPrefix),this.logPrefix=e.logPrefix,this.resolveOptions=e.resolveOptions??De}async send(e){let r=this.resolveOptions();if(!r||this.dropIfOversized(e))return;await R(e,r,this.logPrefix)||this.queue.enqueue(e)}async flush(){let e=this.resolveOptions();if(e&&this.queue.tryAcquireLock())try{let r=this.queue.readAll();if(r.length===0)return;let{chunks:n,oversize:o}=Me(r,a=>q(a.payload),l.MAX_POST_BYTES);o.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${o.length} oversized retry ${o.length===1?"entry":"entries"} (each > ${l.MAX_POST_BYTES} bytes \u2014 the backend caps the request body, so they can never be delivered)
14
- `);let i=[].concat(...n),u=0;for(let a of n){if(!await R((0,we.mergeBatch)(a.map(d=>d.payload)),e,this.logPrefix))break;u+=a.length}this.queue.rewrite(i.slice(u))}catch(r){process.stderr.write(`[${this.logPrefix}] flush failed: ${r?.message??String(r)}
15
- `)}finally{this.queue.release()}}dropIfOversized(e){let r=q(e);return r<=l.MAX_POST_BYTES?!1:(process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (${r} > ${l.MAX_POST_BYTES} bytes) \u2014 undeliverable, not queued
16
- `),!0)}};l.DiskTransport=K;var Z=class{queue=new ke.MemoryRetryQueue;logPrefix;resolveOptions;constructor(e){this.logPrefix=e.logPrefix,this.resolveOptions=e.resolveOptions}async send(e){let r=this.resolveOptions();if(!r)return;if(q(e)>l.MAX_POST_BYTES){process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (> ${l.MAX_POST_BYTES} bytes) \u2014 undeliverable, not buffered
17
- `);return}await R(e,r,this.logPrefix)||this.queue.enqueue(e)}async flush(){let e=this.resolveOptions();if(!e)return;let r=this.queue.drain();if(r.length===0)return;let{chunks:n,oversize:o}=Me(r,q,l.MAX_POST_BYTES);o.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${o.length} oversized buffered ${o.length===1?"payload":"payloads"} (each > ${l.MAX_POST_BYTES} bytes \u2014 undeliverable)
18
- `);let i=[].concat(...n),u=0;for(let a of n){if(!await R((0,we.mergeBatch)(a),e,this.logPrefix))break;u+=a.length}for(let a of i.slice(u))this.queue.enqueue(a)}};l.MemoryTransport=Z});var Q=m(E=>{"use strict";var H=E&&E.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(E,"__esModule",{value:!0});E.TraceManager=void 0;E.generateUlid=je;var Ie=H(h("fs")),Dt=H(h("path")),xt=H(h("crypto")),It=j(),W="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function je(){let t=Date.now(),e="",r=t;for(let i=0;i<10;i++)e=W[r&31]+e,r=Math.floor(r/32);let n=xt.default.randomBytes(10),o="";for(let i=0;i<10;i++)o+=W[n[i]&31];for(;o.length<16;)o+=W[0];return e+o}var J=class{tracePath;constructor(e){this.tracePath=e}newTrace(){let e=je();return this.save(e),e}currentTrace(){try{let e=Ie.default.readFileSync(this.tracePath,"utf-8"),{traceId:r}=JSON.parse(e);if(r)return r}catch{}return this.newTrace()}save(e){try{Ie.default.mkdirSync(Dt.default.dirname(this.tracePath),{recursive:!0}),(0,It.atomicWriteFileSync)(this.tracePath,JSON.stringify({traceId:e}))}catch(r){process.stderr.write(`[pinta-core] trace persistence failed: ${r}
19
- `)}}};E.TraceManager=J});var $e=m(S=>{"use strict";var Be=S&&S.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(S,"__esModule",{value:!0});S.MemorySessionTraceManager=S.DiskSessionTraceManager=void 0;var Ne=Be(h("fs")),jt=Be(h("path")),qe=Q(),Nt=j(),Fe=200,ee=class{tracePath;maxSessions;constructor(e,r={}){this.tracePath=e,this.maxSessions=r.maxSessions??Fe}read(){try{let e=JSON.parse(Ne.default.readFileSync(this.tracePath,"utf-8"));if(e&&typeof e=="object"&&!("traceId"in e))return e}catch{}return{}}write(e){try{Ne.default.mkdirSync(jt.default.dirname(this.tracePath),{recursive:!0});let r=Object.entries(e),n=r.length>this.maxSessions?Object.fromEntries(r.slice(-this.maxSessions)):e;(0,Nt.atomicWriteFileSync)(this.tracePath,JSON.stringify(n))}catch{}}newTrace(e){let r=e||"default",n=(0,qe.generateUlid)(),o=this.read();return o[r]=n,this.write(o),n}currentTrace(e){let r=e||"default",n=this.read()[r];return n||this.newTrace(r)}};S.DiskSessionTraceManager=ee;var te=class{map=new Map;maxSessions;constructor(e={}){this.maxSessions=e.maxSessions??Fe}newTrace(e){let r=e||"default",n=(0,qe.generateUlid)();if(this.map.set(r,n),this.map.size>this.maxSessions){let o=this.map.keys().next().value;o!==void 0&&this.map.delete(o)}return n}currentTrace(e){let r=e||"default";return this.map.get(r)??this.newTrace(r)}};S.MemorySessionTraceManager=te});var Ve=m(b=>{"use strict";var re=b&&b.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(b,"__esModule",{value:!0});b.envFilePath=Ft;b.parseEnvFile=Ge;b.loadEnvFile=$t;var Bt=re(h("node:fs")),qt=re(h("node:os")),Ce=re(h("node:path"));function Ft(t,e,r){let n=r?process.env[r]:void 0,o=n&&n.length>0?n:Ce.default.join(qt.default.homedir(),t);return Ce.default.join(o,e)}function Ge(t){let e={};for(let r of t.split(`
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 $t(t){let e;try{e=Bt.default.readFileSync(t,"utf-8")}catch{return}let r=Ge(e);for(let[n,o]of Object.entries(r))process.env[n]===void 0&&(process.env[n]=o)}});var T=m(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.GUARD_STATUS_REFUSED=s.evaluateGuard=s.MAX_SPAN_BYTES=s.enforceSpanBudget=s.mergeBatch=s.attachGuard=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 v=V();Object.defineProperty(s,"redact",{enumerable:!0,get:function(){return v.redact}});Object.defineProperty(s,"truncate",{enumerable:!0,get:function(){return v.truncate}});Object.defineProperty(s,"truncateTo",{enumerable:!0,get:function(){return v.truncateTo}});Object.defineProperty(s,"collectMatches",{enumerable:!0,get:function(){return v.collectMatches}});Object.defineProperty(s,"resolveOverlaps",{enumerable:!0,get:function(){return v.resolveOverlaps}});Object.defineProperty(s,"applyMatches",{enumerable:!0,get:function(){return v.applyMatches}});Object.defineProperty(s,"PATTERNS",{enumerable:!0,get:function(){return v.PATTERNS}});Object.defineProperty(s,"MAX_BYTES",{enumerable:!0,get:function(){return v.MAX_BYTES}});var y=U();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,"attachGuard",{enumerable:!0,get:function(){return y.attachGuard}});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 Le=Ee();Object.defineProperty(s,"evaluateGuard",{enumerable:!0,get:function(){return Le.evaluateGuard}});Object.defineProperty(s,"GUARD_STATUS_REFUSED",{enumerable:!0,get:function(){return Le.GUARD_STATUS_REFUSED}});var Ue=z();Object.defineProperty(s,"DiskRetryQueue",{enumerable:!0,get:function(){return Ue.DiskRetryQueue}});Object.defineProperty(s,"MemoryRetryQueue",{enumerable:!0,get:function(){return Ue.MemoryRetryQueue}});var k=xe();Object.defineProperty(s,"DiskTransport",{enumerable:!0,get:function(){return k.DiskTransport}});Object.defineProperty(s,"MemoryTransport",{enumerable:!0,get:function(){return k.MemoryTransport}});Object.defineProperty(s,"postOtlp",{enumerable:!0,get:function(){return k.postOtlp}});Object.defineProperty(s,"envOptionsResolver",{enumerable:!0,get:function(){return k.envOptionsResolver}});Object.defineProperty(s,"parseHeadersEnv",{enumerable:!0,get:function(){return k.parseHeadersEnv}});Object.defineProperty(s,"MAX_POST_BYTES",{enumerable:!0,get:function(){return k.MAX_POST_BYTES}});var Xe=Q();Object.defineProperty(s,"TraceManager",{enumerable:!0,get:function(){return Xe.TraceManager}});Object.defineProperty(s,"generateUlid",{enumerable:!0,get:function(){return Xe.generateUlid}});var Ye=$e();Object.defineProperty(s,"DiskSessionTraceManager",{enumerable:!0,get:function(){return Ye.DiskSessionTraceManager}});Object.defineProperty(s,"MemorySessionTraceManager",{enumerable:!0,get:function(){return Ye.MemorySessionTraceManager}});var ne=Ve();Object.defineProperty(s,"envFilePath",{enumerable:!0,get:function(){return ne.envFilePath}});Object.defineProperty(s,"parseEnvFile",{enumerable:!0,get:function(){return ne.parseEnvFile}});Object.defineProperty(s,"loadEnvFile",{enumerable:!0,get:function(){return ne.loadEnvFile}})});var Ke=O(T(),1);import Gt from"node:fs";import oe from"node:path";var D=O(T(),1);function Ct(){return(0,D.envFilePath)(".config/opencode","pinta-opencode.env","OPENCODE_CONFIG_DIR")}function ze(t=Ct()){(0,D.loadEnvFile)(t)}function se(...t){return t.find(e=>e)||void 0}function Vt(t){let e=se(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 Lt="opencode-ai";function Ut(){let t=oe.dirname(process.execPath);for(let e=0;e<5;e++){try{let n=JSON.parse(Gt.readFileSync(oe.join(t,"package.json"),"utf8"));if(n.name===Lt&&typeof n.version=="string"&&n.version)return n.version}catch{}let r=oe.dirname(t);if(r===t)break;t=r}}function Xt(){let t=process.env.OPENCODE_VERSION?.trim();return t||Ut()}function Ze(t={}){ze();let e=se(t.token,process.env.PINTA_OPENCODE_TOKEN),r=(0,Ke.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:Vt(t),headers:r,guardEndpoint:se(t.guard,process.env.PINTA_OPENCODE_GUARD),relayToken:e,guardTimeoutMs:n,guardDisabled:process.env.PINTA_OPENCODE_GUARD_DISABLED==="1",serviceVersion:Xt()}}var We=O(T(),1),F=class extends We.MemoryTransport{constructor(e){super({logPrefix:"pinta-opencode",resolveOptions:()=>e.endpoint?{endpoint:e.endpoint,headers:e.headers}:null})}};var ie=O(T(),1);var nt=O(T(),1);var Je=O(T(),1);var $="0.9.0";var Yt=50,zt=`pinta-opencode/${$}`;function He(t,e,r={}){return(0,Je.evaluateGuard)(t,e,{timeoutMs:r.timeoutMs??Yt,token:r.token,disabled:r.disabled,userAgent:zt})}var rt=O(T(),1);import ae from"os";var w=O(T(),1);var C;function Kt(){if(C===void 0)try{C=ae.userInfo().username}catch{C=process.env.USER??process.env.LOGNAME??(typeof process.getuid=="function"?String(process.getuid()):"unknown")}return C}var Qe=$,Zt=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"]),Wt=new Set(["opencode.tool_input","opencode.tool_response"]),Jt={skipRedactKeys:Zt,bashContextKeys:Wt};function Ht(t){let e=[{key:"ingest.type",value:{stringValue:"opencode"}}];return e.push(...(0,w.attrsFromRecord)(t,"opencode",Jt)),e}function Qt(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:Qe}},{key:"process.pid",value:{intValue:process.pid}},{key:"process.owner",value:{stringValue:Kt()}},{key:"host.name",value:{stringValue:ae.hostname()}},{key:"host.arch",value:{stringValue:ae.arch()}}]}function et(t){return(0,w.buildPayload)({traceId:t.traceId,spanName:t.name,attributes:Ht(t.fields),resource:Qt(t.serviceVersion),scope:{name:"pinta-opencode",version:Qe},now:t.now})}var G=class{constructor(e,r,n){this.transport=e;this.trace=r;this.config=n}build(e,r,n){let o=this.trace.currentTrace(r);return et({name:e,traceId:o,fields:n,serviceVersion:this.config.serviceVersion})}async send(e){await this.transport.send(e)}async emit(e,r,n){await this.send(this.build(e,r,n))}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()}toolBeforePayload(e,r){return this.build("opencode.tool.before",e.sessionID,{...tt("tool.execute.before",e),tool_input:r})}async toolBefore(e,r,n){await this.send((0,rt.attachGuard)(this.toolBeforePayload(e,r),n))}async toolAfter(e,r){let n=r.metadata??{};await this.emit("opencode.tool.after",e.sessionID,{...tt("tool.execute.after",e),title:r.title,tool_response:r.output,exit:n.exit,truncated:n.truncated})}};function tt(t,e){return{hook:t,tool_name:e.tool,session_id:e.sessionID,tool_use_id:e.callID,cwd:process.cwd()}}function ot(t,e){process.stderr.write(`[pinta-opencode] ${t}: ${e?.message??String(e)}
21
- `)}function ue(t,e){return async(...r)=>{try{await e(...r)}catch(n){ot(t,n)}}}var er=async(t,e)=>{let r=Ze(e??{}),n=new F({endpoint:r.endpoint,headers:r.headers}),o=new ie.MemorySessionTraceManager,i=new G(n,o,r);async function u(a,c){let d=null;try{let _=i.toolBeforePayload(a,c);d=await He(_,r.guardEndpoint,{timeoutMs:r.guardTimeoutMs,token:r.relayToken,disabled:r.guardDisabled}),await i.send((0,nt.attachGuard)(_,d))}catch(_){ot("tool.execute.before",_)}return d}return{"chat.message":ue("chat.message",async a=>{o.newTrace(a?.sessionID)}),event:ue("event",async a=>{a?.event&&await i.lifecycle(a.event)}),"tool.execute.before":async(a,c)=>{let d=await u(a,c?.args);if(d?.decision==="DENY")throw new Error(d.userMessage??d.reason??"guard_deny")},"tool.execute.after":ue("tool.execute.after",(a,c)=>i.toolAfter(a,c))}},xr=er;export{er as PintaOpencode,xr as default};
7
+ `)}catch(t){process.stderr.write(`[${this.logPrefix}] retry-queue rewrite failed: ${t}
8
+ `)}}tryAcquireLock(){let e=Date.now();try{m.default.mkdirSync(L.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<Ft;)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=$t,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 Ye=E(f=>{"use strict";Object.defineProperty(f,"__esModule",{value:!0});f.MemoryTransport=f.DiskTransport=f.MAX_POST_BYTES=void 0;f.parseHeadersEnv=Le;f.envOptionsResolver=Xe;f.postOtlp=B;var $e=oe(),Ue=re(),Ut=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 Ve(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 Xe(){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(),Ut);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??Xe}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}=Ve(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,Ue.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}=Ve(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,Ue.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=E(k=>{"use strict";var de=k&&k.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(k,"__esModule",{value:!0});k.TraceManager=void 0;k.generateUlid=We;var ze=de(y("fs")),Vt=de(y("path")),Lt=de(y("crypto")),Xt=V(),ue="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function We(){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=Lt.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=We();return this.save(e),e}currentTrace(){try{let e=ze.default.readFileSync(this.tracePath,"utf-8"),{traceId:t}=JSON.parse(e);if(t)return t}catch{}return this.newTrace()}save(e){try{ze.default.mkdirSync(Vt.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
+ `)}}};k.TraceManager=ce});var Qe=E(M=>{"use strict";var Ze=M&&M.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(M,"__esModule",{value:!0});M.MemorySessionTraceManager=M.DiskSessionTraceManager=void 0;var Ke=Ze(y("fs")),Yt=Ze(y("path")),He=le(),zt=V(),Je=200,fe=class{tracePath;maxSessions;constructor(e,t={}){this.tracePath=e,this.maxSessions=t.maxSessions??Je}read(){try{let e=JSON.parse(Ke.default.readFileSync(this.tracePath,"utf-8"));if(e&&typeof e=="object"&&!("traceId"in e))return e}catch{}return{}}write(e){try{Ke.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,He.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)}};M.DiskSessionTraceManager=fe;var pe=class{map=new Map;maxSessions;constructor(e={}){this.maxSessions=e.maxSessions??Je}newTrace(e){let t=e||"default",n=(0,He.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)}};M.MemorySessionTraceManager=pe});var rt=E(R=>{"use strict";var ge=R&&R.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(R,"__esModule",{value:!0});R.envFilePath=Zt;R.parseEnvFile=tt;R.loadEnvFile=Ht;var Wt=ge(y("node:fs")),Kt=ge(y("node:os")),et=ge(y("node:path"));function Zt(r,e,t){let n=t?process.env[t]:void 0,s=n&&n.length>0?n:et.default.join(Kt.default.homedir(),r);return et.default.join(s,e)}function tt(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 Ht(r){let e;try{e=Wt.default.readFileSync(r,"utf-8")}catch{return}let t=tt(e);for(let[n,s]of Object.entries(t))process.env[n]===void 0&&(process.env[n]=s)}});var S=E(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_BUDGET_HEADER=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 A=ee();Object.defineProperty(a,"redact",{enumerable:!0,get:function(){return A.redact}});Object.defineProperty(a,"truncate",{enumerable:!0,get:function(){return A.truncate}});Object.defineProperty(a,"truncateTo",{enumerable:!0,get:function(){return A.truncateTo}});Object.defineProperty(a,"collectMatches",{enumerable:!0,get:function(){return A.collectMatches}});Object.defineProperty(a,"resolveOverlaps",{enumerable:!0,get:function(){return A.resolveOverlaps}});Object.defineProperty(a,"applyMatches",{enumerable:!0,get:function(){return A.applyMatches}});Object.defineProperty(a,"PATTERNS",{enumerable:!0,get:function(){return A.PATTERNS}});Object.defineProperty(a,"MAX_BYTES",{enumerable:!0,get:function(){return A.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 he=Be();Object.defineProperty(a,"evaluateGuard",{enumerable:!0,get:function(){return he.evaluateGuard}});Object.defineProperty(a,"GUARD_STATUS_REFUSED",{enumerable:!0,get:function(){return he.GUARD_STATUS_REFUSED}});Object.defineProperty(a,"GUARD_BUDGET_HEADER",{enumerable:!0,get:function(){return he.GUARD_BUDGET_HEADER}});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=Ye();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=Qe();Object.defineProperty(a,"DiskSessionTraceManager",{enumerable:!0,get:function(){return ot.DiskSessionTraceManager}});Object.defineProperty(a,"MemorySessionTraceManager",{enumerable:!0,get:function(){return ot.MemorySessionTraceManager}});var me=rt();Object.defineProperty(a,"envFilePath",{enumerable:!0,get:function(){return me.envFilePath}});Object.defineProperty(a,"parseEnvFile",{enumerable:!0,get:function(){return me.parseEnvFile}});Object.defineProperty(a,"loadEnvFile",{enumerable:!0,get:function(){return me.loadEnvFile}})});var at=D(S(),1);import Qt from"node:fs";import ye from"node:path";var G=D(S(),1);function Jt(){return(0,G.envFilePath)(".config/opencode","pinta-opencode.env","OPENCODE_CONFIG_DIR")}function it(r=Jt()){(0,G.loadEnvFile)(r)}function _e(...r){return r.find(e=>e)||void 0}function er(r){let e=_e(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=ye.dirname(process.execPath);for(let e=0;e<5;e++){try{let n=JSON.parse(Qt.readFileSync(ye.join(r,"package.json"),"utf8"));if(n.name===tr&&typeof n.version=="string"&&n.version)return n.version}catch{}let t=ye.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=_e(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)||100);return{endpoint:er(r),headers:t,guardEndpoint:_e(r.guard,process.env.PINTA_OPENCODE_GUARD),relayToken:e,guardTimeoutMs:n,guardDisabled:process.env.PINTA_OPENCODE_GUARD_DISABLED==="1",serviceVersion:nr()}}var ct=D(S(),1),z=class extends ct.MemoryTransport{constructor(e){super({logPrefix:"pinta-opencode",resolveOptions:()=>e.endpoint?{endpoint:e.endpoint,headers:e.headers}:null})}};var ve=D(S(),1);var mt=D(S(),1);var dt=D(S(),1);var W="0.11.0";var sr=100,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=D(S(),1);import Te from"os";var j=D(S(),1);var K;function ir(){if(K===void 0)try{K=Te.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:Te.hostname()}},{key:"host.arch",value:{stringValue:Te.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 b(r){return r!==null&&typeof r=="object"&&!Array.isArray(r)?r:void 0}function v(r,e,t){let n=b(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 Oe(r,e){let t=b(e.info),n=b(e.part);return typeof r=="string"&&r.startsWith("session.")?C(e.sessionID,t?.id):C(e.sessionID,t?.sessionID,n?.sessionID)}var F=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 F(N.messages,e),this.requests=new F(N.requests,e),this.calls=new F(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=Oe(e,t),s=b(t.info),i=b(t.part),c=["session.idle","session.deleted","session.error"].includes(e??"")||e==="session.status"&&b(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(b(s.time)?.created)){let O=this.messages.get(T(n,o)),Ee=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:Ee?void 0:l,ambiguous:Ee})}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 H(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 J=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=b(e.properties)??{},n=Oe(e.type,t),s=this.models.event(e.type,t);await this.emit(`opencode.event.${e.type??"unknown"}`,n,H({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,H({...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,H({...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 ve.MemorySessionTraceManager,i=new J(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.9.0",
3
+ "version": "0.11.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.8.0",
39
+ "@pinta-ai/core": "^0.9.0",
40
40
  "@types/node": "^22.0.0",
41
41
  "esbuild": "0.24.2",
42
42
  "typescript": "^5.7.0",