@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 +39 -0
- package/README.md +57 -3
- package/dist/index.mjs +325 -46
- package/dist/plugin.js +19 -19
- package/package.json +2 -2
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 `
|
|
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
|
|
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 (
|
|
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
|
|
20
|
-
if (!__hasOwnProp.call(to,
|
|
21
|
-
__defProp(to,
|
|
19
|
+
for (let key2 of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key2) && key2 !== except)
|
|
21
|
+
__defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
|
|
22
22
|
}
|
|
23
23
|
return to;
|
|
24
24
|
};
|
|
@@ -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(
|
|
219
|
+
function maybeRedactString(key2, raw, policy) {
|
|
220
220
|
const truncated = (0, redact_js_1.truncate)(raw);
|
|
221
|
-
if ((policy.skipRedactKeys ?? EMPTY_SET).has(
|
|
221
|
+
if ((policy.skipRedactKeys ?? EMPTY_SET).has(key2))
|
|
222
222
|
return truncated;
|
|
223
|
-
const context = (policy.bashContextKeys ?? EMPTY_SET).has(
|
|
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(
|
|
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(
|
|
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(
|
|
240
|
+
return { stringValue: maybeRedactString(key2, JSON.stringify(v), policy) };
|
|
241
241
|
} catch {
|
|
242
|
-
return { stringValue: maybeRedactString(
|
|
242
|
+
return { stringValue: maybeRedactString(key2, String(v), policy) };
|
|
243
243
|
}
|
|
244
244
|
default:
|
|
245
|
-
return { stringValue: maybeRedactString(
|
|
245
|
+
return { stringValue: maybeRedactString(key2, String(v), policy) };
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
|
-
function attrsFromRecord2(
|
|
248
|
+
function attrsFromRecord2(record2, prefix, policy = {}) {
|
|
249
249
|
const out = [];
|
|
250
|
-
for (const [k, v] of Object.entries(
|
|
251
|
-
const
|
|
252
|
-
const value = toOtlpValue(
|
|
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
|
|
931
|
+
const key2 = sessionId || "default";
|
|
928
932
|
const traceId = (0, trace_js_1.generateUlid)();
|
|
929
933
|
const map = this.read();
|
|
930
|
-
map[
|
|
934
|
+
map[key2] = traceId;
|
|
931
935
|
this.write(map);
|
|
932
936
|
return traceId;
|
|
933
937
|
}
|
|
934
938
|
currentTrace(sessionId) {
|
|
935
|
-
const
|
|
936
|
-
const existing = this.read()[
|
|
939
|
+
const key2 = sessionId || "default";
|
|
940
|
+
const existing = this.read()[key2];
|
|
937
941
|
if (existing)
|
|
938
942
|
return existing;
|
|
939
|
-
return this.newTrace(
|
|
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
|
|
954
|
+
const key2 = sessionId || "default";
|
|
951
955
|
const traceId = (0, trace_js_1.generateUlid)();
|
|
952
|
-
this.map.set(
|
|
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
|
|
962
|
-
return this.map.get(
|
|
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
|
|
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 (
|
|
1003
|
-
out[
|
|
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 [
|
|
1016
|
-
if (process.env[
|
|
1017
|
-
process.env[
|
|
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) ||
|
|
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.
|
|
1252
|
+
var ADAPTER_VERSION = "0.11.0";
|
|
1246
1253
|
|
|
1247
1254
|
// src/core/guard.ts
|
|
1248
|
-
var DEFAULT_TIMEOUT_MS =
|
|
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 =
|
|
1352
|
-
|
|
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
|
-
|
|
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
|
|
3
|
-
`;
|
|
4
|
-
`)}}readAll(){try{let e=
|
|
5
|
-
`))if(n.trim())try{let
|
|
2
|
+
var _t=Object.create;var Pe=Object.defineProperty;var vt=Object.getOwnPropertyDescriptor;var Tt=Object.getOwnPropertyNames;var Ot=Object.getPrototypeOf,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(
|
|
8
|
-
`)}}tryAcquireLock(){let e=Date.now();try{
|
|
9
|
-
`),!1}for(;Date.now()-e<
|
|
10
|
-
`),!1;try{let i=
|
|
11
|
-
`),this.rewrite(e.slice(
|
|
12
|
-
`),!1}return!0}catch(i){return process.stderr.write(`[${
|
|
13
|
-
`),!1}finally{clearTimeout(
|
|
14
|
-
`);let i=[].concat(...n),
|
|
15
|
-
`)}finally{this.queue.release()}}dropIfOversized(e){let
|
|
16
|
-
`),!0)}};
|
|
17
|
-
`);return}await
|
|
18
|
-
`);let i=[].concat(...n),
|
|
19
|
-
`)}}};
|
|
20
|
-
`)){let n=r.trim();if(!n||n.startsWith("#"))continue;let o=n.indexOf("=");if(o<0)continue;let i=n.slice(0,o).trim(),u=n.slice(o+1).trim();(u.startsWith('"')&&u.endsWith('"')||u.startsWith("'")&&u.endsWith("'"))&&(u=u.slice(1,-1)),i&&(e[i]=u)}return e}function $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
|
|
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.
|
|
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.
|
|
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",
|