@keepdb/cc 0.1.7
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/bin/cli.js +27 -0
- package/package.json +30 -0
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{z as p}from"zod";import{readFileSync as q,existsSync as k}from"fs";import{resolve as T,join as N}from"path";import{homedir as _}from"os";import{parse as U}from"@keepdb/kit/yaml";var A=["haiku","sonnet","opus"],W=t=>{if(typeof t!="string")return null;let[e,o]=t.split("@");return{model:e,weight:Number(o)||1}},D=p.object({name:p.string().min(1),url:p.string().url(),apiKey:p.string().min(1),models:p.record(p.string(),p.string()).refine(t=>Object.keys(t).some(e=>A.includes(e)),{message:"\u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u6709\u6548\u7684 tier (haiku/sonnet/opus)"})}),J=p.object({providers:p.array(D).min(1),fetchTimeout:p.number().int().positive().default(3e4)}),V=t=>{if(t)return T(t);let e=T("ismartify.config.yaml");return k(e)?e:N(_(),".keepdb","ismartify.config.yaml")},E=t=>{let e=V(t);if(!k(e))throw new Error(t?`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${e}`:"\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF0C\u8BF7\u521B\u5EFA ismartify.config.yaml \u6216 ~/.keepdb/ismartify.config.yaml");let o=U(q(e,"utf-8"));if(!o.codeplan)throw new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF: \u7F3A\u5C11\u9876\u5C42 "codeplan:" key\u3002
|
|
3
|
+
\u8BF7\u53C2\u8003\u683C\u5F0F:
|
|
4
|
+
|
|
5
|
+
codeplan:
|
|
6
|
+
fetchTimeout: 30000
|
|
7
|
+
providers:
|
|
8
|
+
- name: example
|
|
9
|
+
url: https://api.example.com/anthropic
|
|
10
|
+
apiKey: sk-xxx
|
|
11
|
+
models:
|
|
12
|
+
sonnet: model-name@1`);let r=J.parse(o.codeplan),s=Object.fromEntries(r.providers.map(n=>[n.name,{name:n.name,url:n.url,apiKey:n.apiKey}])),i=r.providers.flatMap(n=>Object.entries(n.models).filter(([c])=>A.includes(c)).map(([c,a])=>{let m=W(a);return m?{tier:c,targetModel:m.model,weight:m.weight,provider:n.name}:null}).filter(Boolean));return{providers:s,slots:i,fetchTimeout:r.fetchTimeout,getProvider:n=>s[n]}};import{Hono as X}from"hono";import{logger as B}from"hono/logger";import{Hono as z}from"hono";var h=t=>{let e=new z;return e.get("/",o=>{let r=Object.values(t.providers).map(({name:s,url:i})=>({name:s,url:i,models:t.slots.filter(n=>n.provider===s).map(({tier:n,targetModel:c,weight:a})=>({tier:n,targetModel:c,weight:a}))}));return o.json({fetchTimeout:t.fetchTimeout,providers:r})}),e};import{Hono as G}from"hono";var S="sonnet",g=["opus","sonnet","haiku"],$=t=>{if(!t||typeof t!="string")return S;let e=t.toLowerCase();return e.includes("haiku")?"haiku":e.includes("opus")?"opus":e.includes("sonnet")?"sonnet":S},C=t=>{let e=g.indexOf(t);return e===-1?g:g.slice(e)};var R=t=>{let e=new Map;return t.forEach((o,r)=>{let s=e.get(o.tier)||[];s.push({...o,originalIndex:r}),e.set(o.tier,s)}),{select:(o,r=[])=>{let s=(e.get(o)||[]).filter(c=>!r.includes(c.originalIndex));if(s.length===0)return null;let i=s.reduce((c,a)=>c+a.weight,0),n=Math.random()*i;for(let c of s){if(n<c.weight)return c;n-=c.weight}return null},countByTier:o=>(e.get(o)||[]).length}};var I=t=>{let e=R(t.slots);return{forward:async({path:r,method:s,jsonBody:i,signal:n})=>{var y;let c=$(i==null?void 0:i.model),a=C(c),m=[];for(let b of a){let F=e.countByTier(b);for(let w=0;w<F;w++){let d=e.select(b,m);if(!d)break;m.push(d.originalIndex);let u=t.getProvider(d.provider);if(!u)continue;let M=i?JSON.stringify({...i,model:d.targetModel}):void 0;try{let f=new AbortController,x=()=>f.abort();n==null||n.addEventListener("abort",x,{once:!0});let j=setTimeout(()=>f.abort(),t.fetchTimeout);try{let l=await fetch(`${u.url}${r}`,{method:s,headers:{"Content-Type":"application/json","Anthropic-Version":"2023-06-01","x-api-key":u.apiKey,"Accept-Encoding":"identity"},body:M,duplex:"half",signal:f.signal});if(l.status===429||l.status>=500){console.warn(`[Fallback] ${u.name} (${d.targetModel}) -> ${l.status}`),await((y=l.body)==null?void 0:y.cancel());continue}let{readable:H,writable:K}=new TransformStream;return l.body.pipeTo(K).catch(v=>{v.name!=="AbortError"&&console.warn(`[Stream] ${u.name}: ${v.message}`)}),new Response(H,{status:l.status,headers:l.headers})}finally{clearTimeout(j),n==null||n.removeEventListener("abort",x)}}catch(f){f.name!=="AbortError"&&console.warn(`[Error] ${u.name}: ${f.message}`)}}}return new Response(JSON.stringify({error:`\u8BE5\u6863\u4F4D (${c}) \u53CA\u5176\u964D\u7EA7\u6863\u4F4D\u6240\u6709\u914D\u7F6E\u7684\u6A21\u578B\u63D2\u69FD\u5747\u5C1D\u8BD5\u5931\u8D25`}),{status:502,headers:{"Content-Type":"application/json"}})}}};var L=t=>{let e=I(t),o=new G;return o.all("/*",async r=>{let{pathname:s,search:i}=new URL(r.req.url),n=s.replace(/^\/anthropic/,"")+i,c=r.req.method,a=null;if(c!=="GET"&&c!=="HEAD")try{a=await r.req.json()}catch{}return e.forward({path:n,method:c,jsonBody:a,signal:r.req.raw.signal})}),o};var O=t=>{let e=new X;return e.use("*",B()),e.route("/config",h(t)),e.route("/status",h(t)),e.route("/anthropic/v1",L(t)),e};import{serve as Q}from"@hono/node-server";var Y=`
|
|
13
|
+
@keepdb/cc - Anthropic API \u4EE3\u7406\u670D\u52A1
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
keepdb-cc start [--config <path>] [--port <number>]
|
|
17
|
+
npx @keepdb/cc start [--config <path>] [--port <number>]
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--config <path> \u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84 (\u9ED8\u8BA4: ./ismartify.config.yaml \u2192 ~/.keepdb/ismartify.config.yaml)
|
|
21
|
+
--port <number> \u76D1\u542C\u7AEF\u53E3 (\u9ED8\u8BA4: 6110)
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
keepdb-cc start
|
|
25
|
+
keepdb-cc start --port 8080
|
|
26
|
+
keepdb-cc start --config /path/to/ismartify.config.yaml
|
|
27
|
+
`.trim(),P=t=>t&&!t.startsWith("-"),Z=t=>{let e=t.slice(2),o={command:null,config:null,port:null};for(let r=0;r<e.length;r++){let s=e[r];if(s==="--config"&&P(e[r+1]))o.config=e[++r];else if(s==="--port"&&P(e[r+1])){let i=Number(e[++r]);(!Number.isFinite(i)||i<1||i>65535)&&(console.error(`\u65E0\u6548\u7AEF\u53E3\u53F7: ${e[r]}`),process.exit(1)),o.port=i}else!s.startsWith("-")&&!o.command&&(o.command=s)}return o},ee=()=>{let{command:t,config:e,port:o}=Z(process.argv);t!=="start"&&(console.log(Y),process.exit(t?1:0));let r;try{r=E(e)}catch(i){console.error(`\u914D\u7F6E\u52A0\u8F7D\u5931\u8D25: ${i.message}`),process.exit(1)}let s=O(r);Q({fetch:s.fetch,port:o||6110},i=>{console.log(`@keepdb/cc \u4EE3\u7406\u670D\u52A1\u5DF2\u542F\u52A8: http://localhost:${i.port}`)})};ee();
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@keepdb/cc",
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "Anthropic API 代理 - 按模型档位路由、权重负载均衡、故障转移",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"keepdb-cc": "./dist/bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@hono/node-server": "^1.14.0",
|
|
14
|
+
"@keepdb/kit": "^0.4.22",
|
|
15
|
+
"hono": "^4.12.9",
|
|
16
|
+
"zod": "^3.24.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"tsup": "^8.4.0",
|
|
20
|
+
"typescript": "^6.0.2",
|
|
21
|
+
"vitest": "^4.1.1"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"dev": "tsx src/cli.js",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"release": "pnpm version patch --no-git-tag-version --no-git-checks && pnpm build && pnpm publish --no-git-checks --access public --registry https://registry.npmjs.org/"
|
|
29
|
+
}
|
|
30
|
+
}
|