clawmoney 0.15.67 → 0.15.68

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.
@@ -994,6 +994,56 @@ function mergeBetas(required, clientBeta) {
994
994
  }
995
995
  return out.join(",");
996
996
  }
997
+ // Scan a passthrough body for any `cache_control: {type: "ephemeral",
998
+ // ttl: "1h"}` block across tools / system / messages. The presence of
999
+ // even one 1h block forces us to upgrade our own injected CC marker in
1000
+ // system to 1h too, because Anthropic rejects requests where a 1h
1001
+ // block appears after any 5m block in the global tools→system→messages
1002
+ // ordering (see long comment in ensureClaudeCodeShell).
1003
+ //
1004
+ // Returns true on the first 1h block found — this is a detect-only
1005
+ // walk, not a rewrite. Safe on malformed bodies (returns false).
1006
+ function bodyHasExtendedCacheBlock(body) {
1007
+ const isExtendedBlock = (block) => {
1008
+ if (!block || typeof block !== "object")
1009
+ return false;
1010
+ const cc = block
1011
+ .cache_control;
1012
+ if (!cc || typeof cc !== "object")
1013
+ return false;
1014
+ return cc.ttl === "1h";
1015
+ };
1016
+ if (Array.isArray(body.tools)) {
1017
+ for (const t of body.tools) {
1018
+ if (isExtendedBlock(t))
1019
+ return true;
1020
+ }
1021
+ }
1022
+ if (Array.isArray(body.system)) {
1023
+ for (const b of body.system) {
1024
+ if (isExtendedBlock(b))
1025
+ return true;
1026
+ }
1027
+ }
1028
+ if (Array.isArray(body.messages)) {
1029
+ for (const m of body.messages) {
1030
+ if (!m || typeof m !== "object")
1031
+ continue;
1032
+ const content = m.content;
1033
+ // Anthropic messages can carry content either as a string (no
1034
+ // cache_control possible) or as an array of content blocks
1035
+ // (each of which can carry cache_control). Only the array form
1036
+ // matters for this check.
1037
+ if (Array.isArray(content)) {
1038
+ for (const c of content) {
1039
+ if (isExtendedBlock(c))
1040
+ return true;
1041
+ }
1042
+ }
1043
+ }
1044
+ }
1045
+ return false;
1046
+ }
997
1047
  // Ensure a passthrough body carries the full Claude Code fingerprint
998
1048
  // shell that Anthropic's OAuth-endpoint validator expects. Called from
999
1049
  // doCallClaudeApiPassthrough as the last body-munging step before the
