playingpack 0.7.0 → 0.7.2

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.
Files changed (2) hide show
  1. package/dist/index.js +4 -4
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import{program as P}from"commander";import vt from"open";import rt from"fastify";import at from"@fastify/cors";import it from"@fastify/websocket";import{fastifyTRPCPlugin as ct}from"@trpc/server/adapters/fastify";import lt from"get-port";import{createHash as Ce}from"crypto";function A(t){if(t==null)return null;if(Array.isArray(t))return t.map(A);if(typeof t=="object"){let e=t,o={},n=Object.keys(e).filter(s=>!["stream","request_id","timestamp"].includes(s)).sort();for(let s of n)o[s]=A(e[s]);return o}return t}function v(t){let e=A(t),o=JSON.stringify(e);return Ce("sha256").update(o).digest("hex")}var R=class{sessions=new Map;settings;listeners=new Set;point1Resolvers=new Map;point2Resolvers=new Map;constructor(e){this.settings={cache:"read-write",intervene:!0,...e}}createSession(e,o){let n=o,s=typeof n?.model=="string"?n.model:"unknown",a=Array.isArray(n?.messages)?n.messages:[],i=n?.stream!==!1,l={id:e,state:this.settings.intervene?"pending":"processing",timestamp:new Date().toISOString(),request:{model:s,messages:a,stream:i,tools:n?.tools,temperature:n?.temperature,maxTokens:n?.max_tokens,raw:o},cacheKey:v(o),cacheAvailable:!1};return this.sessions.set(e,l),this.emit({type:"request_update",session:l}),l}getSession(e){return this.sessions.get(e)}getAllSessions(){return Array.from(this.sessions.values())}updateState(e,o){let n=this.sessions.get(e);n&&(n.state=o,this.emit({type:"request_update",session:n}))}setProcessing(e){let o=this.sessions.get(e);o&&(o.state="processing",o.processingStartedAt||(o.processingStartedAt=new Date().toISOString()),this.emit({type:"request_update",session:o}))}setReviewing(e){this.updateState(e,"reviewing")}setCacheAvailable(e,o){let n=this.sessions.get(e);n&&(n.cacheAvailable=o,this.emit({type:"request_update",session:n}))}setResponseSource(e,o){let n=this.sessions.get(e);n&&(n.responseSource=o,this.emit({type:"request_update",session:n}))}setResponse(e,o,n,s=[]){let a=this.sessions.get(e);a&&(a.response={status:o,content:n,toolCalls:s},this.emit({type:"request_update",session:a}))}addToolCall(e,o){let n=this.sessions.get(e);n&&(n.response||(n.response={status:200,content:"",toolCalls:[]}),n.response.toolCalls.push(o),this.emit({type:"request_update",session:n}))}appendContent(e,o){let n=this.sessions.get(e);n&&(n.response||(n.response={status:200,content:"",toolCalls:[]}),n.response.content+=o)}setFinishReason(e,o){let n=this.sessions.get(e);n&&(n.response||(n.response={status:200,content:"",toolCalls:[]}),n.response.finishReason=o,this.emit({type:"request_update",session:n}))}setUsage(e,o){let n=this.sessions.get(e);n&&(n.response||(n.response={status:200,content:"",toolCalls:[]}),n.response.usage={promptTokens:o.prompt_tokens,completionTokens:o.completion_tokens,totalTokens:o.total_tokens},this.emit({type:"request_update",session:n}))}complete(e){let o=this.sessions.get(e);o&&(o.state="complete",o.completedAt=new Date().toISOString(),this.emit({type:"request_update",session:o}))}error(e,o){let n=this.sessions.get(e);n&&(n.state="complete",n.error=o,n.completedAt=new Date().toISOString(),this.emit({type:"request_update",session:n}))}shouldIntervene(){return this.settings.intervene}async waitForPoint1(e){if(!this.sessions.get(e))throw new Error(`Session ${e} not found`);return new Promise(n=>{this.point1Resolvers.set(e,{resolve:n})})}async waitForPoint2(e){if(!this.sessions.get(e))throw new Error(`Session ${e} not found`);return this.updateState(e,"reviewing"),new Promise(n=>{this.point2Resolvers.set(e,{resolve:n})})}resolvePoint1(e,o){let n=this.point1Resolvers.get(e);return n?(this.point1Resolvers.delete(e),this.setProcessing(e),n.resolve(o),!0):!1}resolvePoint2(e,o){let n=this.point2Resolvers.get(e);return n?(this.point2Resolvers.delete(e),n.resolve(o),!0):!1}getSettings(){return{...this.settings}}updateSettings(e){this.settings={...this.settings,...e}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}emit(e){for(let o of this.listeners)try{o(e)}catch{}}cleanup(){let e=Array.from(this.sessions.entries());if(e.length>100){let o=e.filter(([n,s])=>s.state==="complete").slice(0,e.length-100);for(let[n]of o)this.sessions.delete(n)}}removeSession(e){this.sessions.delete(e),this.point1Resolvers.delete(e),this.point2Resolvers.delete(e)}},C=null;function W(t){return C=new R(t),C}function f(){return C||(C=new R),C}function H(t){return{sessionManager:f(),req:t?.req,res:t?.res}}import{initTRPC as Ee}from"@trpc/server";import{z as S}from"zod";import{createRequire as Ae}from"module";import{fileURLToPath as Te}from"url";import{dirname as Me,resolve as K}from"path";import{z as r}from"zod";var G=r.enum(["off","read","read-write"]),T=r.object({cache:G.optional(),intervene:r.boolean().optional(),upstream:r.string().url().optional(),port:r.number().int().min(1).max(65535).optional(),host:r.string().optional(),cachePath:r.string().optional(),logPath:r.string().optional(),headless:r.boolean().optional()}),Pe=r.object({cache:G,intervene:r.boolean()}),z=r.object({settings:Pe.partial()}),At=r.object({model:r.string(),messages:r.array(r.object({role:r.enum(["system","user","assistant","tool"]),content:r.union([r.string(),r.null(),r.array(r.any())]).optional(),name:r.string().optional(),tool_calls:r.array(r.any()).optional(),tool_call_id:r.string().optional()})),temperature:r.number().optional(),top_p:r.number().optional(),n:r.number().optional(),stream:r.boolean().optional(),stop:r.union([r.string(),r.array(r.string())]).optional(),max_tokens:r.number().optional(),presence_penalty:r.number().optional(),frequency_penalty:r.number().optional(),logit_bias:r.record(r.number()).optional(),user:r.string().optional(),tools:r.array(r.object({type:r.literal("function"),function:r.object({name:r.string(),description:r.string().optional(),parameters:r.any().optional()})})).optional(),tool_choice:r.union([r.literal("none"),r.literal("auto"),r.literal("required"),r.object({type:r.literal("function"),function:r.object({name:r.string()})})]).optional(),response_format:r.object({type:r.enum(["text","json_object"])}).optional(),seed:r.number().optional()}).passthrough(),Re=r.object({data:r.string(),delay:r.number()}),Tt=r.object({hash:r.string(),timestamp:r.string(),request:r.object({model:r.string(),messages:r.array(r.unknown())}),response:r.object({status:r.number(),body:r.unknown().optional(),chunks:r.array(Re).optional()})}),M=r.discriminatedUnion("action",[r.object({action:r.literal("llm")}),r.object({action:r.literal("cache")}),r.object({action:r.literal("mock"),content:r.string()})]),j=r.discriminatedUnion("action",[r.object({action:r.literal("return")}),r.object({action:r.literal("modify"),content:r.string()})]),xe=r.object({type:r.literal("point1_action"),requestId:r.string(),action:M}),_e=r.object({type:r.literal("point2_action"),requestId:r.string(),action:j}),B=r.discriminatedUnion("type",[xe,_e]);var V=Ae(import.meta.url),Z=Me(Te(import.meta.url));function je(){try{return V(K(Z,"../package.json"))}catch{return V(K(Z,"../../package.json"))}}var Ie=je(),b=Ee.context().create(),Q=b.router({getSessions:b.procedure.query(({ctx:t})=>({sessions:t.sessionManager.getAllSessions()})),getSession:b.procedure.input(S.object({id:S.string()})).query(({ctx:t,input:e})=>({session:t.sessionManager.getSession(e.id)||null})),getSettings:b.procedure.query(({ctx:t})=>({settings:t.sessionManager.getSettings(),version:Ie.version})),updateSettings:b.procedure.input(z).mutation(({ctx:t,input:e})=>(t.sessionManager.updateSettings(e.settings),{settings:t.sessionManager.getSettings()})),point1Action:b.procedure.input(S.object({requestId:S.string(),action:M})).mutation(({ctx:t,input:e})=>({success:t.sessionManager.resolvePoint1(e.requestId,e.action)})),point2Action:b.procedure.input(S.object({requestId:S.string(),action:j})).mutation(({ctx:t,input:e})=>({success:t.sessionManager.resolvePoint2(e.requestId,e.action)})),health:b.procedure.query(()=>({status:"ok",timestamp:new Date().toISOString()}))});import{createParser as Ue}from"eventsource-parser";var I=class{callbacks;toolCalls=new Map;accumulatedContent="";finishReason=null;usage=null;parser;constructor(e={}){this.callbacks=e,this.parser=Ue(o=>{o.type==="event"&&this.handleEvent(o.data)})}feed(e){this.parser.feed(e)}getContent(){return this.accumulatedContent}getToolCalls(){return Array.from(this.toolCalls.values())}hasToolCalls(){return this.toolCalls.size>0}getFinishReason(){return this.finishReason}getUsage(){return this.usage}getAssembledMessage(){let e=this.getToolCalls();return e.length>0?{role:"assistant",content:null,tool_calls:e.map(o=>({id:o.id,type:"function",function:{name:o.name,arguments:o.arguments}}))}:{role:"assistant",content:this.accumulatedContent||null}}reset(){this.toolCalls.clear(),this.accumulatedContent="",this.finishReason=null,this.usage=null,this.parser.reset()}handleEvent(e){if(e==="[DONE]"){this.callbacks.onDone?.();return}try{let o=JSON.parse(e);for(let n of o.choices){let s=n.delta;if(s.content&&(this.accumulatedContent+=s.content,this.callbacks.onContent?.(s.content)),s.tool_calls)for(let a of s.tool_calls){let i=this.toolCalls.get(a.index);if(i)a.function?.arguments&&(i.arguments+=a.function.arguments,this.callbacks.onToolCallUpdate?.(a.index,a.function.arguments));else{let l={index:a.index,id:a.id||"",name:a.function?.name||"",arguments:a.function?.arguments||""};this.toolCalls.set(a.index,l),this.callbacks.onToolCall?.(l)}}n.finish_reason&&!this.finishReason&&(this.finishReason=n.finish_reason,this.callbacks.onFinishReason?.(n.finish_reason))}o.usage&&!this.usage&&(this.usage=o.usage,this.callbacks.onUsage?.(o.usage))}catch(o){this.callbacks.onError?.(o instanceof Error?o:new Error(String(o)))}}};function U(t){return new I(t)}var Fe="https://api.openai.com";function X(t){return!t||t.length<8?"****":`${t.substring(0,4)}...${t.substring(t.length-4)}`}function Oe(t,e=!0){let o=new Headers,n=["authorization","content-type","accept","openai-organization","openai-project","user-agent"];for(let s of n){let a=t[s];if(a){let i=Array.isArray(a)?a[0]:a;i&&o.set(s,i)}}return e?o.set("accept","text/event-stream"):o.set("accept","application/json"),o}async function F(t){let o=`${t.upstreamUrl||Fe}${t.path}`,n=typeof t.body=="object"&&t.body!==null&&"stream"in t.body&&typeof t.body.stream=="boolean"?t.body.stream:!0,s=Oe(t.headers,n),a=await fetch(o,{method:t.method,headers:s,body:JSON.stringify(t.body)});return{status:a.status,headers:a.headers,body:a.body,ok:a.ok}}async function*Y(t){let e=t.getReader(),o=new TextDecoder;try{for(;;){let{done:s,value:a}=await e.read();if(s)break;yield o.decode(a,{stream:!0})}let n=o.decode();n&&(yield n)}finally{e.releaseLock()}}import{mkdir as qe,writeFile as Ne,readFile as $e,access as Le}from"fs/promises";import{dirname as De,join as q}from"path";var N=".playingpack/cache";async function ee(t,e=N){let o=v(t),n=q(e,`${o}.json`);try{return await Le(n),!0}catch{return!1}}async function Je(t,e=N){let o=v(t),n=q(e,`${o}.json`);try{let s=await $e(n,"utf-8");return JSON.parse(s)}catch{return null}}var x=class{cachePath;chunks=[];lastChunkTime=0;requestBody;model="unknown";hash="";constructor(e=N){this.cachePath=e}start(e){if(this.requestBody=e,this.chunks=[],this.lastChunkTime=Date.now(),this.hash=v(e),typeof e=="object"&&e!==null){let o=e;typeof o.model=="string"&&(this.model=o.model)}}recordChunk(e){let o=Date.now(),n=this.chunks.length===0?0:o-this.lastChunkTime;this.chunks.push({data:e,delay:n}),this.lastChunkTime=o}async save(e=200){let o={hash:this.hash,timestamp:new Date().toISOString(),request:{model:this.model,messages:this.requestBody?.messages??[]},response:{status:e,chunks:this.chunks}},n=this.getCachePath();return await qe(De(n),{recursive:!0}),await Ne(n,JSON.stringify(o,null,2),"utf-8"),n}getCachePath(){return q(this.cachePath,`${this.hash}.json`)}getHash(){return this.hash}getChunkCount(){return this.chunks.length}},O=class{cached;aborted=!1;constructor(e){this.cached=e}getStatus(){return this.cached.response.status}getHash(){return this.cached.hash}abort(){this.aborted=!0}async*replay(){this.aborted=!1;let e=this.cached.response.chunks??[];for(let o of e){if(this.aborted||(o.delay>0&&await this.delay(o.delay),this.aborted))break;yield o.data}}async*replayFast(){let e=this.cached.response.chunks??[];for(let o of e){if(this.aborted)break;yield o.data}}delay(e){return new Promise(o=>setTimeout(o,e))}};async function te(t,e){let o=await Je(t,e);return o?new O(o):null}function $(){return`chatcmpl-mock-${Date.now()}`}function se(t,e=4){let o=[];for(let n=0;n<t.length;n+=e)o.push(t.slice(n,n+e));return o}function y(t){return`data: ${t}
2
+ import{program as P}from"commander";import Ct from"open";import ct from"fastify";import lt from"@fastify/cors";import ut from"@fastify/websocket";import{fastifyTRPCPlugin as pt}from"@trpc/server/adapters/fastify";import gt from"get-port";import{createHash as xe}from"crypto";function j(n){if(n==null)return null;if(Array.isArray(n))return n.map(j);if(typeof n=="object"){let e=n,t={},o=Object.keys(e).filter(s=>!["stream","request_id","timestamp"].includes(s)).sort();for(let s of o)t[s]=j(e[s]);return t}return n}function S(n){let e=j(n),t=JSON.stringify(e);return xe("sha256").update(t).digest("hex")}var _=class{sessions=new Map;settings;listeners=new Set;point1Resolvers=new Map;point2Resolvers=new Map;constructor(e){this.settings={cache:"read-write",intervene:!0,...e}}createSession(e,t){let o=t,s=typeof o?.model=="string"?o.model:"unknown",a=Array.isArray(o?.messages)?o.messages:[],i=o?.stream===!0,c={id:e,state:this.settings.intervene?"pending":"processing",timestamp:new Date().toISOString(),request:{model:s,messages:a,stream:i,tools:o?.tools,temperature:o?.temperature,maxTokens:o?.max_tokens,raw:t},cacheKey:S(t),cacheAvailable:!1};return this.sessions.set(e,c),this.emit({type:"request_update",session:c}),c}getSession(e){return this.sessions.get(e)}getAllSessions(){return Array.from(this.sessions.values())}updateState(e,t){let o=this.sessions.get(e);o&&(o.state=t,this.emit({type:"request_update",session:o}))}setProcessing(e){let t=this.sessions.get(e);t&&(t.state="processing",t.processingStartedAt||(t.processingStartedAt=new Date().toISOString()),this.emit({type:"request_update",session:t}))}setReviewing(e){this.updateState(e,"reviewing")}setCacheAvailable(e,t){let o=this.sessions.get(e);o&&(o.cacheAvailable=t,this.emit({type:"request_update",session:o}))}setResponseSource(e,t){let o=this.sessions.get(e);o&&(o.responseSource=t,this.emit({type:"request_update",session:o}))}setResponse(e,t,o,s=[]){let a=this.sessions.get(e);a&&(a.response={status:t,content:o,toolCalls:s},this.emit({type:"request_update",session:a}))}addToolCall(e,t){let o=this.sessions.get(e);o&&(o.response||(o.response={status:200,content:"",toolCalls:[]}),o.response.toolCalls.push(t),this.emit({type:"request_update",session:o}))}appendContent(e,t){let o=this.sessions.get(e);o&&(o.response||(o.response={status:200,content:"",toolCalls:[]}),o.response.content+=t)}clearResponse(e){let t=this.sessions.get(e);t&&t.response&&(t.response.content="",t.response.toolCalls=[])}setFinishReason(e,t){let o=this.sessions.get(e);o&&(o.response||(o.response={status:200,content:"",toolCalls:[]}),o.response.finishReason=t,this.emit({type:"request_update",session:o}))}setUsage(e,t){let o=this.sessions.get(e);o&&(o.response||(o.response={status:200,content:"",toolCalls:[]}),o.response.usage={promptTokens:t.prompt_tokens,completionTokens:t.completion_tokens,totalTokens:t.total_tokens},this.emit({type:"request_update",session:o}))}complete(e){let t=this.sessions.get(e);t&&(t.state="complete",t.completedAt=new Date().toISOString(),this.emit({type:"request_update",session:t}))}error(e,t){let o=this.sessions.get(e);o&&(o.state="complete",o.error=t,o.completedAt=new Date().toISOString(),this.emit({type:"request_update",session:o}))}shouldIntervene(){return this.settings.intervene}async waitForPoint1(e){if(!this.sessions.get(e))throw new Error(`Session ${e} not found`);return new Promise(o=>{this.point1Resolvers.set(e,{resolve:o})})}async waitForPoint2(e){if(!this.sessions.get(e))throw new Error(`Session ${e} not found`);return this.updateState(e,"reviewing"),new Promise(o=>{this.point2Resolvers.set(e,{resolve:o})})}resolvePoint1(e,t){let o=this.point1Resolvers.get(e);return o?(this.point1Resolvers.delete(e),this.setProcessing(e),o.resolve(t),!0):!1}resolvePoint2(e,t){let o=this.point2Resolvers.get(e);return o?(this.point2Resolvers.delete(e),o.resolve(t),!0):!1}getSettings(){return{...this.settings}}updateSettings(e){this.settings={...this.settings,...e}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}emit(e){for(let t of this.listeners)try{t(e)}catch{}}cleanup(){let e=Array.from(this.sessions.entries());if(e.length>100){let t=e.filter(([o,s])=>s.state==="complete").slice(0,e.length-100);for(let[o]of t)this.sessions.delete(o)}}removeSession(e){this.sessions.delete(e),this.point1Resolvers.delete(e),this.point2Resolvers.delete(e)}},R=null;function z(n){return R=new _(n),R}function f(){return R||(R=new _),R}function B(n){return{sessionManager:f(),req:n?.req,res:n?.res}}import{initTRPC as Me}from"@trpc/server";import{z as w}from"zod";import{createRequire as je}from"module";import{fileURLToPath as Ue}from"url";import{dirname as Ie,resolve as Q}from"path";import{z as r}from"zod";var K=r.enum(["off","read","read-write"]),U=r.object({cache:K.optional(),intervene:r.boolean().optional(),upstream:r.string().url().optional(),port:r.number().int().min(1).max(65535).optional(),host:r.string().optional(),cachePath:r.string().optional(),logPath:r.string().optional(),headless:r.boolean().optional()}),_e=r.object({cache:K,intervene:r.boolean()}),V=r.object({settings:_e.partial()}),Mt=r.object({model:r.string(),messages:r.array(r.object({role:r.enum(["system","user","assistant","tool"]),content:r.union([r.string(),r.null(),r.array(r.any())]).optional(),name:r.string().optional(),tool_calls:r.array(r.any()).optional(),tool_call_id:r.string().optional()})),temperature:r.number().optional(),top_p:r.number().optional(),n:r.number().optional(),stream:r.boolean().optional(),stop:r.union([r.string(),r.array(r.string())]).optional(),max_tokens:r.number().optional(),presence_penalty:r.number().optional(),frequency_penalty:r.number().optional(),logit_bias:r.record(r.number()).optional(),user:r.string().optional(),tools:r.array(r.object({type:r.literal("function"),function:r.object({name:r.string(),description:r.string().optional(),parameters:r.any().optional()})})).optional(),tool_choice:r.union([r.literal("none"),r.literal("auto"),r.literal("required"),r.object({type:r.literal("function"),function:r.object({name:r.string()})})]).optional(),response_format:r.object({type:r.enum(["text","json_object"])}).optional(),seed:r.number().optional()}).passthrough(),Ee=r.object({data:r.string(),delay:r.number()}),jt=r.object({hash:r.string(),timestamp:r.string(),request:r.object({model:r.string(),messages:r.array(r.unknown())}),response:r.object({status:r.number(),body:r.unknown().optional(),chunks:r.array(Ee).optional()})}),I=r.discriminatedUnion("action",[r.object({action:r.literal("llm")}),r.object({action:r.literal("cache")}),r.object({action:r.literal("mock"),content:r.string()})]),O=r.discriminatedUnion("action",[r.object({action:r.literal("return")}),r.object({action:r.literal("modify"),content:r.string()})]),Ae=r.object({type:r.literal("point1_action"),requestId:r.string(),action:I}),Te=r.object({type:r.literal("point2_action"),requestId:r.string(),action:O}),Z=r.discriminatedUnion("type",[Ae,Te]);var X=je(import.meta.url),Y=Ie(Ue(import.meta.url));function Oe(){try{return X(Q(Y,"../package.json"))}catch{return X(Q(Y,"../../package.json"))}}var Fe=Oe(),b=Me.context().create(),ee=b.router({getSessions:b.procedure.query(({ctx:n})=>({sessions:n.sessionManager.getAllSessions()})),getSession:b.procedure.input(w.object({id:w.string()})).query(({ctx:n,input:e})=>({session:n.sessionManager.getSession(e.id)||null})),getSettings:b.procedure.query(({ctx:n})=>({settings:n.sessionManager.getSettings(),version:Fe.version})),updateSettings:b.procedure.input(V).mutation(({ctx:n,input:e})=>(n.sessionManager.updateSettings(e.settings),{settings:n.sessionManager.getSettings()})),point1Action:b.procedure.input(w.object({requestId:w.string(),action:I})).mutation(({ctx:n,input:e})=>({success:n.sessionManager.resolvePoint1(e.requestId,e.action)})),point2Action:b.procedure.input(w.object({requestId:w.string(),action:O})).mutation(({ctx:n,input:e})=>({success:n.sessionManager.resolvePoint2(e.requestId,e.action)})),health:b.procedure.query(()=>({status:"ok",timestamp:new Date().toISOString()}))});import{createParser as Ne}from"eventsource-parser";var F=class{callbacks;toolCalls=new Map;accumulatedContent="";finishReason=null;usage=null;parser;constructor(e={}){this.callbacks=e,this.parser=Ne(t=>{t.type==="event"&&this.handleEvent(t.data)})}feed(e){this.parser.feed(e)}getContent(){return this.accumulatedContent}getToolCalls(){return Array.from(this.toolCalls.values())}hasToolCalls(){return this.toolCalls.size>0}getFinishReason(){return this.finishReason}getUsage(){return this.usage}getAssembledMessage(){let e=this.getToolCalls();return e.length>0?{role:"assistant",content:null,tool_calls:e.map(t=>({id:t.id,type:"function",function:{name:t.name,arguments:t.arguments}}))}:{role:"assistant",content:this.accumulatedContent||null}}reset(){this.toolCalls.clear(),this.accumulatedContent="",this.finishReason=null,this.usage=null,this.parser.reset()}handleEvent(e){if(e==="[DONE]"){this.callbacks.onDone?.();return}try{let t=JSON.parse(e);for(let o of t.choices){let s=o.delta;if(s.content&&(this.accumulatedContent+=s.content,this.callbacks.onContent?.(s.content)),s.tool_calls)for(let a of s.tool_calls){let i=this.toolCalls.get(a.index);if(i)a.function?.arguments&&(i.arguments+=a.function.arguments,this.callbacks.onToolCallUpdate?.(a.index,a.function.arguments));else{let c={index:a.index,id:a.id||"",name:a.function?.name||"",arguments:a.function?.arguments||""};this.toolCalls.set(a.index,c),this.callbacks.onToolCall?.(c)}}o.finish_reason&&!this.finishReason&&(this.finishReason=o.finish_reason,this.callbacks.onFinishReason?.(o.finish_reason))}t.usage&&!this.usage&&(this.usage=t.usage,this.callbacks.onUsage?.(t.usage))}catch(t){this.callbacks.onError?.(t instanceof Error?t:new Error(String(t)))}}};function N(n){return new F(n)}var $e="https://api.openai.com";function te(n){return!n||n.length<8?"****":`${n.substring(0,4)}...${n.substring(n.length-4)}`}function qe(n,e=!0){let t=new Headers,o=["authorization","content-type","accept","openai-organization","openai-project","user-agent"];for(let s of o){let a=n[s];if(a){let i=Array.isArray(a)?a[0]:a;i&&t.set(s,i)}}return e?t.set("accept","text/event-stream"):t.set("accept","application/json"),t}async function $(n){let t=`${n.upstreamUrl||$e}${n.path}`,o=typeof n.body=="object"&&n.body!==null&&"stream"in n.body&&typeof n.body.stream=="boolean"?n.body.stream:!0,s=qe(n.headers,o),a=await fetch(t,{method:n.method,headers:s,body:JSON.stringify(n.body)});return{status:a.status,headers:a.headers,body:a.body,ok:a.ok}}async function*ne(n){let e=n.getReader(),t=new TextDecoder;try{for(;;){let{done:s,value:a}=await e.read();if(s)break;yield t.decode(a,{stream:!0})}let o=t.decode();o&&(yield o)}finally{e.releaseLock()}}import{mkdir as Le,writeFile as De,readFile as Je,access as We}from"fs/promises";import{dirname as He,join as L}from"path";var D=".playingpack/cache";async function oe(n,e=D){let t=S(n),o=L(e,`${t}.json`);try{return await We(o),!0}catch{return!1}}async function Ge(n,e=D){let t=S(n),o=L(e,`${t}.json`);try{let s=await Je(o,"utf-8");return JSON.parse(s)}catch{return null}}var E=class{cachePath;chunks=[];lastChunkTime=0;requestBody;model="unknown";hash="";constructor(e=D){this.cachePath=e}start(e){if(this.requestBody=e,this.chunks=[],this.lastChunkTime=Date.now(),this.hash=S(e),typeof e=="object"&&e!==null){let t=e;typeof t.model=="string"&&(this.model=t.model)}}recordChunk(e){let t=Date.now(),o=this.chunks.length===0?0:t-this.lastChunkTime;this.chunks.push({data:e,delay:o}),this.lastChunkTime=t}async save(e=200){let t={hash:this.hash,timestamp:new Date().toISOString(),request:{model:this.model,messages:this.requestBody?.messages??[]},response:{status:e,chunks:this.chunks}},o=this.getCachePath();return await Le(He(o),{recursive:!0}),await De(o,JSON.stringify(t,null,2),"utf-8"),o}getCachePath(){return L(this.cachePath,`${this.hash}.json`)}getHash(){return this.hash}getChunkCount(){return this.chunks.length}},q=class{cached;aborted=!1;constructor(e){this.cached=e}getStatus(){return this.cached.response.status}getHash(){return this.cached.hash}abort(){this.aborted=!0}async*replay(){this.aborted=!1;let e=this.cached.response.chunks??[];for(let t of e){if(this.aborted||(t.delay>0&&await this.delay(t.delay),this.aborted))break;yield t.data}}async*replayFast(){let e=this.cached.response.chunks??[];for(let t of e){if(this.aborted)break;yield t.data}}delay(e){return new Promise(t=>setTimeout(t,e))}};async function se(n,e){let t=await Ge(n,e);return t?new q(t):null}function J(){return`chatcmpl-mock-${Date.now()}`}function ie(n,e=4){let t=[];for(let o=0;o<n.length;o+=e)t.push(n.slice(o,o+e));return t}function y(n){return`data: ${n}
3
3
 
4
- `}function ne(t,e,o,n=null){let s={id:t,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:e,choices:[{index:0,delta:o!==null?{content:o}:{},finish_reason:n}]};return JSON.stringify(s)}function oe(t,e,o,n,s,a=!1,i=null){let l={index:0};a?(l.id=o,l.type="function",l.function={name:n,arguments:s}):l.function={arguments:s};let d={id:t,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:e,choices:[{index:0,delta:{tool_calls:[l]},finish_reason:i}]};return JSON.stringify(d)}async function*re(t,e={}){let{model:o="gpt-4",delayMs:n=20}=e,s=$(),a=se(t),i={id:s,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:o,choices:[{index:0,delta:{role:"assistant",content:""},finish_reason:null}]};yield y(JSON.stringify(i));for(let l of a)n>0&&await ue(n),yield y(ne(s,o,l));yield y(ne(s,o,null,"stop")),yield y("[DONE]")}async function*ae(t,e,o={}){let{model:n="gpt-4",delayMs:s=10}=o,a=$(),i=`call_mock_${Date.now()}`,l=se(e,10),d={id:a,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:n,choices:[{index:0,delta:{role:"assistant",content:null},finish_reason:null}]};yield y(JSON.stringify(d)),yield y(oe(a,n,i,t,l[0]||"",!0));for(let g=1;g<l.length;g++)s>0&&await ue(s),yield y(oe(a,n,i,t,l[g]||""));let u={id:a,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:n,choices:[{index:0,delta:{},finish_reason:"tool_calls"}]};yield y(JSON.stringify(u)),yield y("[DONE]")}function ie(t,e="invalid_request_error",o=null){return JSON.stringify({error:{message:t,type:e,param:null,code:o}})}function ce(t,e,o="gpt-4"){let n=$(),s=Math.floor(Date.now()/1e3),a={role:"assistant",content:e?null:t};e&&e.length>0&&(a.tool_calls=e);let i=e&&e.length>0?"tool_calls":"stop";return{id:n,object:"chat.completion",created:s,model:o,choices:[{index:0,message:a,finish_reason:i}],usage:{prompt_tokens:0,completion_tokens:0,total_tokens:0}}}function le(t){let e=t.trim();if(e.startsWith("ERROR:"))return{type:"error",content:e.slice(6).trim()};try{let o=JSON.parse(e);if(o&&typeof o=="object"&&"function"in o)return{type:"tool_call",functionName:o.function,content:JSON.stringify(o.arguments||{})}}catch{}return{type:"text",content:e}}function ue(t){return new Promise(e=>setTimeout(e,t))}import{appendFile as We,mkdir as He}from"fs/promises";import{join as Ge}from"path";var L=null,D=null;async function pe(t){L=t,await He(L,{recursive:!0});let e=new Date().toISOString().split("T")[0];D=Ge(L,`server-${e}.log`)}function ze(t){let e=`[${t.timestamp}] [${t.level.toUpperCase()}] ${t.message}`;return t.data!==void 0?`${e} ${JSON.stringify(t.data)}
4
+ `}function re(n,e,t,o=null){let s={id:n,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:e,choices:[{index:0,delta:t!==null?{content:t}:{},finish_reason:o}]};return JSON.stringify(s)}function ae(n,e,t,o,s,a=!1,i=null){let c={index:0};a?(c.id=t,c.type="function",c.function={name:o,arguments:s}):c.function={arguments:s};let g={id:n,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:e,choices:[{index:0,delta:{tool_calls:[c]},finish_reason:i}]};return JSON.stringify(g)}async function*ce(n,e={}){let{model:t="gpt-4",delayMs:o=20}=e,s=J(),a=ie(n),i={id:s,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:t,choices:[{index:0,delta:{role:"assistant",content:""},finish_reason:null}]};yield y(JSON.stringify(i));for(let c of a)o>0&&await de(o),yield y(re(s,t,c));yield y(re(s,t,null,"stop")),yield y("[DONE]")}async function*le(n,e,t={}){let{model:o="gpt-4",delayMs:s=10}=t,a=J(),i=`call_mock_${Date.now()}`,c=ie(e,10),g={id:a,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:o,choices:[{index:0,delta:{role:"assistant",content:null},finish_reason:null}]};yield y(JSON.stringify(g)),yield y(ae(a,o,i,n,c[0]||"",!0));for(let d=1;d<c.length;d++)s>0&&await de(s),yield y(ae(a,o,i,n,c[d]||""));let p={id:a,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:o,choices:[{index:0,delta:{},finish_reason:"tool_calls"}]};yield y(JSON.stringify(p)),yield y("[DONE]")}function ue(n,e="invalid_request_error",t=null){return JSON.stringify({error:{message:n,type:e,param:null,code:t}})}function pe(n,e,t="gpt-4"){let o=J(),s=Math.floor(Date.now()/1e3),a={role:"assistant",content:e?null:n};e&&e.length>0&&(a.tool_calls=e);let i=e&&e.length>0?"tool_calls":"stop";return{id:o,object:"chat.completion",created:s,model:t,choices:[{index:0,message:a,finish_reason:i}],usage:{prompt_tokens:0,completion_tokens:0,total_tokens:0}}}function ge(n){let e=n.trim();if(e.startsWith("ERROR:"))return{type:"error",content:e.slice(6).trim()};try{let t=JSON.parse(e);if(t&&typeof t=="object"&&"function"in t)return{type:"tool_call",functionName:t.function,content:JSON.stringify(t.arguments||{})}}catch{}return{type:"text",content:e}}function de(n){return new Promise(e=>setTimeout(e,n))}import{appendFile as ze,mkdir as Be}from"fs/promises";import{join as Ke}from"path";var W=null,H=null;async function he(n){W=n,await Be(W,{recursive:!0});let e=new Date().toISOString().split("T")[0];H=Ke(W,`server-${e}.log`)}function Ve(n){let e=`[${n.timestamp}] [${n.level.toUpperCase()}] ${n.message}`;return n.data!==void 0?`${e} ${JSON.stringify(n.data)}
5
5
  `:`${e}
6
- `}async function _(t,e,o){if(!D)return;let n={timestamp:new Date().toISOString(),level:t,message:e,data:o};try{await We(D,ze(n))}catch{}}var k={info:(t,e)=>_("info",t,e),warn:(t,e)=>_("warn",t,e),error:(t,e)=>_("error",t,e),debug:(t,e)=>_("debug",t,e)};var m={upstream:"https://api.openai.com",cachePath:".playingpack/cache",cache:"read-write"};function de(t,e){e&&(m={...m,...e}),t.post("/v1/chat/completions",async(o,n)=>{await Be(o,n)}),t.get("/health",async(o,n)=>n.send({status:"ok"})),t.all("/v1/*",async(o,n)=>{await Qe(o,n)})}async function Be(t,e){let o=f(),n=crypto.randomUUID(),s=t.body,a=s.stream!==!1,i=t.headers.authorization||"";console.log(`[${n.slice(0,8)}] POST /v1/chat/completions`),console.log(` Model: ${s.model||"unknown"}`),console.log(` Stream: ${a}`),console.log(` Auth: ${X(i.replace("Bearer ",""))}`),k.info("Request received",{requestId:n,path:"/v1/chat/completions",model:s.model,stream:a}),o.createSession(n,s);let l=m.cache!=="off",d=m.cache==="read-write",u=m.cache==="read",g=l&&await ee(s,m.cachePath);o.setCacheAvailable(n,g);let h=g?"cache":"llm",w;if(o.shouldIntervene()){console.log(` [POINT 1] Waiting for human action (cache: ${g?"yes":"no"})`);let p=await o.waitForPoint1(n);switch(p.action){case"cache":h="cache",console.log(" [ACTION] Use cache");break;case"llm":h="llm",console.log(" [ACTION] Call LLM");break;case"mock":h="mock",w=p.content,console.log(" [ACTION] Mock response");break}}else g?(console.log(" [AUTO] Using cached response"),h="cache"):(console.log(" [AUTO] Calling LLM"),h="llm");try{let p;switch(h){case"cache":{if(!g)throw new Error("Cache requested but no cached response available");p={...await Ke(t,e,n,s,a),cached:!0};break}case"llm":if(u){console.log(" [ERROR] Cache-only mode but no cache available"),o.error(n,"No cached response (cache: read mode)"),e.code(404).send({error:{message:"No cached response found (cache mode: read)",type:"cache_not_found"}});return}p=await Ve(t,e,n,s,a,d);break;case"mock":p=await ge(e,n,w||"",a);break}if(o.shouldIntervene()&&p){console.log(" [POINT 2] Waiting for human action");let c=await o.waitForPoint2(n);switch(c.action){case"return":console.log(" [ACTION] Return as-is");break;case"modify":console.log(" [ACTION] Modify response"),c.content&&(p=await ge(e,n,c.content,a));break}}p&&Ze(e,p,a),o.setResponseSource(n,h),o.complete(n),k.info("Request completed",{requestId:n,source:h})}catch(p){let c=p instanceof Error?p.message:"Unknown error";console.error(` [ERROR] ${c}`),k.error("Request failed",{requestId:n,error:c}),o.error(n,c),e.sent||e.code(500).send({error:{message:c,type:"proxy_error"}})}}async function Ke(t,e,o,n,s){let a=f(),i=await te(n,m.cachePath);if(!i)throw new Error("Failed to load cached response");let l=U({onToolCall:u=>{a.addToolCall(o,u),console.log(` [TOOL CALL] ${u.name}`)},onContent:u=>{a.appendContent(o,u)}}),d="";for await(let u of i.replay())l.feed(u),d+=u;return{content:d,status:i.getStatus(),cached:!0}}async function Ve(t,e,o,n,s,a){let i=f(),d={...n,stream_options:{include_usage:!0}},u=await F({method:"POST",path:"/v1/chat/completions",headers:t.headers,body:d,upstreamUrl:m.upstream});if(!u.ok||!u.body)return{content:u.body?await he(u.body):"",status:u.status};let g=a?new x(m.cachePath):null;g?.start(n);let h=U({onToolCall:c=>{i.addToolCall(o,c),console.log(` [TOOL CALL] ${c.name}`)},onContent:c=>{i.appendContent(o,c)},onFinishReason:c=>{i.setFinishReason(o,c)},onUsage:c=>{i.setUsage(o,c),console.log(` [USAGE] ${c.prompt_tokens} prompt, ${c.completion_tokens} completion`)}}),w="",p=Y(u.body);for await(let c of p)h.feed(c),g?.recordChunk(c),w+=c;if(g)try{let c=await g.save(u.status);console.log(` [CACHE] Saved to ${c}`)}catch(c){console.error(" [CACHE ERROR] Failed to save:",c)}return{content:w,status:u.status}}async function ge(t,e,o,n=!0){let s=le(o);if(s.type==="error")return{content:ie(s.content),status:400,mocked:!0};if(n){let a=s.type==="tool_call"?ae(s.functionName||"mock_function",s.content):re(s.content),i="";for await(let l of a)i+=l;return{content:i,status:200,mocked:!0}}else{let a=s.type==="tool_call"?[{id:`call_mock_${Date.now()}`,type:"function",function:{name:s.functionName||"mock_function",arguments:s.content}}]:void 0,i=s.type==="text"?s.content:null,l=ce(i,a);return{content:JSON.stringify(l),status:200,mocked:!0}}}function Ze(t,e,o){let n={};if(e.cached&&(n["x-playingpack-cached"]="true"),e.mocked&&(n["x-playingpack-mocked"]="true"),o&&e.content.includes("data: ")){t.code(e.status).header("content-type","text/event-stream").header("cache-control","no-cache").header("connection","keep-alive");for(let[s,a]of Object.entries(n))t.header(s,a);t.raw.write(e.content),t.raw.end()}else{t.code(e.status).header("content-type","application/json");for(let[s,a]of Object.entries(n))t.header(s,a);t.send(e.content)}}async function Qe(t,e){try{let o=await F({method:t.method,path:t.url,headers:t.headers,body:t.body,upstreamUrl:m.upstream});if(e.code(o.status),o.headers.forEach((n,s)=>{["content-encoding","transfer-encoding"].includes(s.toLowerCase())||e.header(s,n)}),o.body){let n=await he(o.body);e.send(n)}else e.send()}catch(o){let n=o instanceof Error?o.message:"Unknown error";e.code(500).send({error:{message:n,type:"proxy_error"}})}}async function he(t){if(!t)return"";let e=t.getReader(),o=new TextDecoder,n="";for(;;){let{done:s,value:a}=await e.read();if(s)break;n+=o.decode(a,{stream:!0})}return n}import{join as me,dirname as Xe}from"path";import{fileURLToPath as Ye}from"url";import{access as et}from"fs/promises";import tt from"@fastify/static";var nt=Xe(Ye(import.meta.url));function fe(){return me(nt,"../public")}async function ot(){try{return await et(me(fe(),"index.html")),!0}catch{return!1}}async function ye(t){let e=fe();return await ot()?(await t.register(tt,{root:e,prefix:"/",wildcard:!1}),t.setNotFoundHandler((n,s)=>n.url.startsWith("/v1")||n.url.startsWith("/api")||n.url.startsWith("/ws")?s.code(404).send({error:"Not Found"}):s.sendFile("index.html")),console.log(" UI available at root path"),!0):(console.log(" UI not available (development mode)"),console.log(" Run the UI separately: cd packages/web && pnpm dev"),!1)}var E=new Set;function be(t){E.add(t);let e=f(),o=e.getAllSessions();for(let s of o)J(t,{type:"request_update",session:s});let n=e.subscribe(s=>{J(t,s)});t.on("message",s=>{try{let a=JSON.parse(s.toString());st(t,a)}catch{}}),t.on("close",()=>{E.delete(t),n()}),t.on("error",()=>{E.delete(t),n()})}function st(t,e){let o=B.safeParse(e);if(!o.success){if(typeof e=="object"&&e!==null&&e.type==="ping"){J(t,{type:"request_update",session:null});return}return}let n=o.data,s=f();switch(n.type){case"point1_action":s.resolvePoint1(n.requestId,n.action);break;case"point2_action":s.resolvePoint2(n.requestId,n.action);break}}function J(t,e){if(t.readyState===t.OPEN)try{t.send(JSON.stringify(e))}catch{E.delete(t)}}async function ke(t){let e=t.port,o=t.host;W({cache:t.cache,intervene:t.intervene}),await pe(t.logPath),await k.info("Server starting",{upstream:t.upstream,cachePath:t.cachePath,cache:t.cache,intervene:t.intervene,headless:t.headless});let n=await lt({port:e});n!==e&&console.log(` Port ${e} in use, using ${n}`);let s=rt({logger:!1,bodyLimit:50*1024*1024});return await s.register(at,{origin:!0,credentials:!0}),await s.register(it),s.get("/ws",{websocket:!0},a=>{be(a)}),await s.register(ct,{prefix:"/api/trpc",trpcOptions:{router:Q,createContext:H}}),de(s,{upstream:t.upstream,cachePath:t.cachePath,cache:t.cache}),t.headless||await ye(s),await s.listen({port:n,host:o}),await k.info("Server listening",{port:n,host:o}),{server:s,port:n,host:o}}import{readFile as ut}from"fs/promises";import{existsSync as pt}from"fs";import{join as ve}from"path";import{createJiti as gt}from"jiti";var dt=["playingpack.config.ts","playingpack.config.mts","playingpack.config.js","playingpack.config.mjs"],ht=["playingpack.config.jsonc","playingpack.config.json",".playingpackrc.json",".playingpackrc"],mt={cache:"read-write",intervene:!0,upstream:"https://api.openai.com",port:4747,host:"0.0.0.0",cachePath:".playingpack/cache",logPath:".playingpack/logs",headless:!1};function ft(t){let e=t.replace(/\/\*[\s\S]*?\*\//g,"");return e=e.replace(/(?<!["'])\/\/.*$/gm,""),e}async function yt(t){for(let e of dt){let o=ve(t,e);if(pt(o))try{let s=await gt(import.meta.url,{interopDefault:!0}).import(o),a=s&&typeof s=="object"&&"default"in s?s.default:s;if(!a||typeof a!="object"){console.warn(` Warning: ${e} must export a config object`);continue}return{config:T.parse(a),filename:e}}catch(n){console.warn(` Warning: Error loading ${e}:`,n.message)}}return null}async function bt(t){for(let e of ht){let o=ve(t,e);try{let n=await ut(o,"utf-8"),s=JSON.parse(ft(n));return{config:T.parse(s),filename:e}}catch(n){n.code!=="ENOENT"&&(n instanceof SyntaxError||n.name==="ZodError")&&console.warn(` Warning: Invalid config in ${e}:`,n.message)}}return null}async function kt(t){let e=await yt(t);if(e)return console.log(` Config loaded from ${e.filename}`),e.config;let o=await bt(t);return o?(console.log(` Config loaded from ${o.filename}`),o.config):{}}async function Se(t={}){let e=process.cwd(),o=await kt(e),n={...mt,...o};return t.port!==void 0&&(n.port=t.port),t.host!==void 0&&(n.host=t.host),t.ui!==void 0&&(n.headless=!t.ui),t.upstream!==void 0&&(n.upstream=t.upstream),t.cachePath!==void 0&&(n.cachePath=t.cachePath),t.cache!==void 0&&(n.cache=t.cache),t.intervene!==void 0&&(n.intervene=t.intervene),n}var we="1.0.0";P.name("playingpack").description("Chrome DevTools for AI Agents - Debug and test your AI agent LLM calls").version(we);P.command("start").description("Start the PlayingPack proxy server").option("-p, --port <port>","Port to listen on").option("-h, --host <host>","Host to bind to").option("--no-ui","Run without UI (headless mode for CI/CD)").option("--upstream <url>","Upstream API URL (default: https://api.openai.com)").option("--cache-path <path>","Directory for cached responses (default: .playingpack/cache)").option("--cache <mode>","Cache mode: off, read, read-write (default: read-write)").option("--no-intervene","Disable intervention mode (no pausing for human actions)").action(async t=>{console.log(),console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),console.log(" \u2551 \u2551"),console.log(" \u2551 PlayingPack - Debug your AI Agents \u2551"),console.log(" \u2551 \u2551"),console.log(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"),console.log();try{let e=await Se({port:t.port?parseInt(t.port,10):void 0,host:t.host,ui:t.ui,upstream:t.upstream,cachePath:t.cachePath,cache:t.cache,intervene:t.intervene}),{port:o,host:n}=await ke(e),s=`http://localhost:${o}`,a=n==="0.0.0.0"?`http://<your-ip>:${o}`:`http://${n}:${o}`;console.log(),console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),console.log(" \u2502 Server running! \u2502"),console.log(" \u2502 \u2502"),console.log(` \u2502 Local: ${s.padEnd(44)}\u2502`),console.log(` \u2502 Network: ${a.padEnd(44)}\u2502`),console.log(" \u2502 \u2502"),console.log(" \u2502 Settings: \u2502"),console.log(` \u2502 Cache: ${e.cache.padEnd(41)}\u2502`),console.log(` \u2502 Intervene: ${String(e.intervene).padEnd(41)}\u2502`),console.log(" \u2502 \u2502"),console.log(" \u2502 To use with your AI agent, set: \u2502"),console.log(` \u2502 baseURL = "${s}/v1"`.padEnd(60)+"\u2502"),console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"),console.log(),e.headless?console.log(" Running in headless mode (no UI)"):(console.log(" Opening dashboard in browser..."),await vt(s)),console.log(),console.log(" Waiting for requests..."),console.log();let i=async()=>{console.log(`
7
- Shutting down...`),process.exit(0)};process.on("SIGINT",i),process.on("SIGTERM",i)}catch(e){console.error("Failed to start server:",e),process.exit(1)}});P.command("version").description("Show version").action(()=>{console.log(`playingpack v${we}`)});process.argv.length===2?P.parse(["node","playingpack","start"]):P.parse();
6
+ `}async function A(n,e,t){if(!H)return;let o={timestamp:new Date().toISOString(),level:n,message:e,data:t};try{await ze(H,Ve(o))}catch{}}var k={info:(n,e)=>A("info",n,e),warn:(n,e)=>A("warn",n,e),error:(n,e)=>A("error",n,e),debug:(n,e)=>A("debug",n,e)};var v={upstream:"https://api.openai.com",cachePath:".playingpack/cache",cache:"read-write"};function fe(n,e){e&&(v={...v,...e}),n.post("/v1/chat/completions",async(t,o)=>{await Ze(t,o)}),n.get("/health",async(t,o)=>o.send({status:"ok"})),n.all("/v1/*",async(t,o)=>{await et(t,o)})}async function Ze(n,e){let t=f(),o=crypto.randomUUID(),s=n.body,a=s.stream!==!1,i=n.headers.authorization||"";console.log(`[${o.slice(0,8)}] POST /v1/chat/completions`),console.log(` Model: ${s.model||"unknown"}`),console.log(` Stream: ${a}`),console.log(` Auth: ${te(i.replace("Bearer ",""))}`),k.info("Request received",{requestId:o,path:"/v1/chat/completions",model:s.model,stream:a}),t.createSession(o,s);let c=t.getSettings().cache,g=c!=="off",p=c==="read-write",d=c==="read",h=g&&await oe(s,v.cachePath);t.setCacheAvailable(o,h);let m=h?"cache":"llm",M;if(t.shouldIntervene()){console.log(` [POINT 1] Waiting for human action (cache: ${h?"yes":"no"})`);let l=await t.waitForPoint1(o);switch(l.action){case"cache":m="cache",console.log(" [ACTION] Use cache");break;case"llm":m="llm",console.log(" [ACTION] Call LLM");break;case"mock":m="mock",M=l.content,console.log(" [ACTION] Mock response");break}}else h?(console.log(" [AUTO] Using cached response"),m="cache"):(console.log(" [AUTO] Calling LLM"),m="llm");try{let l;switch(m){case"cache":{if(!h)throw new Error("Cache requested but no cached response available");l={...await Qe(n,e,o,s,a),cached:!0};break}case"llm":if(d){console.log(" [ERROR] Cache-only mode but no cache available"),t.error(o,"No cached response (cache: read mode)"),e.code(404).send({error:{message:"No cached response found (cache mode: read)",type:"cache_not_found"}});return}l=await Xe(n,e,o,s,a,p);break;case"mock":l=await me(e,o,M||"",a);break}if(t.shouldIntervene()&&l){console.log(" [POINT 2] Waiting for human action");let u=await t.waitForPoint2(o);switch(u.action){case"return":console.log(" [ACTION] Return as-is");break;case"modify":console.log(" [ACTION] Modify response"),u.content&&(t.clearResponse(o),l=await me(e,o,u.content,a));break}}l&&Ye(e,l,a),t.setResponseSource(o,m),t.complete(o),k.info("Request completed",{requestId:o,source:m})}catch(l){let u=l instanceof Error?l.message:"Unknown error";console.error(` [ERROR] ${u}`),k.error("Request failed",{requestId:o,error:u}),t.error(o,u),e.sent||e.code(500).send({error:{message:u,type:"proxy_error"}})}}async function Qe(n,e,t,o,s){let a=f(),i=await se(o,v.cachePath);if(!i)throw new Error("Failed to load cached response");let c=N({onToolCall:p=>{a.addToolCall(t,p),console.log(` [TOOL CALL] ${p.name}`)},onContent:p=>{a.appendContent(t,p)}}),g="";for await(let p of i.replay())c.feed(p),g+=p;return{content:g,status:i.getStatus(),cached:!0}}async function Xe(n,e,t,o,s,a){let i=f(),c=o,g=c.stream===!0?{...c,stream_options:{...typeof c.stream_options=="object"&&c.stream_options!==null?c.stream_options:{},include_usage:!0}}:c,p=await $({method:"POST",path:"/v1/chat/completions",headers:n.headers,body:g,upstreamUrl:v.upstream});if(!p.ok||!p.body)return{content:p.body?await ye(p.body):"",status:p.status};let d=a?new E(v.cachePath):null;d?.start(o);let h="",m=ne(p.body);for await(let l of m)d?.recordChunk(l),h+=l;if(c.stream===!0)N({onToolCall:u=>{i.addToolCall(t,u),console.log(` [TOOL CALL] ${u.name}`)},onContent:u=>{i.appendContent(t,u)},onFinishReason:u=>{i.setFinishReason(t,u)},onUsage:u=>{i.setUsage(t,u),console.log(` [USAGE] ${u.prompt_tokens} prompt, ${u.completion_tokens} completion`)}}).feed(h);else try{let l=JSON.parse(h),u=l.choices?.[0],C=u?.message;if(C?.content&&i.appendContent(t,C.content),C?.tool_calls)for(let x of C.tool_calls)i.addToolCall(t,{id:x.id,name:x.function?.name||"",arguments:x.function?.arguments||""}),console.log(` [TOOL CALL] ${x.function?.name}`);u?.finish_reason&&i.setFinishReason(t,u.finish_reason),l.usage&&(i.setUsage(t,l.usage),console.log(` [USAGE] ${l.usage.prompt_tokens} prompt, ${l.usage.completion_tokens} completion`))}catch(l){console.error(" [PARSE ERROR] Failed to parse non-streaming response:",l)}if(d)try{let l=await d.save(p.status);console.log(` [CACHE] Saved to ${l}`)}catch(l){console.error(" [CACHE ERROR] Failed to save:",l)}return{content:h,status:p.status}}async function me(n,e,t,o=!0){let s=f(),a=ge(t);if(a.type==="error"){let i=ue(a.content);return s.appendContent(e,a.content),{content:i,status:400,mocked:!0}}if(o){let i=a.type==="tool_call"?le(a.functionName||"mock_function",a.content):ce(a.content),c="";for await(let g of i)c+=g;return s.appendContent(e,a.content),a.type==="tool_call"&&s.addToolCall(e,{id:`call_mock_${Date.now()}`,name:a.functionName||"mock_function",arguments:a.content}),{content:c,status:200,mocked:!0}}else{let i=a.type==="tool_call"?[{id:`call_mock_${Date.now()}`,type:"function",function:{name:a.functionName||"mock_function",arguments:a.content}}]:void 0,c=a.type==="text"?a.content:null,g=pe(c,i);return{content:JSON.stringify(g),status:200,mocked:!0}}}function Ye(n,e,t){let o={};if(e.cached&&(o["x-playingpack-cached"]="true"),e.mocked&&(o["x-playingpack-mocked"]="true"),t&&e.content.includes("data: ")){n.code(e.status).header("content-type","text/event-stream").header("cache-control","no-cache").header("connection","keep-alive");for(let[s,a]of Object.entries(o))n.header(s,a);n.raw.write(e.content),n.raw.end()}else{n.code(e.status).header("content-type","application/json");for(let[s,a]of Object.entries(o))n.header(s,a);n.send(e.content)}}async function et(n,e){try{let t=await $({method:n.method,path:n.url,headers:n.headers,body:n.body,upstreamUrl:v.upstream});if(e.code(t.status),t.headers.forEach((o,s)=>{["content-encoding","transfer-encoding"].includes(s.toLowerCase())||e.header(s,o)}),t.body){let o=await ye(t.body);e.send(o)}else e.send()}catch(t){let o=t instanceof Error?t.message:"Unknown error";e.code(500).send({error:{message:o,type:"proxy_error"}})}}async function ye(n){if(!n)return"";let e=n.getReader(),t=new TextDecoder,o="";for(;;){let{done:s,value:a}=await e.read();if(s)break;o+=t.decode(a,{stream:!0})}return o}import{join as be,dirname as tt}from"path";import{fileURLToPath as nt}from"url";import{access as ot}from"fs/promises";import st from"@fastify/static";var rt=tt(nt(import.meta.url));function ke(){return be(rt,"../public")}async function at(){try{return await ot(be(ke(),"index.html")),!0}catch{return!1}}async function ve(n){let e=ke();return await at()?(await n.register(st,{root:e,prefix:"/",wildcard:!1}),n.setNotFoundHandler((o,s)=>o.url.startsWith("/v1")||o.url.startsWith("/api")||o.url.startsWith("/ws")?s.code(404).send({error:"Not Found"}):s.sendFile("index.html")),console.log(" UI available at root path"),!0):(console.log(" UI not available (development mode)"),console.log(" Run the UI separately: cd packages/web && pnpm dev"),!1)}var T=new Set;function Se(n){T.add(n);let e=f(),t=e.getAllSessions();for(let s of t)G(n,{type:"request_update",session:s});let o=e.subscribe(s=>{G(n,s)});n.on("message",s=>{try{let a=JSON.parse(s.toString());it(n,a)}catch{}}),n.on("close",()=>{T.delete(n),o()}),n.on("error",()=>{T.delete(n),o()})}function it(n,e){let t=Z.safeParse(e);if(!t.success){if(typeof e=="object"&&e!==null&&e.type==="ping"){G(n,{type:"request_update",session:null});return}return}let o=t.data,s=f();switch(o.type){case"point1_action":s.resolvePoint1(o.requestId,o.action);break;case"point2_action":s.resolvePoint2(o.requestId,o.action);break}}function G(n,e){if(n.readyState===n.OPEN)try{n.send(JSON.stringify(e))}catch{T.delete(n)}}async function we(n){let e=n.port,t=n.host;z({cache:n.cache,intervene:n.intervene}),await he(n.logPath),await k.info("Server starting",{upstream:n.upstream,cachePath:n.cachePath,cache:n.cache,intervene:n.intervene,headless:n.headless});let o=await gt({port:e});o!==e&&console.log(` Port ${e} in use, using ${o}`);let s=ct({logger:!1,bodyLimit:50*1024*1024});return await s.register(lt,{origin:!0,credentials:!0}),await s.register(ut),s.get("/ws",{websocket:!0},a=>{Se(a)}),await s.register(pt,{prefix:"/api/trpc",trpcOptions:{router:ee,createContext:B}}),fe(s,{upstream:n.upstream,cachePath:n.cachePath,cache:n.cache}),n.headless||await ve(s),await s.listen({port:o,host:t}),await k.info("Server listening",{port:o,host:t}),{server:s,port:o,host:t}}import{readFile as dt}from"fs/promises";import{existsSync as ht}from"fs";import{join as Ce}from"path";import{createJiti as mt}from"jiti";var ft=["playingpack.config.ts","playingpack.config.mts","playingpack.config.js","playingpack.config.mjs"],yt=["playingpack.config.jsonc","playingpack.config.json",".playingpackrc.json",".playingpackrc"],bt={cache:"read-write",intervene:!0,upstream:"https://api.openai.com",port:4747,host:"0.0.0.0",cachePath:".playingpack/cache",logPath:".playingpack/logs",headless:!1};function kt(n){let e=n.replace(/\/\*[\s\S]*?\*\//g,"");return e=e.replace(/(?<!["'])\/\/.*$/gm,""),e}async function vt(n){for(let e of ft){let t=Ce(n,e);if(ht(t))try{let s=await mt(import.meta.url,{interopDefault:!0}).import(t),a=s&&typeof s=="object"&&"default"in s?s.default:s;if(!a||typeof a!="object"){console.warn(` Warning: ${e} must export a config object`);continue}return{config:U.parse(a),filename:e}}catch(o){console.warn(` Warning: Error loading ${e}:`,o.message)}}return null}async function St(n){for(let e of yt){let t=Ce(n,e);try{let o=await dt(t,"utf-8"),s=JSON.parse(kt(o));return{config:U.parse(s),filename:e}}catch(o){o.code!=="ENOENT"&&(o instanceof SyntaxError||o.name==="ZodError")&&console.warn(` Warning: Invalid config in ${e}:`,o.message)}}return null}async function wt(n){let e=await vt(n);if(e)return console.log(` Config loaded from ${e.filename}`),e.config;let t=await St(n);return t?(console.log(` Config loaded from ${t.filename}`),t.config):{}}async function Re(n={}){let e=process.cwd(),t=await wt(e),o={...bt,...t};return n.port!==void 0&&(o.port=n.port),n.host!==void 0&&(o.host=n.host),n.ui!==void 0&&(o.headless=!n.ui),n.upstream!==void 0&&(o.upstream=n.upstream),n.cachePath!==void 0&&(o.cachePath=n.cachePath),n.cache!==void 0&&(o.cache=n.cache),n.intervene!==void 0&&(o.intervene=n.intervene),o}var Pe="1.0.0";P.name("playingpack").description("Chrome DevTools for AI Agents - Debug and test your AI agent LLM calls").version(Pe);P.command("start").description("Start the PlayingPack proxy server").option("-p, --port <port>","Port to listen on").option("-h, --host <host>","Host to bind to").option("--no-ui","Run without UI (headless mode for CI/CD)").option("--upstream <url>","Upstream API URL (default: https://api.openai.com)").option("--cache-path <path>","Directory for cached responses (default: .playingpack/cache)").option("--cache <mode>","Cache mode: off, read, read-write (default: read-write)").option("--no-intervene","Disable intervention mode (no pausing for human actions)").action(async n=>{console.log(),console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),console.log(" \u2551 \u2551"),console.log(" \u2551 PlayingPack - Debug your AI Agents \u2551"),console.log(" \u2551 \u2551"),console.log(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"),console.log();try{let e=await Re({port:n.port?parseInt(n.port,10):void 0,host:n.host,ui:n.ui,upstream:n.upstream,cachePath:n.cachePath,cache:n.cache,intervene:n.intervene}),{port:t,host:o}=await we(e),s=`http://localhost:${t}`,a=o==="0.0.0.0"?`http://<your-ip>:${t}`:`http://${o}:${t}`;console.log(),console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),console.log(" \u2502 Server running! \u2502"),console.log(" \u2502 \u2502"),console.log(` \u2502 Local: ${s.padEnd(44)}\u2502`),console.log(` \u2502 Network: ${a.padEnd(44)}\u2502`),console.log(" \u2502 \u2502"),console.log(" \u2502 Settings: \u2502"),console.log(` \u2502 Cache: ${e.cache.padEnd(41)}\u2502`),console.log(` \u2502 Intervene: ${String(e.intervene).padEnd(41)}\u2502`),console.log(" \u2502 \u2502"),console.log(" \u2502 To use with your AI agent, set: \u2502"),console.log(` \u2502 baseURL = "${s}/v1"`.padEnd(60)+"\u2502"),console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"),console.log(),e.headless?console.log(" Running in headless mode (no UI)"):(console.log(" Opening dashboard in browser..."),await Ct(s)),console.log(),console.log(" Waiting for requests..."),console.log();let i=async()=>{console.log(`
7
+ Shutting down...`),process.exit(0)};process.on("SIGINT",i),process.on("SIGTERM",i)}catch(e){console.error("Failed to start server:",e),process.exit(1)}});P.command("version").description("Show version").action(()=>{console.log(`playingpack v${Pe}`)});process.argv.length===2?P.parse(["node","playingpack","start"]):P.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playingpack",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Chrome DevTools for AI Agents - Local reverse proxy and debugger for LLM API calls",
5
5
  "type": "module",
6
6
  "bin": {