diff options
| author | polwex <polwex@sortug.com> | 2025-07-23 05:41:58 +0700 |
|---|---|---|
| committer | polwex <polwex@sortug.com> | 2025-07-23 05:41:58 +0700 |
| commit | 9766782648617e232fbc4e40ea96a0e567c7cc73 (patch) | |
| tree | 084943c250a0ad6d7a3227a6b10b237ee07b2388 | |
| parent | 42dd99bfac9777a4ecc6700b87edf26a5c984de6 (diff) | |
something like that. man anthropic is old
| -rw-r--r-- | bun.lock | 69 | ||||
| -rw-r--r-- | index.ts | 75 | ||||
| -rw-r--r-- | package.json | 22 | ||||
| -rw-r--r-- | src/claude.ts | 104 | ||||
| -rw-r--r-- | src/gemini.ts | 121 | ||||
| -rw-r--r-- | src/generic.ts | 165 | ||||
| -rw-r--r-- | src/types/index.ts | 33 |
7 files changed, 343 insertions, 246 deletions
@@ -4,32 +4,31 @@ "": { "name": "models", "dependencies": { - "@anthropic-ai/sdk": "^0.36.3", - "@google/genai": "^0.13.0", - "@google/generative-ai": "^0.21.0", + "@anthropic-ai/sdk": "latest", + "@google/genai": "latest", "bcp-47": "^2.1.0", - "franc-all": "^7.2.0", - "groq-sdk": "^0.15.0", + "franc-all": "latest", + "groq-sdk": "latest", "iso-639-3": "^3.0.1", - "openai": "^4.84.0", + "mime-types": "^3.0.1", + "openai": "latest", "playht": "^0.16.0", - "replicate": "^1.0.1", - "sortug": "file://home/y/code/npm/sortug", + "replicate": "latest", + "sortug": "file://../sortug", }, "devDependencies": { - "@types/bun": "^1.2.12", + "@types/bun": "latest", + "@types/mime-types": "^3.0.1", }, "peerDependencies": { - "typescript": "^5.7.3", + "typescript": "latest", }, }, }, "packages": { - "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.36.3", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-+c0mMLxL/17yFZ4P5+U6bTWiCSFZUKJddrv01ud2aFBWnTPLdRncYV76D3q1tqfnL7aCnhRtykFnoCFzvr4U3Q=="], + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.57.0", "", { "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-z5LMy0MWu0+w2hflUgj4RlJr1R+0BxKXL7ldXTO8FasU8fu599STghO+QKwId2dAD0d464aHtU+ChWuRHw4FNw=="], - "@google/genai": ["@google/genai@0.13.0", "", { "dependencies": { "google-auth-library": "^9.14.2", "ws": "^8.18.0", "zod": "^3.22.4", "zod-to-json-schema": "^3.22.4" } }, "sha512-eaEncWt875H7046T04mOpxpHJUM+jLIljEf+5QctRyOeChylE/nhpwm1bZWTRWoOu/t46R9r+PmgsJFhTpE7tQ=="], - - "@google/generative-ai": ["@google/generative-ai@0.21.0", "", {}, "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg=="], + "@google/genai": ["@google/genai@1.10.0", "", { "dependencies": { "google-auth-library": "^9.14.2", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.11.0" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-PR4tLuiIFMrpAiiCko2Z16ydikFsPF1c5TBfI64hlZcv3xBEApSCceLuDYu1pNMq2SkNh4r66J4AG+ZexBnMLw=="], "@grpc/grpc-js": ["@grpc/grpc-js@1.12.6", "", { "dependencies": { "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-JXUj6PI0oqqzTGvKtzOkxtpsyPRNsrmhh41TtIz/zEB6J+AUiZZ0dxWzcMwO9Ns5rmSPuMdghlTbUuqIM48d3Q=="], @@ -59,13 +58,15 @@ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - "@types/bun": ["@types/bun@1.2.12", "", { "dependencies": { "bun-types": "1.2.12" } }, "sha512-lY/GQTXDGsolT/TiH72p1tuyUORuRrdV7VwOTOjDOt8uTBJQOJc5zz3ufwwDl0VBaoxotSk4LdP0hhjLJ6ypIQ=="], + "@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="], + + "@types/mime-types": ["@types/mime-types@3.0.1", "", {}, "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ=="], "@types/node": ["@types/node@18.19.75", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw=="], "@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="], - "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], @@ -91,7 +92,7 @@ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], - "bun-types": ["bun-types@1.2.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-tvWMx5vPqbRXgE8WUZI94iS1xAYs8bkqESR9cxBB1Wi+urvfTrF1uzuDgBHFAdO0+d2lmsbG3HmeKMvUyj6pWA=="], + "bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="], "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], @@ -105,6 +106,8 @@ "cross-fetch": ["cross-fetch@4.1.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw=="], + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], @@ -143,7 +146,7 @@ "google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="], - "groq-sdk": ["groq-sdk@0.15.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-aYDEdr4qczx3cLCRRe+Beb37I7g/9bD5kHF+EEDxcrREWw1vKoRcfP3vHEkJB7Ud/8oOuF0scRwDpwWostTWuQ=="], + "groq-sdk": ["groq-sdk@0.27.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-fiEou5jdIgXGolo6fz5N2G++0fGMRhC/GzAqKtSnagt9eOwFn7C8FYjVeqs7ah6/VN/yygEUvFVJa0XD6T+3mg=="], "gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="], @@ -175,9 +178,9 @@ "long": ["long@5.3.0", "", {}, "sha512-5vvY5yF1zF/kXk+L94FRiTDa1Znom46UjPCH6/XbSvS8zBKMFBHTJk8KDMqJ+2J6QezQFi7k1k8v21ClJYHPaw=="], - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -187,7 +190,7 @@ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - "openai": ["openai@4.84.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-Smu45QjqWcPXkTHAI7GSeW+sI9ZOqB9VW4wiuhBvWLGHYC9dn/3rIpG8PUysbCT+ciVyDdEdsx0zkgKQDoOx9Q=="], + "openai": ["openai@5.10.2", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-n+vi74LzHtvlKcDPn9aApgELGiu5CwhaLG40zxLTlFQdoSJCLACORIPC2uVQ3JEYAbqapM+XyRKFy2Thej7bIw=="], "peek-readable": ["peek-readable@5.4.2", "", {}, "sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg=="], @@ -209,7 +212,7 @@ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - "sortug": ["sortug@file:../../../npm/sortug", { "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }], + "sortug": ["sortug@file:../sortug", { "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }], "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -227,7 +230,7 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], @@ -249,32 +252,20 @@ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - "zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="], - - "zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="], - "@types/node-fetch/@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="], - "@types/ws/@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="], - "bun-types/@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="], - "protobufjs/@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="], + "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "sortug/@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="], + "protobufjs/@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="], "@types/node-fetch/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "@types/ws/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "bun-types/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "protobufjs/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "sortug/@types/bun/bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="], - - "sortug/@types/bun/bun-types/@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="], + "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "sortug/@types/bun/bun-types/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + "protobufjs/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], } } @@ -2,35 +2,68 @@ import Claude from "./src/claude"; import Gemini from "./src/gemini"; import Generic from "./src/generic"; -import type { AIModelAPI, AIModelChoice } from "./src/types"; +import type { AIModelAPI, LLMChoice } from "./src/types"; export type * from "./src/types"; export * as NLP from "./src/nlp"; -export default function (choice: AIModelChoice): AIModelAPI { +export default function (choice: LLMChoice): AIModelAPI { const api = - "other" in choice - ? new Generic(choice.other) - : choice.name === "deepseek" - ? new Generic({ - baseURL: "https://api.deepseek.com", - apiKey: Bun.env.DEEPSEEK_API_KEY!, - model: "deepseek-chat", - }) - : choice.name === "grok" + "gemini" in choice + ? new Gemini(choice.gemini) + : "claude" in choice + ? new Claude(choice.claude) + : "chatgpt" in choice ? new Generic({ - baseURL: "https://api.x.ai/v1", - apiKey: Bun.env.GROK_API_KEY!, - model: "grok-2-latest", + baseURL: "https://api.openai.com/v1", + apiKey: Bun.env.OPENAI_API_KEY!, + model: choice.chatgpt || "o4-mini", }) - : choice.name === "chatgpt" + : "deepseek" in choice ? new Generic({ - baseURL: "https://api.openai.com/v1", - apiKey: Bun.env.OPENAI_API_KEY!, - model: "gpt-4o", + baseURL: "https://api.deepseek.com", + apiKey: Bun.env.DEEPSEEK_API_KEY!, + model: "deepseek-reasoner", }) - : choice.name === "claude" - ? new Claude() - : new Gemini(); + : "kimi" in choice + ? new Generic({ + baseURL: "https://api.moonshot.ai/v1", + apiKey: Bun.env.MOONSHOT_API_KEY!, + model: "kimi-k2-0711-preview", // "kimi-latest"? + }) + : "grok" in choice + ? new Generic({ + baseURL: "https://api.x.ai/v1", + apiKey: Bun.env.XAI_API_KEY!, + model: "grok-4", // "kimi-latest"? + }) + : new Generic({ + baseURL: choice.openai.url, + apiKey: choice.openai.apiKey, + model: choice.openai.model, + }); + // "" in choice + // ? new Generic(choice.other) + // : choice.name === "deepseek" + // ? new Generic({ + // baseURL: "https://api.deepseek.com", + // apiKey: Bun.env.DEEPSEEK_API_KEY!, + // model: "deepseek-chat", + // }) + // : choice.name === "grok" + // ? new Generic({ + // baseURL: "https://api.x.ai/v1", + // apiKey: Bun.env.GROK_API_KEY!, + // model: "grok-2-latest", + // }) + // : choice.name === "chatgpt" + // ? new Generic({ + // baseURL: "https://api.openai.com/v1", + // apiKey: Bun.env.OPENAI_API_KEY!, + // model: "gpt-4o", + // }) + // : choice.name === "claude" + // ? new Claude() + // : new Gemini(); return api; } diff --git a/package.json b/package.json index 53e21ee..277fb2e 100644 --- a/package.json +++ b/package.json @@ -2,23 +2,25 @@ "name": "models", "module": "index.ts", "type": "module", + "version": "2.0.0", "devDependencies": { - "@types/bun": "^1.2.12" + "@types/bun": "latest", + "@types/mime-types": "^3.0.1" }, "peerDependencies": { - "typescript": "^5.7.3" + "typescript": "latest" }, "dependencies": { - "@anthropic-ai/sdk": "^0.36.3", - "@google/genai": "^0.13.0", - "@google/generative-ai": "^0.21.0", + "@anthropic-ai/sdk": "latest", + "@google/genai": "latest", "bcp-47": "^2.1.0", - "franc-all": "^7.2.0", - "groq-sdk": "^0.15.0", + "franc-all": "latest", + "groq-sdk": "latest", "iso-639-3": "^3.0.1", - "openai": "^4.84.0", + "mime-types": "^3.0.1", + "openai": "latest", "playht": "^0.16.0", - "replicate": "^1.0.1", - "sortug": "file://home/y/code/npm/sortug" + "replicate": "latest", + "sortug": "file://../sortug" } } diff --git a/src/claude.ts b/src/claude.ts index 2a56bc1..629f9c2 100644 --- a/src/claude.ts +++ b/src/claude.ts @@ -1,26 +1,21 @@ import Claude from "@anthropic-ai/sdk"; import { RESPONSE_LENGTH } from "./logic/constants"; -import type { - AIModelAPI, - ChatMessage, - OChoice, - OChunk, - OMessage, -} from "./types"; +import type { AIModelAPI, ChatMessage } from "./types"; import { BOOKWORM_SYS } from "./prompts"; import type { AsyncRes } from "sortug"; +import type { MessageCreateParamsStreaming } from "@anthropic-ai/sdk/resources"; type Message = Claude.Messages.MessageParam; export default class ClaudeAPI implements AIModelAPI { - private model: string = "claude-3-7-sonnet-20250219"; + private model: string = "claude-opus-4-20250514"; tokenizer: (text: string) => number; maxTokens: number; // model: string = "claude-3-5-sonnet-20241022"; constructor( + model: string, maxTokens = 200_000, tokenizer: (text: string) => number = (text) => text.length / 3, - model?: string, ) { this.maxTokens = maxTokens; this.tokenizer = tokenizer; @@ -36,30 +31,17 @@ export default class ClaudeAPI implements AIModelAPI { }); } - private mapMessagesR1(input: ChatMessage[]): Message[] { - return input.reduce((acc: Message[], m, i) => { - const prev = acc[i - 1]; - const role: any = m.author === "claude" ? "assistant" : "user"; - const msg = { role, content: m.text }; - if (prev?.role === role) acc[i - 1] = msg; - else acc = [...acc, msg]; - return acc; - }, []); - } - - public async send(sys: string, input: ChatMessage[]) { - const messages = this.mapMessages(input); + public async send(input: string | ChatMessage[], sys?: string) { + const msgs: ChatMessage[] = + typeof input === "string" + ? [{ author: "user", text: input, sent: 0 }] + : input; + const messages = this.mapMessages(msgs); const truncated = this.truncateHistory(messages); - const res = await this.apiCall(sys, truncated); + const res = await this.apiCall(truncated, sys); return res; } - public async sendR1(input: ChatMessage[]) { - const messages = this.mapMessagesR1(input); - const truncated = this.truncateHistory(messages); - const res = await this.apiCall("", truncated, true); - return res; - } public async sendDoc(data: string) { const sys = BOOKWORM_SYS; const msg: Message = { @@ -75,24 +57,22 @@ export default class ClaudeAPI implements AIModelAPI { }, ], }; - const res = await this.apiCall(sys, [msg]); + const res = await this.apiCall([msg], sys); return res; } public async stream( - sys: string, - input: ChatMessage[], + input: string | ChatMessage[], handle: (c: any) => void, + sys?: string, ) { - const messages = this.mapMessages(input); + const msgs: ChatMessage[] = + typeof input === "string" + ? [{ author: "user", text: input, sent: 0 }] + : input; + const messages = this.mapMessages(msgs); const truncated = this.truncateHistory(messages); - await this.apiCallStream(sys, truncated, handle); - } - - public async streamR1(input: ChatMessage[], handle: (c: any) => void) { - const messages = this.mapMessagesR1(input); - const truncated = this.truncateHistory(messages); - await this.apiCallStream("", truncated, handle, true); + await this.apiCallStream(truncated, handle, sys); } private truncateHistory(messages: Message[]): Message[] { @@ -108,26 +88,30 @@ export default class ClaudeAPI implements AIModelAPI { // TODO // https://docs.anthropic.com/en/api/messages-examples#putting-words-in-claudes-mouth private async apiCall( - system: string, messages: Message[], - isR1: boolean = false, - ): Promise<AsyncRes<string[]>> { + system?: string, + ): Promise<AsyncRes<string>> { try { const claud = new Claude(); - // const list = await claud.models.list(); - // console.log(list.data); - const res = await claud.messages.create({ + const params = { model: this.model, max_tokens: RESPONSE_LENGTH, - system, messages, - }); - return { - ok: res.content.reduce((acc: string[], item) => { - if (item.type === "tool_use") return acc; - else return [...acc, item.text]; - }, []), }; + const res = await claud.messages.create( + system ? { ...params, system } : params, + ); + const resm: string = res.content.reduce((acc: string, item) => { + if (item.type === "tool_use") return acc; + else if (item.type === "text") return `${acc}\n${item.text}`; + else return acc; + }, ""); + // const resm = res.content.reduce((acc: string[], item) => { + // if (item.type === "tool_use") return acc; + // else if (item.type === "text") return [...acc, item.text] + // else return acc; + // }, []); + return { ok: resm }; } catch (e) { console.log(e, "error in claude api"); return { error: `${e}` }; @@ -135,20 +119,22 @@ export default class ClaudeAPI implements AIModelAPI { } private async apiCallStream( - system: string, messages: Message[], handle: (c: any) => void, - isR1: boolean = false, + system?: string, ): Promise<void> { try { const claud = new Claude(); - const stream = await claud.messages.create({ + const params = { model: this.model, max_tokens: RESPONSE_LENGTH, - system, messages, - stream: true, - }); + stream: true as true, + }; + const fparams: MessageCreateParamsStreaming = system + ? { ...params, system } + : params; + const stream = await claud.messages.create(fparams); for await (const part of stream) { if (part.type === "message_start") continue; diff --git a/src/gemini.ts b/src/gemini.ts index 3e636c2..5c7267b 100644 --- a/src/gemini.ts +++ b/src/gemini.ts @@ -1,21 +1,19 @@ +import mime from "mime-types"; import { Chat, + createPartFromBase64, + createPartFromUri, + createUserContent, GoogleGenAI, type Content, type GeneratedImage, type GeneratedVideo, + type Part, } from "@google/genai"; -import { RESPONSE_LENGTH } from "./logic/constants"; -import type { - AIModelAPI, - ChatMessage, - OChoice, - OChunk, - OMessage, -} from "./types"; -import type { AsyncRes } from "sortug"; +import type { AIModelAPI, InputToken } from "./types"; +import type { AsyncRes, Result } from "sortug"; -export default class GeminiAPI { +export default class GeminiAPI implements AIModelAPI { tokenizer: (text: string) => number; maxTokens: number; private model: string; @@ -23,70 +21,109 @@ export default class GeminiAPI { chats: Map<string, Chat> = new Map<string, Chat>(); constructor( + model?: string, maxTokens = 200_000, tokenizer: (text: string) => number = (text) => text.length / 3, - model?: string, ) { this.maxTokens = maxTokens; this.tokenizer = tokenizer; const gem = new GoogleGenAI({ apiKey: Bun.env["GEMINI_API_KEY"]! }); this.api = gem; - this.model = model || "gemini-2.5-pro-preview-05-06 "; + this.model = model || "gemini-2.5-pro"; } - createChat({ name, history }: { name?: string; history?: Content[] }) { - const chat = this.api.chats.create({ model: this.model, history }); - this.chats.set(name ? name : Date.now().toString(), chat); + // input data in gemini gets pretty involved + // + // data + // Union type + // data can be only one of the following: + // text + // string + // Inline text. + + // inlineData + // object (Blob) + // Inline media bytes. + + // functionCall + // object (FunctionCall) + // A predicted FunctionCall returned from the model that contains a string representing the FunctionDeclaration.name with the arguments and their values. + + // functionResponse + // object (FunctionResponse) + // The result output of a FunctionCall that contains a string representing the FunctionDeclaration.name and a structured JSON object containing any output from the function is used as context to the model. + + // fileData + // object (FileData) + // URI based data. + + // executableCode + // object (ExecutableCode) + // Code generated by the model that is meant to be executed. + + // codeExecutionResult + // object (CodeExecutionResult) + // Result of executing the ExecutableCode. + + // metadata + // Union type + public setModel(model: string) { + this.model = model; } - async followChat(name: string, message: string): AsyncRes<string> { - const chat = this.chats.get(name); - if (!chat) return { error: "no chat with that name" }; - else { - const response = await chat.sendMessage({ message }); - const text = response.text; - return { ok: text || "" }; - } + private contentFromImage(imageString: string): Result<Part> { + // TODO + const mimeType = mime.lookup(imageString); + if (!mimeType) return { error: "no mimetype" }; + const url = URL.parse(imageString); + if (url) { + const part = createPartFromUri(imageString, mimeType); + return { ok: part }; + } else return { ok: createPartFromBase64(imageString, mimeType) }; } - async followChatStream( - name: string, - message: string, - handler: (data: string) => void, - ) { - const chat = this.chats.get(name); - if (!chat) throw new Error("no chat!"); - else { - const response = await chat.sendMessageStream({ message }); - for await (const chunk of response) { - const text = chunk.text; - handler(text || ""); - } + public buildInput(tokens: InputToken[]): Result<Content> { + try { + const input = createUserContent( + tokens.map((t) => { + if ("text" in t) return t.text; + if ("img" in t) { + const imagePart = this.contentFromImage(t.img); + if ("error" in imagePart) throw new Error("image failed"); + else return imagePart.ok; + } + return "oy vey"; + }), + ); + return { ok: input }; + } catch (e) { + return { error: `${e}` }; } } - async send(message: string, systemPrompt?: string): AsyncRes<string> { + async send(input: string | Content, systemPrompt?: string): AsyncRes<string> { try { const opts = { model: this.model, - contents: message, + contents: input, }; const fopts = systemPrompt ? { ...opts, config: { systemInstruction: systemPrompt } } : opts; const response = await this.api.models.generateContent(fopts); - return { ok: response.text || "" }; + if (!response.text) return { error: "no text in response" }; + return { ok: response.text }; } catch (e) { return { error: `${e}` }; } } - async sendStream( + async stream( + input: string | Content, handler: (s: string) => void, - message: string, systemPrompt?: string, ) { const opts = { model: this.model, - contents: message, + contents: input, }; const fopts = systemPrompt ? { ...opts, config: { systemInstruction: systemPrompt } } diff --git a/src/generic.ts b/src/generic.ts index 50c4435..ac6b55b 100644 --- a/src/generic.ts +++ b/src/generic.ts @@ -1,9 +1,13 @@ import OpenAI from "openai"; import { MAX_TOKENS, RESPONSE_LENGTH } from "./logic/constants"; -import type { AIModelAPI, ChatMessage, OChoice } from "./types"; +import type { AIModelAPI, ChatMessage, InputToken, OChoice } from "./types"; import type { AsyncRes } from "sortug"; - -type Message = OpenAI.Chat.Completions.ChatCompletionMessageParam; +import type { + ResponseCreateParamsBase, + ResponseCreateParamsNonStreaming, + ResponseCreateParamsStreaming, + ResponseInput, +} from "openai/resources/responses/responses.mjs"; type Props = { baseURL: string; @@ -31,25 +35,35 @@ export default class OpenAIAPI implements AIModelAPI { public setModel(model: string) { this.model = model; } - private mapMessages(input: ChatMessage[]): Message[] { - return input.map((m) => { - return { role: m.author as any, content: m.text, name: m.author }; - }); + + public buildInput(tokens: InputToken[]): ResponseInput { + return [ + { + role: "user", + content: tokens.map((t) => + "text" in t + ? { type: "input_text", text: t.text } + : "img" in t + ? { type: "input_image", image_url: t.img, detail: "auto" } + : { type: "input_text", text: "oy vey" }, + ), + }, + ]; } - public async send(sys: string, input: ChatMessage[]): AsyncRes<string[]> { - const messages = this.mapMessages(input); - const sysMsg: Message = { role: "system", content: sys }; - const allMessages = [sysMsg, ...messages]; - console.log("before truncation", allMessages); - const truncated = this.truncateHistory(allMessages); - const res = await this.apiCall(truncated); + // OpenAI SDK has three kinds ReponseInputContent: text image and file + // images can be URLs or base64 dataurl thingies + // + public async send( + input: string | ResponseInput, + sys?: string, + ): AsyncRes<string> { + const params = sys ? { instructions: sys, input } : { input }; + const res = await this.apiCall(params); if ("error" in res) return res; else { try { - // TODO type this properly - const choices: OChoice[] = res.ok; - return { ok: choices.map((c) => c.message.content!) }; + return { ok: res.ok.output_text }; } catch (e) { return { error: `${e}` }; } @@ -57,41 +71,29 @@ export default class OpenAIAPI implements AIModelAPI { } public async stream( - sys: string, - input: ChatMessage[], + input: string, handle: (c: string) => void, + sys?: string, ) { - const messages = this.mapMessages(input); - const sysMsg: Message = { role: "system", content: sys }; - const allMessages = [sysMsg, ...messages]; - const truncated = this.truncateHistory(allMessages); - await this.apiCallStream(truncated, handle); - } - - private truncateHistory(messages: Message[]): Message[] { - const totalTokens = messages.reduce((total, message) => { - return total + this.tokenizer(message.content as string); - }, 0); - while (totalTokens > this.maxTokens && messages.length > 1) { - // Always keep the system message if it exists - const startIndex = messages[0].role === "system" ? 1 : 0; - messages.splice(startIndex, 1); - } - return messages; + const params = sys ? { instructions: sys, input } : { input }; + await this.apiCallStream(params, handle); } // TODO custom temperature? - private async apiCall(messages: Message[]): AsyncRes<OChoice[]> { - console.log({ messages }, "at the very end"); + private async apiCall( + params: ResponseCreateParamsNonStreaming, + ): AsyncRes<OpenAI.Responses.Response> { try { - const completion = await this.api.chat.completions.create({ - temperature: 1.3, - model: this.model, - messages, - max_tokens: RESPONSE_LENGTH, + const res = await this.api.responses.create({ + ...params, + // temperature: 1.3, + model: params.model || this.model, + input: params.input, + max_output_tokens: params.max_output_tokens || RESPONSE_LENGTH, + stream: false, }); - if (!completion) return { error: "null response from openai" }; - return { ok: completion.choices }; + // TODO damn there's a lot of stuff here + return { ok: res }; } catch (e) { console.log(e, "error in openai api"); return { error: `${e}` }; @@ -99,30 +101,65 @@ export default class OpenAIAPI implements AIModelAPI { } private async apiCallStream( - messages: Message[], - handle: (c: string) => void, - ): Promise<void> { + params: ResponseCreateParamsBase, + handler: (c: string) => void, + ) { + // temperature: 1.3, + const pms: ResponseCreateParamsStreaming = { + ...params, + stream: true, + model: params.model || this.model, + input: params.input, + max_output_tokens: params.max_output_tokens || RESPONSE_LENGTH, + }; try { - const stream = await this.api.chat.completions.create({ - temperature: 1.3, - model: this.model, - messages, - max_tokens: RESPONSE_LENGTH, - stream: true, - }); - - for await (const chunk of stream) { - for (const choice of chunk.choices) { - console.log({ choice }); - if (!choice.delta) continue; - const cont = choice.delta.content; - if (!cont) continue; - handle(cont); + const stream = await this.api.responses.create(pms); + for await (const event of stream) { + console.log(event); + switch (event.type) { + // TODO deal with audio and whatever + case "response.output_text.delta": + handler(event.delta); + break; + case "response.completed": + break; + default: + break; } + // if (event.type === "response.completed") + // wtf how do we use this } } catch (e) { console.log(e, "error in openai api"); - handle(`Error streaming OpenAI, ${e}`); + return { error: `${e}` }; } } + + // private async apiCallStream( + // messages: Message[], + // handle: (c: string) => void, + // ): Promise<void> { + // try { + // const stream = await this.api.chat.completions.create({ + // temperature: 1.3, + // model: this.model, + // messages, + // max_tokens: RESPONSE_LENGTH, + // stream: true, + // }); + + // for await (const chunk of stream) { + // for (const choice of chunk.choices) { + // console.log({ choice }); + // if (!choice.delta) continue; + // const cont = choice.delta.content; + // if (!cont) continue; + // handle(cont); + // } + // } + // } catch (e) { + // console.log(e, "error in openai api"); + // handle(`Error streaming OpenAI, ${e}`); + // } + // } } diff --git a/src/types/index.ts b/src/types/index.ts index b276457..6c16e0d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,3 @@ -import type OpenAI from "openai"; import type { AsyncRes } from "sortug"; export type ChatMessage = { author: string; @@ -7,23 +6,35 @@ export type ChatMessage = { reasoning?: string; }; +export type InputToken = { text: string } | { img: string }; +// me +export type RequestOptions = { + textOutput: boolean; +}; +export const defaultOptions: RequestOptions = { + textOutput: true, +}; // openai -export type OChoice = OpenAI.Chat.Completions.ChatCompletion.Choice; -export type OChunk = OpenAI.Chat.Completions.ChatCompletionChunk.Choice; -export type OMessage = OpenAI.Chat.Completions.ChatCompletionMessageParam; export type ContentType = { text: string } | { audio: Response }; -export type AIModelChoice = - | { name: "deepseek" | "chatgpt" | "claude" | "gemini" | "grok" } - | { other: { baseURL: string; apiKey: string } }; + export interface AIModelAPI { setModel: (model: string) => void; tokenizer: (text: string) => number; maxTokens: number; - send: (systemPrompt: string, input: ChatMessage[]) => AsyncRes<string[]>; + send: (input: string, systemPrompt?: string) => AsyncRes<string>; stream: ( - systemPrompt: string, - input: ChatMessage[], - handler: (data: any) => void, + input: string, + handler: (data: string) => void, + systemPrompt?: string, ) => void; } + +export type LLMChoice = + | { gemini: string } + | { claude: string } + | { chatgpt: string } + | { grok: string } + | { deepseek: string } + | { kimi: string } + | { openai: { url: string; apiKey: string; model: string } }; |