@@ -1057,34 +1107,69 @@ function ensureClaudeCodeShell(body, fingerprint) {
1057
1107
  const firstUserMsg = extractFirstUserMessageText(body.messages);
1058
1108
  const freshHeader = buildClaudeAttributionHeader(firstUserMsg, fingerprint.cc_version, fingerprint.cc_entrypoint);
1059
1109
  // ── Inject CC marker if missing ──
1060
- // Position: right after the billing header slot (idx 1), or right
1061
- // after any buyer-prefixed system blocks (at head) if we're also
1062
- // inserting the billing header.
1063
1110
  //
1064
- // Anthropic has a hard ordering rule within the system array: any
1065
- // cache_control block with `ttl="1h"` MUST come before any block with
1066
- // `ttl="5m"`. Our injected marker uses the default ephemeral TTL
1067
- // (5m), so if the buyer's original system array contains a 1h block
1068
- // further down, naively inserting at index 1 puts the 1h block AFTER
1069
- // our 5m block and Anthropic 400s with
1070
- // system.N.cache_control.ttl: a ttl='1h' cache_control block must
1071
- // not come after a ttl='5m' cache_control block.
1072
- // Walk from the end to find the last 1h block; if one exists, drop
1073
- // the marker immediately after it so the 1h-before-5m invariant
1074
- // holds. Otherwise fall back to the original "index 1 (or 0)" slot.
1111
+ // Anthropic has a hard GLOBAL ordering rule across the whole request:
1112
+ // within the linear processing order `tools system messages`,
1113
+ // any block with `cache_control.ttl="1h"` MUST come before any block
1114
+ // with `ttl="5m"`. Not just within one section globally. A 5m block
1115
+ // in system comes before any 1h block in messages and that's a 400.
1116
+ //
1117
+ // Our injected CC marker lives in system. Its default TTL is 5m
1118
+ // (what real Claude Code uses). When a buyer request carries any 1h
1119
+ // cache_control block ANYWHERE (their own system, or inside any
1120
+ // message content block, or in tools), naively injecting a 5m marker
1121
+ // in system causes:
1122
+ // system.N.cache_control.ttl — when the 1h is in system below us
1123
+ // messages.N.content.M.cache_control.ttl — when the 1h is in messages
1124
+ // Anthropic 400s with:
1125
+ // a ttl='1h' cache_control block must not come after a ttl='5m'
1126
+ // cache_control block. Note that blocks are processed in the
1127
+ // following order: `tools`, `system`, `messages`.
1128
+ //
1129
+ // Fix: detect whether the buyer's body touches 1h cache anywhere.
1130
+ // If yes, upgrade our marker's TTL to 1h too — then the whole request
1131
+ // is uniformly 1h from our side, no 1h-after-5m violation possible.
1132
+ // If no, keep the default 5m (matches real Claude Code fingerprint).
1133
+ //
1134
+ // The 1h TTL won't actually materialise extra cost for our marker
1135
+ // because our system block is < 1024 tokens and below Anthropic's
1136
+ // minimum cache token threshold, so neither 5m nor 1h actually
1137
+ // produces a cache write or read. The TTL label is purely a shape
1138
+ // marker that unblocks the ordering validator.
1075
1139
  if (!hasCcMarker) {
1140
+ const buyerUsesExtendedCache = bodyHasExtendedCacheBlock(body);
1076
1141
  const markerBlock = {
1077
1142
  type: "text",
1078
1143
  text: `${CLAUDE_CODE_SYSTEM_PROMPT_LEAD}\n\n${RELAY_INSTRUCTIONS}`,
1079
- cache_control: { type: "ephemeral" },
1144
+ cache_control: buyerUsesExtendedCache
1145
+ ? { type: "ephemeral", ttl: "1h" }
1146
+ : { type: "ephemeral" },
1080
1147
  };
1148
+ // Insert position inside system:
1149
+ // - If our marker is 5m: put it AFTER any existing 1h block in
1150
+ // system so system-internal ordering holds (1h-before-5m).
1151
+ // - If our marker is 1h: put it BEFORE any existing 5m block in
1152
+ // system for the same reason (1h-before-5m). No 5m block →
1153
+ // default slot.
1081
1154
  let insertAt = hasBillingHeaderFirst ? 1 : 0;
1082
- for (let i = system.length - 1; i >= 0; i--) {
1083
- const cc = system[i]
1084
- ?.cache_control;
1085
- if (cc && typeof cc === "object" && cc.ttl === "1h") {
1086
- insertAt = i + 1;
1087
- break;
1155
+ if (buyerUsesExtendedCache) {
1156
+ for (let i = 0; i < system.length; i++) {
1157
+ const cc = system[i]
1158
+ ?.cache_control;
1159
+ if (cc && typeof cc === "object" && (cc.ttl ?? "5m") === "5m") {
1160
+ insertAt = i;
1161
+ break;
1162
+ }
1163
+ }
1164
+ }
1165
+ else {
1166
+ for (let i = system.length - 1; i >= 0; i--) {
1167
+ const cc = system[i]
1168
+ ?.cache_control;
1169
+ if (cc && typeof cc === "object" && cc.ttl === "1h") {
1170
+ insertAt = i + 1;
1171
+ break;
1172
+ }
1088
1173
  }
1089
1174
  }
1090
1175
  system.splice(insertAt, 0, markerBlock);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.15.67",
3
+ "version": "0.15.68",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {