playingpack 0.5.2 → 0.6.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/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{program as C}from"commander";import mt from"open";import et from"fastify";import tt from"@fastify/cors";import nt from"@fastify/websocket";import{fastifyTRPCPlugin as ot}from"@trpc/server/adapters/fastify";import st from"get-port";import{createHash as ve}from"crypto";function M(t){if(t==null)return null;if(Array.isArray(t))return t.map(M);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]=M(e[s]);return o}return t}function v(t){let e=M(t),o=JSON.stringify(e);return ve("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,c={id:e,state:this.settings.intervene?"pending":"processing",timestamp:new Date().toISOString(),request:{model:s,messages:a,stream:i},cacheKey:v(o),cacheHit:!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,o){let n=this.sessions.get(e);n&&(n.state=o,this.emit({type:"request_update",session:n}))}setProcessing(e){this.updateState(e,"processing")}setReviewing(e){this.updateState(e,"reviewing")}setCacheHit(e,o){let n=this.sessions.get(e);n&&(n.cacheHit=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)}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.updateState(e,"processing"),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)}},S=null;function J(t){return S=new R(t),S}function f(){return S||(S=new R),S}function W(t){return{sessionManager:f(),req:t?.req,res:t?.res}}import{initTRPC as Re}from"@trpc/server";import{z as w}from"zod";import{createRequire as xe}from"module";import{z as r}from"zod";var G=r.enum(["off","read","read-write"]),A=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()}),we=r.object({cache:G,intervene:r.boolean()}),z=r.object({settings:we.partial()}),Pt=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(),Se=r.object({data:r.string(),delay:r.number()}),Rt=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(Se).optional()})}),j=r.discriminatedUnion("action",[r.object({action:r.literal("llm")}),r.object({action:r.literal("cache")}),r.object({action:r.literal("mock"),content:r.string()})]),T=r.discriminatedUnion("action",[r.object({action:r.literal("return")}),r.object({action:r.literal("modify"),content:r.string()})]),Ce=r.object({type:r.literal("point1_action"),requestId:r.string(),action:j}),Pe=r.object({type:r.literal("point2_action"),requestId:r.string(),action:T}),B=r.discriminatedUnion("type",[Ce,Pe]);var _e=xe(import.meta.url),Ee=_e("../package.json"),b=Re.context().create(),K=b.router({getSessions:b.procedure.query(({ctx:t})=>({sessions:t.sessionManager.getAllSessions()})),getSession:b.procedure.input(w.object({id:w.string()})).query(({ctx:t,input:e})=>({session:t.sessionManager.getSession(e.id)||null})),getSettings:b.procedure.query(({ctx:t})=>({settings:t.sessionManager.getSettings(),version:Ee.version})),updateSettings:b.procedure.input(z).mutation(({ctx:t,input:e})=>(t.sessionManager.updateSettings(e.settings),{settings:t.sessionManager.getSettings()})),point1Action:b.procedure.input(w.object({requestId:w.string(),action:j})).mutation(({ctx:t,input:e})=>({success:t.sessionManager.resolvePoint1(e.requestId,e.action)})),point2Action:b.procedure.input(w.object({requestId:w.string(),action:T})).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 Me}from"eventsource-parser";var I=class{callbacks;toolCalls=new Map;accumulatedContent="";parser;constructor(e={}){this.callbacks=e,this.parser=Me(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}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.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 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)}}}}catch(o){this.callbacks.onError?.(o instanceof Error?o:new Error(String(o)))}}};function O(t){return new I(t)}var Ae="https://api.openai.com";function V(t){return!t||t.length<8?"****":`${t.substring(0,4)}...${t.substring(t.length-4)}`}function je(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 q(t){let o=`${t.upstreamUrl||Ae}${t.path}`,n=typeof t.body=="object"&&t.body!==null&&"stream"in t.body&&typeof t.body.stream=="boolean"?t.body.stream:!0,s=je(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*Z(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 Te,writeFile as Ie,readFile as Oe,access as qe}from"fs/promises";import{dirname as Fe,join as N}from"path";var U=".playingpack/cache";async function Q(t,e=U){let o=v(t),n=N(e,`${o}.json`);try{return await qe(n),!0}catch{return!1}}async function Ne(t,e=U){let o=v(t),n=N(e,`${o}.json`);try{let s=await Oe(n,"utf-8");return JSON.parse(s)}catch{return null}}var x=class{cachePath;chunks=[];lastChunkTime=0;requestBody;model="unknown";hash="";constructor(e=U){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 Te(Fe(n),{recursive:!0}),await Ie(n,JSON.stringify(o,null,2),"utf-8"),n}getCachePath(){return N(this.cachePath,`${this.hash}.json`)}getHash(){return this.hash}getChunkCount(){return this.chunks.length}},F=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 X(t,e){let o=await Ne(t,e);return o?new F(o):null}function $(){return`chatcmpl-mock-${Date.now()}`}function te(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 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}
|
|
3
3
|
|
|
4
|
-
`}function
|
|
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)}
|
|
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 Ue(D,De(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 h={upstream:"https://api.openai.com",cachePath:".playingpack/cache",cache:"read-write"};function ue(t,e){e&&(h={...h,...e}),t.post("/v1/chat/completions",async(o,n)=>{await He(o,n)}),t.get("/health",async(o,n)=>n.send({status:"ok"})),t.all("/v1/*",async(o,n)=>{await ze(o,n)})}async function He(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: ${V(i.replace("Bearer ",""))}`),k.info("Request received",{requestId:n,path:"/v1/chat/completions",model:s.model,stream:a}),o.createSession(n,s);let c=h.cache!=="off",u=h.cache==="read-write",p=h.cache==="read",g=c&&await Q(s,h.cachePath);o.setCacheHit(n,g);let d=g?"cache":"llm",P;if(o.shouldIntervene()){console.log(` [POINT 1] Waiting for human action (cache: ${g?"yes":"no"})`);let l=await o.waitForPoint1(n);switch(l.action){case"cache":d="cache",console.log(" [ACTION] Use cache");break;case"llm":d="llm",console.log(" [ACTION] Call LLM");break;case"mock":d="mock",P=l.content,console.log(" [ACTION] Mock response");break}}else g?(console.log(" [AUTO] Using cached response"),d="cache"):(console.log(" [AUTO] Calling LLM"),d="llm");try{let l;switch(d){case"cache":{if(!g)throw new Error("Cache requested but no cached response available");l={...await Je(t,e,n,s,a),cached:!0};break}case"llm":if(p){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}l=await We(t,e,n,s,a,u);break;case"mock":l=await le(e,n,P||"",a);break}if(o.shouldIntervene()&&l){console.log(" [POINT 2] Waiting for human action");let m=await o.waitForPoint2(n);switch(m.action){case"return":console.log(" [ACTION] Return as-is");break;case"modify":console.log(" [ACTION] Modify response"),m.content&&(l=await le(e,n,m.content,a));break}}l&&Ge(e,l,a),o.complete(n),k.info("Request completed",{requestId:n,source:d})}catch(l){let m=l instanceof Error?l.message:"Unknown error";console.error(` [ERROR] ${m}`),k.error("Request failed",{requestId:n,error:m}),o.error(n,m),e.sent||e.code(500).send({error:{message:m,type:"proxy_error"}})}}async function Je(t,e,o,n,s){let a=f(),i=await X(n,h.cachePath);if(!i)throw new Error("Failed to load cached response");let c=O({onToolCall:p=>{a.addToolCall(o,p),console.log(` [TOOL CALL] ${p.name}`)},onContent:p=>{a.appendContent(o,p)}}),u="";for await(let p of i.replay())c.feed(p),u+=p;return{content:u,status:i.getStatus(),cached:!0}}async function We(t,e,o,n,s,a){let i=f(),c=n,u=await q({method:"POST",path:"/v1/chat/completions",headers:t.headers,body:c,upstreamUrl:h.upstream});if(!u.ok||!u.body)return{content:u.body?await pe(u.body):"",status:u.status};let p=a?new x(h.cachePath):null;p?.start(n);let g=O({onToolCall:l=>{i.addToolCall(o,l),console.log(` [TOOL CALL] ${l.name}`)},onContent:l=>{i.appendContent(o,l)}}),d="",P=Z(u.body);for await(let l of P)g.feed(l),p?.recordChunk(l),d+=l;if(p)try{let l=await p.save(u.status);console.log(` [CACHE] Saved to ${l}`)}catch(l){console.error(" [CACHE ERROR] Failed to save:",l)}return{content:d,status:u.status}}async function le(t,e,o,n=!0){let s=ae(o);if(s.type==="error")return{content:se(s.content),status:400,mocked:!0};if(n){let a=s.type==="tool_call"?oe(s.functionName||"mock_function",s.content):ne(s.content),i="";for await(let c of a)i+=c;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,c=re(i,a);return{content:JSON.stringify(c),status:200,mocked:!0}}}function Ge(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 ze(t,e){try{let o=await q({method:t.method,path:t.url,headers:t.headers,body:t.body,upstreamUrl:h.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 pe(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 pe(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 ge,dirname as Be}from"path";import{fileURLToPath as Ke}from"url";import{access as Ve}from"fs/promises";import Ze from"@fastify/static";var Qe=Be(Ke(import.meta.url));function de(){return ge(Qe,"../public")}async function Xe(){try{return await Ve(ge(de(),"index.html")),!0}catch{return!1}}async function he(t){let e=de();return await Xe()?(await t.register(Ze,{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 me(t){E.add(t);let e=f(),o=e.getAllSessions();for(let s of o)H(t,{type:"request_update",session:s});let n=e.subscribe(s=>{H(t,s)});t.on("message",s=>{try{let a=JSON.parse(s.toString());Ye(t,a)}catch{}}),t.on("close",()=>{E.delete(t),n()}),t.on("error",()=>{E.delete(t),n()})}function Ye(t,e){let o=B.safeParse(e);if(!o.success){if(typeof e=="object"&&e!==null&&e.type==="ping"){H(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 H(t,e){if(t.readyState===t.OPEN)try{t.send(JSON.stringify(e))}catch{E.delete(t)}}async function fe(t){let e=t.port,o=t.host;J({cache:t.cache,intervene:t.intervene}),await ce(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 st({port:e});n!==e&&console.log(` Port ${e} in use, using ${n}`);let s=et({logger:!1,bodyLimit:50*1024*1024});return await s.register(tt,{origin:!0,credentials:!0}),await s.register(nt),s.get("/ws",{websocket:!0},a=>{me(a)}),await s.register(ot,{prefix:"/api/trpc",trpcOptions:{router:K,createContext:W}}),ue(s,{upstream:t.upstream,cachePath:t.cachePath,cache:t.cache}),t.headless||await he(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 rt}from"fs/promises";import{existsSync as at}from"fs";import{join as ye}from"path";import{createJiti as it}from"jiti";var ct=["playingpack.config.ts","playingpack.config.mts","playingpack.config.js","playingpack.config.mjs"],lt=["playingpack.config.jsonc","playingpack.config.json",".playingpackrc.json",".playingpackrc"],ut={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 pt(t){let e=t.replace(/\/\*[\s\S]*?\*\//g,"");return e=e.replace(/(?<!["'])\/\/.*$/gm,""),e}async function gt(t){for(let e of ct){let o=ye(t,e);if(at(o))try{let s=await it(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:A.parse(a),filename:e}}catch(n){console.warn(` Warning: Error loading ${e}:`,n.message)}}return null}async function dt(t){for(let e of lt){let o=ye(t,e);try{let n=await rt(o,"utf-8"),s=JSON.parse(pt(n));return{config:A.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 ht(t){let e=await gt(t);if(e)return console.log(` Config loaded from ${e.filename}`),e.config;let o=await dt(t);return o?(console.log(` Config loaded from ${o.filename}`),o.config):{}}async function be(t={}){let e=process.cwd(),o=await ht(e),n={...ut,...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 ke="1.0.0";C.name("playingpack").description("Chrome DevTools for AI Agents - Debug and test your AI agent LLM calls").version(ke);C.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("--intervene","Enable intervention mode (pause 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 be({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 fe(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 mt(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)}});
|
|
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("--intervene","Enable intervention mode (pause 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();
|