@pinta-ai/pinta-opencode 0.3.1 → 0.4.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,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ - Consolidate the duplicated low-level utilities (`guard`, `otlp`, `redact`,
6
+ `transport`, `trace`) into the shared private package `@pinta-ai/core`. `src/core/*`
7
+ is now a thin opencode-specific binding layer: the plugin keeps its 50ms
8
+ fail-open guard default, its `pinta-opencode/<version>` User-Agent, explicit
9
+ (non-`process.env`) options, and core's **in-memory** retry queue +
10
+ `MemorySessionTraceManager`, since it is a long-lived in-process plugin.
11
+ - `@pinta-ai/core` is a devDependency, bundled into `dist/plugin.js` by esbuild at
12
+ build time, so npmjs consumers never need private-registry access.
13
+ - Bump `@pinta-ai/core` `^0.2.0` → `^0.3.0`. The upgrade is additive; no adapter
14
+ source change was needed. Tool spans now carry `pinta.client.rtt_ms` /
15
+ `pinta.client.op` for free: the plugin already forwards its guard result
16
+ through `telemetry.toolBefore` → `buildOtlpPayload` → core's `buildPayload`,
17
+ and core `0.3.0` derives the client-call timing from the
18
+ `GuardResult.clientRttMs` it now measures. The manager can only time its own
19
+ handler, so `clientRttMs - durationMs` gives it the transport overhead.
20
+ - Fix `package-lock.json`, which resolved `@pinta-ai/core` as a local link to
21
+ `../pinta-core` (a dev-machine-only path) and recorded it as a runtime
22
+ dependency. It now resolves from GitHub Packages with an integrity hash, as a
23
+ devDependency, so `npm ci` works in CI.
24
+ - Restore npm OIDC trusted publishing in the publish workflow (the committed
25
+ `.npmrc` pointed npmjs auth at a nonexistent `NPM_TOKEN` secret, which would
26
+ have failed `npm publish` with ENEEDAUTH), and pin npm to `11.18.0` (npm 12.x
27
+ requires node >=22.22, but the job runs node 20).
28
+
3
29
  ## 0.2.1
4
30
 
