@washanhanzi/claude-code-router 2.0.9 → 2.0.10

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/cli.js +1 -1
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -844,7 +844,7 @@ Stack: ${e.stack}`)}}async registerDefaultTransformersInternal(){try{Object.valu
844
844
  `),e.data&&(e.data.type==="done"?r+=`data: [DONE]
845
845
  `:r+=`data: ${JSON.stringify(e.data)}
846
846
  `),r+=`
847
- `,t.enqueue(r)}})}};vy();_y();Ry();_k();var gu=new Map,g0=new Map,zy={name:"token-speed",version:"1.0.0",description:"Statistics for streaming response token generation speed",register:(0,z8.default)(async(e,t)=>{let r={reporter:["console","temp-file"],...t},n=Array.isArray(r.reporter)?r.reporter:[r.reporter];if(r.outputHandlers&&r.outputHandlers.length>0)wu.registerHandlers(r.outputHandlers);else{let o=[];for(let s of n)s==="console"?o.push({type:"console",enabled:!0,config:{colors:!0,level:"log"}}):s==="temp-file"?o.push({type:"temp-file",enabled:!0,config:{subdirectory:"claude-code-router",extension:"json",includeTimestamp:!0,prefix:"session"}}):s==="webhook"&&console.warn("[TokenSpeedPlugin] Webhook reporter requires explicit configuration in outputHandlers");o.length>0&&wu.registerHandlers(o)}r.outputOptions&&wu.setDefaultOptions(r.outputOptions);let i=async o=>{let s=e.tokenizerService;if(!s)return e.log?.warn("TokenizerService not available"),null;if(!o.provider||!o.model)return null;let a=o.provider,u=o.model,l=`${a}:${u}`;if(g0.has(l))return g0.get(l);let c=s.getTokenizerConfigForModel(a,u);if(!c)return e.log?.debug(`No tokenizer config for ${a}:${u}, using fallback`),null;try{let h=await s.getTokenizer(c);return g0.set(l,h),e.log?.info(`Created tokenizer for ${a}:${u} - ${h.name}`),h}catch(h){return e.log?.warn(`Failed to create tokenizer for ${a}:${u}: ${h.message}`),null}};e.addHook("onRequest",async o=>{new URL(`http://127.0.0.1${o.url}`).pathname.endsWith("/v1/messages")&&(o.requestStartTime=performance.now())}),e.addHook("onSend",async(o,s,a)=>{let u=o.requestStartTime;if(!u)return;let l=o.id||Date.now().toString(),c;try{let d=o.body?.metadata?.user_id;if(d&&typeof d=="string"){let f=d.match(/_session_([a-f0-9-]+)/i);c=f?f[1]:void 0}}catch{}if(!c)return;let h=await i(o);if(a instanceof ReadableStream){gu.set(l,{requestId:l,sessionId:c,startTime:u,lastTokenTime:u,tokenCount:0,tokensPerSecond:0,tokenTimestamps:[],stream:!0});let[d,f]=a.tee();return(async()=>{let g=null,E=async C=>{let m=gu.get(l);if(!m)return;let D=performance.now();if(C){let B=(m.lastTokenTime-m.startTime)/1e3;B>0&&(m.tokensPerSecond=Math.round(m.tokenCount/B))}else{let B=D-1e3;m.tokenTimestamps=m.tokenTimestamps.filter(I=>I>B),m.tokensPerSecond=m.tokenTimestamps.length}await lm(m,n,r.outputOptions,C).catch(B=>{e.log?.warn(`Failed to output streaming stats: ${B.message}`)})};try{let C=f.pipeThrough(new TextDecoderStream).pipeThrough(new Z8).getReader();for(g=setInterval(async()=>{gu.get(l)&&await E(!1)},1e3);;){let{done:m,value:D}=await C.read();if(m)break;let B=D,I=gu.get(l);if(!I)continue;let y=performance.now();if(!I.firstTokenTime&&(B.event==="content_block_start"||B.event==="content_block_delta"||B.event==="text_block"||B.event==="content_block")&&(I.firstTokenTime=y,I.timeToFirstToken=Math.round(y-I.startTime)),B.event==="content_block_delta"&&B.data?.delta){let b=B.data.delta.type,w="";if(b==="text_delta"?w=B.data.delta.text||"":b==="input_json_delta"?w=B.data.delta.partial_json||"":b==="thinking_delta"&&(w=B.data.delta.thinking||""),w){let F=h&&h.encodeText?h.encodeText(w).length:uc(w);I.tokenCount+=F,I.lastTokenTime=y;for(let x=0;x<F;x++)I.tokenTimestamps.push(y)}}B.event==="message_stop"&&(g&&(clearInterval(g),g=null),await E(!0),gu.delete(l))}}catch(C){g&&clearInterval(g),C.name!=="AbortError"&&C.code!=="ERR_STREAM_PREMATURE_CLOSE"&&e.log?.warn(`Error processing token stats: ${C.message}`)}})().catch(g=>{console.log(g),e.log?.warn(`Background stats processing failed: ${g.message}`)}),d}let p=performance.now(),A=0;if(a&&typeof a=="string")try{let d=JSON.parse(a);if(d.usage?.output_tokens)A=d.usage.output_tokens;else{let f=d.content||d.message?.content||"";if(h)Array.isArray(f)?A=f.reduce((g,E)=>{if(E.type==="text"){let C=E.text||"";return g+(h.encodeText?h.encodeText(C).length:uc(C))}return g},0):typeof f=="string"&&(A=h.encodeText?h.encodeText(f).length:uc(f));else{let g=Array.isArray(f)?f.map(E=>E.text).join(""):f;A=uc(g)}}}catch{}if(A>0){let d=(p-u)/1e3,f={requestId:l,sessionId:c,startTime:u,lastTokenTime:p,tokenCount:A,tokensPerSecond:d>0?Math.round(A/d):0,timeToFirstToken:Math.round(p-u),stream:!1,tokenTimestamps:[]};await lm(f,n,r.outputOptions,!0)}return a})})};function uc(e){let t=(e.match(/[\u4e00-\u9fa5]/g)||[]).length,r=e.length-t;return Math.ceil(t/1.5+r/4)}async function lm(e,t,r,n=!1){let i=n?"[Token Speed Final]":"[Token Speed]",o={requestId:e.requestId.substring(0,8),sessionId:e.sessionId,stream:e.stream,tokenCount:e.tokenCount,tokensPerSecond:e.tokensPerSecond,timeToFirstToken:e.timeToFirstToken?`${e.timeToFirstToken}ms`:"N/A",duration:`${((e.lastTokenTime-e.startTime)/1e3).toFixed(2)}s`,timestamp:Date.now()},s={prefix:i,metadata:{sessionId:e.sessionId},...r};for(let a of t)try{await wu.outputToType(a,o,s)}catch(u){console.error(`[TokenSpeedPlugin] Failed to output to ${a}:`,u)}}function K8(e={}){let t=(0,nN.default)({bodyLimit:52428800,...e});return t.setErrorHandler(Rk),t.register(iN.default),t}var X8=class{app;configService;providerService;transformerService;tokenizerService;constructor(e={}){let{initialConfig:t,...r}=e;this.app=K8({...r,logger:r.logger??!0}),this.configService=new ME(e),this.transformerService=new XE(this.configService,this.app.log),this.tokenizerService=new am(this.configService,this.app.log),this.transformerService.initialize().finally(()=>{this.providerService=new HE(this.configService,this.transformerService,this.app.log)}),this.tokenizerService.initialize().catch(n=>{this.app.log.error(`Failed to initialize TokenizerService: ${n}`)})}async register(e,t){await this.app.register(e,t)}addHook(e,t){this.app.addHook(e,t)}async registerNamespace(e,t){if(!e)throw new Error("name is required");if(e==="/"){await this.app.register(async s=>{s.decorate("configService",this.configService),s.decorate("transformerService",this.transformerService),s.decorate("providerService",this.providerService),s.decorate("tokenizerService",this.tokenizerService),s.addHook("preHandler",async(a,u)=>{new URL(`http://127.0.0.1${a.url}`).pathname.endsWith("/v1/messages")&&await um(a,u,{configService:this.configService,tokenizerService:this.tokenizerService})}),await $E(s)});return}if(!t)throw new Error("options is required");let r=new ME({initialConfig:{providers:t.Providers,Router:t.Router}}),n=new XE(r,this.app.log);await n.initialize();let i=new HE(r,n,this.app.log),o=new am(r,this.app.log);await o.initialize(),await this.app.register(async s=>{s.decorate("configService",r),s.decorate("transformerService",n),s.decorate("providerService",i),s.decorate("tokenizerService",o),s.addHook("preHandler",async(a,u)=>{new URL(`http://127.0.0.1${a.url}`).pathname.endsWith("/v1/messages")&&await um(a,u,{configService:r,tokenizerService:o})}),await $E(s)},{prefix:e})}async start(){console.log("[CCR] Server.start() called");try{this.app._server=this,this.app.addHook("preHandler",(i,o,s)=>{if(new URL(`http://127.0.0.1${i.url}`).pathname.endsWith("/v1/messages")&&i.body){let a=i.body;i.log.info({data:a,type:"request body"}),a.stream||(a.stream=!1)}s()}),console.log("[CCR] Registering namespace..."),await this.registerNamespace("/"),console.log("[CCR] Namespace registered"),this.app.addHook("preHandler",async(i,o)=>{if(new URL(`http://127.0.0.1${i.url}`).pathname.endsWith("/v1/messages")&&i.body)try{let s=i.body;if(!s||!s.model)return o.code(400).send({error:"Missing model in request body"});let[a,...u]=s.model.split(",");s.model=u.join(","),i.provider=a,i.model=u;return}catch(s){return i.log.error({error:s},"Error in modelProviderMiddleware:"),o.code(500).send({error:"Internal server error"})}});let e=parseInt(this.configService.get("PORT")||"3000",10),t=this.configService.get("HOST")||"127.0.0.1";console.log(`[CCR] Starting listen on ${t}:${e}...`);let r=await this.app.listen({port:e,host:t});console.log(`[CCR] \u{1F680} Server listening on ${r}`),this.app.log.info(`\u{1F680} LLMs API server listening on ${r}`);let n=async i=>{this.app.log.info(`Received ${i}, shutting down gracefully...`),await this.app.close(),process.exit(0)};process.on("SIGINT",()=>n("SIGINT")),process.on("SIGTERM",()=>n("SIGTERM"))}catch(e){console.error("[CCR] Error starting server:",e),this.app.log.error(`Error starting server: ${e}`),process.exit(1)}}},e3=X8,vs=require("path"),t3=Nr(IT()),En=require("fs"),E0=require("os"),zr=Nr(Fc()),r3=Nr(kT()),n3=Nr(VT()),i3=async e=>{let t=new e3(e),r=t.app;r.register(r3.default,{limits:{fileSize:50*1024*1024}}),r.post("/v1/messages/count_tokens",async(i,o)=>{let{messages:s,tools:a,system:u,model:l}=i.body,c=r._server.tokenizerService;if(l&&l.includes(",")&&c)try{let[h,p]=l.split(",");i.log?.info(`Looking up tokenizer for provider: ${h}, model: ${p}`);let A=c.getTokenizerConfigForModel(h,p);A?i.log?.info(`Using tokenizer config: ${JSON.stringify(A)}`):i.log?.warn(`No tokenizer config found for ${h},${p}, using default tiktoken`);let d=await c.countTokens({messages:s,system:u,tools:a},A);return{input_tokens:d.tokenCount,tokenizer:d.tokenizerUsed}}catch(h){i.log?.error(`Error using configured tokenizer: ${h.message}`),i.log?.error(h.stack)}else l?l.includes(",")?c||i.log?.warn("TokenizerService not available, using default tiktoken"):i.log?.info(`Model "${l}" does not contain comma, using default tiktoken`):i.log?.info("No model specified, using default tiktoken");return{input_tokens:Wy(s,u,a)}}),r.get("/health",async(i,o)=>({status:"ok"})),r.get("/api/config",async(i,o)=>await wf()),r.get("/api/transformers",async(i,o)=>{let s=r._server.transformerService.getAllTransformers();return{transformers:Array.from(s.entries()).map(([a,u])=>({name:a,endpoint:u.endPoint||null}))}}),r.post("/api/config",async(i,o)=>{let s=i.body,a=await If();return a&&console.log(`Backed up existing configuration file to ${a}`),await bf(s),{success:!0,message:"Config saved successfully"}}),r.register(t3.default,{root:(0,vs.join)(__dirname,"..","dist"),prefix:"/ui/",maxAge:"1h"}),r.get("/ui",async(i,o)=>o.redirect("/ui/")),r.get("/api/logs/files",async(i,o)=>{try{let s=(0,vs.join)((0,E0.homedir)(),".claude-code-router","logs"),a=[];if((0,En.existsSync)(s)){let u=(0,En.readdirSync)(s);for(let l of u)if(l.endsWith(".log")){let c=(0,vs.join)(s,l),h=(0,En.statSync)(c);a.push({name:l,path:c,size:h.size,lastModified:h.mtime.toISOString()})}a.sort((l,c)=>new Date(c.lastModified).getTime()-new Date(l.lastModified).getTime())}return a}catch(s){console.error("Failed to get log files:",s),o.status(500).send({error:"Failed to get log files"})}}),r.get("/api/logs",async(i,o)=>{try{let s=i.query.file,a;return s?a=s:a=(0,vs.join)((0,E0.homedir)(),".claude-code-router","logs","app.log"),(0,En.existsSync)(a)?(0,En.readFileSync)(a,"utf8").split(`
847
+ `,t.enqueue(r)}})}};vy();_y();Ry();_k();var gu=new Map,g0=new Map,zy={name:"token-speed",version:"1.0.0",description:"Statistics for streaming response token generation speed",register:(0,z8.default)(async(e,t)=>{let r={reporter:["console","temp-file"],...t},n=Array.isArray(r.reporter)?r.reporter:[r.reporter];if(r.outputHandlers&&r.outputHandlers.length>0)wu.registerHandlers(r.outputHandlers);else{let o=[];for(let s of n)s==="console"?o.push({type:"console",enabled:!0,config:{colors:!0,level:"log"}}):s==="temp-file"?o.push({type:"temp-file",enabled:!0,config:{subdirectory:"claude-code-router",extension:"json",includeTimestamp:!0,prefix:"session"}}):s==="webhook"&&console.warn("[TokenSpeedPlugin] Webhook reporter requires explicit configuration in outputHandlers");o.length>0&&wu.registerHandlers(o)}r.outputOptions&&wu.setDefaultOptions(r.outputOptions);let i=async o=>{let s=e.tokenizerService;if(!s)return e.log?.warn("TokenizerService not available"),null;if(!o.provider||!o.model)return null;let a=o.provider,u=o.model,l=`${a}:${u}`;if(g0.has(l))return g0.get(l);let c=s.getTokenizerConfigForModel(a,u);if(!c)return e.log?.debug(`No tokenizer config for ${a}:${u}, using fallback`),null;try{let h=await s.getTokenizer(c);return g0.set(l,h),e.log?.info(`Created tokenizer for ${a}:${u} - ${h.name}`),h}catch(h){return e.log?.warn(`Failed to create tokenizer for ${a}:${u}: ${h.message}`),null}};e.addHook("onRequest",async o=>{new URL(`http://127.0.0.1${o.url}`).pathname.endsWith("/v1/messages")&&(o.requestStartTime=performance.now())}),e.addHook("onSend",async(o,s,a)=>{let u=o.requestStartTime;if(!u)return;let l=o.id||Date.now().toString(),c;try{let d=o.body?.metadata?.user_id;if(d&&typeof d=="string"){let f=d.match(/_session_([a-f0-9-]+)/i);c=f?f[1]:void 0}}catch{}if(!c)return;let h=await i(o);if(a instanceof ReadableStream){gu.set(l,{requestId:l,sessionId:c,startTime:u,lastTokenTime:u,tokenCount:0,tokensPerSecond:0,tokenTimestamps:[],stream:!0});let[d,f]=a.tee();return(async()=>{let g=null,E=async C=>{let m=gu.get(l);if(!m)return;let D=performance.now();if(C){let B=(m.lastTokenTime-m.startTime)/1e3;B>0&&(m.tokensPerSecond=Math.round(m.tokenCount/B))}else{let B=D-1e3;m.tokenTimestamps=m.tokenTimestamps.filter(I=>I>B),m.tokensPerSecond=m.tokenTimestamps.length}await lm(m,n,r.outputOptions,C).catch(B=>{e.log?.warn(`Failed to output streaming stats: ${B.message}`)})};try{let C=f.pipeThrough(new TextDecoderStream).pipeThrough(new Z8).getReader();for(g=setInterval(async()=>{gu.get(l)&&await E(!1)},1e3);;){let{done:m,value:D}=await C.read();if(m)break;let B=D,I=gu.get(l);if(!I)continue;let y=performance.now();if(!I.firstTokenTime&&(B.event==="content_block_start"||B.event==="content_block_delta"||B.event==="text_block"||B.event==="content_block")&&(I.firstTokenTime=y,I.timeToFirstToken=Math.round(y-I.startTime)),B.event==="content_block_delta"&&B.data?.delta){let b=B.data.delta.type,w="";if(b==="text_delta"?w=B.data.delta.text||"":b==="input_json_delta"?w=B.data.delta.partial_json||"":b==="thinking_delta"&&(w=B.data.delta.thinking||""),w){let F=h&&h.encodeText?h.encodeText(w).length:uc(w);I.tokenCount+=F,I.lastTokenTime=y;for(let x=0;x<F;x++)I.tokenTimestamps.push(y)}}B.event==="message_stop"&&(g&&(clearInterval(g),g=null),await E(!0),gu.delete(l))}}catch(C){g&&clearInterval(g),C.name!=="AbortError"&&C.code!=="ERR_STREAM_PREMATURE_CLOSE"&&e.log?.warn(`Error processing token stats: ${C.message}`)}})().catch(g=>{console.log(g),e.log?.warn(`Background stats processing failed: ${g.message}`)}),d}let p=performance.now(),A=0;if(a&&typeof a=="string")try{let d=JSON.parse(a);if(d.usage?.output_tokens)A=d.usage.output_tokens;else{let f=d.content||d.message?.content||"";if(h)Array.isArray(f)?A=f.reduce((g,E)=>{if(E.type==="text"){let C=E.text||"";return g+(h.encodeText?h.encodeText(C).length:uc(C))}return g},0):typeof f=="string"&&(A=h.encodeText?h.encodeText(f).length:uc(f));else{let g=Array.isArray(f)?f.map(E=>E.text).join(""):f;A=uc(g)}}}catch{}if(A>0){let d=(p-u)/1e3,f={requestId:l,sessionId:c,startTime:u,lastTokenTime:p,tokenCount:A,tokensPerSecond:d>0?Math.round(A/d):0,timeToFirstToken:Math.round(p-u),stream:!1,tokenTimestamps:[]};await lm(f,n,r.outputOptions,!0)}return a})})};function uc(e){let t=(e.match(/[\u4e00-\u9fa5]/g)||[]).length,r=e.length-t;return Math.ceil(t/1.5+r/4)}async function lm(e,t,r,n=!1){let i=n?"[Token Speed Final]":"[Token Speed]",o={requestId:e.requestId.substring(0,8),sessionId:e.sessionId,stream:e.stream,tokenCount:e.tokenCount,tokensPerSecond:e.tokensPerSecond,timeToFirstToken:e.timeToFirstToken?`${e.timeToFirstToken}ms`:"N/A",duration:`${((e.lastTokenTime-e.startTime)/1e3).toFixed(2)}s`,timestamp:Date.now()},s={prefix:i,metadata:{sessionId:e.sessionId},...r};for(let a of t)try{await wu.outputToType(a,o,s)}catch(u){console.error(`[TokenSpeedPlugin] Failed to output to ${a}:`,u)}}function K8(e={}){let t=(0,nN.default)({bodyLimit:52428800,...e});return t.setErrorHandler(Rk),t.register(iN.default),t}var X8=class{app;configService;providerService;transformerService;tokenizerService;constructor(e={}){let{initialConfig:t,...r}=e;this.app=K8({...r,logger:r.logger??!0}),this.configService=new ME(e),this.transformerService=new XE(this.configService,this.app.log),this.tokenizerService=new am(this.configService,this.app.log),this.transformerService.initialize().finally(()=>{this.providerService=new HE(this.configService,this.transformerService,this.app.log)}),this.tokenizerService.initialize().catch(n=>{this.app.log.error(`Failed to initialize TokenizerService: ${n}`)})}async register(e,t){await this.app.register(e,t)}addHook(e,t){this.app.addHook(e,t)}async registerNamespace(e,t){if(!e)throw new Error("name is required");if(e==="/"){await this.app.register(async s=>{s.decorate("configService",this.configService),s.decorate("transformerService",this.transformerService),s.decorate("providerService",this.providerService),s.decorate("tokenizerService",this.tokenizerService),s.addHook("preHandler",async(a,u)=>{new URL(`http://127.0.0.1${a.url}`).pathname.endsWith("/v1/messages")&&await um(a,u,{configService:this.configService,tokenizerService:this.tokenizerService})}),await $E(s)});return}if(!t)throw new Error("options is required");let r=new ME({initialConfig:{providers:t.Providers,Router:t.Router}}),n=new XE(r,this.app.log);await n.initialize();let i=new HE(r,n,this.app.log),o=new am(r,this.app.log);await o.initialize(),await this.app.register(async s=>{s.decorate("configService",r),s.decorate("transformerService",n),s.decorate("providerService",i),s.decorate("tokenizerService",o),s.addHook("preHandler",async(a,u)=>{new URL(`http://127.0.0.1${a.url}`).pathname.endsWith("/v1/messages")&&await um(a,u,{configService:r,tokenizerService:o})}),await $E(s)},{prefix:e})}async start(){console.log("[CCR] Server.start() called");try{this.app._server=this,this.app.addHook("preHandler",(i,o,s)=>{if(new URL(`http://127.0.0.1${i.url}`).pathname.endsWith("/v1/messages")&&i.body){let a=i.body;i.log.info({data:a,type:"request body"}),a.stream||(a.stream=!1)}s()}),console.log("[CCR] Registering namespace..."),await this.registerNamespace("/"),console.log("[CCR] Namespace registered"),this.app.addHook("preHandler",async(i,o)=>{if(new URL(`http://127.0.0.1${i.url}`).pathname.endsWith("/v1/messages")&&i.body)try{let s=i.body;if(!s||!s.model)return o.code(400).send({error:"Missing model in request body"});let[a,...u]=s.model.split(",");s.model=u.join(","),i.provider=a,i.model=u;return}catch(s){return i.log.error({error:s},"Error in modelProviderMiddleware:"),o.code(500).send({error:"Internal server error"})}});let e=parseInt(this.configService.get("PORT")||"3000",10),t=this.configService.get("HOST")||"127.0.0.1";console.log(`[CCR] Starting listen on ${t}:${e}...`);let r=await this.app.listen({port:e,host:t});console.log(`[CCR] \u{1F680} Server listening on ${r}`),this.app.log.info(`\u{1F680} LLMs API server listening on ${r}`);let n=async i=>{this.app.log.info(`Received ${i}, shutting down gracefully...`),await this.app.close(),process.exit(0)};process.on("SIGINT",()=>n("SIGINT")),process.on("SIGTERM",()=>n("SIGTERM"))}catch(e){console.error("[CCR] Error starting server:",e),this.app.log.error(`Error starting server: ${e}`),process.exit(1)}}},e3=X8,vs=require("path"),t3=Nr(IT()),En=require("fs"),E0=require("os"),zr=Nr(Fc()),r3=Nr(kT()),n3=Nr(VT()),i3=async e=>{let t=new e3(e),r=t.app;r.register(r3.default,{limits:{fileSize:50*1024*1024}}),r.post("/v1/messages/count_tokens",async(i,o)=>{let{messages:s,tools:a,system:u,model:l}=i.body,c=r._server.tokenizerService;if(l&&l.includes(",")&&c)try{let[h,p]=l.split(",");i.log?.info(`Looking up tokenizer for provider: ${h}, model: ${p}`);let A=c.getTokenizerConfigForModel(h,p);A?i.log?.info(`Using tokenizer config: ${JSON.stringify(A)}`):i.log?.warn(`No tokenizer config found for ${h},${p}, using default tiktoken`);let d=await c.countTokens({messages:s,system:u,tools:a},A);return{input_tokens:d.tokenCount,tokenizer:d.tokenizerUsed}}catch(h){i.log?.error(`Error using configured tokenizer: ${h.message}`),i.log?.error(h.stack)}else l?l.includes(",")?c||i.log?.warn("TokenizerService not available, using default tiktoken"):i.log?.info(`Model "${l}" does not contain comma, using default tiktoken`):i.log?.info("No model specified, using default tiktoken");return{input_tokens:Wy(s,u,a)}}),r.get("/api/config",async(i,o)=>await wf()),r.get("/api/transformers",async(i,o)=>{let s=r._server.transformerService.getAllTransformers();return{transformers:Array.from(s.entries()).map(([a,u])=>({name:a,endpoint:u.endPoint||null}))}}),r.post("/api/config",async(i,o)=>{let s=i.body,a=await If();return a&&console.log(`Backed up existing configuration file to ${a}`),await bf(s),{success:!0,message:"Config saved successfully"}}),r.register(t3.default,{root:(0,vs.join)(__dirname,"..","dist"),prefix:"/ui/",maxAge:"1h"}),r.get("/ui",async(i,o)=>o.redirect("/ui/")),r.get("/api/logs/files",async(i,o)=>{try{let s=(0,vs.join)((0,E0.homedir)(),".claude-code-router","logs"),a=[];if((0,En.existsSync)(s)){let u=(0,En.readdirSync)(s);for(let l of u)if(l.endsWith(".log")){let c=(0,vs.join)(s,l),h=(0,En.statSync)(c);a.push({name:l,path:c,size:h.size,lastModified:h.mtime.toISOString()})}a.sort((l,c)=>new Date(c.lastModified).getTime()-new Date(l.lastModified).getTime())}return a}catch(s){console.error("Failed to get log files:",s),o.status(500).send({error:"Failed to get log files"})}}),r.get("/api/logs",async(i,o)=>{try{let s=i.query.file,a;return s?a=s:a=(0,vs.join)((0,E0.homedir)(),".claude-code-router","logs","app.log"),(0,En.existsSync)(a)?(0,En.readFileSync)(a,"utf8").split(`
848
848
  `).filter(u=>u.trim()):[]}catch(s){console.error("Failed to get logs:",s),o.status(500).send({error:"Failed to get logs"})}}),r.delete("/api/logs",async(i,o)=>{try{let s=i.query.file,a;return s?a=s:a=(0,vs.join)((0,E0.homedir)(),".claude-code-router","logs","app.log"),(0,En.existsSync)(a)&&(0,En.writeFileSync)(a,"","utf8"),{success:!0,message:"Logs cleared successfully"}}catch(s){console.error("Failed to clear logs:",s),o.status(500).send({error:"Failed to clear logs"})}}),r.get("/api/presets",async(i,o)=>{try{let s=(0,vs.join)(zr.HOME_DIR,"presets");if(!(0,En.existsSync)(s))return{presets:[]};let a=(0,En.readdirSync)(s,{withFileTypes:!0}).filter(l=>l.isDirectory()&&!l.name.startsWith(".")).map(l=>l.name),u=[];for(let l of a){let c=(0,vs.join)(s,l);try{let h=(0,vs.join)(c,"manifest.json"),p=(0,En.readFileSync)(h,"utf-8"),A=JSON.parse(p),{Providers:d,Router:f,PORT:g,HOST:E,API_TIMEOUT_MS:C,PROXY_URL:m,LOG:D,LOG_LEVEL:B,StatusLine:I,NON_INTERACTIVE_MODE:y,...b}=A;u.push({id:l,name:b.name||l,version:b.version||"1.0.0",description:b.description,author:b.author,homepage:b.homepage,repository:b.repository,license:b.license,keywords:b.keywords,ccrVersion:b.ccrVersion,source:b.source,sourceType:b.sourceType,checksum:b.checksum,installed:!0})}catch(h){console.error(`Failed to read preset ${l}:`,h)}}return{presets:u}}catch(s){console.error("Failed to get presets:",s),o.status(500).send({error:"Failed to get presets"})}}),r.get("/api/presets/:name",async(i,o)=>{try{let{name:s}=i.params,a=(0,zr.getPresetDir)(s);if(!(0,En.existsSync)(a)){o.status(404).send({error:"Preset not found"});return}let u=await(0,zr.readManifestFromDir)(a);return{...(0,zr.manifestToPresetFile)(u),config:(0,zr.loadConfigFromManifest)(u,a),userValues:u.userValues||{}}}catch(s){console.error("Failed to get preset:",s),o.status(500).send({error:s.message||"Failed to get preset"})}}),r.post("/api/presets/:name/apply",async(i,o)=>{try{let{name:s}=i.params,{secrets:a}=i.body,u=(0,zr.getPresetDir)(s);if(!(0,En.existsSync)(u)){o.status(404).send({error:"Preset not found"});return}let l={...await(0,zr.readManifestFromDir)(u)};return a&&Object.keys(a).length>0&&(l.userValues={...l.userValues,...a}),await(0,zr.saveManifest)(s,l),{success:!0,message:"Preset applied successfully"}}catch(s){console.error("Failed to apply preset:",s),o.status(500).send({error:s.message||"Failed to apply preset"})}}),r.delete("/api/presets/:name",async(i,o)=>{try{let{name:s}=i.params,a=(0,zr.getPresetDir)(s);if(!(0,En.existsSync)(a)){o.status(404).send({error:"Preset not found"});return}return(0,En.rmSync)(a,{recursive:!0,force:!0}),{success:!0,message:"Preset deleted successfully"}}catch(s){console.error("Failed to delete preset:",s),o.status(500).send({error:s.message||"Failed to delete preset"})}}),r.get("/api/presets/market",async(i,o)=>{try{return{presets:await(0,zr.getMarketPresets)()}}catch(s){console.error("Failed to get market presets:",s),o.status(500).send({error:s.message||"Failed to get market presets"})}}),r.post("/api/presets/install/github",async(i,o)=>{try{let{presetName:s}=i.body;if(!s){o.status(400).send({error:"Preset name is required"});return}let a=await(0,zr.findMarketPresetByName)(s);if(!a){o.status(400).send({error:"Preset not found in marketplace",message:`Preset '${s}' is not available in the official marketplace. Please check the available presets.`});return}if(!a.repo){o.status(400).send({error:"Invalid preset data",message:`Preset '${s}' does not have repository information`});return}let u=a.repo.match(/(?:github\.com[:/]|^)([^/]+)\/([^/\s#]+?)(?:\.git)?$/);if(!u){o.status(400).send({error:"Invalid GitHub repository URL"});return}let[,l,c]=u,h=a.name||s;if(await(0,zr.isPresetInstalled)(h)){o.status(409).send({error:"Preset already installed",message:`Preset '${h}' is already installed. To update or reconfigure, please delete it first using the delete button.`,presetName:h});return}let p=`https://github.com/${l}/${c}/archive/refs/heads/main.zip`,A=await(0,zr.downloadPresetToTemp)(p),d=await n(A);if(await(0,zr.isPresetInstalled)(h)){(0,En.unlinkSync)(A),o.status(409).send({error:"Preset already installed",message:`Preset '${h}' was installed while downloading. Please try again.`,presetName:h});return}let f=(0,zr.getPresetDir)(h);await(0,zr.extractPreset)(A,f);let g=await(0,zr.readManifestFromDir)(f);return g.repository=a.repo,a.url&&(g.source=a.url),await(0,zr.saveManifest)(h,g),(0,En.unlinkSync)(A),{success:!0,presetName:h,preset:{...d.metadata,installed:!0}}}catch(s){console.error("Failed to install preset from GitHub:",s),o.status(500).send({error:s.message||"Failed to install preset from GitHub"})}});async function n(i){let o=new n3.default(i),s=o.getEntry("manifest.json");if(s||(s=o.getEntries().find(u=>u.entryName.includes("manifest.json"))||null),!s)throw new Error("Invalid preset file: manifest.json not found");let a=JSON.parse(s.getData().toString("utf-8"));return(0,zr.manifestToPresetFile)(a)}return t},s3=e=>async(t,r,n)=>{if(["/","/health"].includes(t.url)||t.url.startsWith("/ui"))return n();let i=e.Providers||e.providers||[];if(!i||i.length===0)return n();let o=e.APIKEY;if(!o){let l=[`http://127.0.0.1:${e.PORT||3456}`,`http://localhost:${e.PORT||3456}`];if(t.headers.origin&&!l.includes(t.headers.origin)){r.status(403).send("CORS not allowed for this origin");return}else r.header("Access-Control-Allow-Origin",`http://127.0.0.1:${e.PORT||3456}`),r.header("Access-Control-Allow-Origin",`http://localhost:${e.PORT||3456}`);return n()}let s=t.headers.authorization||t.headers["x-api-key"],a=Array.isArray(s)?s[0]:s||"";if(!a){r.status(401).send("APIKEY is missing");return}let u="";if(a.startsWith("Bearer")?u=a.split(" ")[1]:u=a,u!==o){r.status(401).send("Invalid API key");return}n()},m0=Nr(Fc()),o3=require("child_process"),a3=require("zlib"),u3=require("stream"),Ic=require("fs"),Mn=require("fs/promises"),bc=require("path"),l3=require("util"),c3=require("timers");async function cm(e){return new Promise(t=>(0,Ic.access)(e,Ic.constants.F_OK,r=>t(!r)))}var Am=class extends Error{code="RFS-TOO-MANY";constructor(){super("Too many destination file attempts")}},A3=class extends u3.Writable{createGzip;exec;file;filename;finished;fsCreateReadStream;fsCreateWriteStream;fsOpen;fsReadFile;fsStat;fsUnlink;generator;initPromise;last;maxTimeout;next;options;prev;rotation;size;stdout;timeout;timeoutPromise;constructor(e,t){let{encoding:r,history:n,maxFiles:i,maxSize:o,path:s}=t;super({decodeStrings:!0,defaultEncoding:r}),this.createGzip=a3.createGzip,this.exec=o3.exec,this.filename=s+e(null),this.fsCreateReadStream=Ic.createReadStream,this.fsCreateWriteStream=Ic.createWriteStream,this.fsOpen=Mn.open,this.fsReadFile=Mn.readFile,this.fsStat=Mn.stat,this.fsUnlink=Mn.unlink,this.generator=e,this.maxTimeout=2147483640,this.options=t,this.stdout=process.stdout,(i||o)&&(t.history=s+(n||this.generator(null)+".txt")),this.on("close",()=>this.finished?null:this.emit("finish")),this.on("finish",()=>this.finished=this.clear()),(async()=>{try{this.initPromise=this.init(),await this.initPromise,delete this.initPromise}catch{}})()}_destroy(e,t){this.refinal(e,t)}_final(e){this.refinal(void 0,e)}_write(e,t,r){this.rewrite([{chunk:e,encoding:t}],0,r)}_writev(e,t){this.rewrite(e,0,t)}async refinal(e,t){try{this.clear(),this.initPromise&&await this.initPromise,this.timeoutPromise&&await this.timeoutPromise,await this.reclose()}catch(r){return t(e||r)}t(e)}async rewrite(e,t,r){let{size:n,teeToStdout:i}=this.options;try{this.initPromise&&await this.initPromise;for(let o=0;o<e.length;++o){let{chunk:s}=e[o];this.size+=s.length,this.timeoutPromise&&await this.timeoutPromise,await this.file.write(s),i&&!this.stdout.destroyed&&this.stdout.write(s),n&&this.size>=n&&await this.rotate()}}catch(o){return r(o)}r()}async init(){let{immutable:e,initialRotation:t,interval:r,size:n}=this.options;if(e)return new Promise((o,s)=>process.nextTick(()=>this.immutate(!0).then(o).catch(s)));let i;try{i=await(0,Mn.stat)(this.filename)}catch(o){if(o.code!=="ENOENT")throw o;return this.reopen(0)}if(!i.isFile())throw new Error(`Can't write on: ${this.filename} (it is not a file)`);if(t){this.intervalBounds(this.now());let o=this.prev;if(this.intervalBounds(new Date(i.mtime.getTime())),o!==this.prev)return this.rotate()}return this.size=i.size,!n||i.size<n?this.reopen(i.size):(r&&this.intervalBounds(this.now()),this.rotate())}async makePath(e){return(0,Mn.mkdir)(e.split(bc.sep).slice(0,-1).join(bc.sep),{recursive:!0})}async reopen(e){let t;try{t=await(0,Mn.open)(this.filename,"a",this.options.mode)}catch(r){if(r.code!=="ENOENT")throw r;await this.makePath(this.filename),t=await(0,Mn.open)(this.filename,"a",this.options.mode)}this.file=t,this.size=e,this.interval(),this.emit("open",this.filename)}async reclose(){let{file:e}=this;if(e)return delete this.file,e.close()}now(){return new Date}async rotate(){let{immutable:e,rotate:t}=this.options;return this.size=0,this.rotation=this.now(),this.clear(),this.emit("rotation"),await this.reclose(),t?this.classical():e?this.immutate(!1):this.move()}async findName(){let{interval:e,path:t,intervalBoundary:r}=this.options;for(let n=1;n<1e3;++n){let i=t+this.generator(e&&r?new Date(this.prev):this.rotation,n);if(!await cm(i))return i}throw new Am}async move(){let{compress:e}=this.options,t=await this.findName();return await this.touch(t),e?await this.compress(t):await(0,Mn.rename)(this.filename,t),this.rotated(t)}async touch(e){let t;try{t=await this.fsOpen(e,"a")}catch(r){if(r.code!=="ENOENT")throw r;await this.makePath(e),t=await(0,Mn.open)(e,"a")}return await t.close(),this.unlink(e)}async classical(){let{compress:e,path:t,rotate:r}=this.options,n="";for(let i=r;i>0;--i){let o=t+this.generator(i),s=i===1?this.filename:t+this.generator(i-1);if(await cm(s))if(n||(n=o),i===1&&e)await this.compress(o);else try{await(0,Mn.rename)(s,o)}catch(a){if(a.code!=="ENOENT")throw a;await this.makePath(o),await(0,Mn.rename)(s,o)}}return this.rotated(n)}clear(){return this.timeout&&(clearTimeout(this.timeout),this.timeout=null),!0}intervalBoundsBig(e){let t=this.options.intervalUTC?e.getUTCFullYear():e.getFullYear(),r=this.options.intervalUTC?e.getUTCMonth():e.getMonth(),n=this.options.intervalUTC?e.getUTCDate():e.getDate(),i=this.options.intervalUTC?e.getUTCHours():e.getHours(),{num:o,unit:s}=this.options.interval;s==="M"?(n=1,i=0):s==="d"?i=0:i=parseInt(i/o,10)*o,this.prev=new Date(t,r,n,i,0,0,0).getTime(),s==="M"?r+=o:s==="d"?n+=o:i+=o,this.next=new Date(t,r,n,i,0,0,0).getTime()}intervalBounds(e){let t=this.options.interval.unit;if(t==="M"||t==="d"||t==="h")this.intervalBoundsBig(e);else{let r=1e3*this.options.interval.num;t==="m"&&(r*=60),this.prev=parseInt(e.getTime()/r,10)*r,this.next=this.prev+r}return new Date(this.prev)}interval(){if(!this.options.interval)return;this.intervalBounds(this.now());let e=async()=>{let t=this.next-this.now().getTime();if(t<=0)try{this.timeoutPromise=this.rotate(),await this.timeoutPromise,delete this.timeoutPromise}catch{}else this.timeout=(0,c3.setTimeout)(e,t>this.maxTimeout?this.maxTimeout:t),this.timeout.unref()};e()}async compress(e){let{compress:t}=this.options;return typeof t=="function"?await new Promise((r,n)=>{this.exec(t(this.filename,e),(i,o,s)=>{this.emit("external",o,s),i?n(i):r()})}):await this.gzip(e),this.unlink(this.filename)}async gzip(e){let{mode:t}=this.options,r=t?{mode:t}:{},n=this.fsCreateReadStream(this.filename,{}),i=this.fsCreateWriteStream(e,r),o=this.createGzip();return new Promise((s,a)=>{n.once("error",a),i.once("error",a),o.once("error",a),i.once("finish",s),n.pipe(o).pipe(i)})}async rotated(e){let{maxFiles:t,maxSize:r}=this.options;return(t||r)&&await this.history(e),this.emit("rotated",e),this.reopen(0)}async history(e){let{history:t,maxFiles:r,maxSize:n}=this.options,i=[],o=[e];try{o=[...(await this.fsReadFile(t,"utf8")).toString().split(`
849
849
  `),e]}catch(s){if(s.code!=="ENOENT")throw s}for(let s of o)if(s)try{let a=await this.fsStat(s);a.isFile()?i.push({name:s,size:a.size,time:a.mtime.getTime()}):this.emit("warning",new Error(`File '${s}' contained in history is not a regular file`))}catch(a){if(a.code!=="ENOENT")throw a}if(i.sort((s,a)=>s.time-a.time),r)for(;i.length>r;){let s=i.shift();await this.unlink(s.name),this.emit("removed",s.name,!0)}if(n)for(;i.reduce((s,a)=>s+a.size,0)>n;){let s=i.shift();await this.unlink(s.name),this.emit("removed",s.name,!1)}await(0,Mn.writeFile)(t,i.map(s=>s.name).join(`
850
850
  `)+`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@washanhanzi/claude-code-router",
3
- "version": "2.0.9",
3
+ "version": "2.0.10",
4
4
  "description": "Use Claude Code without an Anthropics account and route it to another LLM provider",
5
5
  "scripts": {
6
6
  "build": "pnpm build:shared && pnpm build:core && pnpm build:server && pnpm build:cli && pnpm build:ui",