@yanzc/claude-pro 0.4.4 → 0.4.6

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
@@ -75,7 +75,7 @@ claude-pro config set CLAUDE_PRO_PROVIDER claude-code
75
75
  什么都没配置时(没有 `CLAUDE_PRO_PROVIDER`,也没有 `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN`/`ANTHROPIC_API_KEY`/`ANTHROPIC_MODEL`)默认用**网关**,第一次启动直接弹出登录选择(网关排第一);配置了 API 端点、密钥或模型的老用户仍然默认 `api`。交互界面里 `/login` 选择模型请求发往哪里(记在 `~/.claude-pro/settings.json` 的 `CLAUDE_PRO_PROVIDER`,每个后端各自记住 `/model`、`/effort`):
76
76
 
77
77
  - **Claude 订阅**(`claude-code`):本机 Claude Code 的登录,见上一节。
78
- - **网关**(`gateway`):[codex-gateway](../codex-gateway) 的账户,默认 `https://gw.predix.icu`(`CLAUDE_PRO_GATEWAY_URL` 可改)。第一次选它会在浏览器打开网关的登录页,登录(或注册)后点“授权”,浏览器被带回本机,claude-pro 用一次性 code 换到这台电脑专用的 API Key,存在 `~/.claude-pro/gateway.json`(仅本人可读)。费用从网关账户余额扣,`/cost` 显示余额,充值和用量在网关的 `/user` 页面。模型默认 `opus`,`/model` 可选 `sonnet`、`haiku`、`gpt-6.1-sol`(网关用它自己的 Codex 登录转发,按 OpenAI 官方价扣费);子 agent 在 Claude 主模型下用 `sonnet`。网关每次请求按 `max_tokens` 预扣余额,所以这里默认 `max_tokens` 是 32K。`/logout` 删除本机的网关 Key。
78
+ - **网关**(`gateway`):[codex-gateway](../codex-gateway) 的账户,默认 `https://gw.predix.icu`(`CLAUDE_PRO_GATEWAY_URL` 可改)。第一次选它会在浏览器打开网关的登录页,登录(或注册)后点“授权”,浏览器被带回本机,claude-pro 用一次性 code 换到这台电脑专用的 API Key,存在 `~/.claude-pro/gateway.json`(仅本人可读)。费用从网关账户余额扣,`/cost` 显示余额,充值和用量在网关的 `/user` 页面。模型默认 `opus`,`/model` 可选 `sonnet`、`haiku`、`gpt-6.1-sol`(网关用它自己的 Codex 登录转发,按 OpenAI 官方价扣费);子 agent 在 Claude 主模型下用 `sonnet`。网关每次请求按 `max_tokens` 预扣余额,所以这里默认 `max_tokens` 是 32K。每个请求带上会话 ID(`X-Claude-Code-Session-Id`,和 Claude Code 一样;子 agent 用主对话的,`/clear` 换新的,`--resume` 和自动重启沿用原来的),网关据此让同一个对话 1 小时内继续用同一个上游账号,提示词缓存才不会因为换账号而失效。`/logout` 删除本机的网关 Key。
79
79
  - **Codex 订阅**(`codex`):本机 `codex login`(ChatGPT 套餐)写下的 `~/.codex/auth.json`(`CODEX_HOME` 可改)。请求直接发往 Codex 后端,Anthropic 格式在本机和 OpenAI Responses 格式互相转换(工具调用、推理、缓存用量都对应上);访问令牌快到期时用同一文件里的刷新令牌续期并写回,Codex CLI 也能接着用。模型默认取 `~/.codex/config.toml` 的 `model`,否则 `gpt-6.1-sol`;不设 effort 时用模型自己的默认值(`models_cache.json` 里写的)。
80
80
 
81
81
  ## 配置
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yanzc/claude-pro",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "claude-pro: a self-implemented coding agent for Anthropic-compatible APIs or local Claude Code subscription login. Full access with no permission prompts; file edits are checkpointed and can be rewound.",
5
5
  "type": "module",
