@hera-al/atn-proxy 1.0.0 → 1.0.1

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/proxy.js +1 -1
  2. package/package.json +1 -1
package/dist/proxy.js CHANGED
@@ -1 +1 @@
1
- import t from"node:http";import e from"node:https";import n from"node:crypto";const o=new Map,r=new Map;function s(t,e){if(t.length<=e)return t;const s=n.createHash("sha256").update(t).digest("hex").slice(0,8),a=`${t.slice(0,e-9)}_${s}`;return o.set(a,t),r.set(t,a),a}function a(t){return o.get(t)||t}function c(t){if(!t)return t;t.usage||(t.usage={});const e=t.usage;return e.input_tokens=e.input_tokens??0,e.output_tokens=e.output_tokens??0,e.cache_creation_input_tokens=e.cache_creation_input_tokens??0,e.cache_read_input_tokens=e.cache_read_input_tokens??0,t}function i(t){if(!t.startsWith("data: "))return t;const e=t.slice(6);if("[DONE]"===e)return t;try{const n=JSON.parse(e);let o=!1;if("content_block_start"===n.type&&"tool_use"===n.content_block?.type&&n.content_block?.name&&(n.content_block.name=a(n.content_block.name),o=!0),"message_start"===n.type&&n.message){if(c(n.message),Array.isArray(n.message.content))for(const t of n.message.content)"tool_use"===t.type&&t.name&&(t.name=a(t.name),o=!0);o=!0}return"message_delta"===n.type&&n.usage&&(n.usage.input_tokens=n.usage.input_tokens??0,n.usage.output_tokens=n.usage.output_tokens??0,n.usage.cache_creation_input_tokens=n.usage.cache_creation_input_tokens??0,n.usage.cache_read_input_tokens=n.usage.cache_read_input_tokens??0,o=!0),o?`data: ${JSON.stringify(n)}`:t}catch{return t}}export function createProxyServer(n,o){return t.createServer(async(t,u)=>{const f=Date.now();if(o.info("Proxy",`-> ${t.method} ${t.url}`),t.url?.includes("/count_tokens")){const e=[];for await(const n of t)e.push(n);const n=Buffer.concat(e).toString("utf8");let r=1e3;try{const t=JSON.parse(n),e=JSON.stringify(t.messages||[]).length,o=JSON.stringify(t.tools||[]).length;r=Math.ceil((e+o)/3.5)}catch{}o.info("Proxy",`count_tokens -> local estimate: ~${r} tokens`);const s=JSON.stringify({input_tokens:r});return u.writeHead(200,{"content-type":"application/json","content-length":Buffer.byteLength(s).toString()}),void u.end(s)}const p=[];for await(const e of t)p.push(e);const g=Buffer.concat(p);let m,h=g;if("POST"===t.method&&g.length>0)try{m=JSON.parse(g.toString("utf8")),n.verbose&&o.debug("Proxy",`Request: model=${m.model} stream=${m.stream} tools=${m.tools?.length??0} msgs=${m.messages?.length??0}`);const{body:t,hadChanges:e}=function(t,e,n){let o=!1;if(Array.isArray(t.tools))for(const r of t.tools)if(r.name&&r.name.length>e){const t=r.name;r.name=s(t,e),o=!0,n.debug("Proxy",`Tool name truncated: ${t} -> ${r.name}`)}if(Array.isArray(t.messages))for(const e of t.messages)if(Array.isArray(e.content))for(const t of e.content)if("tool_use"===t.type&&t.name){const e=r.get(t.name);e&&(t.name=e,o=!0)}return{body:t,hadChanges:o}}(m,n.maxToolName,o);e&&(h=Buffer.from(JSON.stringify(t),"utf8"),o.info("Proxy","Tool names truncated in request"))}catch{}const d=!0===m?.stream,y=new URL(t.url,n.target),l={};for(const[e,n]of Object.entries(t.headers))"host"!==e&&"accept-encoding"!==e&&n&&(l[e]=Array.isArray(n)?n.join(", "):n);l.host=y.host,l["content-length"]=h.length.toString();let _=n.targetPathPrefix+y.pathname+y.search;if(y.searchParams.has("beta")){y.searchParams.delete("beta");const t=y.searchParams.toString();_=n.targetPathPrefix+y.pathname+(t?`?${t}`:"");const e=l["anthropic-beta"]||"";e.includes("prompt-caching")||(l["anthropic-beta"]=e?`${e},prompt-caching-2024-07-31`:"prompt-caching-2024-07-31"),o.debug("Proxy","Rewritten ?beta=true -> anthropic-beta header")}o.debug("Proxy",`Target: ${n.target}${_}`);const k={hostname:y.hostname,port:y.port||443,path:_,method:t.method,headers:l},S=e.request(k,t=>{const e=Date.now()-f;o.info("Proxy",`<- ${t.statusCode} (${e}ms) stream=${d}`);const r={};for(const[e,n]of Object.entries(t.headers))"transfer-encoding"!==e&&n&&(r[e]=n);if(d){r["transfer-encoding"]="chunked",u.writeHead(t.statusCode,r);let e="";t.on("data",t=>{e+=t.toString("utf8");const n=e.split("\n");e=n.pop()||"";for(const t of n)u.write(i(t)+"\n")}),t.on("end",()=>{e&&u.write(i(e)+"\n"),u.end(),o.info("Proxy",`Stream completed (${Date.now()-f}ms)`)})}else{const e=[];t.on("data",t=>e.push(t)),t.on("end",()=>{let s=Buffer.concat(e);try{const t=JSON.parse(s.toString("utf8"));n.verbose&&o.debug("Proxy",`Response: type=${t.type} error=${JSON.stringify(t.error)}`);const e=function(t){if(!t)return t;if(c(t),Array.isArray(t.content))for(const e of t.content)"tool_use"===e.type&&e.name&&(e.name=a(e.name));return t}(t);s=Buffer.from(JSON.stringify(e),"utf8")}catch{o.warn("Proxy",`Non-JSON response (${s.length} bytes)`);const e={type:"error",error:{type:"api_error",message:`OpenRouter returned non-JSON response (${t.statusCode}). Endpoint may not be supported.`},usage:{input_tokens:0,output_tokens:0,cache_creation_input_tokens:0,cache_read_input_tokens:0}};s=Buffer.from(JSON.stringify(e),"utf8"),r["content-type"]="application/json",o.info("Proxy","Generated Anthropic-compatible error response")}r["content-length"]=s.length.toString(),u.writeHead(t.statusCode,r),u.end(s),o.info("Proxy",`Response sent (${Date.now()-f}ms)`)})}});S.on("error",t=>{o.error("Proxy",`Upstream error: ${t.message}`),u.writeHead(502,{"content-type":"application/json"}),u.end(JSON.stringify({error:{message:`Proxy error: ${t.message}`}}))}),S.write(h),S.end()})}
1
+ import t from"node:http";import e from"node:https";import n from"node:crypto";const o=new Map,r=new Map;function s(t,e){if(t.length<=e)return t;const s=n.createHash("sha256").update(t).digest("hex").slice(0,8),a=`${t.slice(0,e-9)}_${s}`;return o.set(a,t),r.set(t,a),a}function a(t){return o.get(t)||t}function c(t){if(!t)return t;t.usage||(t.usage={});const e=t.usage;return e.input_tokens=e.input_tokens??0,e.output_tokens=e.output_tokens??0,e.cache_creation_input_tokens=e.cache_creation_input_tokens??0,e.cache_read_input_tokens=e.cache_read_input_tokens??0,t}function i(t){if(!t.startsWith("data: "))return t;const e=t.slice(6);if("[DONE]"===e)return t;try{const n=JSON.parse(e);let o=!1;if("content_block_start"===n.type&&"tool_use"===n.content_block?.type&&n.content_block?.name&&(n.content_block.name=a(n.content_block.name),o=!0),"message_start"===n.type&&n.message){if(c(n.message),Array.isArray(n.message.content))for(const t of n.message.content)"tool_use"===t.type&&t.name&&(t.name=a(t.name),o=!0);o=!0}return"message_delta"===n.type&&n.usage&&(n.usage.input_tokens=n.usage.input_tokens??0,n.usage.output_tokens=n.usage.output_tokens??0,n.usage.cache_creation_input_tokens=n.usage.cache_creation_input_tokens??0,n.usage.cache_read_input_tokens=n.usage.cache_read_input_tokens??0,o=!0),o?`data: ${JSON.stringify(n)}`:t}catch{return t}}export function createProxyServer(n,o){return t.createServer(async(t,r)=>{const u=Date.now();if(o.info("Proxy",`-> ${t.method} ${t.url}`),t.url?.includes("/count_tokens")){const e=[];for await(const n of t)e.push(n);const n=Buffer.concat(e).toString("utf8");let s=1e3;try{const t=JSON.parse(n),e=JSON.stringify(t.messages||[]).length,o=JSON.stringify(t.tools||[]).length;s=Math.ceil((e+o)/3.5)}catch{}o.info("Proxy",`count_tokens -> local estimate: ~${s} tokens`);const a=JSON.stringify({input_tokens:s});return r.writeHead(200,{"content-type":"application/json","content-length":Buffer.byteLength(a).toString()}),void r.end(a)}const p=[];for await(const e of t)p.push(e);const f=Buffer.concat(p);let g,l=f;if("POST"===t.method&&f.length>0)try{g=JSON.parse(f.toString("utf8")),n.verbose&&o.debug("Proxy",`Request: model=${g.model} stream=${g.stream} tools=${g.tools?.length??0} msgs=${g.messages?.length??0}`);const{body:t,hadChanges:e}=function(t,e,n){let o=!1;if(Array.isArray(t.tools))for(const r of t.tools)if(r.name&&r.name.length>e){const t=r.name;r.name=s(t,e),o=!0,n.debug("Proxy",`Tool name truncated: ${t} -> ${r.name}`)}if(Array.isArray(t.messages))for(let e=0;e<t.messages.length;e++){const r=t.messages[e];if(Array.isArray(r.content)){const t=r.content.map(t=>t.type).join(", ");n.debug("Proxy",` msg[${e}] role=${r.role} content=[${t}]`)}else n.debug("Proxy",` msg[${e}] role=${r.role} content=${"string"==typeof r.content?"string":"other"}`);Array.isArray(r.content)&&(r.content=r.content.map(t=>{if("tool_use"===t.type){const e=t.name??"unknown",n=JSON.stringify(t.input??{});return o=!0,{type:"text",text:`[Calling tool: ${a(e)}]\n${n}`}}if("tool_result"===t.type){let e;return e="string"==typeof t.content?t.content:Array.isArray(t.content)?t.content.filter(t=>"text"===t.type&&t.text).map(t=>t.text).join("\n"):JSON.stringify(t.content??""),o=!0,{type:"text",text:`[Tool result]\n${e}`}}return"text"!==t.type&&"image"!==t.type?(o=!0,null):t}).filter(Boolean),0===r.content.length&&(r.content=[{type:"text",text:"(continued)"}],o=!0))}return{body:t,hadChanges:o}}(g,n.maxToolName,o);e&&(l=Buffer.from(JSON.stringify(t),"utf8"),o.info("Proxy","Tool names truncated in request"))}catch{}const y=!0===g?.stream,m=new URL(t.url,n.target),h={};for(const[e,n]of Object.entries(t.headers))"host"!==e&&"accept-encoding"!==e&&n&&(h[e]=Array.isArray(n)?n.join(", "):n);h.host=m.host,h["content-length"]=l.length.toString();let d=n.targetPathPrefix+m.pathname+m.search;if(m.searchParams.has("beta")){m.searchParams.delete("beta");const t=m.searchParams.toString();d=n.targetPathPrefix+m.pathname+(t?`?${t}`:"");const e=h["anthropic-beta"]||"";e.includes("prompt-caching")||(h["anthropic-beta"]=e?`${e},prompt-caching-2024-07-31`:"prompt-caching-2024-07-31"),o.debug("Proxy","Rewritten ?beta=true -> anthropic-beta header")}o.debug("Proxy",`Target: ${n.target}${d}`);const _={hostname:m.hostname,port:m.port||443,path:d,method:t.method,headers:h},$=e.request(_,t=>{const e=Date.now()-u;o.info("Proxy",`<- ${t.statusCode} (${e}ms) stream=${y}`);const s={};for(const[e,n]of Object.entries(t.headers))"transfer-encoding"!==e&&n&&(s[e]=n);if(y){s["transfer-encoding"]="chunked",r.writeHead(t.statusCode,s);let e="";t.on("data",t=>{e+=t.toString("utf8");const n=e.split("\n");e=n.pop()||"";for(const t of n)r.write(i(t)+"\n")}),t.on("end",()=>{e&&r.write(i(e)+"\n"),r.end(),o.info("Proxy",`Stream completed (${Date.now()-u}ms)`)})}else{const e=[];t.on("data",t=>e.push(t)),t.on("end",()=>{let i=Buffer.concat(e);try{const t=JSON.parse(i.toString("utf8"));n.verbose&&o.debug("Proxy",`Response: type=${t.type} error=${JSON.stringify(t.error)}`);const e=function(t){if(!t)return t;if(c(t),Array.isArray(t.content))for(const e of t.content)"tool_use"===e.type&&e.name&&(e.name=a(e.name));return t}(t);i=Buffer.from(JSON.stringify(e),"utf8")}catch{o.warn("Proxy",`Non-JSON response (${i.length} bytes)`);const e={type:"error",error:{type:"api_error",message:`OpenRouter returned non-JSON response (${t.statusCode}). Endpoint may not be supported.`},usage:{input_tokens:0,output_tokens:0,cache_creation_input_tokens:0,cache_read_input_tokens:0}};i=Buffer.from(JSON.stringify(e),"utf8"),s["content-type"]="application/json",o.info("Proxy","Generated Anthropic-compatible error response")}s["content-length"]=i.length.toString(),r.writeHead(t.statusCode,s),r.end(i),o.info("Proxy",`Response sent (${Date.now()-u}ms)`)})}});$.on("error",t=>{o.error("Proxy",`Upstream error: ${t.message}`),r.writeHead(502,{"content-type":"application/json"}),r.end(JSON.stringify({error:{message:`Proxy error: ${t.message}`}}))}),$.write(l),$.end()})}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hera-al/atn-proxy",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Anthropic Tool Name Proxy — translates tool names > 64 chars for OpenAI-compatible endpoints",
5
5
  "license": "MIT",
6
6
  "author": "TGP <heralife.dev@gmail.com>",