@pinta-ai/pinta-opencode 0.3.1 → 0.5.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/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.5.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.5.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,11 +1,12 @@
1
1
  {
2
2
  "name": "@pinta-ai/pinta-opencode",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "OTLP forwarder + guard for opencode (in-process plugin)",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.js",
7
7
  "exports": {
8
- ".": "./dist/plugin.js"
8
+ ".": "./dist/plugin.js",
9
+ "./lifecycle": "./dist/index.mjs"
9
10
  },
10
11
  "license": "PolyForm-Noncommercial-1.0.0",
11
12
  "engines": {
@@ -27,15 +28,22 @@
27
28
  },
28
29
  "scripts": {
29
30
  "bump": "node scripts/bump.mjs",
30
- "build": "tsc",
31
+ "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);\" && esbuild src/index.ts --bundle --platform=node --format=esm --target=node18 --outfile=dist/index.mjs --banner:js=\"import { createRequire as __pintaCreateRequire } from 'module'; const require = __pintaCreateRequire(import.meta.url);\"",
31
32
  "dev": "tsc --watch",
32
33
  "test": "vitest run",
33
34
  "test:watch": "vitest",
35
+ "smoke": "node scripts/smoke.mjs",
34
36
  "prepublishOnly": "npm run build"
35
37
  },
36
38
  "devDependencies": {
39
+ "@pinta-ai/core": "^0.3.0",
37
40
  "@types/node": "^22.0.0",
41
+ "esbuild": "0.24.2",
38
42
  "typescript": "^5.7.0",
39
43
  "vitest": "^2.1.0"
44
+ },
45
+ "publishConfig": {
46
+ "registry": "https://registry.npmjs.org",
47
+ "access": "public"
40
48
  }
41
49
  }
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 {};