6
6
  "repository": {
package/src/api.js CHANGED
@@ -193,6 +193,9 @@ const outputCaps = new Map();
193
193
  // Models that refused the effort parameter (Haiku does): it is left out for them from then on.
194
194
  const noEffort = new Set();
195
195
  export const effortSupported = (modelId) => !noEffort.has(modelId);
196
+ // Models that refused thinking "disabled" (MAX_THINKING_TOKENS=0): Opus 5.5, Fable, Sonnet 5.5, and Opus 5 at a
197
+ // high effort. Their requests leave `thinking` out from then on, which is adaptive thinking.
198
+ const thinkingStaysOn = new Set();
196
199
 
197
200
  // The effort a model uses when none is sent (docs.claude.com, "Effort"): medium on Opus 5.5 and Haiku 5.5, high on
198
201
  // the other models that take one; for a GPT model, the gateway's metadata or the local Codex models cache. null for
@@ -243,8 +246,10 @@ export function parseMaxTokensLimit(message) {
243
246
  // Codex backend only: Anthropic refuses their signatures ("Invalid `signature` in `thinking` block"), so a
244
247
  // conversation that moves from a GPT model to Claude, or to another provider, sends them without those blocks.
245
248
  // The session keeps them, for a later switch back. `inTurn`: blocks were taken out of the turn in progress (the
246
- // tool loop since the last prompt), whose assistant messages Anthropic wants to start with thinking when thinking
247
- // is on; such a turn goes on without thinking until the next prompt.
249
+ // tool loop since the last prompt), whose assistant messages Anthropic wants to start with thinking when a thinking
250
+ // budget is set; such a turn's requests leave `thinking` out until the next prompt. That is no thinking on older
251
+ // models and adaptive thinking on those that cannot turn it off (Opus 5.5, Fable, Sonnet 5.5 refuse "disabled"),
252
+ // which takes assistant messages without thinking.
248
253
  const isCodexThinking = (b) => b?.type === 'thinking' && typeof b.signature === 'string' && b.signature.startsWith(SIGNATURE_PREFIX);
249
254
  const isPrompt = (m) => m.role === 'user' && !(Array.isArray(m.content) && m.content.some((b) => b.type === 'tool_result'));
250
255
  function withoutCodexThinking(messages) {
@@ -294,9 +299,9 @@ export function buildParams(config, { model, system, messages, tools, maxTokens,
294
299
  const eff = effortToSend(config, model.id, (effort === undefined ? config.effort : effort) || (/^gpt-/i.test(model.id) ? defaultEffort(model.id, config) : null));
295
300
  if (eff && !noEffort.has(model.id)) params.output_config = { effort: eff };
296
301
  const budget = config.thinkingBudget;
297
- if (budget === 0) params.thinking = { type: 'disabled' };
302
+ if (budget === 0 && !thinkingStaysOn.has(model.id)) params.thinking = { type: 'disabled' };
298
303
  else if (budget > 0) params.thinking = { type: 'enabled', budget_tokens: Math.min(budget, params.max_tokens - 1) };
299
- if (turnLostThinking) params.thinking = { type: 'disabled' };
304
+ if (turnLostThinking) delete params.thinking;
300
305
  return params;
301
306
  }
302
307
 
@@ -365,6 +370,9 @@ export async function streamMessage(config, request, { signal, onEvent = () => {
365
370
  }
366
371
  let client = getClient(config, login);
367
372
  const params = buildParams(config, request, login);
373
+ // The gateway keeps a conversation on the upstream account that served it, where its prompt cache is; the
374
+ // session id names the conversation, in the header Claude Code sends it in.
375
+ const options = { signal, ...(config.usesGateway && config.sessionId ? { headers: { 'X-Claude-Code-Session-Id': config.sessionId } } : {}) };
368
376
  let authReloaded = false;
369
377
  const refused = new Set(); // what the endpoint refused for this request: remembered once a request got through
370
378
  const retryState = { attempt: 0, rateLimitHits: 0, waitedMs: 0, overloaded: 0, overloadedWaitMs: 0 };
@@ -373,7 +381,7 @@ export async function streamMessage(config, request, { signal, onEvent = () => {
373
381
  let emitted = false;
374
382
  const startedAt = Date.now(); // the cache TTL counts from the start of the request, not from the end of the reply
375
383
  try {
376
- const stream = await client.messages.create(params, { signal });
384
+ const stream = await client.messages.create(params, options);
377
385
  if (paused) {
378
386
  const overloaded = paused.overloaded;
379
387
  paused = null;
@@ -477,6 +485,12 @@ export async function streamMessage(config, request, { signal, onEvent = () => {
477
485
  delete params.output_config;
478
486
  continue;
479
487
  }
488
+ // A model whose thinking cannot be turned off: carry on with its adaptive thinking.
489
+ if (err.status === 400 && params.thinking?.type === 'disabled' && /thinking\.type\.disabled|"disabled"/i.test(err.message) && /not supported/i.test(err.message)) {
490
+ thinkingStaysOn.add(params.model);
491
+ delete params.thinking;
492
+ continue;
493
+ }
480
494
  // The endpoint does not take cache_control (or the 1-hour ttl) where it was sent: carry on without it
481
495
  // instead of failing the request, and do not send it there again (see refuseCache).
482
496
  if (err.status === 400 && /cache_control|\bttl\b/i.test(err.message)) {
package/src/print.js CHANGED
@@ -62,6 +62,7 @@ export async function runPrint({ prompt, flags, cwd, stdout = process.stdout, st
62
62
  store.systemPrompt = loaded.systemPrompt;
63
63
  runtime.resumeSystemPrompt(loaded.systemPrompt); // continue behind the prompt (and the cache) the conversation had
64
64
  }
65
+ runtime.config.sessionId = store.id; // sent to the gateway with each request (see streamMessage)
65
66
  const ctrl = new AbortController();
66
67
  const onSigint = () => ctrl.abort();
67
68
  process.on('SIGINT', onSigint);
package/src/ui/repl.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  import { createRuntime } from '../runtime.js';
18
18
  import { SessionStore, firstUserText } from '../session.js';
19
19
  import { defaultEffort, effortSupported, effortToSend, quickQuery } from '../api.js';
20
- import { CONFIG_DIR, VERSION, APP_NAME, INSTALL_DIR, rememberChoice } from '../config.js';
20
+ import { CONFIG_DIR, VERSION, APP_NAME, INSTALL_DIR } from '../config.js';
21
21
  import { readGatewayLogin } from '../gateway.js';
22
22
  import { TOPIC_TITLE_SYSTEM, topicTitlePrompt } from '../prompts.js';
23
23
  import { buildCommands } from '../commands.js';
@@ -210,6 +210,8 @@ export class App {
210
210
 
211
211
  await this.startPeer();
212
212
  this.runtime = await createRuntime({ cwd: this.cwd, flags: this.flags, io: this });
213
+ // The conversation's id, sent to the gateway with each request (see streamMessage); it follows /clear and /resume.
214
+ Object.defineProperty(this.runtime.config, 'sessionId', { get: () => this.store.id, configurable: true });
213
215
  this.commands = buildCommands(this);
214
216
  // Keys typed while starting up stay buffered in stdin until we listen.
215
217
  stdin.on('keypress', (str, key) => {
@@ -461,14 +463,13 @@ export class App {
461
463
  // In the process a restart started: take up the session where the one before it left it.
462
464
  resumeAfterRestart(state, resumable) {
463
465
  const cfg = this.config;
464
- const kept = {};
465
- if (state.modelOverride) cfg.modelOverride = kept.model = state.modelOverride;
466
- if (state.effortOverride) cfg.effortOverride = kept.effort = state.effortOverride;
466
+ // The model and effort the session had, none chosen included: the remembered /model and /effort are shared by
467
+ // every window on this machine, and another one may have changed them since (an update restarts them all).
468
+ // A restart is not a choice either, so it leaves what is remembered as it is.
469
+ if ('modelOverride' in state) cfg.modelOverride = state.modelOverride || null;
470
+ if ('effortOverride' in state) cfg.effortOverride = state.effortOverride || null;
467
471
  // The Effort tool's level holds for the request the restart continues, not for the user's next one.
468
472
  if (state.effortBoost) { cfg.effortBoost = state.effortBoost; cfg.effortBoostKeepNext = Boolean(state.continueWith); }
469
- // These were chosen with /model and /effort, which are kept for later sessions. One made by a version of
470
- // the program that did not keep them yet is saved now.
471
- try { rememberChoice(cfg.provider, kept); } catch { /* it still holds for this session */ }
472
473
  if (state.mode === 'plan') this.runtime.modes.setMode('plan');
473
474
  this.restarts = (Array.isArray(state.restarts) ? state.restarts : []).filter((t) => Number.isFinite(t));
474
475
  if (resumable) {