clawmoney 0.15.9 → 0.15.10

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.
@@ -937,41 +937,115 @@ function mergeBetas(required, clientBeta) {
937
937
  }
938
938
  return out.join(",");
939
939
  }
940
- // Rewrite the first system block's x-anthropic-billing-header (if present)
941
- // so cc_version and FP3 match OUR fingerprint and the buyer's actual
942
- // first user message. Real Claude Code always emits this block; sub2api's
943
- // gateway_service.go mirrors it verbatim but rewrites the version to
944
- // match the account's pinned UA (syncBillingHeaderVersion, gateway_billing_header.go).
940
+ // Ensure a passthrough body carries the full Claude Code fingerprint
941
+ // shell that Anthropic's OAuth-endpoint validator expects. Called from
942
+ // doCallClaudeApiPassthrough as the last body-munging step before the
943
+ // HTTP request goes out.
945
944
  //
946
- // Critical because Anthropic's validator expects cc_version.<FP3> where
947
- // FP3 is a deterministic hash of (message_chars + cli_version). If we
948
- // leave the buyer's FP3 in place but their UA was a different version
949
- // from our pinned UA, the FP3 no longer matches cli_version in the header
950
- // and the validator rejects the request.
951
- function syncPassthroughBillingHeader(body, fingerprint) {
952
- if (!Array.isArray(body.system))
953
- return;
945
+ // The three fingerprint-sensitive fields that MUST be present on every
946
+ // real Claude Code /v1/messages request:
947
+ //
948
+ // 1. system[0] = {type:"text", text:"x-anthropic-billing-header: ..."}
949
+ // always the FIRST block. Contains cc_version.<FP3> where FP3 is
950
+ // SHA256(SALT + chars_from_first_user_msg + cli_version).hex[:3].
951
+ // 2. system[i] = {type:"text", text:"You are a Claude agent, built on
952
+ // Anthropic's Claude Agent SDK..."} with cache_control ephemeral.
953
+ // — the template-mode "CC identity marker" that passes the dice-
954
+ // coefficient validator.
955
+ // 3. thinking: {type, budget_tokens?} — on Claude 4+ models real CLI
956
+ // always sends this; zero-thinking accounts stand out.
957
+ // 4. tools: array (empty [] is fine) — real CLI always sends the
958
+ // field, missing means the request shape doesn't match.
959
+ //
960
+ // For real Claude Code / anthropic SDK clients that already send a full
961
+ // body (via /v1/messages passthrough path), every check here no-ops —
962
+ // the body is already in CC shape and we don't touch it.
963
+ //
964
+ // For OpenAI-SDK-style clients going through /v1/chat/completions (the
965
+ // Hub's chat→anthropic converter produces a minimal body), we augment
966
+ // with the missing shell fields so the outbound request is
967
+ // indistinguishable from a real CC request that happens to have a
968
+ // user-provided system prompt and no local tools.
969
+ //
970
+ // All buyer content (messages, their own system text, their own tools,
971
+ // thinking config if they sent one) is preserved.
972
+ function ensureClaudeCodeShell(body, fingerprint) {
973
+ // ── Normalize system to an array of content blocks ──
974
+ if (!Array.isArray(body.system)) {
975
+ if (typeof body.system === "string" && body.system.length > 0) {
976
+ // String-shaped system (anthropic SDK convenience form) →
977
+ // wrap in a single text block so we can prepend.
978
+ body.system = [{ type: "text", text: body.system }];
979
+ }
980
+ else {
981
+ body.system = [];
982
+ }
983
+ }
954
984
  const system = body.system;
955
- if (system.length === 0)
956
- return;
985
+ // ── Detect CC identity marker anywhere in system ──
986
+ const hasCcMarker = system.some((b) => b &&
987
+ typeof b === "object" &&
988
+ b.type === "text" &&
989
+ typeof b.text === "string" &&
990
+ b.text.includes(CLAUDE_CODE_SYSTEM_PROMPT_LEAD));
991
+ // ── Detect billing header in system[0] ──
957
992
  const firstBlock = system[0];
958
- if (!firstBlock ||
959
- typeof firstBlock !== "object" ||
960
- firstBlock.type !== "text" ||
961
- typeof firstBlock.text !== "string") {
962
- return;
993
+ const hasBillingHeaderFirst = !!firstBlock &&
994
+ typeof firstBlock === "object" &&
995
+ firstBlock.type === "text" &&
996
+ typeof firstBlock.text === "string" &&
997
+ firstBlock.text.startsWith("x-anthropic-billing-header:");
998
+ // ── Build the attribution header (always recompute so cc_version + FP3
999
+ // match OUR fingerprint and the buyer's actual first user message) ──
1000
+ const firstUserMsg = extractFirstUserMessageText(body.messages);
1001
+ const freshHeader = buildClaudeAttributionHeader(firstUserMsg, fingerprint.cc_version, fingerprint.cc_entrypoint);
1002
+ // ── Inject CC marker if missing ──
1003
+ // Position: right after the billing header slot (idx 1), or right
1004
+ // after any buyer-prefixed system blocks (at head) if we're also
1005
+ // inserting the billing header.
1006
+ if (!hasCcMarker) {
1007
+ const markerBlock = {
1008
+ type: "text",
1009
+ text: `${CLAUDE_CODE_SYSTEM_PROMPT_LEAD}\n\n${RELAY_INSTRUCTIONS}`,
1010
+ cache_control: { type: "ephemeral" },
1011
+ };
1012
+ // Insert at index 1 (slot after billing header), or 0 if we'll be
1013
+ // unshifting the billing header next.
1014
+ if (hasBillingHeaderFirst) {
1015
+ system.splice(1, 0, markerBlock);
1016
+ }
1017
+ else {
1018
+ system.unshift(markerBlock);
1019
+ }
963
1020
  }
964
- const currentText = firstBlock.text;
965
- if (!currentText.startsWith("x-anthropic-billing-header:")) {
966
- // Non-CC client didn't include a billing header leave system alone.
967
- // If we're strict about this we could PREPEND one, but for now we
968
- // only touch what exists so non-CC passthrough (e.g. anthropic SDK
969
- // direct) works without extra surgery.
970
- return;
1021
+ // ── Update or inject billing header at index 0 ──
1022
+ if (hasBillingHeaderFirst) {
1023
+ // Rewrite in place so cc_version reflects OUR fingerprint, not the
1024
+ // buyer's original (which might have been from a different CLI
1025
+ // version than our pinned fingerprint).
1026
+ firstBlock.text = freshHeader;
1027
+ }
1028
+ else {
1029
+ system.unshift({ type: "text", text: freshHeader });
1030
+ }
1031
+ // ── Ensure tools array exists ──
1032
+ if (!Array.isArray(body.tools)) {
1033
+ body.tools = [];
1034
+ }
1035
+ // ── Inject thinking config if missing ──
1036
+ // Real CLI always sends this for Claude 4+ models; zero-thinking
1037
+ // accounts are a relay-farm tell. pickClaudeThinkingConfig picks the
1038
+ // right shape (adaptive for 4-6, enabled-with-budget for 4-5/haiku).
1039
+ if (!body.thinking || typeof body.thinking !== "object") {
1040
+ const rawMaxTokens = typeof body.max_tokens === "number" && body.max_tokens > 0
1041
+ ? body.max_tokens
1042
+ : 4096;
1043
+ const { config, adjustedMaxTokens } = pickClaudeThinkingConfig(body.model ?? "", rawMaxTokens);
1044
+ if (config) {
1045
+ body.thinking = config;
1046
+ body.max_tokens = adjustedMaxTokens;
1047
+ }
971
1048
  }
972
- const firstUserMsg = extractFirstUserMessageText(body.messages);
973
- const newHeader = buildClaudeAttributionHeader(firstUserMsg, fingerprint.cc_version, fingerprint.cc_entrypoint);
974
- firstBlock.text = newHeader;
975
1049
  }
976
1050
  // Walk system text blocks and rewrite third-party identity sentences
977
1051
  // (OpenCode, etc.) to the Claude Code banner. sub2api does the same thing
@@ -1025,7 +1099,7 @@ async function doCallClaudeApiPassthrough(opts) {
1025
1099
  // Sanitize system: replace third-party identity sentences + sync
1026
1100
  // billing header cc_version to match our pinned CLI version.
1027
1101
  sanitizePassthroughSystemArray(body);
1028
- syncPassthroughBillingHeader(body, fp);
1102
+ ensureClaudeCodeShell(body, fp);
1029
1103
  // Clamp thinking.budget_tokens to Anthropic's minimum so buyer-chosen
1030
1104
  // small budgets don't 400. If max_tokens < budget_tokens + 1, bump
1031
1105
  // max_tokens too so the request stays valid.
@@ -77,9 +77,17 @@ const MAX_TRANSIENT_RETRIES = 2;
77
77
  // Codex responses on small prompts come back in <10s; we give a generous
78
78
  // ceiling to tolerate slow tokens without hanging the daemon forever.
79
79
  const WS_OVERALL_TIMEOUT_MS = 180 * 1000;
80
- // Default relay instructions for Codex. Upstream treats `instructions` as
81
- // the system prompt. Keep minimal so the buyer's prompt gets full focus.
80
+ // Default instructions for Codex template mode. Template mode flattens
81
+ // messages into a single prompt and drops buyer's tools the "plain
82
+ // text only" hint aligns model behavior with what template can actually
83
+ // deliver.
82
84
  const RELAY_INSTRUCTIONS = "You are a helpful AI assistant operating in relay mode. Respond to the user's message with plain text only. Be concise.";
85
+ // Neutral fallback for Codex passthrough mode when the buyer did NOT
86
+ // supply their own instructions. Unlike the template-mode string, this
87
+ // one does NOT forbid tool use — if the buyer sent a tools array we
88
+ // want the model to use them. Kept intentionally vague so it doesn't
89
+ // bias the model's behavior when the buyer's intent is unspecified.
90
+ const CODEX_PASSTHROUGH_FALLBACK_INSTRUCTIONS = "You are a helpful coding assistant. Use the available tools when appropriate to answer the user.";
83
91
  // ── Proxy ──
84
92
  //
85
93
  // We configure the global undici dispatcher for the OAuth refresh fetch()
@@ -1051,10 +1059,13 @@ function buildCodexPassthroughFrame(clientBody, model, fingerprint, sessionId, t
1051
1059
  if (frame.parallel_tool_calls === undefined) {
1052
1060
  frame.parallel_tool_calls = false;
1053
1061
  }
1054
- // Instructions: if buyer didn't send one, fall back to the template
1055
- // mode's RELAY_INSTRUCTIONS so the model still has guidance.
1062
+ // Instructions: if buyer didn't send one, fall back to a neutral
1063
+ // tool-friendly default so the model still has guidance while not
1064
+ // forbidding tool use (unlike template mode's RELAY_INSTRUCTIONS,
1065
+ // which says "plain text only" — wrong fit for passthrough where
1066
+ // buyer's tools should actually be used).
1056
1067
  if (typeof frame.instructions !== "string" || !frame.instructions) {
1057
- frame.instructions = RELAY_INSTRUCTIONS;
1068
+ frame.instructions = CODEX_PASSTHROUGH_FALLBACK_INSTRUCTIONS;
1058
1069
  }
1059
1070
  if (warmup) {
1060
1071
  // Real CLI's prewarm flow: first frame of each turn has generate:false.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.15.9",
3
+ "version": "0.15.10",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {