@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 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 fs3 = require("fs");
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) fs3.mkdirSync(path3.dirname(file), { recursive: true });
4518
- const fd = fs3.openSync(file, flags, mode);
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
- fs3.mkdir(path3.dirname(file), { recursive: true }, (err) => {
4525
+ fs4.mkdir(path3.dirname(file), { recursive: true }, (err) => {
4526
4526
  if (err) return fileOpened(err);
4527
- fs3.open(file, flags, mode, fileOpened);
4527
+ fs4.open(file, flags, mode, fileOpened);
4528
4528
  });
4529
4529
  } else {
4530
- fs3.open(file, flags, mode, fileOpened);
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 = () => fs3.writeSync(this.fd, this._writingBuf);
4572
- fsWrite = () => fs3.write(this.fd, this._writingBuf, this.release);
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 = () => fs3.writeSync(this.fd, this._writingBuf, "utf8");
4580
- fsWrite = () => fs3.write(this.fd, this._writingBuf, "utf8", this.release);
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
- fs3.fsyncSync(this.fd);
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
- fs3.fsync(this.fd, (err) => {
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
- fs3.close(fd, (err) => {
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 = fs3.writeSync(this.fd, buf, "utf8");
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
- fs3.fsyncSync(this.fd);
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 = fs3.writeSync(this.fd, buf);
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 = fs3.writeSync(this.fd, this._writingBuf, "utf8");
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
- fs3.write(this.fd, this._writingBuf, "utf8", release);
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 = fs3.writeSync(this.fd, this._writingBuf);
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
- fs3.write(this.fd, this._writingBuf, release);
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
- fs3.fsync(sonic.fd, closeWrapped);
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
- fs3.close(sonic.fd, done);
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, fs3, exportName) {
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, fs3[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 fs3 = require("fs");
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 (fs3.existsSync(filepath)) {
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 (fs3.existsSync(possibleVaultPath)) {
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(fs3.readFileSync(path4, { encoding }));
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 fs3 = require("fs");
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 = fs3.readFileSync(candidate);
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 fs3 = require("fs");
56389
- fs3.unlinkSync(PID_FILE);
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.23";
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 service
56587
- stop Stop service
56588
- status Show service status
56589
- code Execute code command
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
- require("fs").unlinkSync(REFERENCE_COUNT_FILE);
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.24",
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
+ }