@musistudio/claude-code-router 1.0.24 → 1.0.25
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/README.md +39 -0
- package/README_zh.md +39 -0
- package/dist/cli.js +42 -41
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -226,6 +226,45 @@ You can also switch models dynamically in Claude Code with the `/model` command:
|
|
|
226
226
|
`/model provider_name,model_name`
|
|
227
227
|
Example: `/model openrouter,anthropic/claude-3.5-sonnet`
|
|
228
228
|
|
|
229
|
+
#### Custom Router
|
|
230
|
+
|
|
231
|
+
For more advanced routing logic, you can specify a custom router script via the `CUSTOM_ROUTER_PATH` in your `config.json`. This allows you to implement complex routing rules beyond the default scenarios.
|
|
232
|
+
|
|
233
|
+
In your `config.json`:
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"CUSTOM_ROUTER_PATH": "$HOME/.claude-code-router/custom-router.js"
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The custom router file must be a JavaScript module that exports an `async` function. This function receives the request object and the config object as arguments and should return the provider and model name as a string (e.g., `"provider_name,model_name"`), or `null` to fall back to the default router.
|
|
242
|
+
|
|
243
|
+
Here is an example of a `custom-router.js` based on `custom-router.example.js`:
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
// $HOME/.claude-code-router/custom-router.js
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* A custom router function to determine which model to use based on the request.
|
|
250
|
+
*
|
|
251
|
+
* @param {object} req - The request object from Claude Code, containing the request body.
|
|
252
|
+
* @param {object} config - The application's config object.
|
|
253
|
+
* @returns {Promise<string|null>} - A promise that resolves to the "provider,model_name" string, or null to use the default router.
|
|
254
|
+
*/
|
|
255
|
+
module.exports = async function router(req, config) {
|
|
256
|
+
const userMessage = req.body.messages.find(m => m.role === 'user')?.content;
|
|
257
|
+
|
|
258
|
+
if (userMessage && userMessage.includes('explain this code')) {
|
|
259
|
+
// Use a powerful model for code explanation
|
|
260
|
+
return 'openrouter,anthropic/claude-3.5-sonnet';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Fallback to the default router configuration
|
|
264
|
+
return null;
|
|
265
|
+
};
|
|
266
|
+
```
|
|
267
|
+
|
|
229
268
|
|
|
230
269
|
## 🤖 GitHub Actions
|
|
231
270
|
|
package/README_zh.md
CHANGED
|
@@ -222,6 +222,45 @@ Transformers 允许您修改请求和响应负载,以确保与不同提供商
|
|
|
222
222
|
`/model provider_name,model_name`
|
|
223
223
|
示例: `/model openrouter,anthropic/claude-3.5-sonnet`
|
|
224
224
|
|
|
225
|
+
#### 自定义路由器
|
|
226
|
+
|
|
227
|
+
对于更高级的路由逻辑,您可以在 `config.json` 中通过 `CUSTOM_ROUTER_PATH` 字段指定一个自定义路由器脚本。这允许您实现超出默认场景的复杂路由规则。
|
|
228
|
+
|
|
229
|
+
在您的 `config.json` 中配置:
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"CUSTOM_ROUTER_PATH": "$HOME/.claude-code-router/custom-router.js"
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
自定义路由器文件必须是一个导出 `async` 函数的 JavaScript 模块。该函数接收请求对象和配置对象作为参数,并应返回提供商和模型名称的字符串(例如 `"provider_name,model_name"`),如果返回 `null` 则回退到默认路由。
|
|
238
|
+
|
|
239
|
+
这是一个基于 `custom-router.example.js` 的 `custom-router.js` 示例:
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
// $HOME/.claude-code-router/custom-router.js
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 一个自定义路由函数,用于根据请求确定使用哪个模型。
|
|
246
|
+
*
|
|
247
|
+
* @param {object} req - 来自 Claude Code 的请求对象,包含请求体。
|
|
248
|
+
* @param {object} config - 应用程序的配置对象。
|
|
249
|
+
* @returns {Promise<string|null>} - 一个解析为 "provider,model_name" 字符串的 Promise,如果返回 null,则使用默认路由。
|
|
250
|
+
*/
|
|
251
|
+
module.exports = async function router(req, config) {
|
|
252
|
+
const userMessage = req.body.messages.find(m => m.role === 'user')?.content;
|
|
253
|
+
|
|
254
|
+
if (userMessage && userMessage.includes('解释这段代码')) {
|
|
255
|
+
// 为代码解释任务使用更强大的模型
|
|
256
|
+
return 'openrouter,anthropic/claude-3.5-sonnet';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 回退到默认的路由配置
|
|
260
|
+
return null;
|
|
261
|
+
};
|
|
262
|
+
```
|
|
263
|
+
|
|
225
264
|
|
|
226
265
|
## 🤖 GitHub Actions
|
|
227
266
|
|
package/dist/cli.js
CHANGED
|
@@ -4457,7 +4457,7 @@ var require_atomic_sleep = __commonJS({
|
|
|
4457
4457
|
var require_sonic_boom = __commonJS({
|
|
4458
4458
|
"node_modules/.pnpm/sonic-boom@4.2.0/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
4459
4459
|
"use strict";
|
|
4460
|
-
var
|
|
4460
|
+
var fs4 = require("fs");
|
|
4461
4461
|
var EventEmitter = require("events");
|
|
4462
4462
|
var inherits = require("util").inherits;
|
|
4463
4463
|
var path3 = require("path");
|
|
@@ -4514,20 +4514,20 @@ var require_sonic_boom = __commonJS({
|
|
|
4514
4514
|
const mode = sonic.mode;
|
|
4515
4515
|
if (sonic.sync) {
|
|
4516
4516
|
try {
|
|
4517
|
-
if (sonic.mkdir)
|
|
4518
|
-
const fd =
|
|
4517
|
+
if (sonic.mkdir) fs4.mkdirSync(path3.dirname(file), { recursive: true });
|
|
4518
|
+
const fd = fs4.openSync(file, flags, mode);
|
|
4519
4519
|
fileOpened(null, fd);
|
|
4520
4520
|
} catch (err) {
|
|
4521
4521
|
fileOpened(err);
|
|
4522
4522
|
throw err;
|
|
4523
4523
|
}
|
|
4524
4524
|
} else if (sonic.mkdir) {
|
|
4525
|
-
|
|
4525
|
+
fs4.mkdir(path3.dirname(file), { recursive: true }, (err) => {
|
|
4526
4526
|
if (err) return fileOpened(err);
|
|
4527
|
-
|
|
4527
|
+
fs4.open(file, flags, mode, fileOpened);
|
|
4528
4528
|
});
|
|
4529
4529
|
} else {
|
|
4530
|
-
|
|
4530
|
+
fs4.open(file, flags, mode, fileOpened);
|
|
4531
4531
|
}
|
|
4532
4532
|
}
|
|
4533
4533
|
function SonicBoom(opts) {
|
|
@@ -4568,16 +4568,16 @@ var require_sonic_boom = __commonJS({
|
|
|
4568
4568
|
this.flush = flushBuffer;
|
|
4569
4569
|
this.flushSync = flushBufferSync;
|
|
4570
4570
|
this._actualWrite = actualWriteBuffer;
|
|
4571
|
-
fsWriteSync = () =>
|
|
4572
|
-
fsWrite = () =>
|
|
4571
|
+
fsWriteSync = () => fs4.writeSync(this.fd, this._writingBuf);
|
|
4572
|
+
fsWrite = () => fs4.write(this.fd, this._writingBuf, this.release);
|
|
4573
4573
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
4574
4574
|
this._writingBuf = "";
|
|
4575
4575
|
this.write = write;
|
|
4576
4576
|
this.flush = flush;
|
|
4577
4577
|
this.flushSync = flushSync;
|
|
4578
4578
|
this._actualWrite = actualWrite;
|
|
4579
|
-
fsWriteSync = () =>
|
|
4580
|
-
fsWrite = () =>
|
|
4579
|
+
fsWriteSync = () => fs4.writeSync(this.fd, this._writingBuf, "utf8");
|
|
4580
|
+
fsWrite = () => fs4.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
4581
4581
|
} else {
|
|
4582
4582
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
4583
4583
|
}
|
|
@@ -4633,7 +4633,7 @@ var require_sonic_boom = __commonJS({
|
|
|
4633
4633
|
}
|
|
4634
4634
|
}
|
|
4635
4635
|
if (this._fsync) {
|
|
4636
|
-
|
|
4636
|
+
fs4.fsyncSync(this.fd);
|
|
4637
4637
|
}
|
|
4638
4638
|
const len = this._len;
|
|
4639
4639
|
if (this._reopening) {
|
|
@@ -4745,7 +4745,7 @@ var require_sonic_boom = __commonJS({
|
|
|
4745
4745
|
const onDrain = () => {
|
|
4746
4746
|
if (!this._fsync) {
|
|
4747
4747
|
try {
|
|
4748
|
-
|
|
4748
|
+
fs4.fsync(this.fd, (err) => {
|
|
4749
4749
|
this._flushPending = false;
|
|
4750
4750
|
cb(err);
|
|
4751
4751
|
});
|
|
@@ -4847,7 +4847,7 @@ var require_sonic_boom = __commonJS({
|
|
|
4847
4847
|
const fd = this.fd;
|
|
4848
4848
|
this.once("ready", () => {
|
|
4849
4849
|
if (fd !== this.fd) {
|
|
4850
|
-
|
|
4850
|
+
fs4.close(fd, (err) => {
|
|
4851
4851
|
if (err) {
|
|
4852
4852
|
return this.emit("error", err);
|
|
4853
4853
|
}
|
|
@@ -4896,7 +4896,7 @@ var require_sonic_boom = __commonJS({
|
|
|
4896
4896
|
buf = this._bufs[0];
|
|
4897
4897
|
}
|
|
4898
4898
|
try {
|
|
4899
|
-
const n =
|
|
4899
|
+
const n = fs4.writeSync(this.fd, buf, "utf8");
|
|
4900
4900
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
4901
4901
|
buf = releasedBufObj.writingBuf;
|
|
4902
4902
|
this._len = releasedBufObj.len;
|
|
@@ -4912,7 +4912,7 @@ var require_sonic_boom = __commonJS({
|
|
|
4912
4912
|
}
|
|
4913
4913
|
}
|
|
4914
4914
|
try {
|
|
4915
|
-
|
|
4915
|
+
fs4.fsyncSync(this.fd);
|
|
4916
4916
|
} catch {
|
|
4917
4917
|
}
|
|
4918
4918
|
}
|
|
@@ -4933,7 +4933,7 @@ var require_sonic_boom = __commonJS({
|
|
|
4933
4933
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
4934
4934
|
}
|
|
4935
4935
|
try {
|
|
4936
|
-
const n =
|
|
4936
|
+
const n = fs4.writeSync(this.fd, buf);
|
|
4937
4937
|
buf = buf.subarray(n);
|
|
4938
4938
|
this._len = Math.max(this._len - n, 0);
|
|
4939
4939
|
if (buf.length <= 0) {
|
|
@@ -4961,13 +4961,13 @@ var require_sonic_boom = __commonJS({
|
|
|
4961
4961
|
this._writingBuf = this._writingBuf || this._bufs.shift() || "";
|
|
4962
4962
|
if (this.sync) {
|
|
4963
4963
|
try {
|
|
4964
|
-
const written =
|
|
4964
|
+
const written = fs4.writeSync(this.fd, this._writingBuf, "utf8");
|
|
4965
4965
|
release(null, written);
|
|
4966
4966
|
} catch (err) {
|
|
4967
4967
|
release(err);
|
|
4968
4968
|
}
|
|
4969
4969
|
} else {
|
|
4970
|
-
|
|
4970
|
+
fs4.write(this.fd, this._writingBuf, "utf8", release);
|
|
4971
4971
|
}
|
|
4972
4972
|
}
|
|
4973
4973
|
function actualWriteBuffer() {
|
|
@@ -4976,7 +4976,7 @@ var require_sonic_boom = __commonJS({
|
|
|
4976
4976
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
4977
4977
|
if (this.sync) {
|
|
4978
4978
|
try {
|
|
4979
|
-
const written =
|
|
4979
|
+
const written = fs4.writeSync(this.fd, this._writingBuf);
|
|
4980
4980
|
release(null, written);
|
|
4981
4981
|
} catch (err) {
|
|
4982
4982
|
release(err);
|
|
@@ -4985,7 +4985,7 @@ var require_sonic_boom = __commonJS({
|
|
|
4985
4985
|
if (kCopyBuffer) {
|
|
4986
4986
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
4987
4987
|
}
|
|
4988
|
-
|
|
4988
|
+
fs4.write(this.fd, this._writingBuf, release);
|
|
4989
4989
|
}
|
|
4990
4990
|
}
|
|
4991
4991
|
function actualClose(sonic) {
|
|
@@ -5001,12 +5001,12 @@ var require_sonic_boom = __commonJS({
|
|
|
5001
5001
|
sonic._lens = [];
|
|
5002
5002
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
5003
5003
|
try {
|
|
5004
|
-
|
|
5004
|
+
fs4.fsync(sonic.fd, closeWrapped);
|
|
5005
5005
|
} catch {
|
|
5006
5006
|
}
|
|
5007
5007
|
function closeWrapped() {
|
|
5008
5008
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
5009
|
-
|
|
5009
|
+
fs4.close(sonic.fd, done);
|
|
5010
5010
|
} else {
|
|
5011
5011
|
done();
|
|
5012
5012
|
}
|
|
@@ -18779,12 +18779,12 @@ var require_dist2 = __commonJS({
|
|
|
18779
18779
|
throw new Error(`Unknown format "${name}"`);
|
|
18780
18780
|
return f;
|
|
18781
18781
|
};
|
|
18782
|
-
function addFormats(ajv, list,
|
|
18782
|
+
function addFormats(ajv, list, fs4, exportName) {
|
|
18783
18783
|
var _a;
|
|
18784
18784
|
var _b;
|
|
18785
18785
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
18786
18786
|
for (const f of list)
|
|
18787
|
-
ajv.addFormat(f,
|
|
18787
|
+
ajv.addFormat(f, fs4[f]);
|
|
18788
18788
|
}
|
|
18789
18789
|
module2.exports = exports2 = formatsPlugin;
|
|
18790
18790
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -33518,7 +33518,7 @@ var require_package2 = __commonJS({
|
|
|
33518
33518
|
// node_modules/.pnpm/dotenv@16.6.1/node_modules/dotenv/lib/main.js
|
|
33519
33519
|
var require_main = __commonJS({
|
|
33520
33520
|
"node_modules/.pnpm/dotenv@16.6.1/node_modules/dotenv/lib/main.js"(exports2, module2) {
|
|
33521
|
-
var
|
|
33521
|
+
var fs4 = require("fs");
|
|
33522
33522
|
var path3 = require("path");
|
|
33523
33523
|
var os2 = require("os");
|
|
33524
33524
|
var crypto = require("crypto");
|
|
@@ -33627,7 +33627,7 @@ var require_main = __commonJS({
|
|
|
33627
33627
|
if (options && options.path && options.path.length > 0) {
|
|
33628
33628
|
if (Array.isArray(options.path)) {
|
|
33629
33629
|
for (const filepath of options.path) {
|
|
33630
|
-
if (
|
|
33630
|
+
if (fs4.existsSync(filepath)) {
|
|
33631
33631
|
possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
33632
33632
|
}
|
|
33633
33633
|
}
|
|
@@ -33637,7 +33637,7 @@ var require_main = __commonJS({
|
|
|
33637
33637
|
} else {
|
|
33638
33638
|
possibleVaultPath = path3.resolve(process.cwd(), ".env.vault");
|
|
33639
33639
|
}
|
|
33640
|
-
if (
|
|
33640
|
+
if (fs4.existsSync(possibleVaultPath)) {
|
|
33641
33641
|
return possibleVaultPath;
|
|
33642
33642
|
}
|
|
33643
33643
|
return null;
|
|
@@ -33686,7 +33686,7 @@ var require_main = __commonJS({
|
|
|
33686
33686
|
const parsedAll = {};
|
|
33687
33687
|
for (const path4 of optionPaths) {
|
|
33688
33688
|
try {
|
|
33689
|
-
const parsed = DotenvModule.parse(
|
|
33689
|
+
const parsed = DotenvModule.parse(fs4.readFileSync(path4, { encoding }));
|
|
33690
33690
|
DotenvModule.populate(parsedAll, parsed, options);
|
|
33691
33691
|
} catch (e) {
|
|
33692
33692
|
if (debug) {
|
|
@@ -54835,7 +54835,7 @@ var require_tiktoken = __commonJS({
|
|
|
54835
54835
|
var imports = {};
|
|
54836
54836
|
imports["./tiktoken_bg.js"] = wasm;
|
|
54837
54837
|
var path3 = require("path");
|
|
54838
|
-
var
|
|
54838
|
+
var fs4 = require("fs");
|
|
54839
54839
|
var candidates = __dirname.split(path3.sep).reduce((memo, _, index, array) => {
|
|
54840
54840
|
const prefix = array.slice(0, index + 1).join(path3.sep) + path3.sep;
|
|
54841
54841
|
if (!prefix.includes("node_modules" + path3.sep)) {
|
|
@@ -54855,7 +54855,7 @@ var require_tiktoken = __commonJS({
|
|
|
54855
54855
|
var bytes = null;
|
|
54856
54856
|
for (const candidate of candidates) {
|
|
54857
54857
|
try {
|
|
54858
|
-
bytes =
|
|
54858
|
+
bytes = fs4.readFileSync(candidate);
|
|
54859
54859
|
break;
|
|
54860
54860
|
} catch {
|
|
54861
54861
|
}
|
|
@@ -56266,7 +56266,7 @@ var calculateTokenCount = (messages, system, tools) => {
|
|
|
56266
56266
|
}
|
|
56267
56267
|
return tokenCount;
|
|
56268
56268
|
};
|
|
56269
|
-
var getUseModel = (req, tokenCount, config) => {
|
|
56269
|
+
var getUseModel = async (req, tokenCount, config) => {
|
|
56270
56270
|
if (req.body.model.includes(",")) {
|
|
56271
56271
|
return req.body.model;
|
|
56272
56272
|
}
|
|
@@ -56305,7 +56305,7 @@ var router = async (req, _res, config) => {
|
|
|
56305
56305
|
}
|
|
56306
56306
|
}
|
|
56307
56307
|
if (!model) {
|
|
56308
|
-
model = getUseModel(req, tokenCount, config);
|
|
56308
|
+
model = await getUseModel(req, tokenCount, config);
|
|
56309
56309
|
}
|
|
56310
56310
|
req.body.model = model;
|
|
56311
56311
|
} catch (error) {
|
|
@@ -56385,8 +56385,8 @@ function savePid(pid) {
|
|
|
56385
56385
|
function cleanupPidFile() {
|
|
56386
56386
|
if ((0, import_fs2.existsSync)(PID_FILE)) {
|
|
56387
56387
|
try {
|
|
56388
|
-
const
|
|
56389
|
-
|
|
56388
|
+
const fs4 = require("fs");
|
|
56389
|
+
fs4.unlinkSync(PID_FILE);
|
|
56390
56390
|
} catch (e) {
|
|
56391
56391
|
}
|
|
56392
56392
|
}
|
|
@@ -56572,21 +56572,22 @@ async function executeCodeCommand(args = []) {
|
|
|
56572
56572
|
}
|
|
56573
56573
|
|
|
56574
56574
|
// package.json
|
|
56575
|
-
var version = "1.0.
|
|
56575
|
+
var version = "1.0.25";
|
|
56576
56576
|
|
|
56577
56577
|
// src/cli.ts
|
|
56578
56578
|
var import_child_process2 = require("child_process");
|
|
56579
|
-
var import_fs5 = require("fs");
|
|
56579
|
+
var import_fs5 = __toESM(require("fs"));
|
|
56580
56580
|
var import_path4 = require("path");
|
|
56581
56581
|
var command = process.argv[2];
|
|
56582
56582
|
var HELP_TEXT = `
|
|
56583
56583
|
Usage: ccr [command]
|
|
56584
56584
|
|
|
56585
56585
|
Commands:
|
|
56586
|
-
start Start
|
|
56587
|
-
stop Stop
|
|
56588
|
-
|
|
56589
|
-
|
|
56586
|
+
start Start server
|
|
56587
|
+
stop Stop server
|
|
56588
|
+
restart Restart server
|
|
56589
|
+
status Show server status
|
|
56590
|
+
code Execute claude command
|
|
56590
56591
|
-v, version Show version information
|
|
56591
56592
|
-h, help Show help information
|
|
56592
56593
|
|
|
@@ -56618,7 +56619,7 @@ async function main() {
|
|
|
56618
56619
|
cleanupPidFile();
|
|
56619
56620
|
if ((0, import_fs5.existsSync)(REFERENCE_COUNT_FILE)) {
|
|
56620
56621
|
try {
|
|
56621
|
-
|
|
56622
|
+
import_fs5.default.unlinkSync(REFERENCE_COUNT_FILE);
|
|
56622
56623
|
} catch (e) {
|
|
56623
56624
|
}
|
|
56624
56625
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@musistudio/claude-code-router",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.25",
|
|
4
4
|
"description": "Use Claude Code without an Anthropics account and route it to another LLM provider",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ccr": "./dist/cli.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "esbuild src/cli.ts --bundle --platform=node --outfile=dist/cli.js && shx cp node_modules/tiktoken/tiktoken_bg.wasm dist/tiktoken_bg.wasm"
|
|
9
|
+
"build": "esbuild src/cli.ts --bundle --platform=node --outfile=dist/cli.js && shx cp node_modules/tiktoken/tiktoken_bg.wasm dist/tiktoken_bg.wasm",
|
|
10
|
+
"release": "npm run build && npm publish"
|
|
10
11
|
},
|
|
11
12
|
"keywords": [
|
|
12
13
|
"claude",
|
|
@@ -37,4 +38,4 @@
|
|
|
37
38
|
"screenshots/"
|
|
38
39
|
]
|
|
39
40
|
}
|
|
40
|
-
}
|
|
41
|
+
}
|