5
31
  - Fix `package-lock.json` so CI `npm ci` resolves (regenerated with a full
package/dist/plugin.js CHANGED
@@ -1,85 +1,14 @@
1
- import { resolveConfig } from "./config.js";
2
- import { Transport } from "./core/transport.js";
3
- import { TraceManager } from "./core/trace.js";
4
- import { evaluateGuard } from "./core/guard.js";
5
- import { Telemetry } from "./telemetry.js";
6
- function warn(scope, err) {
7
- process.stderr.write(`[pinta-opencode] ${scope}: ${err?.message ?? String(err)}\n`);
8
- }
9
- /**
10
- * pinta-opencode plugin entry. opencode invokes this once per instance with
11
- * `(input, options)` and keeps the returned hooks for the instance lifetime
12
- * (verified H-C1), so config + transport + trace state live in this closure.
13
- *
14
- * - Telemetry (event / tool.execute.after): best-effort OTLP, never blocks.
15
- * - Governance (tool.execute.before): guard query; DENY → throw(reason), which
16
- * blocks only that tool and surfaces the reason to the model (verified H-A1).
17
- *
18
- * Fail-open invariant: every telemetry/guard error is swallowed; the only
19
- * intentional throw is a guard DENY.
20
- */
21
- export const PintaOpencode = async (_input, options) => {
22
- const config = resolveConfig(options ?? {});
23
- const transport = new Transport({ endpoint: config.endpoint, headers: config.headers });
24
- const trace = new TraceManager();
25
- const telemetry = new Telemetry(transport, trace, config);
26
- return {
27
- // turn-START → rotate a new trace for this session.
28
- "chat.message": async (input) => {
29
- try {
30
- trace.newTrace(input?.sessionID);
31
- }
32
- catch (err) {
33
- warn("chat.message", err);
34
- }
35
- },
36
- // lifecycle telemetry; flushes the retry buffer on session.idle (turn-END).
37
- event: async (input) => {
38
- try {
39
- if (input?.event)
40
- await telemetry.lifecycle(input.event);
41
- }
42
- catch (err) {
43
- warn("event", err);
44
- }
45
- },
46
- // ★ governance gate: guard query → DENY throws (blocks just this tool).
47
- "tool.execute.before": async (input, output) => {
48
- let guard = null;
49
- try {
50
- guard = await evaluateGuard({
51
- spanId: input.sessionID,
52
- toolName: input.tool,
53
- toolInput: output?.args,
54
- rawTextFields: { toolInput: safeStringify(output?.args) },
55
- }, config.guardEndpoint, { timeoutMs: config.guardTimeoutMs, token: config.relayToken, disabled: config.guardDisabled });
56
- await telemetry.toolBefore(input, output?.args, guard);
57
- }
58
- catch (err) {
59
- warn("tool.execute.before", err); // telemetry/guard infra errors are fail-open
60
- }
61
- if (guard?.decision === "DENY") {
62
- throw new Error(guard.userMessage ?? guard.reason ?? "guard_deny");
63
- }
64
- },
65
- // tool-result telemetry.
66
- "tool.execute.after": async (input, output) => {
67
- try {
68
- await telemetry.toolAfter(input, output);
69
- }
70
- catch (err) {
71
- warn("tool.execute.after", err);
72
- }
73
- },
74
- };
75
- };
76
- function safeStringify(v) {
77
- try {
78
- return typeof v === "string" ? v : JSON.stringify(v) ?? "";
79
- }
80
- catch {
81
- return String(v);
82
- }
83
- }
84
- export default PintaOpencode;
85
- //# sourceMappingURL=plugin.js.map
1
+ import { createRequire as __pintaCreateRequire } from 'module'; const require = __pintaCreateRequire(import.meta.url);
2
+ var Ue=Object.create;var ne=Object.defineProperty;var Ze=Object.getOwnPropertyDescriptor;var Ke=Object.getOwnPropertyNames;var Xe=Object.getPrototypeOf,Ye=Object.prototype.hasOwnProperty;var p=(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 y=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var ze=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Ke(e))!Ye.call(t,s)&&s!==r&&ne(t,s,{get:()=>e[s],enumerable:!(n=Ze(e,s))||n.enumerable});return t};var b=(t,e,r)=>(r=t!=null?Ue(Xe(t)):{},ze(e||!t||!t.__esModule?ne(r,"default",{value:t,enumerable:!0}):r,t));var N=y(c=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});c.PATTERNS=c.MAX_BYTES=void 0;c.truncate=Je;c.collectMatches=se;c.resolveOverlaps=oe;c.applyMatches=ie;c.redact=He;c.MAX_BYTES=102400;function Je(t){let e=Buffer.from(t,"utf-8");return e.length<=c.MAX_BYTES?t:`${e.subarray(0,c.MAX_BYTES).toString("utf-8")}\u2026[TRUNCATED:${e.length}]`}c.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 se(t,e){let r=[];for(let n of c.PATTERNS){if(n.requireContext&&n.requireContext!==e.context)continue;let s=new RegExp(n.regex.source,n.regex.flags),i;for(;(i=s.exec(t))!==null;){let a=n.captureGroup??0,u=i[a];if(u===void 0){i.index===s.lastIndex&&s.lastIndex++;continue}let f=i.index,g=i.index+i[0].length,M=f+i[0].indexOf(u),$e=M+u.length;r.push({start:f,end:g,replaceStart:M,replaceEnd:$e,type:n.type}),i.index===s.lastIndex&&s.lastIndex++}}return r}function oe(t){let e=[...t].sort((s,i)=>s.start!==i.start?s.start-i.start:i.end-i.start-(s.end-s.start)),r=[],n=-1;for(let s of e)s.start<n||(r.push(s),n=s.end);return r}function ie(t,e){let r=[...e].sort((s,i)=>i.replaceStart-s.replaceStart),n=t;for(let s of r)n=n.slice(0,s.replaceStart)+`[REDACTED:${s.type}]`+n.slice(s.replaceEnd);return n}function He(t,e={}){if(t.length===0)return t;let r=se(t,e);if(r.length===0)return t;let n=oe(r);return ie(t,n)}});var q=y(d=>{"use strict";var Qe=d&&d.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(d,"__esModule",{value:!0});d.ulidToTraceId=ce;d.newSpanId=le;d.snakeCase=tt;d.toOtlpValue=de;d.attrsFromRecord=rt;d.guardAttrs=pe;d.clientAttrs=fe;d.buildPayload=nt;d.mergeBatch=st;var We=Qe(p("crypto")),ae=N(),et="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function ce(t){if(t.length!==26)throw new Error(`ulidToTraceId: expected 26 chars, got ${t.length}`);let e=0n;for(let n of t){let s=et.indexOf(n);if(s<0)throw new Error(`ulidToTraceId: invalid Crockford char "${n}"`);e=e<<5n|BigInt(s)}let r=(1n<<128n)-1n;return e&=r,e.toString(16).padStart(32,"0")}function le(){return We.default.randomBytes(8).toString("hex")}function tt(t){return t.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}var ue=new Set;function R(t,e,r){let n=(0,ae.truncate)(e);if((r.skipRedactKeys??ue).has(t))return n;let s=(r.bashContextKeys??ue).has(t)?"bash":void 0;return(0,ae.redact)(n,{context:s})}function de(t,e,r={}){if(e==null)return null;switch(typeof e){case"string":return{stringValue:R(t,e,r)};case"boolean":return{boolValue:e};case"number":return Number.isInteger(e)?{intValue:e}:{doubleValue:e};case"object":try{return{stringValue:R(t,JSON.stringify(e),r)}}catch{return{stringValue:R(t,String(e),r)}}default:return{stringValue:R(t,String(e),r)}}}function rt(t,e,r={}){let n=[];for(let[s,i]of Object.entries(t)){let a=`${e}.${s}`,u=de(a,i,r);u!==null&&n.push({key:a,value:u})}return n}function pe(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 fe(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}}]}function nt(t){let e=t.now??Date.now(),r=(BigInt(e)*1000000n).toString(),n=[...t.attributes];t.guard&&n.push(...pe(t.guard));let s=t.clientTiming??(t.guard?.clientRttMs!==void 0?{op:"guard",rttMs:t.guard.clientRttMs}:null);s&&n.push(...fe(s));let i={traceId:ce(t.traceId),spanId:le(),name:t.spanName,kind:t.spanKind??1,startTimeUnixNano:r,endTimeUnixNano:r,attributes:n};return{resourceSpans:[{resource:{attributes:t.resource},scopeSpans:[{scope:t.scope,spans:[i]}]}]}}function st(t){let e=[];for(let r of t)e.push(...r.resourceSpans);return{resourceSpans:e}}});var ye=y(C=>{"use strict";Object.defineProperty(C,"__esModule",{value:!0});C.evaluateGuard=ut;var ot=1e4,it="pinta-core";function ge(t,e){return{decision:"ALLOW",reason:null,userMessage:null,durationMs:t,clientRttMs:t,failOpenReason:e}}function at(t){return new Promise((e,r)=>setTimeout(()=>{let n=new Error("Guard request timed out");n.name="TimeoutError",r(n)},t))}async function ut(t,e,r={}){if(!e||r.disabled)return null;let n=r.timeoutMs??ot,s=Date.now();try{let i=await Promise.race([fetch(e,{method:"POST",headers:{"content-type":"application/json","user-agent":r.userAgent??it,"x-pinta-relay-token":r.token??""},body:JSON.stringify({input:t})}),at(n)]);if(i.status!==200)return ge(Date.now()-s,"error");let a=await i.json(),u=Date.now()-s;return{decision:a.decision,reason:a.reason,userMessage:a.userMessage??null,durationMs:a.durationMs??u,clientRttMs:u}}catch(i){let a=i.name==="TimeoutError"?"timeout":"error";return ge(Date.now()-s,a)}}});var V=y(m=>{"use strict";var he=m&&m.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(m,"__esModule",{value:!0});m.MemoryRetryQueue=m.DiskRetryQueue=void 0;var l=he(p("fs")),x=he(p("path")),I=1e3,ct=50,lt=5,F=class{filePath;lockPath;logPrefix;constructor(e,r){this.filePath=x.default.join(e,"failed-spans.jsonl"),this.lockPath=this.filePath+".lock",this.logPrefix=r}enqueue(e){try{l.default.mkdirSync(x.default.dirname(this.filePath),{recursive:!0});let r=JSON.stringify({savedAt:new Date().toISOString(),payload:e})+`
3
+ `;l.default.appendFileSync(this.filePath,r),this.trim()}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue enqueue failed: ${r}
4
+ `)}}readAll(){try{let e=l.default.readFileSync(this.filePath,"utf-8"),r=[];for(let n of e.split(`
5
+ `))if(n.trim())try{r.push(JSON.parse(n))}catch{}return r}catch{return[]}}rewrite(e){try{if(e.length===0){l.default.existsSync(this.filePath)&&l.default.unlinkSync(this.filePath);return}l.default.mkdirSync(x.default.dirname(this.filePath),{recursive:!0}),l.default.writeFileSync(this.filePath,e.map(r=>JSON.stringify(r)).join(`
6
+ `)+`
7
+ `)}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue rewrite failed: ${r}
8
+ `)}}tryAcquireLock(){let e=Date.now();for(l.default.mkdirSync(x.default.dirname(this.lockPath),{recursive:!0});Date.now()-e<ct;)try{let r=l.default.openSync(this.lockPath,"wx");return l.default.writeSync(r,String(process.pid)),l.default.closeSync(r),!0}catch(r){if(r?.code!=="EEXIST")return process.stderr.write(`[${this.logPrefix}] retry-queue lock open failed: ${r}
9
+ `),!1;try{let i=l.default.statSync(this.lockPath);if(Date.now()-i.mtimeMs>3e4){l.default.unlinkSync(this.lockPath);continue}}catch{}let n=lt,s=Date.now()+n;for(;Date.now()<s;);}return!1}release(){try{l.default.unlinkSync(this.lockPath)}catch{}}trim(){let e=this.readAll();if(e.length<=I)return;let r=e.length-I;process.stderr.write(`[${this.logPrefix}] retry-queue full, dropping ${r} oldest entries
10
+ `),this.rewrite(e.slice(r))}};m.DiskRetryQueue=F;var G=class{entries=[];enqueue(e){this.entries.push(e),this.entries.length>I&&this.entries.splice(0,this.entries.length-I)}drain(){let e=this.entries;return this.entries=[],e}get size(){return this.entries.length}};m.MemoryRetryQueue=G});var Pe=y(h=>{"use strict";Object.defineProperty(h,"__esModule",{value:!0});h.MemoryTransport=h.DiskTransport=void 0;h.parseHeadersEnv=Oe;h.envOptionsResolver=Te;h.postOtlp=k;var _e=V(),me=q(),dt=5e3;function Oe(t){if(!t)return{};if(typeof t=="object")return{...t};let e={};for(let r of t.split(",")){let[n,...s]=r.split("=");n&&s.length>0&&(e[n.trim()]=s.join("=").trim())}return e}function Te(){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:Oe(process.env.OTEL_EXPORTER_OTLP_HEADERS)}:null}async function k(t,e,r){let n=new AbortController,s=setTimeout(()=>n.abort(),dt);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 a="";try{a=(await i.text()).slice(0,200)}catch{}let u=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}${u}${a?` body=${a}`:""}
11
+ `),!1}return!0}catch(i){return process.stderr.write(`[${r}] OTLP POST failed: ${i.message??String(i)}
12
+ `),!1}finally{clearTimeout(s)}}var L=class{queue;logPrefix;resolveOptions;constructor(e){this.queue=new _e.DiskRetryQueue(e.pluginData,e.logPrefix),this.logPrefix=e.logPrefix,this.resolveOptions=e.resolveOptions??Te}async send(e){let r=this.resolveOptions();if(!r)return;await k(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 n=(0,me.mergeBatch)(r.map(i=>i.payload));await k(n,e,this.logPrefix)&&this.queue.rewrite([])}finally{this.queue.release()}}};h.DiskTransport=L;var B=class{queue=new _e.MemoryRetryQueue;logPrefix;resolveOptions;constructor(e){this.logPrefix=e.logPrefix,this.resolveOptions=e.resolveOptions}async send(e){let r=this.resolveOptions();if(!r)return;await k(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 n=(0,me.mergeBatch)(r);if(!await k(n,e,this.logPrefix))for(let i of r)this.queue.enqueue(i)}};h.MemoryTransport=B});var X=y(T=>{"use strict";var K=T&&T.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(T,"__esModule",{value:!0});T.TraceManager=void 0;T.generateUlid=ve;var $=K(p("fs")),pt=K(p("path")),ft=K(p("crypto")),U="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function ve(){let t=Date.now(),e="",r=t;for(let i=0;i<10;i++)e=U[r&31]+e,r=Math.floor(r/32);let n=ft.default.randomBytes(10),s="";for(let i=0;i<10;i++)s+=U[n[i]&31];for(;s.length<16;)s+=U[0];return e+s}var Z=class{tracePath;constructor(e){this.tracePath=e}newTrace(){let e=ve();return this.save(e),e}currentTrace(){try{let e=$.default.readFileSync(this.tracePath,"utf-8"),{traceId:r}=JSON.parse(e);if(r)return r}catch{}return this.newTrace()}save(e){$.default.mkdirSync(pt.default.dirname(this.tracePath),{recursive:!0}),$.default.writeFileSync(this.tracePath,JSON.stringify({traceId:e}))}};T.TraceManager=Z});var ke=y(O=>{"use strict";var Ee=O&&O.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(O,"__esModule",{value:!0});O.MemorySessionTraceManager=O.DiskSessionTraceManager=void 0;var Y=Ee(p("fs")),gt=Ee(p("path")),be=X(),Se=200,z=class{tracePath;maxSessions;constructor(e,r={}){this.tracePath=e,this.maxSessions=r.maxSessions??Se}read(){try{let e=JSON.parse(Y.default.readFileSync(this.tracePath,"utf-8"));if(e&&typeof e=="object"&&!("traceId"in e))return e}catch{}return{}}write(e){try{Y.default.mkdirSync(gt.default.dirname(this.tracePath),{recursive:!0});let r=Object.entries(e),n=r.length>this.maxSessions?Object.fromEntries(r.slice(-this.maxSessions)):e;Y.default.writeFileSync(this.tracePath,JSON.stringify(n))}catch{}}newTrace(e){let r=e||"default",n=(0,be.generateUlid)(),s=this.read();return s[r]=n,this.write(s),n}currentTrace(e){let r=e||"default",n=this.read()[r];return n||this.newTrace(r)}};O.DiskSessionTraceManager=z;var J=class{map=new Map;maxSessions;constructor(e={}){this.maxSessions=e.maxSessions??Se}newTrace(e){let r=e||"default",n=(0,be.generateUlid)();if(this.map.set(r,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 r=e||"default";return this.map.get(r)??this.newTrace(r)}};O.MemorySessionTraceManager=J});var Me=y(P=>{"use strict";var H=P&&P.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(P,"__esModule",{value:!0});P.envFilePath=_t;P.parseEnvFile=Ae;P.loadEnvFile=mt;var yt=H(p("node:fs")),ht=H(p("node:os")),we=H(p("node:path"));function _t(t,e,r){let n=r?process.env[r]:void 0,s=n&&n.length>0?n:we.default.join(ht.default.homedir(),t);return we.default.join(s,e)}function Ae(t){let e={};for(let r of t.split(`
13
+ `)){let n=r.trim();if(!n||n.startsWith("#"))continue;let s=n.indexOf("=");if(s<0)continue;let i=n.slice(0,s).trim(),a=n.slice(s+1).trim();(a.startsWith('"')&&a.endsWith('"')||a.startsWith("'")&&a.endsWith("'"))&&(a=a.slice(1,-1)),i&&(e[i]=a)}return e}function mt(t){let e;try{e=yt.default.readFileSync(t,"utf-8")}catch{return}let r=Ae(e);for(let[n,s]of Object.entries(r))process.env[n]===void 0&&(process.env[n]=s)}});var E=y(o=>{"use strict";Object.defineProperty(o,"__esModule",{value:!0});o.loadEnvFile=o.parseEnvFile=o.envFilePath=o.MemorySessionTraceManager=o.DiskSessionTraceManager=o.generateUlid=o.TraceManager=o.parseHeadersEnv=o.envOptionsResolver=o.postOtlp=o.MemoryTransport=o.DiskTransport=o.MemoryRetryQueue=o.DiskRetryQueue=o.evaluateGuard=o.mergeBatch=o.buildPayload=o.clientAttrs=o.guardAttrs=o.attrsFromRecord=o.toOtlpValue=o.snakeCase=o.newSpanId=o.ulidToTraceId=o.MAX_BYTES=o.PATTERNS=o.applyMatches=o.resolveOverlaps=o.collectMatches=o.truncate=o.redact=void 0;var v=N();Object.defineProperty(o,"redact",{enumerable:!0,get:function(){return v.redact}});Object.defineProperty(o,"truncate",{enumerable:!0,get:function(){return v.truncate}});Object.defineProperty(o,"collectMatches",{enumerable:!0,get:function(){return v.collectMatches}});Object.defineProperty(o,"resolveOverlaps",{enumerable:!0,get:function(){return v.resolveOverlaps}});Object.defineProperty(o,"applyMatches",{enumerable:!0,get:function(){return v.applyMatches}});Object.defineProperty(o,"PATTERNS",{enumerable:!0,get:function(){return v.PATTERNS}});Object.defineProperty(o,"MAX_BYTES",{enumerable:!0,get:function(){return v.MAX_BYTES}});var _=q();Object.defineProperty(o,"ulidToTraceId",{enumerable:!0,get:function(){return _.ulidToTraceId}});Object.defineProperty(o,"newSpanId",{enumerable:!0,get:function(){return _.newSpanId}});Object.defineProperty(o,"snakeCase",{enumerable:!0,get:function(){return _.snakeCase}});Object.defineProperty(o,"toOtlpValue",{enumerable:!0,get:function(){return _.toOtlpValue}});Object.defineProperty(o,"attrsFromRecord",{enumerable:!0,get:function(){return _.attrsFromRecord}});Object.defineProperty(o,"guardAttrs",{enumerable:!0,get:function(){return _.guardAttrs}});Object.defineProperty(o,"clientAttrs",{enumerable:!0,get:function(){return _.clientAttrs}});Object.defineProperty(o,"buildPayload",{enumerable:!0,get:function(){return _.buildPayload}});Object.defineProperty(o,"mergeBatch",{enumerable:!0,get:function(){return _.mergeBatch}});var Ot=ye();Object.defineProperty(o,"evaluateGuard",{enumerable:!0,get:function(){return Ot.evaluateGuard}});var Re=V();Object.defineProperty(o,"DiskRetryQueue",{enumerable:!0,get:function(){return Re.DiskRetryQueue}});Object.defineProperty(o,"MemoryRetryQueue",{enumerable:!0,get:function(){return Re.MemoryRetryQueue}});var w=Pe();Object.defineProperty(o,"DiskTransport",{enumerable:!0,get:function(){return w.DiskTransport}});Object.defineProperty(o,"MemoryTransport",{enumerable:!0,get:function(){return w.MemoryTransport}});Object.defineProperty(o,"postOtlp",{enumerable:!0,get:function(){return w.postOtlp}});Object.defineProperty(o,"envOptionsResolver",{enumerable:!0,get:function(){return w.envOptionsResolver}});Object.defineProperty(o,"parseHeadersEnv",{enumerable:!0,get:function(){return w.parseHeadersEnv}});var xe=X();Object.defineProperty(o,"TraceManager",{enumerable:!0,get:function(){return xe.TraceManager}});Object.defineProperty(o,"generateUlid",{enumerable:!0,get:function(){return xe.generateUlid}});var Ie=ke();Object.defineProperty(o,"DiskSessionTraceManager",{enumerable:!0,get:function(){return Ie.DiskSessionTraceManager}});Object.defineProperty(o,"MemorySessionTraceManager",{enumerable:!0,get:function(){return Ie.MemorySessionTraceManager}});var Q=Me();Object.defineProperty(o,"envFilePath",{enumerable:!0,get:function(){return Q.envFilePath}});Object.defineProperty(o,"parseEnvFile",{enumerable:!0,get:function(){return Q.parseEnvFile}});Object.defineProperty(o,"loadEnvFile",{enumerable:!0,get:function(){return Q.loadEnvFile}})});var je=b(E(),1);var A=b(E(),1);function Tt(){return(0,A.envFilePath)(".config/opencode","pinta-opencode.env","OPENCODE_CONFIG_DIR")}function De(t=Tt()){(0,A.loadEnvFile)(t)}function W(...t){return t.find(e=>e)||void 0}function Pt(t){let e=W(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"}function Ne(t={}){De();let e=W(t.token,process.env.PINTA_OPENCODE_TOKEN),r=(0,je.parseHeadersEnv)(t.headers??process.env.PINTA_OPENCODE_HEADERS??process.env.OTEL_EXPORTER_OTLP_HEADERS);e&&!Object.keys(r).some(s=>s.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:Pt(t),headers:r,guardEndpoint:W(t.guard,process.env.PINTA_OPENCODE_GUARD),relayToken:e,guardTimeoutMs:n,guardDisabled:process.env.PINTA_OPENCODE_GUARD_DISABLED==="1",serviceVersion:process.env.OPENCODE_VERSION||"unknown"}}var qe=b(E(),1),D=class extends qe.MemoryTransport{constructor(e){super({logPrefix:"pinta-opencode",resolveOptions:()=>e.endpoint?{endpoint:e.endpoint,headers:e.headers}:null})}};var ee=b(E(),1);var Ce=b(E(),1),vt=50,Et="pinta-opencode/0.4.0";function Fe(t,e,r={}){return(0,Ce.evaluateGuard)(t,e,{timeoutMs:r.timeoutMs??vt,token:r.token,disabled:r.disabled,userAgent:Et})}var S=b(E(),1);import te from"os";var Ge="0.4.0",bt=new Set(["opencode.kind","opencode.event_type","opencode.tool","opencode.session_id","opencode.sessionID","opencode.call_id","opencode.callID","opencode.agent","opencode.model","opencode.exit","opencode.truncated","opencode.title"]),St=new Set(["opencode.args","opencode.input","opencode.output","opencode.tool_input"]),kt={skipRedactKeys:bt,bashContextKeys:St};function wt(t){let e=[{key:"ingest.type",value:{stringValue:"opencode"}}];return e.push(...(0,S.attrsFromRecord)(t,"opencode",kt)),e}function At(t){return[{key:"service.name",value:{stringValue:"opencode"}},{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:Ge}},{key:"process.pid",value:{intValue:process.pid}},{key:"process.owner",value:{stringValue:te.userInfo().username}},{key:"host.name",value:{stringValue:te.hostname()}},{key:"host.arch",value:{stringValue:te.arch()}}]}function Ve(t){return(0,S.buildPayload)({traceId:t.traceId,spanName:t.name,attributes:wt(t.fields),resource:At(t.serviceVersion),scope:{name:"pinta-opencode",version:Ge},now:t.now,guard:t.guard})}var j=class{constructor(e,r,n){this.transport=e;this.trace=r;this.config=n}async emit(e,r,n,s){let i=this.trace.currentTrace(r),a=Ve({name:e,traceId:i,fields:n,guard:s,serviceVersion:this.config.serviceVersion});await this.transport.send(a)}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,{kind:"event",event_type:e.type,session_id:n,...r}),e.type==="session.idle"&&await this.transport.flush()}async toolBefore(e,r,n){await this.emit("opencode.tool.before",e.sessionID,{...Le("tool.before",e),args:r},n)}async toolAfter(e,r){let n=r.metadata??{};await this.emit("opencode.tool.after",e.sessionID,{...Le("tool.after",e),title:r.title,output:r.output,exit:n.exit,truncated:n.truncated})}};function Le(t,e){return{kind:t,tool:e.tool,session_id:e.sessionID,call_id:e.callID}}function Be(t,e){process.stderr.write(`[pinta-opencode] ${t}: ${e?.message??String(e)}
14
+ `)}function re(t,e){return async(...r)=>{try{await e(...r)}catch(n){Be(t,n)}}}var Mt=async(t,e)=>{let r=Ne(e??{}),n=new D({endpoint:r.endpoint,headers:r.headers}),s=new ee.MemorySessionTraceManager,i=new j(n,s,r);async function a(u,f){let g=null;try{g=await Fe({spanId:u.sessionID,toolName:u.tool,toolInput:f,rawTextFields:{toolInput:Rt(f)}},r.guardEndpoint,{timeoutMs:r.guardTimeoutMs,token:r.relayToken,disabled:r.guardDisabled}),await i.toolBefore(u,f,g)}catch(M){Be("tool.execute.before",M)}return g}return{"chat.message":re("chat.message",async u=>{s.newTrace(u?.sessionID)}),event:re("event",async u=>{u?.event&&await i.lifecycle(u.event)}),"tool.execute.before":async(u,f)=>{let g=await a(u,f?.args);if(g?.decision==="DENY")throw new Error(g.userMessage??g.reason??"guard_deny")},"tool.execute.after":re("tool.execute.after",(u,f)=>i.toolAfter(u,f))}};function Rt(t){try{return typeof t=="string"?t:JSON.stringify(t)??""}catch{return String(t)}}var rr=Mt;export{Mt as PintaOpencode,rr as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pinta-ai/pinta-opencode",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "OTLP forwarder + guard for opencode (in-process plugin)",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.js",
@@ -27,15 +27,21 @@
27
27
  },
28
28
  "scripts": {
29
29
  "bump": "node scripts/bump.mjs",
30
- "build": "tsc",
30
+ "build": "tsc --noEmit && esbuild src/plugin.ts --bundle --platform=node --format=esm --target=node18 --minify --outfile=dist/plugin.js --banner:js=\"import { createRequire as __pintaCreateRequire } from 'module'; const require = __pintaCreateRequire(import.meta.url);\"",
31
31
  "dev": "tsc --watch",
32
32
  "test": "vitest run",
33
33
  "test:watch": "vitest",
34
34
  "prepublishOnly": "npm run build"
35
35
  },
36
36
  "devDependencies": {
37
+ "@pinta-ai/core": "^0.3.0",
37
38
  "@types/node": "^22.0.0",
39
+ "esbuild": "0.24.2",
38
40
  "typescript": "^5.7.0",
39
41
  "vitest": "^2.1.0"
42
+ },
43
+ "publishConfig": {
44
+ "registry": "https://registry.npmjs.org",
45
+ "access": "public"
40
46
  }
41
47
  }
package/dist/config.d.ts DELETED
@@ -1,27 +0,0 @@
1
- /** Options object passed via `opencode.json` → `plugin:[["@pinta-ai/pinta-opencode", {…}]]`. */
2
- export interface PintaOptions {
3
- /** Full OTLP/HTTP traces URL. */
4
- endpoint?: string;
5
- /** `key=val,key=val` request headers, or a parsed record. */
6
- headers?: string | Record<string, string>;
7
- /** Guard policy server URL. */
8
- guard?: string;
9
- /** Relay token (sent as x-pinta-relay-token). */
10
- token?: string;
11
- /** Guard client timeout in ms (default 50). */
12
- guardTimeoutMs?: number;
13
- }
14
- export interface ResolvedConfig {
15
- endpoint?: string;
16
- headers: Record<string, string>;
17
- guardEndpoint?: string;
18
- relayToken?: string;
19
- guardTimeoutMs: number;
20
- guardDisabled: boolean;
21
- serviceVersion: string;
22
- }
23
- /**
24
- * Resolve runtime config. Precedence: plugin options → process.env →
25
- * env-file (unset-only). Both options and env are visible at runtime (verified G5).
26
- */
27
- export declare function resolveConfig(options?: PintaOptions): ResolvedConfig;
package/dist/config.js DELETED
@@ -1,49 +0,0 @@
1
- import { loadEnvFile } from "./env-file.js";
2
- function parseHeaders(raw) {
3
- if (!raw)
4
- return {};
5
- if (typeof raw === "object")
6
- return { ...raw };
7
- const out = {};
8
- for (const pair of raw.split(",")) {
9
- const [k, ...rest] = pair.split("=");
10
- if (k && rest.length > 0)
11
- out[k.trim()] = rest.join("=").trim();
12
- }
13
- return out;
14
- }
15
- function resolveEndpoint(options) {
16
- const full = options.endpoint ||
17
- process.env.PINTA_OPENCODE_ENDPOINT ||
18
- process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
19
- if (full)
20
- return full.replace(/\/+$/, "");
21
- const base = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
22
- if (base)
23
- return base.replace(/\/+$/, "") + "/v1/traces";
24
- return undefined;
25
- }
26
- /**
27
- * Resolve runtime config. Precedence: plugin options → process.env →
28
- * env-file (unset-only). Both options and env are visible at runtime (verified G5).
29
- */
30
- export function resolveConfig(options = {}) {
31
- loadEnvFile(); // lowest priority — fills only unset process.env keys
32
- const relayToken = options.token || process.env.PINTA_OPENCODE_TOKEN || undefined;
33
- const headers = parseHeaders(options.headers ?? process.env.PINTA_OPENCODE_HEADERS ?? process.env.OTEL_EXPORTER_OTLP_HEADERS);
34
- // Auto-attach the relay token as a header if one is set and not already present.
35
- if (relayToken && !Object.keys(headers).some((k) => k.toLowerCase() === "x-pinta-relay-token")) {
36
- headers["x-pinta-relay-token"] = relayToken;
37
- }
38
- const guardTimeoutMs = options.guardTimeoutMs ?? (Number(process.env.PINTA_OPENCODE_GUARD_TIMEOUT_MS) || 50);
39
- return {
40
- endpoint: resolveEndpoint(options),
41
- headers,
42
- guardEndpoint: options.guard || process.env.PINTA_OPENCODE_GUARD || undefined,
43
- relayToken,
44
- guardTimeoutMs,
45
- guardDisabled: process.env.PINTA_OPENCODE_GUARD_DISABLED === "1",
46
- serviceVersion: process.env.OPENCODE_VERSION || "unknown",
47
- };
48
- }
49
- //# sourceMappingURL=config.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AA0B5C,SAAS,YAAY,CAAC,GAAgD;IACpE,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,EAAE,GAAG,GAAG,EAAE,CAAC;IAC/C,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAClE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,eAAe,CAAC,OAAqB;IAC5C,MAAM,IAAI,GACR,OAAO,CAAC,QAAQ;QAChB,OAAO,CAAC,GAAG,CAAC,uBAAuB;QACnC,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC;IACjD,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC;IACrD,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,YAAY,CAAC;IACzD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,UAAwB,EAAE;IACtD,WAAW,EAAE,CAAC,CAAC,sDAAsD;IAErE,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,SAAS,CAAC;IAElF,MAAM,OAAO,GAAG,YAAY,CAC1B,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAChG,CAAC;IACF,iFAAiF;IACjF,IAAI,UAAU,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,qBAAqB,CAAC,EAAE,CAAC;QAC/F,OAAO,CAAC,qBAAqB,CAAC,GAAG,UAAU,CAAC;IAC9C,CAAC;IAED,MAAM,cAAc,GAClB,OAAO,CAAC,cAAc,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,IAAI,EAAE,CAAC,CAAC;IAExF,OAAO;QACL,QAAQ,EAAE,eAAe,CAAC,OAAO,CAAC;QAClC,OAAO;QACP,aAAa,EAAE,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,SAAS;QAC7E,UAAU;QACV,cAAc;QACd,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,6BAA6B,KAAK,GAAG;QAChE,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,SAAS;KAC1D,CAAC;AACJ,CAAC"}
@@ -1,29 +0,0 @@
1
- export interface GuardInput {
2
- spanId: string;
3
- toolName?: string;
4
- toolInput?: unknown;
5
- rawTextFields?: Record<string, string>;
6
- }
7
- export interface GuardResult {
8
- decision: "ALLOW" | "DENY" | "REVIEW";
9
- reason: string | null;
10
- userMessage: string | null;
11
- durationMs: number;
12
- failOpenReason?: "timeout" | "refused" | "error";
13
- }
14
- export interface GuardOptions {
15
- /** Hard timeout. 50ms default keeps the hook snappy; 300ms recommended in prod. */
16
- timeoutMs?: number;
17
- /** Sent as x-pinta-relay-token. */
18
- token?: string;
19
- /** Force-disable even if an endpoint is configured. */
20
- disabled?: boolean;
21
- }
22
- /**
23
- * Query the external guard policy server. Fail-open on every error path
24
- * (no endpoint / disabled / non-200 / timeout / throw → ALLOW). Options are
25
- * passed explicitly (not read from process.env) because the opencode plugin
26
- * is a long-lived in-process module whose config is resolved at init, after
27
- * this module is already imported.
28
- */
29
- export declare function evaluateGuard(input: GuardInput, endpoint: string | undefined, opts?: GuardOptions): Promise<GuardResult | null>;
@@ -1,55 +0,0 @@
1
- // Self-identify to the manager's guard route so it can attribute calls to this
2
- // adaptor (the route parses `pinta-*/<version>` out of the User-Agent). Keep the
3
- // version in sync with package.json.
4
- const GUARD_UA = "pinta-opencode/0.3.1";
5
- function sleep(ms) {
6
- return new Promise((_, reject) => setTimeout(() => {
7
- const err = new Error("Guard request timed out");
8
- err.name = "TimeoutError";
9
- reject(err);
10
- }, ms));
11
- }
12
- /**
13
- * Query the external guard policy server. Fail-open on every error path
14
- * (no endpoint / disabled / non-200 / timeout / throw → ALLOW). Options are
15
- * passed explicitly (not read from process.env) because the opencode plugin
16
- * is a long-lived in-process module whose config is resolved at init, after
17
- * this module is already imported.
18
- */
19
- export async function evaluateGuard(input, endpoint, opts = {}) {
20
- if (!endpoint)
21
- return null;
22
- if (opts.disabled)
23
- return null;
24
- const timeoutMs = opts.timeoutMs ?? 50;
25
- const start = Date.now();
26
- try {
27
- const res = await Promise.race([
28
- fetch(endpoint, {
29
- method: "POST",
30
- headers: {
31
- "content-type": "application/json",
32
- "user-agent": GUARD_UA,
33
- "x-pinta-relay-token": opts.token ?? "",
34
- },
35
- body: JSON.stringify({ input }),
36
- }),
37
- sleep(timeoutMs),
38
- ]);
39
- if (res.status !== 200) {
40
- return { decision: "ALLOW", reason: null, userMessage: null, durationMs: Date.now() - start, failOpenReason: "error" };
41
- }
42
- const body = (await res.json());
43
- return {
44
- decision: body.decision,
45
- reason: body.reason,
46
- userMessage: body.userMessage ?? null,
47
- durationMs: body.durationMs ?? Date.now() - start,
48
- };
49
- }
50
- catch (err) {
51
- const reason = err.name === "TimeoutError" ? "timeout" : "error";
52
- return { decision: "ALLOW", reason: null, userMessage: null, durationMs: Date.now() - start, failOpenReason: reason };
53
- }
54
- }
55
- //# sourceMappingURL=guard.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"guard.js","sourceRoot":"","sources":["../../src/core/guard.ts"],"names":[],"mappings":"AA2BA,+EAA+E;AAC/E,iFAAiF;AACjF,qCAAqC;AACrC,MAAM,QAAQ,GAAG,sBAAsB,CAAC;AAExC,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAC/B,UAAU,CAAC,GAAG,EAAE;QACd,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACjD,GAAG,CAAC,IAAI,GAAG,cAAc,CAAC;QAC1B,MAAM,CAAC,GAAG,CAAC,CAAC;IACd,CAAC,EAAE,EAAE,CAAC,CACP,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAiB,EACjB,QAA4B,EAC5B,OAAqB,EAAE;IAEvB,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YAC7B,KAAK,CAAC,QAAQ,EAAE;gBACd,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,YAAY,EAAE,QAAQ;oBACtB,qBAAqB,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE;iBACxC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;aAChC,CAAC;YACF,KAAK,CAAC,SAAS,CAAC;SACjB,CAAC,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;QACzH,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAK7B,CAAC;QACF,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;YACrC,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;SAClD,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,GAAmC,GAAa,CAAC,IAAI,KAAK,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;QAC3G,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC;IACxH,CAAC;AACH,CAAC"}
@@ -1,51 +0,0 @@
1
- import type { GuardResult } from "./guard.js";
2
- export interface OtlpAttribute {
3
- key: string;
4
- value: {
5
- stringValue: string;
6
- } | {
7
- intValue: number;
8
- } | {
9
- doubleValue: number;
10
- } | {
11
- boolValue: boolean;
12
- };
13
- }
14
- export interface OtlpSpan {
15
- traceId: string;
16
- spanId: string;
17
- name: string;
18
- kind: number;
19
- startTimeUnixNano: string;
20
- endTimeUnixNano: string;
21
- attributes: OtlpAttribute[];
22
- }
23
- export interface ResourceSpans {
24
- resource: {
25
- attributes: OtlpAttribute[];
26
- };
27
- scopeSpans: Array<{
28
- scope: {
29
- name: string;
30
- version: string;
31
- };
32
- spans: OtlpSpan[];
33
- }>;
34
- }
35
- export interface OtlpPayload {
36
- resourceSpans: ResourceSpans[];
37
- }
38
- /** Convert a 26-char Crockford ULID into 32 lowercase hex chars for an OTLP traceId. */
39
- export declare function ulidToTraceId(ulid: string): string;
40
- /** Generate a fresh 16-hex-char (8-byte) span ID. */
41
- export declare function newSpanId(): string;
42
- export declare function buildOtlpPayload(args: {
43
- name: string;
44
- traceId: string;
45
- fields: Record<string, unknown>;
46
- serviceVersion: string;
47
- now?: number;
48
- guard?: GuardResult | null;
49
- }): OtlpPayload;
50
- /** Concatenate per-event payloads' resourceSpans into one OTLP payload. */
51
- export declare function mergeBatch(payloads: OtlpPayload[]): OtlpPayload;
package/dist/core/otlp.js DELETED
@@ -1,135 +0,0 @@
1
- import crypto from "crypto";
2
- import os from "os";
3
- import { redact, truncate } from "./redact.js";
4
- const SDK_VERSION = "0.3.1"; // keep in sync with package.json
5
- const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
6
- /** Convert a 26-char Crockford ULID into 32 lowercase hex chars for an OTLP traceId. */
7
- export function ulidToTraceId(ulid) {
8
- if (ulid.length !== 26)
9
- throw new Error(`ulidToTraceId: expected 26 chars, got ${ulid.length}`);
10
- let n = 0n;
11
- for (const ch of ulid) {
12
- const idx = CROCKFORD.indexOf(ch);
13
- if (idx < 0)
14
- throw new Error(`ulidToTraceId: invalid Crockford char "${ch}"`);
15
- n = (n << 5n) | BigInt(idx);
16
- }
17
- n &= (1n << 128n) - 1n;
18
- return n.toString(16).padStart(32, "0");
19
- }
20
- /** Generate a fresh 16-hex-char (8-byte) span ID. */
21
- export function newSpanId() {
22
- return crypto.randomBytes(8).toString("hex");
23
- }
24
- /** Identifier/enum keys for which redaction is skipped (truncation still applies). */
25
- const SKIP_REDACT_KEYS = new Set([
26
- "opencode.kind",
27
- "opencode.event_type",
28
- "opencode.tool",
29
- "opencode.session_id", "opencode.sessionID",
30
- "opencode.call_id", "opencode.callID",
31
- "opencode.agent",
32
- "opencode.model",
33
- "opencode.exit",
34
- "opencode.truncated",
35
- "opencode.title",
36
- ]);
37
- /** Keys that may carry shell command / tool payload text → bash redaction context. */
38
- const BASH_CONTEXT_KEYS = new Set([
39
- "opencode.args",
40
- "opencode.input",
41
- "opencode.output",
42
- "opencode.tool_input",
43
- ]);
44
- function maybeRedactString(key, raw) {
45
- const truncated = truncate(raw);
46
- if (SKIP_REDACT_KEYS.has(key))
47
- return truncated;
48
- const context = BASH_CONTEXT_KEYS.has(key) ? "bash" : undefined;
49
- return redact(truncated, { context });
50
- }
51
- /** Convert a JS value into an OTLP attribute value. Returns null to omit. */
52
- function toOtlpValue(key, v) {
53
- if (v === null || v === undefined)
54
- return null;
55
- switch (typeof v) {
56
- case "string":
57
- return { stringValue: maybeRedactString(key, v) };
58
- case "boolean":
59
- return { boolValue: v };
60
- case "number":
61
- return Number.isInteger(v) ? { intValue: v } : { doubleValue: v };
62
- case "object":
63
- try {
64
- return { stringValue: maybeRedactString(key, JSON.stringify(v)) };
65
- }
66
- catch {
67
- return { stringValue: maybeRedactString(key, String(v)) };
68
- }
69
- default:
70
- return { stringValue: maybeRedactString(key, String(v)) };
71
- }
72
- }
73
- /** Bronze flattening: every field becomes an `opencode.<key>` attribute, losslessly. */
74
- function flattenFields(fields) {
75
- // Discriminator first so aware-backend's detectIngestType hits it cheaply.
76
- const out = [{ key: "ingest.type", value: { stringValue: "opencode" } }];
77
- for (const [k, v] of Object.entries(fields)) {
78
- const key = `opencode.${k}`;
79
- const value = toOtlpValue(key, v);
80
- if (value === null)
81
- continue;
82
- out.push({ key, value });
83
- }
84
- return out;
85
- }
86
- function resourceAttrs(serviceVersion) {
87
- return [
88
- { key: "service.name", value: { stringValue: "opencode" } },
89
- { key: "service.version", value: { stringValue: serviceVersion } },
90
- { key: "telemetry.sdk.name", value: { stringValue: "pinta-opencode" } },
91
- { key: "telemetry.sdk.language", value: { stringValue: "nodejs" } },
92
- { key: "telemetry.sdk.version", value: { stringValue: SDK_VERSION } },
93
- { key: "process.pid", value: { intValue: process.pid } },
94
- { key: "process.owner", value: { stringValue: os.userInfo().username } },
95
- { key: "host.name", value: { stringValue: os.hostname() } },
96
- { key: "host.arch", value: { stringValue: os.arch() } },
97
- ];
98
- }
99
- export function buildOtlpPayload(args) {
100
- const ts = args.now ?? Date.now();
101
- const tsNano = (BigInt(ts) * 1000000n).toString();
102
- const attrs = flattenFields(args.fields);
103
- if (args.guard) {
104
- attrs.push({ key: "pinta.guard.decision", value: { stringValue: args.guard.decision.toLowerCase() } }, { key: "pinta.guard.duration_ms", value: { intValue: args.guard.durationMs } });
105
- if (args.guard.reason)
106
- attrs.push({ key: "pinta.guard.matched_rule", value: { stringValue: args.guard.reason } });
107
- if (args.guard.failOpenReason)
108
- attrs.push({ key: "pinta.guard.fail_open_reason", value: { stringValue: args.guard.failOpenReason } });
109
- }
110
- const span = {
111
- traceId: ulidToTraceId(args.traceId),
112
- spanId: newSpanId(),
113
- name: args.name,
114
- kind: 1, // SPAN_KIND_INTERNAL
115
- startTimeUnixNano: tsNano,
116
- endTimeUnixNano: tsNano,
117
- attributes: attrs,
118
- };
119
- return {
120
- resourceSpans: [
121
- {
122
- resource: { attributes: resourceAttrs(args.serviceVersion) },
123
- scopeSpans: [{ scope: { name: "pinta-opencode", version: SDK_VERSION }, spans: [span] }],
124
- },
125
- ],
126
- };
127
- }
128
- /** Concatenate per-event payloads' resourceSpans into one OTLP payload. */
129
- export function mergeBatch(payloads) {
130
- const out = [];
131
- for (const p of payloads)
132
- out.push(...p.resourceSpans);
133
- return { resourceSpans: out };
134
- }
135
- //# sourceMappingURL=otlp.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"otlp.js","sourceRoot":"","sources":["../../src/core/otlp.ts"],"names":[],"mappings":"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"}
@@ -1,57 +0,0 @@
1
- export interface RedactOptions {
2
- /**
3
- * Optional context hint that enables context-gated patterns. Currently
4
- * only "bash" enables `cli_password_short` (-p<pass>) since matching it
5
- * outside Bash command text is too noisy.
6
- */
7
- context?: "bash";
8
- }
9
- /** Tier 3 cap: per-string byte limit before truncation. */
10
- export declare const MAX_BYTES = 102400;
11
- /**
12
- * Truncate input by UTF-8 byte length. Appends `…[TRUNCATED:<origByteLen>]`
13
- * to indicate elision. Returns input unchanged when within cap.
14
- *
15
- * Uses Buffer for accurate byte counting; slices on byte boundary then
16
- * decodes — may produce a U+FFFD if the boundary lands mid-codepoint, which
17
- * is acceptable for telemetry.
18
- */
19
- export declare function truncate(input: string): string;
20
- export interface Pattern {
21
- /** Token type printed inside `[REDACTED:<type>]`. */
22
- type: string;
23
- /** Regex with global flag (`g`). Multiline (`m`) for line-anchored ones. */
24
- regex: RegExp;
25
- /**
26
- * Capture group index whose substring is replaced. 0 (default) = whole match.
27
- * Use a positive group when the pattern brackets context that should be
28
- * preserved (e.g. `postgres://user:<pwd>@` keeps the URL shape intact).
29
- */
30
- captureGroup?: number;
31
- /**
32
- * If set, this pattern only applies when `RedactOptions.context` equals
33
- * the listed value. Used for false-positive-prone shapes.
34
- */
35
- requireContext?: "bash";
36
- }
37
- export declare const PATTERNS: ReadonlyArray<Pattern>;
38
- interface Match {
39
- start: number;
40
- end: number;
41
- replaceStart: number;
42
- replaceEnd: number;
43
- type: string;
44
- }
45
- export declare function collectMatches(input: string, opts: RedactOptions): Match[];
46
- export declare function resolveOverlaps(matches: Match[]): Match[];
47
- export declare function applyMatches(input: string, matches: Match[]): string;
48
- /**
49
- * Mask high-confidence secret patterns in `input`. Returns a new string with
50
- * matched substrings replaced by `[REDACTED:<type>]`.
51
- *
52
- * - `opts.context = "bash"` enables context-gated patterns.
53
- * - Truncation is NOT applied here — see `truncate()`. The caller decides
54
- * the order (spec §3 Tier 3: truncate first, then redact).
55
- */
56
- export declare function redact(input: string, opts?: RedactOptions): string;
57
- export {};
@@ -1,141 +0,0 @@
1
- /** Tier 3 cap: per-string byte limit before truncation. */
2
- export const MAX_BYTES = 102400;
3
- /**
4
- * Truncate input by UTF-8 byte length. Appends `…[TRUNCATED:<origByteLen>]`
5
- * to indicate elision. Returns input unchanged when within cap.
6
- *
7
- * Uses Buffer for accurate byte counting; slices on byte boundary then
8
- * decodes — may produce a U+FFFD if the boundary lands mid-codepoint, which
9
- * is acceptable for telemetry.
10
- */
11
- export function truncate(input) {
12
- const buf = Buffer.from(input, "utf-8");
13
- if (buf.length <= MAX_BYTES)
14
- return input;
15
- const head = buf.subarray(0, MAX_BYTES).toString("utf-8");
16
- return `${head}…[TRUNCATED:${buf.length}]`;
17
- }
18
- export const PATTERNS = [
19
- { type: "aws_access_key", regex: /AKIA[0-9A-Z]{16}/g },
20
- {
21
- type: "aws_secret_key",
22
- // Context word `aws_secret`/`AWS_SECRET` (with optional separator) followed
23
- // by an assignment-ish character then a 40-char base64-ish blob.
24
- regex: /(?:aws[_-]?secret(?:[_-]?(?:access)?[_-]?key)?)\s*[:=]\s*["']?([A-Za-z0-9/+=]{40})(?![A-Za-z0-9/+=])/gi,
25
- captureGroup: 1,
26
- },
27
- {
28
- type: "gcp_service_account",
29
- // Whole JSON blob starting with the service-account discriminator.
30
- regex: /\{[\s\S]{0,200}?"type"\s*:\s*"service_account"[\s\S]*?\}/g,
31
- },
32
- { type: "github_token", regex: /gh[pousr]_[A-Za-z0-9]{36,}/g },
33
- { type: "gitlab_token", regex: /glpat-[A-Za-z0-9_-]{20}/g },
34
- { type: "slack_token", regex: /xox[abrsp]-[0-9A-Za-z-]{10,}/g },
35
- { type: "openai_key", regex: /sk-(?:proj-)?[A-Za-z0-9_-]{40,}/g },
36
- { type: "anthropic_key", regex: /sk-ant-[A-Za-z0-9_-]{50,}/g },
37
- { type: "stripe_key", regex: /(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{20,}/g },
38
- {
39
- type: "jwt",
40
- regex: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
41
- },
42
- {
43
- type: "private_key_block",
44
- regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
45
- },
46
- { type: "bearer_token", regex: /bearer\s+([A-Za-z0-9._~+/=-]{12,})/gi, captureGroup: 1 },
47
- { type: "basic_auth", regex: /basic\s+([A-Za-z0-9+/=]{12,})/gi, captureGroup: 1 },
48
- {
49
- type: "db_url_password",
50
- regex: /\b(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|redis):\/\/[^:\s/]+:([^@\s]+)@/gi,
51
- captureGroup: 1,
52
- },
53
- {
54
- type: "cli_password_flag",
55
- regex: /(?:--password|--pass|--pwd)[=\s]([^\s'"]+)/g,
56
- captureGroup: 1,
57
- },
58
- {
59
- type: "cli_password_short",
60
- // mysql -p<pass>; only on bash context.
61
- regex: /\s-p([^\s'"]+)/g,
62
- captureGroup: 1,
63
- requireContext: "bash",
64
- },
65
- {
66
- type: "env_var_secret",
67
- // Known false positive: trailing `[A-Z0-9_]*` is greedy, so names like
68
- // `OPENAI_API_KEY_DESCRIPTION=Used` still match. Acceptable for Bronze.
69
- regex: /^(?:export\s+)?([A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PWD|API_KEY)[A-Z0-9_]*)\s*=\s*["']?([^\s"'\n]+)/gm,
70
- captureGroup: 2,
71
- },
72
- ];
73
- export function collectMatches(input, opts) {
74
- const out = [];
75
- for (const pattern of PATTERNS) {
76
- if (pattern.requireContext && pattern.requireContext !== opts.context)
77
- continue;
78
- const re = new RegExp(pattern.regex.source, pattern.regex.flags);
79
- let m;
80
- while ((m = re.exec(input)) !== null) {
81
- const cg = pattern.captureGroup ?? 0;
82
- const captured = m[cg];
83
- if (captured === undefined) {
84
- if (m.index === re.lastIndex)
85
- re.lastIndex++;
86
- continue;
87
- }
88
- const start = m.index;
89
- const end = m.index + m[0].length;
90
- const replaceStart = start + m[0].indexOf(captured);
91
- const replaceEnd = replaceStart + captured.length;
92
- out.push({ start, end, replaceStart, replaceEnd, type: pattern.type });
93
- if (m.index === re.lastIndex)
94
- re.lastIndex++; // guard against zero-length
95
- }
96
- }
97
- return out;
98
- }
99
- export function resolveOverlaps(matches) {
100
- const sorted = [...matches].sort((a, b) => {
101
- if (a.start !== b.start)
102
- return a.start - b.start;
103
- return (b.end - b.start) - (a.end - a.start); // longer whole-match wins on tie
104
- });
105
- const kept = [];
106
- let lastEnd = -1;
107
- for (const m of sorted) {
108
- if (m.start < lastEnd)
109
- continue; // overlapping later match dropped
110
- kept.push(m);
111
- lastEnd = m.end;
112
- }
113
- return kept;
114
- }
115
- export function applyMatches(input, matches) {
116
- // Apply right-to-left so earlier indices remain valid.
117
- const sorted = [...matches].sort((a, b) => b.replaceStart - a.replaceStart);
118
- let out = input;
119
- for (const m of sorted) {
120
- out = out.slice(0, m.replaceStart) + `[REDACTED:${m.type}]` + out.slice(m.replaceEnd);
121
- }
122
- return out;
123
- }
124
- /**
125
- * Mask high-confidence secret patterns in `input`. Returns a new string with
126
- * matched substrings replaced by `[REDACTED:<type>]`.
127
- *
128
- * - `opts.context = "bash"` enables context-gated patterns.
129
- * - Truncation is NOT applied here — see `truncate()`. The caller decides
130
- * the order (spec §3 Tier 3: truncate first, then redact).
131
- */
132
- export function redact(input, opts = {}) {
133
- if (input.length === 0)
134
- return input;
135
- const all = collectMatches(input, opts);
136
- if (all.length === 0)
137
- return input;
138
- const kept = resolveOverlaps(all);
139
- return applyMatches(input, kept);
140
- }
141
- //# sourceMappingURL=redact.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/core/redact.ts"],"names":[],"mappings":"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"}
@@ -1,14 +0,0 @@
1
- import type { OtlpPayload } from "./otlp.js";
2
- /**
3
- * In-memory retry buffer. The plugin is a long-lived in-process module
4
- * (verified H-C1), so failed OTLP payloads are buffered in memory and flushed
5
- * on the next event. Oldest entries are dropped past the cap. (Disk persistence
6
- * across instance restarts is an optional future enhancement — SPEC §6.)
7
- */
8
- export declare class RetryQueue {
9
- private entries;
10
- enqueue(payload: OtlpPayload): void;
11
- /** Remove and return all buffered payloads. */
12
- drain(): OtlpPayload[];
13
- get size(): number;
14
- }
@@ -1,26 +0,0 @@
1
- const MAX_ENTRIES = 1000;
2
- /**
3
- * In-memory retry buffer. The plugin is a long-lived in-process module
4
- * (verified H-C1), so failed OTLP payloads are buffered in memory and flushed
5
- * on the next event. Oldest entries are dropped past the cap. (Disk persistence
6
- * across instance restarts is an optional future enhancement — SPEC §6.)
7
- */
8
- export class RetryQueue {
9
- entries = [];
10
- enqueue(payload) {
11
- this.entries.push(payload);
12
- if (this.entries.length > MAX_ENTRIES) {
13
- this.entries.splice(0, this.entries.length - MAX_ENTRIES);
14
- }
15
- }
16
- /** Remove and return all buffered payloads. */
17
- drain() {
18
- const out = this.entries;
19
- this.entries = [];
20
- return out;
21
- }
22
- get size() {
23
- return this.entries.length;
24
- }
25
- }
26
- //# sourceMappingURL=retry-queue.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"retry-queue.js","sourceRoot":"","sources":["../../src/core/retry-queue.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB;;;;;GAKG;AACH,MAAM,OAAO,UAAU;IACb,OAAO,GAAkB,EAAE,CAAC;IAEpC,OAAO,CAAC,OAAoB;QAC1B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;YACtC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,+CAA+C;IAC/C,KAAK;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;CACF"}
@@ -1,16 +0,0 @@
1
- /**
2
- * Per-turn trace correlation, keyed by `sessionID`.
3
- *
4
- * opencode instantiates the plugin once per instance (verified H-C1), so the
5
- * map lives in memory for the instance lifetime — no file persistence needed.
6
- * `chat.message` (turn-START) rotates a new ULID trace for its session; every
7
- * subsequent hook in the turn reuses it. Keyed by sessionID so concurrent
8
- * sessions don't collide.
9
- */
10
- export declare class TraceManager {
11
- private map;
12
- /** Start a new trace for this session (called on chat.message / turn-START). */
13
- newTrace(sessionId?: string): string;
14
- /** Current trace for this session; create one if none exists yet. */
15
- currentTrace(sessionId?: string): string;
16
- }
@@ -1,50 +0,0 @@
1
- import crypto from "crypto";
2
- const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
3
- function generateUlid() {
4
- const now = Date.now();
5
- let ts = "";
6
- let t = now;
7
- for (let i = 0; i < 10; i++) {
8
- ts = CROCKFORD[t & 31] + ts;
9
- t = Math.floor(t / 32);
10
- }
11
- const rand = crypto.randomBytes(10);
12
- let r = "";
13
- for (let i = 0; i < 10; i++)
14
- r += CROCKFORD[rand[i] & 31];
15
- while (r.length < 16)
16
- r += CROCKFORD[0];
17
- return ts + r;
18
- }
19
- const MAX_SESSIONS = 200;
20
- /**
21
- * Per-turn trace correlation, keyed by `sessionID`.
22
- *
23
- * opencode instantiates the plugin once per instance (verified H-C1), so the
24
- * map lives in memory for the instance lifetime — no file persistence needed.
25
- * `chat.message` (turn-START) rotates a new ULID trace for its session; every
26
- * subsequent hook in the turn reuses it. Keyed by sessionID so concurrent
27
- * sessions don't collide.
28
- */
29
- export class TraceManager {
30
- map = new Map();
31
- /** Start a new trace for this session (called on chat.message / turn-START). */
32
- newTrace(sessionId) {
33
- const key = sessionId || "default";
34
- const traceId = generateUlid();
35
- this.map.set(key, traceId);
36
- // Cap to avoid unbounded growth over a long-lived instance.
37
- if (this.map.size > MAX_SESSIONS) {
38
- const oldest = this.map.keys().next().value;
39
- if (oldest !== undefined)
40
- this.map.delete(oldest);
41
- }
42
- return traceId;
43
- }
44
- /** Current trace for this session; create one if none exists yet. */
45
- currentTrace(sessionId) {
46
- const key = sessionId || "default";
47
- return this.map.get(key) ?? this.newTrace(key);
48
- }
49
- }
50
- //# sourceMappingURL=trace.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"trace.js","sourceRoot":"","sources":["../../src/core/trace.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,MAAM,SAAS,GAAG,kCAAkC,CAAC;AAErD,SAAS,YAAY;IACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,IAAI,CAAC,GAAG,GAAG,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,EAAE,GAAG,SAAS,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;QAC5B,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IACzB,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IACpC,IAAI,CAAC,GAAG,EAAE,CAAC;IACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE;QAAE,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAC1D,OAAO,CAAC,CAAC,MAAM,GAAG,EAAE;QAAE,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC;IACxC,OAAO,EAAE,GAAG,CAAC,CAAC;AAChB,CAAC;AAED,MAAM,YAAY,GAAG,GAAG,CAAC;AAEzB;;;;;;;;GAQG;AACH,MAAM,OAAO,YAAY;IACf,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IAExC,gFAAgF;IAChF,QAAQ,CAAC,SAAkB;QACzB,MAAM,GAAG,GAAG,SAAS,IAAI,SAAS,CAAC;QACnC,MAAM,OAAO,GAAG,YAAY,EAAE,CAAC;QAC/B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC3B,4DAA4D;QAC5D,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,YAAY,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAC5C,IAAI,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,qEAAqE;IACrE,YAAY,CAAC,SAAkB;QAC7B,MAAM,GAAG,GAAG,SAAS,IAAI,SAAS,CAAC;QACnC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACjD,CAAC;CACF"}
@@ -1,20 +0,0 @@
1
- import { type OtlpPayload } from "./otlp.js";
2
- export interface TransportConfig {
3
- /** Full OTLP/HTTP traces URL. Undefined → telemetry silently disabled. */
4
- endpoint?: string;
5
- headers: Record<string, string>;
6
- }
7
- /**
8
- * Best-effort OTLP/HTTP transport with an in-memory retry buffer. Never throws;
9
- * failures are buffered and re-sent (batched) on the next event. Silent-disable
10
- * when no endpoint is configured.
11
- */
12
- export declare class Transport {
13
- private config;
14
- private queue;
15
- constructor(config: TransportConfig);
16
- send(payload: OtlpPayload): Promise<void>;
17
- /** Drain the retry buffer as one batched POST; re-buffer on failure. */
18
- flush(): Promise<void>;
19
- private post;
20
- }
@@ -1,69 +0,0 @@
1
- import { RetryQueue } from "./retry-queue.js";
2
- import { mergeBatch } from "./otlp.js";
3
- const TIMEOUT_MS = 5000;
4
- /**
5
- * Best-effort OTLP/HTTP transport with an in-memory retry buffer. Never throws;
6
- * failures are buffered and re-sent (batched) on the next event. Silent-disable
7
- * when no endpoint is configured.
8
- */
9
- export class Transport {
10
- config;
11
- queue = new RetryQueue();
12
- constructor(config) {
13
- this.config = config;
14
- }
15
- async send(payload) {
16
- if (!this.config.endpoint)
17
- return;
18
- const ok = await this.post(payload);
19
- if (!ok)
20
- this.queue.enqueue(payload);
21
- }
22
- /** Drain the retry buffer as one batched POST; re-buffer on failure. */
23
- async flush() {
24
- if (!this.config.endpoint)
25
- return;
26
- if (this.queue.size === 0)
27
- return;
28
- const entries = this.queue.drain();
29
- const ok = await this.post(mergeBatch(entries));
30
- if (!ok)
31
- for (const e of entries)
32
- this.queue.enqueue(e);
33
- }
34
- async post(payload) {
35
- const endpoint = this.config.endpoint;
36
- if (!endpoint)
37
- return false;
38
- const ctrl = new AbortController();
39
- const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
40
- try {
41
- const res = await fetch(endpoint, {
42
- method: "POST",
43
- headers: { "Content-Type": "application/json", ...this.config.headers },
44
- body: JSON.stringify(payload),
45
- signal: ctrl.signal,
46
- });
47
- if (!res.ok) {
48
- const hint = res.status === 401 || res.status === 403
49
- ? " — check relay token"
50
- : res.status === 404
51
- ? " — check traces endpoint path"
52
- : res.status >= 500
53
- ? " — collector may be down"
54
- : "";
55
- process.stderr.write(`[pinta-opencode] OTLP POST ${res.status} ${endpoint}${hint}\n`);
56
- return false;
57
- }
58
- return true;
59
- }
60
- catch (err) {
61
- process.stderr.write(`[pinta-opencode] OTLP POST failed: ${err.message ?? String(err)}\n`);
62
- return false;
63
- }
64
- finally {
65
- clearTimeout(timer);
66
- }
67
- }
68
- }
69
- //# sourceMappingURL=transport.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/core/transport.ts"],"names":[],"mappings":"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"}
@@ -1,4 +0,0 @@
1
- export declare function envFilePath(): string;
2
- export declare function parseEnvFile(content: string): Record<string, string>;
3
- /** Load the env file (if present) and merge only-unset keys into process.env. */
4
- export declare function loadEnvFile(filePath?: string): void;
package/dist/env-file.js DELETED
@@ -1,53 +0,0 @@
1
- /**
2
- * Graceful env-file loader.
3
- *
4
- * pinta-opencode can read config from `~/.config/opencode/pinta-opencode.env`
5
- * (or `$OPENCODE_CONFIG_DIR/pinta-opencode.env`) — `KEY=VALUE` per line. This is
6
- * the lowest-priority source; plugin options and explicit process.env win.
7
- *
8
- * Resolution precedence (highest → lowest): plugin options → process.env →
9
- * this file (unset keys only). Missing file is a silent no-op.
10
- */
11
- import fs from "node:fs";
12
- import os from "node:os";
13
- import path from "node:path";
14
- function opencodeConfigDir() {
15
- return process.env.OPENCODE_CONFIG_DIR || path.join(os.homedir(), ".config", "opencode");
16
- }
17
- export function envFilePath() {
18
- return path.join(opencodeConfigDir(), "pinta-opencode.env");
19
- }
20
- export function parseEnvFile(content) {
21
- const out = {};
22
- for (const raw of content.split("\n")) {
23
- const line = raw.trim();
24
- if (!line || line.startsWith("#"))
25
- continue;
26
- const idx = line.indexOf("=");
27
- if (idx < 0)
28
- continue;
29
- const key = line.slice(0, idx).trim();
30
- let value = line.slice(idx + 1).trim();
31
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
32
- value = value.slice(1, -1);
33
- }
34
- if (key)
35
- out[key] = value;
36
- }
37
- return out;
38
- }
39
- /** Load the env file (if present) and merge only-unset keys into process.env. */
40
- export function loadEnvFile(filePath = envFilePath()) {
41
- let content;
42
- try {
43
- content = fs.readFileSync(filePath, "utf-8");
44
- }
45
- catch {
46
- return; // missing/unreadable → no-op
47
- }
48
- for (const [key, value] of Object.entries(parseEnvFile(content))) {
49
- if (process.env[key] === undefined)
50
- process.env[key] = value;
51
- }
52
- }
53
- //# sourceMappingURL=env-file.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"env-file.js","sourceRoot":"","sources":["../src/env-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,SAAS,iBAAiB;IACxB,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAC3F,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,oBAAoB,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,GAAG,GAAG,CAAC;YAAE,SAAS;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACrG,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;QACD,IAAI,GAAG;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAC5B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,WAAW,CAAC,WAAmB,WAAW,EAAE;IAC1D,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,6BAA6B;IACvC,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACjE,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAC/D,CAAC;AACH,CAAC"}
package/dist/plugin.d.ts DELETED
@@ -1,27 +0,0 @@
1
- import { type PintaOptions } from "./config.js";
2
- import { type OpencodeEvent, type ToolBeforeInput, type ToolAfterOutput } from "./telemetry.js";
3
- /**
4
- * pinta-opencode plugin entry. opencode invokes this once per instance with
5
- * `(input, options)` and keeps the returned hooks for the instance lifetime
6
- * (verified H-C1), so config + transport + trace state live in this closure.
7
- *
8
- * - Telemetry (event / tool.execute.after): best-effort OTLP, never blocks.
9
- * - Governance (tool.execute.before): guard query; DENY → throw(reason), which
10
- * blocks only that tool and surfaces the reason to the model (verified H-A1).
11
- *
12
- * Fail-open invariant: every telemetry/guard error is swallowed; the only
13
- * intentional throw is a guard DENY.
14
- */
15
- export declare const PintaOpencode: (_input: unknown, options?: PintaOptions) => Promise<{
16
- "chat.message": (input: {
17
- sessionID?: string;
18
- }) => Promise<void>;
19
- event: (input: {
20
- event?: OpencodeEvent;
21
- }) => Promise<void>;
22
- "tool.execute.before": (input: ToolBeforeInput, output: {
23
- args: unknown;
24
- }) => Promise<void>;
25
- "tool.execute.after": (input: ToolBeforeInput, output: ToolAfterOutput) => Promise<void>;
26
- }>;
27
- export default PintaOpencode;
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -1,37 +0,0 @@
1
- import { Transport } from "./core/transport.js";
2
- import { TraceManager } from "./core/trace.js";
3
- import type { GuardResult } from "./core/guard.js";
4
- import type { ResolvedConfig } from "./config.js";
5
- export interface OpencodeEvent {
6
- id?: string;
7
- type?: string;
8
- properties?: Record<string, unknown>;
9
- }
10
- export interface ToolBeforeInput {
11
- tool: string;
12
- sessionID: string;
13
- callID: string;
14
- }
15
- export interface ToolAfterOutput {
16
- title?: string;
17
- output?: unknown;
18
- metadata?: Record<string, unknown>;
19
- }
20
- /**
21
- * Maps opencode hook payloads to OTLP spans (verified payload shapes — SPEC §5).
22
- * Tool spans are built from tool.execute.before/after (richer: args, output,
23
- * exit) rather than the event bus; `event` covers lifecycle + turn boundaries.
24
- */
25
- export declare class Telemetry {
26
- private transport;
27
- private trace;
28
- private config;
29
- constructor(transport: Transport, trace: TraceManager, config: ResolvedConfig);
30
- private emit;
31
- /** Lifecycle span from the `event` hook. Flushes the retry buffer on turn-END. */
32
- lifecycle(ev: OpencodeEvent): Promise<void>;
33
- /** Tool span from `tool.execute.before`, carrying the guard decision. */
34
- toolBefore(input: ToolBeforeInput, args: unknown, guard: GuardResult | null): Promise<void>;
35
- /** Tool result span from `tool.execute.after`, incl. exit code / truncation. */
36
- toolAfter(input: ToolBeforeInput, output: ToolAfterOutput): Promise<void>;
37
- }
package/dist/telemetry.js DELETED
@@ -1,53 +0,0 @@
1
- import { buildOtlpPayload } from "./core/otlp.js";
2
- /**
3
- * Maps opencode hook payloads to OTLP spans (verified payload shapes — SPEC §5).
4
- * Tool spans are built from tool.execute.before/after (richer: args, output,
5
- * exit) rather than the event bus; `event` covers lifecycle + turn boundaries.
6
- */
7
- export class Telemetry {
8
- transport;
9
- trace;
10
- config;
11
- constructor(transport, trace, config) {
12
- this.transport = transport;
13
- this.trace = trace;
14
- this.config = config;
15
- }
16
- async emit(name, sessionId, fields, guard) {
17
- const traceId = this.trace.currentTrace(sessionId);
18
- const payload = buildOtlpPayload({ name, traceId, fields, guard, serviceVersion: this.config.serviceVersion });
19
- await this.transport.send(payload);
20
- }
21
- /** Lifecycle span from the `event` hook. Flushes the retry buffer on turn-END. */
22
- async lifecycle(ev) {
23
- const props = ev.properties ?? {};
24
- const sessionId = typeof props.sessionID === "string" ? props.sessionID : undefined;
25
- await this.emit(`opencode.event.${ev.type ?? "unknown"}`, sessionId, {
26
- kind: "event",
27
- event_type: ev.type,
28
- session_id: sessionId,
29
- ...props,
30
- });
31
- if (ev.type === "session.idle")
32
- await this.transport.flush();
33
- }
34
- /** Tool span from `tool.execute.before`, carrying the guard decision. */
35
- async toolBefore(input, args, guard) {
36
- await this.emit("opencode.tool.before", input.sessionID, { kind: "tool.before", tool: input.tool, session_id: input.sessionID, call_id: input.callID, args }, guard);
37
- }
38
- /** Tool result span from `tool.execute.after`, incl. exit code / truncation. */
39
- async toolAfter(input, output) {
40
- const meta = output.metadata ?? {};
41
- await this.emit("opencode.tool.after", input.sessionID, {
42
- kind: "tool.after",
43
- tool: input.tool,
44
- session_id: input.sessionID,
45
- call_id: input.callID,
46
- title: output.title,
47
- output: output.output,
48
- exit: meta.exit,
49
- truncated: meta.truncated,
50
- });
51
- }
52
- }
53
- //# sourceMappingURL=telemetry.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAsBlD;;;;GAIG;AACH,MAAM,OAAO,SAAS;IAEV;IACA;IACA;IAHV,YACU,SAAoB,EACpB,KAAmB,EACnB,MAAsB;QAFtB,cAAS,GAAT,SAAS,CAAW;QACpB,UAAK,GAAL,KAAK,CAAc;QACnB,WAAM,GAAN,MAAM,CAAgB;IAC7B,CAAC;IAEI,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,SAA6B,EAAE,MAA+B,EAAE,KAA0B;QACzH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;QAC/G,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED,kFAAkF;IAClF,KAAK,CAAC,SAAS,CAAC,EAAiB;QAC/B,MAAM,KAAK,GAAG,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QACpF,MAAM,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,IAAI,IAAI,SAAS,EAAE,EAAE,SAAS,EAAE;YACnE,IAAI,EAAE,OAAO;YACb,UAAU,EAAE,EAAE,CAAC,IAAI;YACnB,UAAU,EAAE,SAAS;YACrB,GAAG,KAAK;SACT,CAAC,CAAC;QACH,IAAI,EAAE,CAAC,IAAI,KAAK,cAAc;YAAE,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IAC/D,CAAC;IAED,yEAAyE;IACzE,KAAK,CAAC,UAAU,CAAC,KAAsB,EAAE,IAAa,EAAE,KAAyB;QAC/E,MAAM,IAAI,CAAC,IAAI,CACb,sBAAsB,EACtB,KAAK,CAAC,SAAS,EACf,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,EACnG,KAAK,CACN,CAAC;IACJ,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,SAAS,CAAC,KAAsB,EAAE,MAAuB;QAC7D,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;QACnC,MAAM,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,SAAS,EAAE;YACtD,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,UAAU,EAAE,KAAK,CAAC,SAAS;YAC3B,OAAO,EAAE,KAAK,CAAC,MAAM;YACrB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;IACL,CAAC;CACF"}