pi-background-tasks 2.5.0 → 2.6.0

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.
Files changed (203) hide show
  1. package/BACKGROUND-TASKS-INSTRUCTIONS.md +1 -1
  2. package/README.md +70 -25
  3. package/TESTING.md +42 -2
  4. package/TEST_PLAN.md +7 -4
  5. package/dist/extensions/anthropic-attribution-child.js +4 -0
  6. package/dist/extensions/anthropic-attribution-child.js.map +1 -0
  7. package/dist/extensions/anthropic-attribution.js +107 -0
  8. package/dist/extensions/anthropic-attribution.js.map +1 -0
  9. package/dist/extensions/background-tasks.js +2 -0
  10. package/dist/extensions/background-tasks.js.map +1 -0
  11. package/dist/extensions/delegate-child.js +2 -0
  12. package/dist/extensions/delegate-child.js.map +1 -0
  13. package/dist/extensions/fusion-child.js +2 -0
  14. package/dist/extensions/fusion-child.js.map +1 -0
  15. package/dist/package.json +5 -0
  16. package/dist/src/core/anthropic-attribution-path.js +24 -0
  17. package/dist/src/core/anthropic-attribution-path.js.map +1 -0
  18. package/dist/src/core/anthropic-attribution.js +2247 -0
  19. package/dist/src/core/anthropic-attribution.js.map +1 -0
  20. package/dist/src/core/attested-pi-contract.js +5 -0
  21. package/dist/src/core/attested-pi-contract.js.map +1 -0
  22. package/dist/src/core/attested-pi-run.js +749 -0
  23. package/dist/src/core/attested-pi-run.js.map +1 -0
  24. package/dist/src/core/canonical-json.js +19 -0
  25. package/dist/src/core/canonical-json.js.map +1 -0
  26. package/dist/src/core/common.js +787 -0
  27. package/dist/src/core/common.js.map +1 -0
  28. package/dist/src/core/config.js +78 -0
  29. package/dist/src/core/config.js.map +1 -0
  30. package/dist/src/core/context/parent-snapshot.js +75 -0
  31. package/dist/src/core/context/parent-snapshot.js.map +1 -0
  32. package/dist/src/core/context/token-budget.js +631 -0
  33. package/dist/src/core/context/token-budget.js.map +1 -0
  34. package/dist/src/core/context/visible-conversation-v2.js +390 -0
  35. package/dist/src/core/context/visible-conversation-v2.js.map +1 -0
  36. package/dist/src/core/delegate/artifacts.js +357 -0
  37. package/dist/src/core/delegate/artifacts.js.map +1 -0
  38. package/dist/src/core/delegate/budget.js +266 -0
  39. package/dist/src/core/delegate/budget.js.map +1 -0
  40. package/dist/src/core/delegate/facade-contract.js +7 -0
  41. package/dist/src/core/delegate/facade-contract.js.map +1 -0
  42. package/dist/src/core/delegate/hook-contract-evidence.json +18 -0
  43. package/dist/src/core/delegate/hook-contract.js +124 -0
  44. package/dist/src/core/delegate/hook-contract.js.map +1 -0
  45. package/dist/src/core/delegate/launch.js +366 -0
  46. package/dist/src/core/delegate/launch.js.map +1 -0
  47. package/dist/src/core/delegate/result-package.js +293 -0
  48. package/dist/src/core/delegate/result-package.js.map +1 -0
  49. package/dist/src/core/delegate/runner.js +398 -0
  50. package/dist/src/core/delegate/runner.js.map +1 -0
  51. package/dist/src/core/delegate/seed.js +334 -0
  52. package/dist/src/core/delegate/seed.js.map +1 -0
  53. package/dist/src/core/delegate/types.js +103 -0
  54. package/dist/src/core/delegate/types.js.map +1 -0
  55. package/dist/src/core/durable-fs.js +372 -0
  56. package/dist/src/core/durable-fs.js.map +1 -0
  57. package/dist/src/core/extension-api.js +382 -0
  58. package/dist/src/core/extension-api.js.map +1 -0
  59. package/dist/src/core/fusion/artifacts.js +654 -0
  60. package/dist/src/core/fusion/artifacts.js.map +1 -0
  61. package/dist/src/core/fusion/budget.js +949 -0
  62. package/dist/src/core/fusion/budget.js.map +1 -0
  63. package/dist/src/core/fusion/child-protocol.js +176 -0
  64. package/dist/src/core/fusion/child-protocol.js.map +1 -0
  65. package/dist/src/core/fusion/claude-cache.js +156 -0
  66. package/dist/src/core/fusion/claude-cache.js.map +1 -0
  67. package/dist/src/core/fusion/clean-context.js +64 -0
  68. package/dist/src/core/fusion/clean-context.js.map +1 -0
  69. package/dist/src/core/fusion/config.js +349 -0
  70. package/dist/src/core/fusion/config.js.map +1 -0
  71. package/dist/src/core/fusion/context.js +184 -0
  72. package/dist/src/core/fusion/context.js.map +1 -0
  73. package/dist/src/core/fusion/evaluation.js +696 -0
  74. package/dist/src/core/fusion/evaluation.js.map +1 -0
  75. package/dist/src/core/fusion/facade-contract.js +3 -0
  76. package/dist/src/core/fusion/facade-contract.js.map +1 -0
  77. package/dist/src/core/fusion/orchestrator.js +867 -0
  78. package/dist/src/core/fusion/orchestrator.js.map +1 -0
  79. package/dist/src/core/fusion/output-contract.js +27 -0
  80. package/dist/src/core/fusion/output-contract.js.map +1 -0
  81. package/dist/src/core/fusion/pi-child.js +1741 -0
  82. package/dist/src/core/fusion/pi-child.js.map +1 -0
  83. package/dist/src/core/fusion/prompts.js +260 -0
  84. package/dist/src/core/fusion/prompts.js.map +1 -0
  85. package/dist/src/core/fusion/result-package.js +857 -0
  86. package/dist/src/core/fusion/result-package.js.map +1 -0
  87. package/dist/src/core/fusion/source-policy.js +259 -0
  88. package/dist/src/core/fusion/source-policy.js.map +1 -0
  89. package/dist/src/core/fusion/types.js +207 -0
  90. package/dist/src/core/fusion/types.js.map +1 -0
  91. package/dist/src/core/fusion/web-fetch.js +777 -0
  92. package/dist/src/core/fusion/web-fetch.js.map +1 -0
  93. package/dist/src/core/fusion/workflows.js +126 -0
  94. package/dist/src/core/fusion/workflows.js.map +1 -0
  95. package/dist/src/core/lazy-module.js +181 -0
  96. package/dist/src/core/lazy-module.js.map +1 -0
  97. package/dist/src/core/pi-launch.js +431 -0
  98. package/dist/src/core/pi-launch.js.map +1 -0
  99. package/dist/src/core/registry.js +3331 -0
  100. package/dist/src/core/registry.js.map +1 -0
  101. package/dist/src/core/reload-shell-owner.js +1396 -0
  102. package/dist/src/core/reload-shell-owner.js.map +1 -0
  103. package/dist/src/core/shell-policy.js +80 -0
  104. package/dist/src/core/shell-policy.js.map +1 -0
  105. package/dist/src/core/task-durable.js +51 -0
  106. package/dist/src/core/task-durable.js.map +1 -0
  107. package/dist/src/core/update-check.js +92 -0
  108. package/dist/src/core/update-check.js.map +1 -0
  109. package/dist/src/core/windows-taskkill.js +185 -0
  110. package/dist/src/core/windows-taskkill.js.map +1 -0
  111. package/dist/src/delegate-child-extension.js +776 -0
  112. package/dist/src/delegate-child-extension.js.map +1 -0
  113. package/dist/src/delegate-extension.js +746 -0
  114. package/dist/src/delegate-extension.js.map +1 -0
  115. package/dist/src/extension.js +953 -0
  116. package/dist/src/extension.js.map +1 -0
  117. package/dist/src/fusion-child-extension.js +760 -0
  118. package/dist/src/fusion-child-extension.js.map +1 -0
  119. package/dist/src/fusion-extension.js +1030 -0
  120. package/dist/src/fusion-extension.js.map +1 -0
  121. package/dist/src/ui/background-tasks-manager.js +689 -0
  122. package/dist/src/ui/background-tasks-manager.js.map +1 -0
  123. package/dist/src/ui/fusion-model-selector.js +277 -0
  124. package/dist/src/ui/fusion-model-selector.js.map +1 -0
  125. package/docs/INDEX.md +35 -33
  126. package/docs/api/eventbus-v1.md +21 -5
  127. package/docs/choose-a-workflow.md +2 -0
  128. package/docs/commands/bg-clear.md +3 -3
  129. package/docs/commands/bg-update.md +3 -3
  130. package/docs/commands/bg.md +18 -6
  131. package/docs/commands/claude-cache.md +4 -4
  132. package/docs/commands/fusion-models.md +49 -4
  133. package/docs/commands/fusion.md +3 -3
  134. package/docs/commands/jobs.md +3 -3
  135. package/docs/commands/kill.md +3 -3
  136. package/docs/commands/logs.md +3 -3
  137. package/docs/commands/task-manager.md +5 -5
  138. package/docs/concepts/completion-delivery.md +6 -2
  139. package/docs/getting-started.md +7 -1
  140. package/docs/manifest.json +228 -42
  141. package/docs/operations/configuration.md +58 -5
  142. package/docs/operations/releasing.md +4 -2
  143. package/docs/operations/testing.md +22 -0
  144. package/docs/operations/troubleshooting.md +6 -2
  145. package/docs/read-before-edit.md +44 -31
  146. package/docs/reference/runtime-contracts.md +87 -51
  147. package/docs/reference/shortcuts-and-dock.md +28 -14
  148. package/docs/subsystems/anthropic-attribution.md +23 -11
  149. package/docs/subsystems/attested-pi-runs.md +12 -7
  150. package/docs/subsystems/background-task-runtime.md +53 -11
  151. package/docs/subsystems/child-launch-durability-and-safety.md +28 -9
  152. package/docs/subsystems/delegation.md +16 -3
  153. package/docs/subsystems/docs-freshness-gate.md +14 -7
  154. package/docs/subsystems/fusion.md +36 -11
  155. package/docs/subsystems/host-ui-and-telemetry.md +22 -9
  156. package/docs/tools/bg_delegate.md +3 -1
  157. package/docs/tools/bg_kill.md +3 -1
  158. package/docs/tools/bg_logs.md +3 -1
  159. package/docs/tools/bg_result.md +5 -3
  160. package/docs/tools/bg_run.md +31 -5
  161. package/docs/tools/bg_run_pi_attested.md +3 -1
  162. package/docs/tools/bg_status.md +3 -1
  163. package/docs/tools/fusion_investigate.md +3 -1
  164. package/docs/tools/fusion_reason.md +3 -1
  165. package/docs/tools/fusion_research.md +3 -1
  166. package/docs/tools/fusion_validate.md +3 -1
  167. package/extensions/anthropic-attribution-child.ts +3 -0
  168. package/extensions/anthropic-attribution.ts +166 -1
  169. package/package.json +12 -10
  170. package/src/core/anthropic-attribution-path.ts +5 -4
  171. package/src/core/anthropic-attribution.ts +293 -113
  172. package/src/core/attested-pi-contract.ts +4 -0
  173. package/src/core/attested-pi-run.ts +468 -81
  174. package/src/core/canonical-json.ts +21 -0
  175. package/src/core/common.ts +450 -40
  176. package/src/core/config.ts +121 -0
  177. package/src/core/context/visible-conversation-v2.ts +3 -6
  178. package/src/core/delegate/artifacts.ts +13 -9
  179. package/src/core/delegate/budget.ts +13 -6
  180. package/src/core/delegate/facade-contract.ts +6 -0
  181. package/src/core/delegate/result-package.ts +32 -14
  182. package/src/core/delegate/runner.ts +129 -58
  183. package/src/core/delegate/seed.ts +5 -7
  184. package/src/core/durable-fs.ts +178 -30
  185. package/src/core/extension-api.ts +36 -8
  186. package/src/core/fusion/artifacts.ts +17 -16
  187. package/src/core/fusion/clean-context.ts +8 -5
  188. package/src/core/fusion/config.ts +2 -1
  189. package/src/core/fusion/context.ts +2 -6
  190. package/src/core/fusion/facade-contract.ts +2 -0
  191. package/src/core/fusion/orchestrator.ts +3 -3
  192. package/src/core/fusion/prompts.ts +1 -1
  193. package/src/core/fusion/result-package.ts +259 -72
  194. package/src/core/fusion/source-policy.ts +43 -17
  195. package/src/core/lazy-module.ts +215 -0
  196. package/src/core/pi-launch.ts +390 -65
  197. package/src/core/registry.ts +2010 -381
  198. package/src/core/reload-shell-owner.ts +1662 -0
  199. package/src/core/shell-policy.ts +134 -0
  200. package/src/core/task-durable.ts +67 -0
  201. package/src/delegate-extension.ts +404 -144
  202. package/src/extension.ts +387 -114
  203. package/src/fusion-extension.ts +202 -96
@@ -0,0 +1,2247 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { appendFileSync, readFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { isAbsolute, join } from 'node:path';
5
+ import { anthropicMessagesApi } from '@earendil-works/pi-ai/compat';
6
+ export const CLAUDE_CODE_SESSION_HEADER = 'X-Claude-Code-Session-Id';
7
+ const CLAUDE_CODE_VERSION = '2.1.251';
8
+ const CLAUDE_CODE_ENTRYPOINT = 'sdk-cli';
9
+ const CLAUDE_CODE_USER_AGENT = 'claude-cli/2.1.251 (external, sdk-cli)';
10
+ export const ANTHROPIC_1M_CONTEXT_BETA = 'context-1m-2025-08-07';
11
+ export const CLAUDE_CODE_200K_SUBSCRIPTION_CONTEXT_WINDOW = 200_000;
12
+ const CLAUDE_CODE_LEGACY_BETA_VALUES = [
13
+ 'claude-code-20250219',
14
+ 'oauth-2025-04-20',
15
+ 'interleaved-thinking-2025-05-14',
16
+ 'thinking-token-count-2026-05-13',
17
+ 'context-management-2025-06-27',
18
+ 'prompt-caching-scope-2026-01-05',
19
+ 'advisor-tool-2026-03-01',
20
+ 'structured-outputs-2025-12-15',
21
+ ];
22
+ const CLAUDE_CODE_ADAPTIVE_200K_BETA_VALUES = [
23
+ 'claude-code-20250219',
24
+ 'oauth-2025-04-20',
25
+ 'interleaved-thinking-2025-05-14',
26
+ 'thinking-token-count-2026-05-13',
27
+ 'context-management-2025-06-27',
28
+ 'prompt-caching-scope-2026-01-05',
29
+ 'mid-conversation-system-2026-04-07',
30
+ ];
31
+ const CLAUDE_CODE_FABLE_5_1_200K_BETA_VALUES = [
32
+ ...CLAUDE_CODE_ADAPTIVE_200K_BETA_VALUES,
33
+ 'thinking-binding-controls-2026-08-01',
34
+ 'cache-diagnosis-2026-04-07',
35
+ ];
36
+ function build200KSubscriptionBetaHeader(values) {
37
+ if (values.includes(ANTHROPIC_1M_CONTEXT_BETA)) {
38
+ throw new Error(`Anthropic attribution 200K subscription policy must not emit ${ANTHROPIC_1M_CONTEXT_BETA}`);
39
+ }
40
+ return values.join(',');
41
+ }
42
+ export const CLAUDE_CODE_BETA = build200KSubscriptionBetaHeader(CLAUDE_CODE_LEGACY_BETA_VALUES);
43
+ const CLAUDE_CODE_ADAPTIVE_200K_BETA = build200KSubscriptionBetaHeader(CLAUDE_CODE_ADAPTIVE_200K_BETA_VALUES);
44
+ const CLAUDE_CODE_FABLE_5_1_200K_BETA = build200KSubscriptionBetaHeader(CLAUDE_CODE_FABLE_5_1_200K_BETA_VALUES);
45
+ const CLAUDE_AGENT_SDK_SYSTEM_TEXT = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
46
+ const FINGERPRINT_SALT = '59cf53e54c78';
47
+ const AUDIT_ENV = 'PIPELINE_ANTHROPIC_ATTRIBUTION_AUDIT_PATH';
48
+ const CACHE_RETENTION_ENV = 'PI_CACHE_RETENTION';
49
+ export const ANTHROPIC_ACCOUNT_CONFIG_PATH_ENV = 'PI_ANTHROPIC_ACCOUNT_CONFIG_PATH';
50
+ export const ANTHROPIC_CACHE_RETENTION_ENTRY = 'pipeline-anthropic-cache-retention';
51
+ const ANTHROPIC_CACHE_RETENTION_SCHEMA = 'pipeline.anthropic_cache_retention.v1';
52
+ export const ANTHROPIC_ATTRIBUTION_CLAIM_CHANNEL = 'pi-anthropic-attribution:claim:v1';
53
+ const ANTHROPIC_ATTRIBUTION_CLAIM_SCHEMA = 'pi-anthropic-attribution.claim.v1';
54
+ const NATIVE_ATTESTATION_PLACEHOLDER = '00000';
55
+ const ANTHROPIC_CACHE_CONTROL_BREAKPOINT_LIMIT = 4;
56
+ const ANTHROPIC_LINEAGE_DIAGNOSTIC_TYPE = 'anthropic-cache-lineage';
57
+ const ANTHROPIC_LINEAGE_SCHEMA = 'pi-anthropic-attribution.lineage.v1';
58
+ // Bump whenever any system/tool/message wire projection changes. Old receipts then
59
+ // become legacy and cannot authorize signature replay under a rewritten prefix.
60
+ const ANTHROPIC_PROJECTION_VERSION = 3;
61
+ const ANTHROPIC_OFFICIAL_ORIGIN = 'https://api.anthropic.com';
62
+ const ANTHROPIC_BETA_MESSAGES_URL = `${ANTHROPIC_OFFICIAL_ORIGIN}/v1/messages?beta=true`;
63
+ const ANTHROPIC_CACHE_DIAGNOSTICS_BETA = 'cache-diagnosis-2026-04-07';
64
+ const ANTHROPIC_THINKING_BINDING_BETA = 'thinking-binding-controls-2026-08-01';
65
+ const COMPACTION_SUMMARY_PREFIX = 'The conversation history before this point was compacted into the following summary:';
66
+ // Sanitization behavior derived from the MIT-licensed ravshansbox/pi-anthropic-sps
67
+ // extension at commit 17409b5615f0ec0625776bc5434f92f2c55e3fd0. Keep exact-match
68
+ // semantics and all known Pi prompt variants; unrelated system text is preserved.
69
+ const ANTHROPIC_SYSTEM_PROMPT_BAD_LINES = new Set([
70
+ '- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md)',
71
+ '- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md)',
72
+ '- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing',
73
+ ]);
74
+ const parseJsonSource = JSON.parse.bind(JSON);
75
+ function parseJsonValue(text, label) {
76
+ try {
77
+ return parseJsonSource(text);
78
+ }
79
+ catch (error) {
80
+ throw new Error(`${label} is invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
81
+ }
82
+ }
83
+ function parseJsonObject(text, label) {
84
+ const parsed = parseJsonValue(text, label);
85
+ if (!isPlainObject(parsed))
86
+ throw new Error(`${label} must be a JSON object`);
87
+ return parsed;
88
+ }
89
+ function claudeCode200KSubscriptionPolicy(modelId, beta, thinkingPolicy, features = {}) {
90
+ if (beta.split(',').includes(ANTHROPIC_1M_CONTEXT_BETA)) {
91
+ throw new Error(`Anthropic attribution 200K subscription policy for ${modelId} must not emit ${ANTHROPIC_1M_CONTEXT_BETA}`);
92
+ }
93
+ return {
94
+ modelId,
95
+ beta,
96
+ thinkingPolicy,
97
+ contextWindow: CLAUDE_CODE_200K_SUBSCRIPTION_CONTEXT_WINDOW,
98
+ enforcesThinkingPrefixBinding: features.enforcesThinkingPrefixBinding === true,
99
+ supportsCacheDiagnostics: features.supportsCacheDiagnostics === true,
100
+ };
101
+ }
102
+ const CLAUDE_CODE_MODEL_POLICIES = Object.freeze({
103
+ 'claude-3-5-haiku-20241022': claudeCode200KSubscriptionPolicy('claude-3-5-haiku-20241022', CLAUDE_CODE_BETA, 'fixed-budget'),
104
+ 'claude-3-5-haiku-latest': claudeCode200KSubscriptionPolicy('claude-3-5-haiku-latest', CLAUDE_CODE_BETA, 'fixed-budget'),
105
+ 'claude-3-5-sonnet-20240620': claudeCode200KSubscriptionPolicy('claude-3-5-sonnet-20240620', CLAUDE_CODE_BETA, 'fixed-budget'),
106
+ 'claude-3-5-sonnet-20241022': claudeCode200KSubscriptionPolicy('claude-3-5-sonnet-20241022', CLAUDE_CODE_BETA, 'fixed-budget'),
107
+ 'claude-3-7-sonnet-20250219': claudeCode200KSubscriptionPolicy('claude-3-7-sonnet-20250219', CLAUDE_CODE_BETA, 'fixed-budget'),
108
+ 'claude-3-haiku-20240307': claudeCode200KSubscriptionPolicy('claude-3-haiku-20240307', CLAUDE_CODE_BETA, 'fixed-budget'),
109
+ 'claude-3-opus-20240229': claudeCode200KSubscriptionPolicy('claude-3-opus-20240229', CLAUDE_CODE_BETA, 'fixed-budget'),
110
+ 'claude-3-sonnet-20240229': claudeCode200KSubscriptionPolicy('claude-3-sonnet-20240229', CLAUDE_CODE_BETA, 'fixed-budget'),
111
+ 'claude-fable-5': claudeCode200KSubscriptionPolicy('claude-fable-5', CLAUDE_CODE_ADAPTIVE_200K_BETA, 'adaptive-effort'),
112
+ 'claude-fable-5-1': claudeCode200KSubscriptionPolicy('claude-fable-5-1', CLAUDE_CODE_FABLE_5_1_200K_BETA, 'adaptive-effort', { enforcesThinkingPrefixBinding: true, supportsCacheDiagnostics: true }),
113
+ 'claude-haiku-4-5': claudeCode200KSubscriptionPolicy('claude-haiku-4-5', CLAUDE_CODE_BETA, 'fixed-budget'),
114
+ 'claude-haiku-4-5-20251001': claudeCode200KSubscriptionPolicy('claude-haiku-4-5-20251001', CLAUDE_CODE_BETA, 'fixed-budget'),
115
+ 'claude-opus-4-0': claudeCode200KSubscriptionPolicy('claude-opus-4-0', CLAUDE_CODE_BETA, 'fixed-budget'),
116
+ 'claude-opus-4-1': claudeCode200KSubscriptionPolicy('claude-opus-4-1', CLAUDE_CODE_BETA, 'fixed-budget'),
117
+ 'claude-opus-4-1-20250805': claudeCode200KSubscriptionPolicy('claude-opus-4-1-20250805', CLAUDE_CODE_BETA, 'fixed-budget'),
118
+ 'claude-opus-4-20250514': claudeCode200KSubscriptionPolicy('claude-opus-4-20250514', CLAUDE_CODE_BETA, 'fixed-budget'),
119
+ 'claude-opus-4-5': claudeCode200KSubscriptionPolicy('claude-opus-4-5', CLAUDE_CODE_BETA, 'fixed-budget'),
120
+ 'claude-opus-4-5-20251101': claudeCode200KSubscriptionPolicy('claude-opus-4-5-20251101', CLAUDE_CODE_BETA, 'fixed-budget'),
121
+ 'claude-opus-4-6': claudeCode200KSubscriptionPolicy('claude-opus-4-6', CLAUDE_CODE_ADAPTIVE_200K_BETA, 'adaptive-effort'),
122
+ 'claude-opus-4-7': claudeCode200KSubscriptionPolicy('claude-opus-4-7', CLAUDE_CODE_ADAPTIVE_200K_BETA, 'adaptive-effort'),
123
+ 'claude-opus-4-8': claudeCode200KSubscriptionPolicy('claude-opus-4-8', CLAUDE_CODE_ADAPTIVE_200K_BETA, 'adaptive-effort'),
124
+ 'claude-opus-5': claudeCode200KSubscriptionPolicy('claude-opus-5', CLAUDE_CODE_ADAPTIVE_200K_BETA, 'adaptive-effort'),
125
+ 'claude-sonnet-4-0': claudeCode200KSubscriptionPolicy('claude-sonnet-4-0', CLAUDE_CODE_BETA, 'fixed-budget'),
126
+ 'claude-sonnet-4-20250514': claudeCode200KSubscriptionPolicy('claude-sonnet-4-20250514', CLAUDE_CODE_BETA, 'fixed-budget'),
127
+ 'claude-sonnet-4-5': claudeCode200KSubscriptionPolicy('claude-sonnet-4-5', CLAUDE_CODE_BETA, 'fixed-budget'),
128
+ 'claude-sonnet-4-5-20250929': claudeCode200KSubscriptionPolicy('claude-sonnet-4-5-20250929', CLAUDE_CODE_BETA, 'fixed-budget'),
129
+ 'claude-sonnet-4-6': claudeCode200KSubscriptionPolicy('claude-sonnet-4-6', CLAUDE_CODE_ADAPTIVE_200K_BETA, 'adaptive-effort'),
130
+ 'claude-sonnet-5': claudeCode200KSubscriptionPolicy('claude-sonnet-5', CLAUDE_CODE_ADAPTIVE_200K_BETA, 'adaptive-effort'),
131
+ });
132
+ class LocalAssistantMessageEventStream {
133
+ queue = [];
134
+ waiting = [];
135
+ done = false;
136
+ finalResultPromise;
137
+ resolveFinalResult;
138
+ constructor() {
139
+ this.finalResultPromise = new Promise((resolve) => {
140
+ this.resolveFinalResult = resolve;
141
+ });
142
+ }
143
+ push(event) {
144
+ if (this.done)
145
+ return;
146
+ if (event.type === 'done') {
147
+ this.done = true;
148
+ this.resolveFinalResult(event.message);
149
+ }
150
+ else if (event.type === 'error') {
151
+ this.done = true;
152
+ this.resolveFinalResult(event.error);
153
+ }
154
+ const waiter = this.waiting.shift();
155
+ if (waiter)
156
+ waiter({ value: event, done: false });
157
+ else
158
+ this.queue.push(event);
159
+ }
160
+ end(result) {
161
+ this.done = true;
162
+ if (result !== undefined)
163
+ this.resolveFinalResult(result);
164
+ while (this.waiting.length > 0) {
165
+ this.waiting.shift()?.({ value: undefined, done: true });
166
+ }
167
+ }
168
+ async *[Symbol.asyncIterator]() {
169
+ for (;;) {
170
+ const queued = this.queue.shift();
171
+ if (queued) {
172
+ yield queued;
173
+ }
174
+ else if (this.done) {
175
+ return;
176
+ }
177
+ else {
178
+ const next = await new Promise((resolve) => this.waiting.push(resolve));
179
+ if (next.done)
180
+ return;
181
+ yield next.value;
182
+ }
183
+ }
184
+ }
185
+ result() {
186
+ return this.finalResultPromise;
187
+ }
188
+ }
189
+ function createAssistantMessageEventStream() {
190
+ return new LocalAssistantMessageEventStream();
191
+ }
192
+ function isPlainObject(value) {
193
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
194
+ }
195
+ function providerEnvValue(name, env) {
196
+ return env?.[name] ?? process.env[name];
197
+ }
198
+ function parseCacheRetention(value, source) {
199
+ if (value === 'none' || value === 'short' || value === 'long')
200
+ return value;
201
+ throw new Error(`Anthropic attribution ${source} must be one of none, short, or long; got ${JSON.stringify(value)}`);
202
+ }
203
+ /** Resolve an explicit request posture. Registered Pi sessions apply the stronger
204
+ * one-hour policy through resolveRegisteredCacheRetention below. */
205
+ export function resolveCacheRetentionPreference(options, sessionOverride) {
206
+ if (options?.cacheRetention !== undefined)
207
+ return options.cacheRetention;
208
+ if (sessionOverride !== undefined)
209
+ return sessionOverride;
210
+ const configured = providerEnvValue(CACHE_RETENTION_ENV, options?.env);
211
+ if (configured !== undefined)
212
+ return parseCacheRetention(configured, CACHE_RETENTION_ENV);
213
+ return 'long';
214
+ }
215
+ function resolveRegisteredCacheRetention(options, sessionOverride) {
216
+ if (options?.cacheRetention === 'none')
217
+ return 'none';
218
+ if (sessionOverride !== undefined)
219
+ return sessionOverride;
220
+ const configured = providerEnvValue(CACHE_RETENTION_ENV, options?.env);
221
+ if (configured !== undefined)
222
+ return parseCacheRetention(configured, CACHE_RETENTION_ENV);
223
+ if (options?.cacheRetention === 'long')
224
+ return 'long';
225
+ // Pi's generic provider default is five minutes. Subscription coding sessions
226
+ // routinely have turns longer than that, so the attributed route pins one hour.
227
+ return 'long';
228
+ }
229
+ /** Restore the latest branch-local command decision; custom entries stay out of LLM context. */
230
+ export function restoreAnthropicSessionCacheRetention(entries) {
231
+ let restored;
232
+ for (const entry of entries) {
233
+ if (!isPlainObject(entry) || entry['type'] !== 'custom')
234
+ continue;
235
+ if (entry['customType'] !== ANTHROPIC_CACHE_RETENTION_ENTRY)
236
+ continue;
237
+ const data = entry['data'];
238
+ if (!isPlainObject(data) ||
239
+ data['schema_version'] !== ANTHROPIC_CACHE_RETENTION_SCHEMA ||
240
+ (data['retention'] !== 'default' &&
241
+ data['retention'] !== 'short' &&
242
+ data['retention'] !== 'long')) {
243
+ throw new Error('Anthropic attribution found a malformed persisted cache retention entry');
244
+ }
245
+ restored = data['retention'] === 'default' ? undefined : data['retention'];
246
+ }
247
+ return restored;
248
+ }
249
+ function resolveAnthropicCacheControl(model, options) {
250
+ const retention = resolveCacheRetentionPreference(options);
251
+ if (retention === 'none')
252
+ return undefined;
253
+ const ttl = retention === 'long' && (model?.compat?.supportsLongCacheRetention ?? true) ? '1h' : undefined;
254
+ return ttl === undefined ? { type: 'ephemeral' } : { type: 'ephemeral', ttl };
255
+ }
256
+ function cloneAnthropicCacheControl(value) {
257
+ if (value === undefined)
258
+ return undefined;
259
+ if (!isPlainObject(value)) {
260
+ throw new Error('Anthropic attribution cannot safely process malformed cache_control; expected an object');
261
+ }
262
+ if (value['type'] !== 'ephemeral') {
263
+ throw new Error('Anthropic attribution cannot safely process malformed cache_control.type; expected "ephemeral"');
264
+ }
265
+ const ttl = value['ttl'];
266
+ if (ttl !== undefined && ttl !== '1h' && ttl !== '5m') {
267
+ throw new Error('Anthropic attribution cannot safely process malformed cache_control.ttl; expected "1h" or "5m"');
268
+ }
269
+ return {
270
+ ...value,
271
+ type: 'ephemeral',
272
+ ...(ttl === undefined ? {} : { ttl }),
273
+ };
274
+ }
275
+ function mergedCacheControl(existing, desired) {
276
+ const existingControl = cloneAnthropicCacheControl(existing);
277
+ if (existingControl === undefined)
278
+ return desired === undefined ? undefined : { ...desired };
279
+ if (desired?.ttl === '1h' && existingControl.ttl !== '1h')
280
+ return { ...existingControl, ttl: '1h' };
281
+ return existingControl;
282
+ }
283
+ function cloneBlockWithCacheControl(block, desired) {
284
+ const next = { ...block };
285
+ const cacheControl = mergedCacheControl(next['cache_control'], desired);
286
+ if (cacheControl !== undefined)
287
+ next['cache_control'] = cacheControl;
288
+ return next;
289
+ }
290
+ function stripAnthropicSystemPromptBadLines(text) {
291
+ return text
292
+ .split('\n')
293
+ .filter((line) => !ANTHROPIC_SYSTEM_PROMPT_BAD_LINES.has(line))
294
+ .join('\n');
295
+ }
296
+ function inspectCacheControls(payload) {
297
+ let count = 0;
298
+ let hasLong = false;
299
+ const inspectBlock = (block) => {
300
+ if (!isPlainObject(block) || block['cache_control'] === undefined)
301
+ return;
302
+ const cacheControl = cloneAnthropicCacheControl(block['cache_control']);
303
+ count += 1;
304
+ if (cacheControl?.ttl === '1h')
305
+ hasLong = true;
306
+ };
307
+ const system = payload['system'];
308
+ if (Array.isArray(system)) {
309
+ for (const block of system)
310
+ inspectBlock(block);
311
+ }
312
+ const tools = payload['tools'];
313
+ if (Array.isArray(tools)) {
314
+ for (const tool of tools)
315
+ inspectBlock(tool);
316
+ }
317
+ const messages = payload['messages'];
318
+ if (Array.isArray(messages)) {
319
+ for (const message of messages) {
320
+ if (!isPlainObject(message))
321
+ continue;
322
+ const content = message['content'];
323
+ if (Array.isArray(content)) {
324
+ for (const block of content)
325
+ inspectBlock(block);
326
+ }
327
+ }
328
+ }
329
+ return { count, retention: count === 0 ? undefined : hasLong ? 'long' : 'short' };
330
+ }
331
+ function cacheControlOccurrences(payload) {
332
+ const occurrences = [];
333
+ const inspectBlocks = (value, path) => {
334
+ if (!Array.isArray(value))
335
+ return;
336
+ value.forEach((block, index) => {
337
+ if (!isPlainObject(block))
338
+ return;
339
+ const blockPath = `${path}[${String(index)}]`;
340
+ if (block['cache_control'] !== undefined) {
341
+ const control = cloneAnthropicCacheControl(block['cache_control']);
342
+ if (control !== undefined)
343
+ occurrences.push({ path: blockPath, control });
344
+ }
345
+ if (block['type'] === 'tool_result')
346
+ inspectBlocks(block['content'], `${blockPath}.content`);
347
+ });
348
+ };
349
+ if (payload['cache_control'] !== undefined) {
350
+ const control = cloneAnthropicCacheControl(payload['cache_control']);
351
+ if (control !== undefined)
352
+ occurrences.push({ path: '$', control });
353
+ }
354
+ inspectBlocks(payload['system'], '$.system');
355
+ inspectBlocks(payload['tools'], '$.tools');
356
+ const messages = payload['messages'];
357
+ if (Array.isArray(messages)) {
358
+ messages.forEach((message, index) => {
359
+ if (isPlainObject(message))
360
+ inspectBlocks(message['content'], `$.messages[${String(index)}].content`);
361
+ });
362
+ }
363
+ return occurrences;
364
+ }
365
+ function countCacheControlBreakpoints(payload) {
366
+ return cacheControlOccurrences(payload).length;
367
+ }
368
+ function cacheControlTopologySha256(payload) {
369
+ return sha256Canonical(cacheControlOccurrences(payload));
370
+ }
371
+ function assertCacheControlBreakpointLimit(payload) {
372
+ const count = countCacheControlBreakpoints(payload);
373
+ if (count > ANTHROPIC_CACHE_CONTROL_BREAKPOINT_LIMIT) {
374
+ throw new Error(`Anthropic attribution produced ${count} cache_control breakpoints; Anthropic supports at most ${ANTHROPIC_CACHE_CONTROL_BREAKPOINT_LIMIT}`);
375
+ }
376
+ }
377
+ function assertNonEmptyString(value, fieldName, configPath) {
378
+ if (typeof value !== 'string' || value.trim().length === 0) {
379
+ throw new Error(`Anthropic attribution config ${configPath} missing/malformed required field ${fieldName}`);
380
+ }
381
+ return value;
382
+ }
383
+ export function extractClaudeAttributionAccount(parsedConfig, configPath) {
384
+ if (!isPlainObject(parsedConfig)) {
385
+ throw new Error(`Anthropic attribution config ${configPath} is not a JSON object`);
386
+ }
387
+ const oauthAccount = parsedConfig['oauthAccount'];
388
+ if (!isPlainObject(oauthAccount)) {
389
+ throw new Error(`Anthropic attribution config ${configPath} missing/malformed required field oauthAccount.accountUuid`);
390
+ }
391
+ return {
392
+ deviceId: assertNonEmptyString(parsedConfig['userID'], 'userID', configPath),
393
+ accountUuid: assertNonEmptyString(oauthAccount['accountUuid'], 'oauthAccount.accountUuid', configPath),
394
+ };
395
+ }
396
+ export function resolveClaudeAttributionConfigPath(explicitConfigPath, env, homeDirectory = homedir()) {
397
+ const configuredPath = providerEnvValue(ANTHROPIC_ACCOUNT_CONFIG_PATH_ENV, env);
398
+ const selectedPath = explicitConfigPath ?? configuredPath ?? join(homeDirectory, '.claude.json');
399
+ const source = explicitConfigPath !== undefined
400
+ ? 'explicit configPath'
401
+ : configuredPath !== undefined
402
+ ? ANTHROPIC_ACCOUNT_CONFIG_PATH_ENV
403
+ : 'default ~/.claude.json path';
404
+ if (selectedPath.trim().length === 0 || !isAbsolute(selectedPath)) {
405
+ throw new Error(`Anthropic attribution ${source} must be a non-empty absolute file path; got ${JSON.stringify(selectedPath)}`);
406
+ }
407
+ return selectedPath;
408
+ }
409
+ export function loadClaudeAttributionAccount(configPath, env) {
410
+ const selectedPath = resolveClaudeAttributionConfigPath(configPath, env);
411
+ let configText;
412
+ try {
413
+ configText = readFileSync(selectedPath, 'utf8');
414
+ }
415
+ catch (error) {
416
+ throw new Error(`Anthropic attribution config ${selectedPath} could not be read: ${error instanceof Error ? error.message : String(error)}`);
417
+ }
418
+ return extractClaudeAttributionAccount(parseJsonValue(configText, `Anthropic attribution config ${selectedPath}`), selectedPath);
419
+ }
420
+ export function isAnthropicContext(ctx) {
421
+ return ctx.model?.provider === 'anthropic';
422
+ }
423
+ function requireSessionId(value, source) {
424
+ if (typeof value !== 'string' || value.trim().length === 0) {
425
+ throw new Error(`Anthropic attribution requires a non-empty ${source}`);
426
+ }
427
+ return value;
428
+ }
429
+ function getSessionId(ctx) {
430
+ return requireSessionId(ctx.sessionManager.getSessionId(), 'Pi session id');
431
+ }
432
+ function normalizedAnthropicModelId(model) {
433
+ if (typeof model.id !== 'string' || model.id.trim().length === 0) {
434
+ throw new Error('Anthropic attribution requires a non-empty model id');
435
+ }
436
+ const providerPrefix = 'anthropic/';
437
+ return model.id.startsWith(providerPrefix) ? model.id.slice(providerPrefix.length) : model.id;
438
+ }
439
+ export function resolveClaudeCodeModelPolicy(model) {
440
+ const modelId = normalizedAnthropicModelId(model);
441
+ const policy = CLAUDE_CODE_MODEL_POLICIES[modelId];
442
+ if (policy === undefined) {
443
+ throw new Error(`Anthropic attribution has no Claude Code model policy for ${modelId}`);
444
+ }
445
+ return policy;
446
+ }
447
+ export function resolveAnthropicMaxTokens(model) {
448
+ return assertPositiveInteger(model.maxTokens, `model.maxTokens for ${normalizedAnthropicModelId(model)}`);
449
+ }
450
+ export function computeClaudeCodeFingerprint(messageText, version = CLAUDE_CODE_VERSION) {
451
+ const chars = [4, 7, 20].map((index) => messageText[index] || '0').join('');
452
+ return createHash('sha256')
453
+ .update(`${FINGERPRINT_SALT}${chars}${version}`)
454
+ .digest('hex')
455
+ .slice(0, 3);
456
+ }
457
+ function firstUserMessageTextFromPayload(payload) {
458
+ const messages = payload['messages'];
459
+ if (!Array.isArray(messages))
460
+ return '';
461
+ for (const message of messages) {
462
+ if (!isPlainObject(message) || message['role'] !== 'user')
463
+ continue;
464
+ const content = message['content'];
465
+ if (typeof content === 'string')
466
+ return content;
467
+ if (Array.isArray(content)) {
468
+ const textBlock = content.find((block) => isPlainObject(block) && block['type'] === 'text' && typeof block['text'] === 'string');
469
+ if (isPlainObject(textBlock) && typeof textBlock['text'] === 'string')
470
+ return textBlock['text'];
471
+ }
472
+ }
473
+ return '';
474
+ }
475
+ export function buildClaudeCodeBillingSystemText(firstUserMessageText) {
476
+ const fingerprint = computeClaudeCodeFingerprint(firstUserMessageText);
477
+ return `x-anthropic-billing-header: cc_version=${CLAUDE_CODE_VERSION}.${fingerprint}; cc_entrypoint=${CLAUDE_CODE_ENTRYPOINT}; cch=${NATIVE_ATTESTATION_PLACEHOLDER};`;
478
+ }
479
+ export function buildAnthropicAttributionHeaders(sessionId, model) {
480
+ const beta = model === undefined ? CLAUDE_CODE_BETA : resolveClaudeCodeModelPolicy(model).beta;
481
+ return {
482
+ [CLAUDE_CODE_SESSION_HEADER]: sessionId,
483
+ 'anthropic-beta': beta,
484
+ 'anthropic-version': '2023-06-01',
485
+ 'User-Agent': CLAUDE_CODE_USER_AGENT,
486
+ 'x-app': 'cli',
487
+ 'anthropic-dangerous-direct-browser-access': 'true',
488
+ };
489
+ }
490
+ export function registerAnthropicAttributionProvider(pi, ctx, getSessionOverride = () => undefined, dependencies = {}) {
491
+ if (!isAnthropicContext(ctx))
492
+ return;
493
+ pi.registerProvider('anthropic', {
494
+ api: 'anthropic-messages',
495
+ headers: buildAnthropicAttributionHeaders(getSessionId(ctx), ctx.model),
496
+ streamSimple: (model, context, options) => streamAnthropicViaBetaMessages(model, context, {
497
+ ...(options ?? {}),
498
+ cacheRetention: resolveRegisteredCacheRetention(options, getSessionOverride()),
499
+ }, dependencies),
500
+ });
501
+ }
502
+ function assertPositiveInteger(value, fieldName) {
503
+ if (!Number.isInteger(value) || typeof value !== 'number' || value <= 0) {
504
+ throw new Error(`Anthropic attribution cannot safely process malformed ${fieldName}; expected a positive integer`);
505
+ }
506
+ return value;
507
+ }
508
+ function rewriteThinking(payload, maxTokens) {
509
+ if (payload['thinking'] === undefined)
510
+ return { thinking: undefined, budgetTokens: undefined };
511
+ if (!isPlainObject(payload['thinking'])) {
512
+ throw new Error('Anthropic attribution cannot safely process malformed thinking; expected an object');
513
+ }
514
+ const thinking = { ...payload['thinking'] };
515
+ if (thinking['type'] === 'disabled')
516
+ return { thinking: { type: 'disabled' }, budgetTokens: undefined };
517
+ if (thinking['budget_tokens'] === undefined)
518
+ return { thinking, budgetTokens: undefined };
519
+ const existingBudget = assertPositiveInteger(thinking['budget_tokens'], 'thinking.budget_tokens');
520
+ if (maxTokens !== undefined && existingBudget >= maxTokens) {
521
+ thinking['budget_tokens'] = maxTokens - 1;
522
+ }
523
+ if (typeof thinking['budget_tokens'] === 'number' && thinking['budget_tokens'] <= 0) {
524
+ throw new Error('Anthropic attribution cannot satisfy thinking.budget_tokens < max_tokens when max_tokens <= 1');
525
+ }
526
+ return { thinking, budgetTokens: thinking['budget_tokens'] };
527
+ }
528
+ function isClaudeCodeIdentityText(text) {
529
+ return (text.startsWith('x-anthropic-billing-header:') ||
530
+ text === CLAUDE_AGENT_SDK_SYSTEM_TEXT ||
531
+ text === "You are Claude Code, Anthropic's official CLI for Claude." ||
532
+ text ===
533
+ "You are Claude Code, Anthropic's official CLI for Claude, running within the Claude Agent SDK.");
534
+ }
535
+ function normalizeSystemBlock(block) {
536
+ if (!isPlainObject(block))
537
+ return block;
538
+ const next = { ...block };
539
+ if (typeof next['text'] === 'string')
540
+ next['text'] = stripAnthropicSystemPromptBadLines(next['text']);
541
+ if (next['cache_control'] !== undefined)
542
+ next['cache_control'] = cloneAnthropicCacheControl(next['cache_control']);
543
+ return next;
544
+ }
545
+ function hasCacheControl(block) {
546
+ return isPlainObject(block) && block['cache_control'] !== undefined;
547
+ }
548
+ function isSystemCacheSurface(block) {
549
+ return isPlainObject(block) && typeof block['text'] === 'string';
550
+ }
551
+ function markSystemCacheSurface(blocks, desired) {
552
+ const output = blocks.map((block) => (isPlainObject(block) ? { ...block } : block));
553
+ if (desired === undefined)
554
+ return output;
555
+ let lastTextBlockIndex = -1;
556
+ for (let index = 0; index < output.length; index += 1) {
557
+ if (isSystemCacheSurface(output[index]))
558
+ lastTextBlockIndex = index;
559
+ }
560
+ if (lastTextBlockIndex === -1)
561
+ return output;
562
+ const withLongRetentionUpgrades = desired.ttl === '1h'
563
+ ? output.map((block) => hasCacheControl(block) ? cloneBlockWithCacheControl(block, desired) : block)
564
+ : output;
565
+ withLongRetentionUpgrades[lastTextBlockIndex] = cloneBlockWithCacheControl(withLongRetentionUpgrades[lastTextBlockIndex], desired);
566
+ return withLongRetentionUpgrades;
567
+ }
568
+ function withClaudeCodeSystemIdentity(system, billingSystemText, cacheControl) {
569
+ const identityBlocks = [
570
+ { type: 'text', text: billingSystemText },
571
+ { type: 'text', text: CLAUDE_AGENT_SDK_SYSTEM_TEXT },
572
+ ];
573
+ if (system === undefined)
574
+ return markSystemCacheSurface(identityBlocks, cacheControl);
575
+ if (Array.isArray(system)) {
576
+ const withoutPriorIdentity = system
577
+ .filter((entry) => {
578
+ if (!isPlainObject(entry) || typeof entry['text'] !== 'string')
579
+ return true;
580
+ return !isClaudeCodeIdentityText(entry['text']);
581
+ })
582
+ .map(normalizeSystemBlock);
583
+ return markSystemCacheSurface([...identityBlocks, ...withoutPriorIdentity], cacheControl);
584
+ }
585
+ if (typeof system === 'string') {
586
+ return markSystemCacheSurface([...identityBlocks, { type: 'text', text: stripAnthropicSystemPromptBadLines(system) }], cacheControl);
587
+ }
588
+ throw new Error('Anthropic attribution cannot safely apply Claude Code system identity to malformed system payload');
589
+ }
590
+ function appendAuditRecord(args) {
591
+ const auditPath = process.env[AUDIT_ENV];
592
+ if (auditPath === undefined || auditPath.length === 0)
593
+ return;
594
+ const record = {
595
+ schema_version: 'pipeline.anthropic_attribution_audit.v1',
596
+ provider: args.provider,
597
+ header_name: CLAUDE_CODE_SESSION_HEADER,
598
+ header_registered: args.headerRegistered,
599
+ anthropic_beta: args.beta,
600
+ anthropic_version: '2023-06-01',
601
+ beta_resource_path: args.betaResourcePath,
602
+ native_attestation: args.nativeAttestation,
603
+ metadata_user_id_keys: ['account_uuid', 'device_id', 'session_id'],
604
+ metadata_session_id_matches_header: args.metadataSessionMatchesHeader,
605
+ account_uuid_present: true,
606
+ device_id_present: true,
607
+ max_tokens: args.maxTokens,
608
+ thinking_budget_tokens: args.thinkingBudgetTokens,
609
+ };
610
+ appendFileSync(auditPath, `${JSON.stringify(record)}\n`, { encoding: 'utf8', mode: 0o600 });
611
+ }
612
+ function requireAttributionAccount(value) {
613
+ if (!isPlainObject(value) ||
614
+ typeof value['deviceId'] !== 'string' ||
615
+ value['deviceId'].trim().length === 0 ||
616
+ typeof value['accountUuid'] !== 'string' ||
617
+ value['accountUuid'].trim().length === 0) {
618
+ throw new Error('Anthropic attribution account loader returned malformed account identity');
619
+ }
620
+ return { deviceId: value['deviceId'], accountUuid: value['accountUuid'] };
621
+ }
622
+ function anthropicMetadataUserId(account, sessionId) {
623
+ return JSON.stringify({
624
+ account_uuid: account.accountUuid,
625
+ device_id: account.deviceId,
626
+ session_id: sessionId,
627
+ });
628
+ }
629
+ function rewriteAnthropicRequestPayloadForSession(args) {
630
+ if (!isPlainObject(args.payload)) {
631
+ throw new Error('Anthropic attribution expected provider payload to be a JSON object');
632
+ }
633
+ const sessionId = requireSessionId(args.sessionId, 'provider options.sessionId');
634
+ const metadata = args.payload['metadata'] === undefined ? {} : args.payload['metadata'];
635
+ if (!isPlainObject(metadata)) {
636
+ throw new Error('Anthropic attribution expected payload.metadata to be an object when present');
637
+ }
638
+ const policy = resolveClaudeCodeModelPolicy(args.model);
639
+ const maxTokens = args.payload['max_tokens'] === undefined
640
+ ? undefined
641
+ : assertPositiveInteger(args.payload['max_tokens'], 'max_tokens');
642
+ const { thinking, budgetTokens } = rewriteThinking(args.payload, maxTokens);
643
+ const billingSystemText = buildClaudeCodeBillingSystemText(firstUserMessageTextFromPayload(args.payload));
644
+ const incomingCache = inspectCacheControls(args.payload);
645
+ // The provider builder has already resolved environment/session defaults and
646
+ // selected the cache surfaces. No incoming marker can therefore be Pi's
647
+ // explicit call-level `cacheRetention: "none"` (used for compaction). Reapplying
648
+ // the process default here would silently defeat that opt-out.
649
+ const configuredCacheRetention = args.cacheRetention ?? incomingCache.retention;
650
+ const cacheControl = configuredCacheRetention === undefined
651
+ ? undefined
652
+ : resolveAnthropicCacheControl(args.model, {
653
+ cacheRetention: configuredCacheRetention,
654
+ });
655
+ const rewritten = {
656
+ ...args.payload,
657
+ metadata: {
658
+ ...metadata,
659
+ user_id: anthropicMetadataUserId(args.account, sessionId),
660
+ },
661
+ system: withClaudeCodeSystemIdentity(args.payload['system'], billingSystemText, cacheControl),
662
+ };
663
+ if (thinking !== undefined)
664
+ rewritten['thinking'] = thinking;
665
+ assertCacheControlBreakpointLimit(rewritten);
666
+ appendAuditRecord({
667
+ provider: 'anthropic',
668
+ headerRegistered: args.headerRegistered ?? true,
669
+ metadataSessionMatchesHeader: true,
670
+ maxTokens,
671
+ thinkingBudgetTokens: budgetTokens,
672
+ beta: policy.beta,
673
+ betaResourcePath: '/v1/messages?beta=true',
674
+ nativeAttestation: 'placeholder-pending-live',
675
+ });
676
+ return rewritten;
677
+ }
678
+ function assertProtectedAnthropicAttribution(args) {
679
+ if (args.payload['model'] !== args.modelId || args.payload['stream'] !== true) {
680
+ throw new Error('Anthropic protected attribution model/stream route changed during payload middleware');
681
+ }
682
+ const metadata = args.payload['metadata'];
683
+ if (!isPlainObject(metadata) ||
684
+ metadata['user_id'] !== anthropicMetadataUserId(args.account, args.sessionId)) {
685
+ throw new Error('Anthropic protected attribution metadata.user_id/account/device/session_id changed during payload middleware');
686
+ }
687
+ const system = args.payload['system'];
688
+ if (!Array.isArray(system)) {
689
+ throw new Error('Anthropic protected attribution system identity was removed during payload middleware');
690
+ }
691
+ const billingBlocks = system.filter((block) => isPlainObject(block) &&
692
+ typeof block['text'] === 'string' &&
693
+ block['text'].startsWith('x-anthropic-billing-header:'));
694
+ const sdkBlocks = system.filter((block) => isPlainObject(block) && block['text'] === CLAUDE_AGENT_SDK_SYSTEM_TEXT);
695
+ if (billingBlocks.length !== 1 ||
696
+ billingBlocks[0]?.['text'] !== args.billingSystemText ||
697
+ sdkBlocks.length !== 1 ||
698
+ system[0] !== billingBlocks[0] ||
699
+ system[1] !== sdkBlocks[0]) {
700
+ throw new Error('Anthropic protected attribution system identity changed during payload middleware');
701
+ }
702
+ assertCacheControlBreakpointLimit(args.payload);
703
+ if (cacheControlTopologySha256(args.payload) !== args.cacheControlTopologySha256) {
704
+ throw new Error('Anthropic protected cache-control topology changed during payload middleware');
705
+ }
706
+ }
707
+ export function rewriteAnthropicRequestPayload(args) {
708
+ if (!isAnthropicContext(args.ctx))
709
+ return undefined;
710
+ return rewriteAnthropicRequestPayloadForSession({
711
+ payload: args.payload,
712
+ model: args.ctx.model ?? {},
713
+ sessionId: getSessionId(args.ctx),
714
+ account: args.account,
715
+ ...(args.headerRegistered === undefined ? {} : { headerRegistered: args.headerRegistered }),
716
+ ...(args.cacheRetention === undefined ? {} : { cacheRetention: args.cacheRetention }),
717
+ });
718
+ }
719
+ function sanitizeSurrogates(text) {
720
+ let sanitized = '';
721
+ for (let index = 0; index < text.length; index += 1) {
722
+ const code = text.charCodeAt(index);
723
+ if (code >= 0xd800 && code <= 0xdbff) {
724
+ const next = index + 1 < text.length ? text.charCodeAt(index + 1) : -1;
725
+ if (next >= 0xdc00 && next <= 0xdfff) {
726
+ sanitized += text[index] ?? '';
727
+ sanitized += text[index + 1] ?? '';
728
+ index += 1;
729
+ }
730
+ else {
731
+ sanitized += '\uFFFD';
732
+ }
733
+ }
734
+ else if (code >= 0xdc00 && code <= 0xdfff) {
735
+ sanitized += '\uFFFD';
736
+ }
737
+ else {
738
+ sanitized += text[index] ?? '';
739
+ }
740
+ }
741
+ return sanitized;
742
+ }
743
+ function convertContentBlocks(content) {
744
+ const blocks = content.map((block) => {
745
+ if (block.type === 'text')
746
+ return { type: 'text', text: sanitizeSurrogates(block.text) };
747
+ return {
748
+ type: 'image',
749
+ source: { type: 'base64', media_type: block.mimeType, data: block.data },
750
+ };
751
+ });
752
+ if (blocks.length === 0)
753
+ blocks.push({ type: 'text', text: '' });
754
+ if (!blocks.some((block) => block['type'] === 'text'))
755
+ blocks.unshift({ type: 'text', text: '(see attached image)' });
756
+ return blocks;
757
+ }
758
+ function cloneMessageForCacheControl(message) {
759
+ const content = message['content'];
760
+ return {
761
+ ...message,
762
+ ...(Array.isArray(content)
763
+ ? { content: content.map((block) => (isPlainObject(block) ? { ...block } : block)) }
764
+ : {}),
765
+ };
766
+ }
767
+ function isCacheableConversationBlock(role, block) {
768
+ if (role === 'assistant')
769
+ return block['type'] === 'text';
770
+ return block['type'] === 'text' || block['type'] === 'image' || block['type'] === 'tool_result';
771
+ }
772
+ function markMessageContentCacheSurface(message, cacheControl) {
773
+ const role = message['role'];
774
+ if (role !== 'user' && role !== 'assistant')
775
+ return false;
776
+ const content = message['content'];
777
+ if (typeof content === 'string') {
778
+ throw new Error('Anthropic attribution cache projection encountered non-canonical string message content');
779
+ }
780
+ if (!Array.isArray(content))
781
+ return false;
782
+ for (let index = content.length - 1; index >= 0; index -= 1) {
783
+ const block = content[index];
784
+ if (!isPlainObject(block) || !isCacheableConversationBlock(role, block))
785
+ continue;
786
+ content[index] = cloneBlockWithCacheControl(block, cacheControl);
787
+ return true;
788
+ }
789
+ return false;
790
+ }
791
+ function markLastConversationCacheSurface(messages, cacheControl) {
792
+ const output = messages.map(cloneMessageForCacheControl);
793
+ if (cacheControl === undefined)
794
+ return output;
795
+ for (let index = output.length - 1; index >= 0; index -= 1) {
796
+ const message = output[index];
797
+ if (message !== undefined && markMessageContentCacheSurface(message, cacheControl))
798
+ break;
799
+ }
800
+ return output;
801
+ }
802
+ function canonicalizeJson(value) {
803
+ if (Array.isArray(value))
804
+ return value.map(canonicalizeJson);
805
+ if (!isPlainObject(value))
806
+ return value;
807
+ const output = {};
808
+ for (const key of Object.keys(value).sort()) {
809
+ const child = value[key];
810
+ if (child !== undefined)
811
+ output[key] = canonicalizeJson(child);
812
+ }
813
+ return output;
814
+ }
815
+ function canonicalJson(value) {
816
+ return JSON.stringify(canonicalizeJson(value));
817
+ }
818
+ function sha256Canonical(value) {
819
+ return createHash('sha256').update(canonicalJson(value), 'utf8').digest('hex');
820
+ }
821
+ function isSha256(value) {
822
+ return typeof value === 'string' && /^[a-f0-9]{64}$/u.test(value);
823
+ }
824
+ function isNonEmptyString(value) {
825
+ return typeof value === 'string' && value.trim().length > 0;
826
+ }
827
+ function blockWithoutCacheControl(block) {
828
+ if (!isPlainObject(block))
829
+ return block;
830
+ const output = { ...block };
831
+ delete output['cache_control'];
832
+ return output;
833
+ }
834
+ function promptMessagesWithoutCacheControls(messages) {
835
+ return messages.map((message) => {
836
+ const content = message['content'];
837
+ return {
838
+ ...message,
839
+ ...(Array.isArray(content) ? { content: content.map(blockWithoutCacheControl) } : {}),
840
+ };
841
+ });
842
+ }
843
+ function promptMessagesHash(messages) {
844
+ return sha256Canonical(promptMessagesWithoutCacheControls(messages));
845
+ }
846
+ function assistantContentHash(content) {
847
+ return sha256Canonical(content);
848
+ }
849
+ function parseAnthropicLineageDetails(message) {
850
+ const diagnostic = [...(message.diagnostics ?? [])]
851
+ .reverse()
852
+ .find((candidate) => candidate.type === ANTHROPIC_LINEAGE_DIAGNOSTIC_TYPE);
853
+ const details = diagnostic?.details;
854
+ if (!isPlainObject(details))
855
+ return undefined;
856
+ const sourceModel = details['source_model'];
857
+ const responseId = details['response_id'];
858
+ const assistantContentSha256 = details['assistant_content_sha256'];
859
+ const conversationStaticSha256 = details['conversation_static_sha256'];
860
+ const requestMessageCount = details['request_message_count'];
861
+ const requestMessagesSha256 = details['request_messages_sha256'];
862
+ const cacheProfileSha256 = details['cache_profile_sha256'];
863
+ const cacheRetention = details['cache_retention'];
864
+ const compactionBoundarySha256 = details['compaction_boundary_sha256'];
865
+ const signatureEpochSha256 = details['signature_epoch_sha256'];
866
+ const signatureEpochInheritsPrior = details['signature_epoch_inherits_prior'];
867
+ const previousMessageId = details['previous_message_id'];
868
+ if (details['schema_version'] !== ANTHROPIC_LINEAGE_SCHEMA ||
869
+ details['projection_version'] !== ANTHROPIC_PROJECTION_VERSION ||
870
+ details['source_provider'] !== 'anthropic' ||
871
+ details['source_api'] !== 'anthropic-messages' ||
872
+ !isNonEmptyString(sourceModel) ||
873
+ !isNonEmptyString(responseId) ||
874
+ !isSha256(assistantContentSha256) ||
875
+ !isSha256(conversationStaticSha256) ||
876
+ typeof requestMessageCount !== 'number' ||
877
+ !Number.isSafeInteger(requestMessageCount) ||
878
+ requestMessageCount < 0 ||
879
+ !isSha256(requestMessagesSha256) ||
880
+ !isSha256(cacheProfileSha256) ||
881
+ (cacheRetention !== 'none' && cacheRetention !== 'short' && cacheRetention !== 'long') ||
882
+ (compactionBoundarySha256 !== null && !isSha256(compactionBoundarySha256)) ||
883
+ !isSha256(signatureEpochSha256) ||
884
+ typeof signatureEpochInheritsPrior !== 'boolean' ||
885
+ (previousMessageId !== null && !isNonEmptyString(previousMessageId))) {
886
+ return undefined;
887
+ }
888
+ return {
889
+ schema_version: ANTHROPIC_LINEAGE_SCHEMA,
890
+ projection_version: ANTHROPIC_PROJECTION_VERSION,
891
+ source_provider: 'anthropic',
892
+ source_api: 'anthropic-messages',
893
+ source_model: sourceModel,
894
+ response_id: responseId,
895
+ assistant_content_sha256: assistantContentSha256,
896
+ conversation_static_sha256: conversationStaticSha256,
897
+ request_message_count: requestMessageCount,
898
+ request_messages_sha256: requestMessagesSha256,
899
+ cache_profile_sha256: cacheProfileSha256,
900
+ cache_retention: cacheRetention,
901
+ compaction_boundary_sha256: compactionBoundarySha256,
902
+ signature_epoch_sha256: signatureEpochSha256,
903
+ signature_epoch_inherits_prior: signatureEpochInheritsPrior,
904
+ previous_message_id: previousMessageId,
905
+ };
906
+ }
907
+ function isSuccessfulLineageStopReason(stopReason) {
908
+ return stopReason === 'stop' || stopReason === 'length' || stopReason === 'toolUse';
909
+ }
910
+ function lineageBindsContainingAssistant(message, lineage) {
911
+ return (message.provider === lineage.source_provider &&
912
+ message.api === lineage.source_api &&
913
+ isNonEmptyString(message.model) &&
914
+ message.model === lineage.source_model &&
915
+ isSuccessfulLineageStopReason(message.stopReason) &&
916
+ isNonEmptyString(message.responseId) &&
917
+ message.responseId === lineage.response_id &&
918
+ lineage.assistant_content_sha256 === assistantContentHash(message.content));
919
+ }
920
+ function canTargetReadAnthropicThinking(sourceModel, targetModel) {
921
+ if (sourceModel === targetModel)
922
+ return true;
923
+ return (targetModel === 'claude-fable-5-1' && CLAUDE_CODE_MODEL_POLICIES[sourceModel] !== undefined);
924
+ }
925
+ function initialSignatureEpochPolicy(targetModelId, cacheRetention, compactionBoundarySha256) {
926
+ return {
927
+ sha256: sha256Canonical({
928
+ projection_version: ANTHROPIC_PROJECTION_VERSION,
929
+ kind: 'initial',
930
+ target_model: targetModelId,
931
+ cache_retention: cacheRetention,
932
+ compaction_boundary_sha256: compactionBoundarySha256,
933
+ }),
934
+ inheritsPrior: compactionBoundarySha256 === null,
935
+ };
936
+ }
937
+ function resolveSignatureEpochPolicy(model, messages, cacheRetention, compactionBoundarySha256) {
938
+ const targetModelId = normalizedAnthropicModelId(model);
939
+ const latestState = targetLineageState({ messages }, targetModelId);
940
+ if (latestState.kind === 'none') {
941
+ return initialSignatureEpochPolicy(targetModelId, cacheRetention, compactionBoundarySha256);
942
+ }
943
+ if (latestState.kind === 'untrusted') {
944
+ return {
945
+ sha256: sha256Canonical({
946
+ projection_version: ANTHROPIC_PROJECTION_VERSION,
947
+ kind: 'untrusted-lineage-reset',
948
+ target_model: targetModelId,
949
+ cache_retention: cacheRetention,
950
+ compaction_boundary_sha256: compactionBoundarySha256,
951
+ latest_assistant_sha256: latestState.assistantSha256,
952
+ }),
953
+ inheritsPrior: false,
954
+ };
955
+ }
956
+ const latest = latestState.lineage;
957
+ const retentionChanged = latest.cache_retention !== cacheRetention;
958
+ const declaredNewCompaction = compactionBoundarySha256 !== null &&
959
+ compactionBoundarySha256 !== latest.compaction_boundary_sha256;
960
+ if (retentionChanged || declaredNewCompaction) {
961
+ return {
962
+ sha256: sha256Canonical({
963
+ projection_version: ANTHROPIC_PROJECTION_VERSION,
964
+ kind: 'transition',
965
+ previous_signature_epoch_sha256: latest.signature_epoch_sha256,
966
+ target_model: targetModelId,
967
+ cache_retention: cacheRetention,
968
+ compaction_boundary_sha256: compactionBoundarySha256,
969
+ reason: retentionChanged ? 'cache-retention' : 'compaction',
970
+ }),
971
+ inheritsPrior: false,
972
+ };
973
+ }
974
+ return {
975
+ sha256: latest.signature_epoch_sha256,
976
+ inheritsPrior: latest.signature_epoch_inherits_prior,
977
+ };
978
+ }
979
+ function isTrustedReplayableAnthropicAssistant(message, targetModel, targetCacheRetention, conversationStaticSha256, wireMessagesBeforeAssistant) {
980
+ if (typeof message.model !== 'string')
981
+ return false;
982
+ const lineage = parseAnthropicLineageDetails(message);
983
+ if (lineage === undefined ||
984
+ !lineageBindsContainingAssistant(message, lineage) ||
985
+ !canTargetReadAnthropicThinking(message.model, normalizedAnthropicModelId(targetModel)) ||
986
+ lineage.cache_retention !== targetCacheRetention ||
987
+ lineage.conversation_static_sha256 !== conversationStaticSha256 ||
988
+ lineage.request_message_count !== wireMessagesBeforeAssistant.length ||
989
+ lineage.request_messages_sha256 !== promptMessagesHash(wireMessagesBeforeAssistant)) {
990
+ return false;
991
+ }
992
+ return true;
993
+ }
994
+ function normalizeAnthropicToolCallId(id) {
995
+ if (/^[a-zA-Z0-9_-]{1,64}$/u.test(id))
996
+ return id;
997
+ const safe = id.replace(/[^a-zA-Z0-9_-]/gu, '_');
998
+ const digest = createHash('sha256').update(id, 'utf8').digest('hex').slice(0, 12);
999
+ const prefix = safe.slice(0, 64 - digest.length - 1) || 'tool';
1000
+ return `${prefix}_${digest}`.slice(0, 64);
1001
+ }
1002
+ function projectedUserContent(content) {
1003
+ if (typeof content === 'string') {
1004
+ return content.trim().length > 0 ? [{ type: 'text', text: sanitizeSurrogates(content) }] : [];
1005
+ }
1006
+ const blocks = [];
1007
+ for (const block of content) {
1008
+ if (block.type === 'text') {
1009
+ if (block.text.trim().length > 0)
1010
+ blocks.push({ type: 'text', text: sanitizeSurrogates(block.text) });
1011
+ continue;
1012
+ }
1013
+ if (block.type === 'image') {
1014
+ blocks.push({
1015
+ type: 'image',
1016
+ source: { type: 'base64', media_type: block.mimeType, data: block.data },
1017
+ });
1018
+ continue;
1019
+ }
1020
+ throw new Error('Anthropic attribution encountered an unsupported user content block');
1021
+ }
1022
+ return blocks;
1023
+ }
1024
+ function compactionBoundarySha256FromPiMessages(messages) {
1025
+ const first = messages[0];
1026
+ if (first?.role !== 'user')
1027
+ return null;
1028
+ const content = projectedUserContent(first.content);
1029
+ return declaredCompactionBoundarySha256([{ role: 'user', content }]);
1030
+ }
1031
+ function convertMessages(model, messages, conversationStaticSha256, cacheRetention, signatureEpoch, cacheControl) {
1032
+ const params = [];
1033
+ const toolCallIdMap = new Map();
1034
+ const normalizedToolCallOwners = new Map();
1035
+ let pendingToolCalls = [];
1036
+ let completedPendingToolCallIds = new Set();
1037
+ const normalizeToolCallId = (id) => {
1038
+ const existing = toolCallIdMap.get(id);
1039
+ if (existing !== undefined)
1040
+ return existing;
1041
+ const normalized = normalizeAnthropicToolCallId(id);
1042
+ const owner = normalizedToolCallOwners.get(normalized);
1043
+ if (owner !== undefined && owner !== id) {
1044
+ throw new Error('Anthropic attribution tool-call ID normalization collision');
1045
+ }
1046
+ normalizedToolCallOwners.set(normalized, id);
1047
+ toolCallIdMap.set(id, normalized);
1048
+ return normalized;
1049
+ };
1050
+ const flushMissingToolResults = () => {
1051
+ const missing = pendingToolCalls.filter((call) => !completedPendingToolCallIds.has(call.id));
1052
+ if (missing.length > 0) {
1053
+ params.push({
1054
+ role: 'user',
1055
+ content: missing.map((call) => ({
1056
+ type: 'tool_result',
1057
+ tool_use_id: call.id,
1058
+ content: 'No result provided',
1059
+ is_error: true,
1060
+ })),
1061
+ });
1062
+ }
1063
+ pendingToolCalls = [];
1064
+ completedPendingToolCallIds = new Set();
1065
+ };
1066
+ for (let index = 0; index < messages.length; index += 1) {
1067
+ const message = messages[index];
1068
+ if (message === undefined)
1069
+ throw new TypeError(`Anthropic message ${index} is missing`);
1070
+ if (message.role === 'user') {
1071
+ flushMissingToolResults();
1072
+ const content = projectedUserContent(message.content);
1073
+ if (content.length > 0)
1074
+ params.push({ role: 'user', content });
1075
+ continue;
1076
+ }
1077
+ if (message.role === 'assistant') {
1078
+ flushMissingToolResults();
1079
+ if (message.stopReason === 'error' || message.stopReason === 'aborted')
1080
+ continue;
1081
+ const lineage = parseAnthropicLineageDetails(message);
1082
+ const epochAllowsReplay = signatureEpoch.inheritsPrior || lineage?.signature_epoch_sha256 === signatureEpoch.sha256;
1083
+ const preserveThinking = epochAllowsReplay &&
1084
+ isTrustedReplayableAnthropicAssistant(message, model, cacheRetention, conversationStaticSha256, params);
1085
+ const content = [];
1086
+ for (const block of message.content) {
1087
+ if (block['type'] === 'text') {
1088
+ if (typeof block['text'] !== 'string')
1089
+ throw new Error('Anthropic attribution encountered malformed assistant text');
1090
+ if (block['text'].trim().length > 0)
1091
+ content.push({ type: 'text', text: sanitizeSurrogates(block['text']) });
1092
+ continue;
1093
+ }
1094
+ if (block['type'] === 'thinking') {
1095
+ if (typeof block['thinking'] !== 'string')
1096
+ throw new Error('Anthropic attribution encountered malformed assistant thinking');
1097
+ const signature = typeof block['thinkingSignature'] === 'string' ? block['thinkingSignature'] : '';
1098
+ if (preserveThinking && signature.trim().length > 0) {
1099
+ content.push(block['redacted'] === true
1100
+ ? { type: 'redacted_thinking', data: signature }
1101
+ : {
1102
+ type: 'thinking',
1103
+ // A signature authenticates the exact provider-returned reasoning bytes.
1104
+ // Provenance and lineage checks above make this raw replay safe; changing
1105
+ // even valid non-BMP Unicode here would invalidate the opaque signature.
1106
+ thinking: block['thinking'],
1107
+ signature,
1108
+ });
1109
+ }
1110
+ else if (block['redacted'] !== true && block['thinking'].trim().length > 0) {
1111
+ content.push({ type: 'text', text: sanitizeSurrogates(block['thinking']) });
1112
+ }
1113
+ continue;
1114
+ }
1115
+ if (block['type'] === 'toolCall') {
1116
+ if (typeof block['id'] !== 'string' || typeof block['name'] !== 'string')
1117
+ throw new Error('Anthropic attribution encountered malformed assistant tool call');
1118
+ const id = normalizeToolCallId(block['id']);
1119
+ content.push({
1120
+ type: 'tool_use',
1121
+ id,
1122
+ name: block['name'],
1123
+ input: canonicalizeJson(block['arguments'] ?? {}),
1124
+ });
1125
+ continue;
1126
+ }
1127
+ throw new Error(`Anthropic attribution encountered unsupported assistant block type ${JSON.stringify(block['type'])}`);
1128
+ }
1129
+ if (content.length > 0) {
1130
+ params.push({ role: 'assistant', content });
1131
+ pendingToolCalls = content
1132
+ .filter((block) => block['type'] === 'tool_use')
1133
+ .map((block) => ({ id: String(block['id']), name: String(block['name']) }));
1134
+ }
1135
+ continue;
1136
+ }
1137
+ const toolResults = [];
1138
+ let lookahead = index;
1139
+ while (lookahead < messages.length && messages[lookahead]?.role === 'toolResult') {
1140
+ const result = messages[lookahead];
1141
+ const toolUseId = normalizeToolCallId(result.toolCallId);
1142
+ completedPendingToolCallIds.add(toolUseId);
1143
+ toolResults.push({
1144
+ type: 'tool_result',
1145
+ tool_use_id: toolUseId,
1146
+ content: convertContentBlocks(result.content),
1147
+ is_error: result.isError === true,
1148
+ });
1149
+ lookahead += 1;
1150
+ }
1151
+ index = lookahead - 1;
1152
+ if (toolResults.length > 0)
1153
+ params.push({ role: 'user', content: toolResults });
1154
+ }
1155
+ flushMissingToolResults();
1156
+ return markLastConversationCacheSurface(params, cacheControl);
1157
+ }
1158
+ function convertTools(tools, cacheControl) {
1159
+ if (!tools || tools.length === 0)
1160
+ return [];
1161
+ return tools.map((tool, index) => {
1162
+ const parameters = isPlainObject(tool.parameters) ? tool.parameters : {};
1163
+ const converted = {
1164
+ name: tool.name,
1165
+ description: tool.description ?? '',
1166
+ input_schema: {
1167
+ type: 'object',
1168
+ properties: canonicalizeJson(isPlainObject(parameters['properties']) ? parameters['properties'] : {}),
1169
+ required: Array.isArray(parameters['required']) ? [...parameters['required']] : [],
1170
+ },
1171
+ };
1172
+ return cacheControl !== undefined && index === tools.length - 1
1173
+ ? cloneBlockWithCacheControl(converted, cacheControl)
1174
+ : converted;
1175
+ });
1176
+ }
1177
+ function thinkingBudgetFor(level, maxTokens, custom) {
1178
+ const defaults = {
1179
+ minimal: 1024,
1180
+ low: 4096,
1181
+ medium: 10240,
1182
+ high: 20480,
1183
+ xhigh: 32768,
1184
+ off: 0,
1185
+ };
1186
+ const requested = level === 'off' ? 0 : (custom?.[level] ?? defaults[level]);
1187
+ return Math.min(maxTokens - 1, requested);
1188
+ }
1189
+ function adaptiveEffortFor(level) {
1190
+ switch (level) {
1191
+ case 'low':
1192
+ case 'medium':
1193
+ case 'high':
1194
+ case 'xhigh':
1195
+ return level;
1196
+ case 'minimal':
1197
+ throw new Error('Anthropic attribution cannot map Pi reasoning=minimal to Claude adaptive effort; use low, medium, high, or xhigh');
1198
+ }
1199
+ }
1200
+ function conversationStaticHash(system, tools) {
1201
+ const normalizedSystem = system
1202
+ .filter((block) => !isPlainObject(block) ||
1203
+ typeof block['text'] !== 'string' ||
1204
+ !isClaudeCodeIdentityText(block['text']))
1205
+ .map(blockWithoutCacheControl);
1206
+ return sha256Canonical({
1207
+ attribution_profile: {
1208
+ claude_code_version: CLAUDE_CODE_VERSION,
1209
+ entrypoint: CLAUDE_CODE_ENTRYPOINT,
1210
+ },
1211
+ system: normalizedSystem,
1212
+ tools: tools.map(blockWithoutCacheControl),
1213
+ });
1214
+ }
1215
+ export function buildAnthropicRequestParams(model, context, options) {
1216
+ return buildAnthropicRequest(model, context, options).params;
1217
+ }
1218
+ function buildAnthropicRequest(model, context, options) {
1219
+ const policy = resolveClaudeCodeModelPolicy(model);
1220
+ const maxTokens = resolveAnthropicMaxTokens(model);
1221
+ const cacheControl = resolveAnthropicCacheControl(model, options);
1222
+ const cacheRetention = cacheControl === undefined ? 'none' : cacheControl.ttl === '1h' ? 'long' : 'short';
1223
+ let signatureEpoch = resolveSignatureEpochPolicy(model, context.messages, cacheRetention, compactionBoundarySha256FromPiMessages(context.messages));
1224
+ const system = context.systemPrompt && context.systemPrompt.trim().length > 0
1225
+ ? markSystemCacheSurface([
1226
+ {
1227
+ type: 'text',
1228
+ text: sanitizeSurrogates(stripAnthropicSystemPromptBadLines(context.systemPrompt)),
1229
+ },
1230
+ ], cacheControl)
1231
+ : [];
1232
+ const tools = convertTools(context.tools, model.compat?.supportsCacheControlOnTools === false ? undefined : cacheControl);
1233
+ const staticSha256 = conversationStaticHash(system, tools);
1234
+ const params = {
1235
+ model: policy.modelId,
1236
+ messages: convertMessages(model, context.messages, staticSha256, cacheRetention, signatureEpoch, cacheControl),
1237
+ max_tokens: maxTokens,
1238
+ stream: true,
1239
+ tools,
1240
+ };
1241
+ if (system.length > 0)
1242
+ params['system'] = system;
1243
+ if (options?.toolChoice !== undefined)
1244
+ params['tool_choice'] = canonicalizeJson(options.toolChoice);
1245
+ const reasoning = options?.reasoning;
1246
+ if (model.reasoning && reasoning !== undefined) {
1247
+ if (reasoning === 'off') {
1248
+ if (policy.enforcesThinkingPrefixBinding) {
1249
+ throw new Error('Anthropic attribution cannot disable thinking for Claude Fable 5.1');
1250
+ }
1251
+ params['thinking'] = { type: 'disabled' };
1252
+ params['temperature'] = options?.temperature ?? 1;
1253
+ }
1254
+ else if (policy.thinkingPolicy === 'adaptive-effort') {
1255
+ params['thinking'] = {
1256
+ type: 'adaptive',
1257
+ ...(policy.enforcesThinkingPrefixBinding
1258
+ ? { block_binding: { prefix_mismatch_behavior: 'error' } }
1259
+ : {}),
1260
+ };
1261
+ params['output_config'] = { effort: adaptiveEffortFor(reasoning) };
1262
+ }
1263
+ else {
1264
+ params['thinking'] = {
1265
+ type: 'enabled',
1266
+ budget_tokens: thinkingBudgetFor(reasoning, maxTokens, options?.thinkingBudgets),
1267
+ };
1268
+ }
1269
+ }
1270
+ else if (policy.enforcesThinkingPrefixBinding) {
1271
+ params['thinking'] = {
1272
+ type: 'adaptive',
1273
+ block_binding: { prefix_mismatch_behavior: 'error' },
1274
+ };
1275
+ }
1276
+ else {
1277
+ params['thinking'] = { type: 'disabled' };
1278
+ params['temperature'] = options?.temperature ?? 1;
1279
+ }
1280
+ const previous = latestLineageForTarget(context, normalizedAnthropicModelId(model));
1281
+ if (previous !== undefined &&
1282
+ signatureEpoch.sha256 === previous.signature_epoch_sha256 &&
1283
+ (previous.cache_profile_sha256 !== cacheProfileHash(model, policy, params, staticSha256) ||
1284
+ !hasLineageRequestPrefix(requestMessagesFromPayload(params), previous))) {
1285
+ // Resume/reload and profile edits can invalidate a lane. Re-project before transport,
1286
+ // rather than just clearing the response ID while retaining prefix-bound signatures.
1287
+ signatureEpoch = {
1288
+ sha256: sha256Canonical({
1289
+ projection_version: ANTHROPIC_PROJECTION_VERSION,
1290
+ kind: 'lineage-reset',
1291
+ previous_signature_epoch_sha256: previous.signature_epoch_sha256,
1292
+ previous_response_id: previous.response_id,
1293
+ cache_profile_sha256: cacheProfileHash(model, policy, params, staticSha256),
1294
+ request_messages_sha256: promptMessagesHash(requestMessagesFromPayload(params)),
1295
+ }),
1296
+ inheritsPrior: false,
1297
+ };
1298
+ params['messages'] = convertMessages(model, context.messages, staticSha256, cacheRetention, signatureEpoch, cacheControl);
1299
+ }
1300
+ assertCacheControlBreakpointLimit(params);
1301
+ return { params, signatureEpoch };
1302
+ }
1303
+ function requestMessagesFromPayload(payload) {
1304
+ const messages = payload['messages'];
1305
+ if (!Array.isArray(messages) || messages.some((message) => !isPlainObject(message))) {
1306
+ throw new Error('Anthropic attribution final payload.messages must be an object array');
1307
+ }
1308
+ for (const message of messages) {
1309
+ if (message['role'] !== 'user' && message['role'] !== 'assistant') {
1310
+ throw new Error('Anthropic attribution final payload message has an unsupported role');
1311
+ }
1312
+ const content = message['content'];
1313
+ if (!Array.isArray(content) || content.some((block) => !isPlainObject(block))) {
1314
+ throw new Error('Anthropic attribution final payload message content must use canonical block arrays');
1315
+ }
1316
+ }
1317
+ return messages;
1318
+ }
1319
+ function systemBlocksFromPayload(payload) {
1320
+ const system = payload['system'];
1321
+ if (system === undefined)
1322
+ return [];
1323
+ if (typeof system === 'string')
1324
+ return [{ type: 'text', text: system }];
1325
+ if (!Array.isArray(system))
1326
+ throw new Error('Anthropic attribution final payload.system must be a string or block array');
1327
+ return system;
1328
+ }
1329
+ function toolsFromPayload(payload) {
1330
+ const tools = payload['tools'];
1331
+ if (tools === undefined)
1332
+ return [];
1333
+ if (!Array.isArray(tools) || tools.some((tool) => !isPlainObject(tool))) {
1334
+ throw new Error('Anthropic attribution final payload.tools must be an object array');
1335
+ }
1336
+ return tools;
1337
+ }
1338
+ function cacheProfileHash(model, policy, payload, staticSha256) {
1339
+ return sha256Canonical({
1340
+ projection_version: ANTHROPIC_PROJECTION_VERSION,
1341
+ model: normalizedAnthropicModelId(model),
1342
+ beta: policy.beta,
1343
+ conversation_static_sha256: staticSha256,
1344
+ thinking: payload['thinking'],
1345
+ output_config: payload['output_config'],
1346
+ tool_choice: payload['tool_choice'],
1347
+ });
1348
+ }
1349
+ function cacheRetentionFromPayload(payload) {
1350
+ return inspectCacheControls(payload).retention ?? 'none';
1351
+ }
1352
+ function untrustedAssistantSha256(message) {
1353
+ return sha256Canonical({
1354
+ provider: message.provider ?? null,
1355
+ api: message.api ?? null,
1356
+ model: message.model ?? null,
1357
+ response_id: message.responseId ?? null,
1358
+ stop_reason: message.stopReason ?? null,
1359
+ assistant_content_sha256: assistantContentHash(message.content),
1360
+ lineage_diagnostics: (message.diagnostics ?? []).filter((diagnostic) => diagnostic.type === ANTHROPIC_LINEAGE_DIAGNOSTIC_TYPE),
1361
+ });
1362
+ }
1363
+ function targetLineageState(context, targetModelId) {
1364
+ for (let index = context.messages.length - 1; index >= 0; index -= 1) {
1365
+ const message = context.messages[index];
1366
+ if (message?.role !== 'assistant')
1367
+ continue;
1368
+ const lineageDiagnostics = (message.diagnostics ?? []).filter((diagnostic) => diagnostic.type === ANTHROPIC_LINEAGE_DIAGNOSTIC_TYPE);
1369
+ const latestRawDetails = lineageDiagnostics.at(-1)?.details;
1370
+ const receiptClaimsTarget = latestRawDetails?.['source_model'] === targetModelId;
1371
+ const assistantClaimsTarget = message.model === targetModelId;
1372
+ if (!assistantClaimsTarget && !receiptClaimsTarget)
1373
+ continue;
1374
+ if ((message.stopReason === 'error' || message.stopReason === 'aborted') &&
1375
+ lineageDiagnostics.length === 0) {
1376
+ continue;
1377
+ }
1378
+ const lineage = parseAnthropicLineageDetails(message);
1379
+ if (lineage?.source_model === targetModelId &&
1380
+ lineageBindsContainingAssistant(message, lineage)) {
1381
+ return { kind: 'trusted', lineage };
1382
+ }
1383
+ return { kind: 'untrusted', assistantSha256: untrustedAssistantSha256(message) };
1384
+ }
1385
+ return { kind: 'none' };
1386
+ }
1387
+ function latestLineageForTarget(context, targetModelId) {
1388
+ const state = targetLineageState(context, targetModelId);
1389
+ return state.kind === 'trusted' ? state.lineage : undefined;
1390
+ }
1391
+ function declaredCompactionBoundarySha256(messages) {
1392
+ const firstMessage = messages[0];
1393
+ if (firstMessage?.['role'] !== 'user' || !Array.isArray(firstMessage['content']))
1394
+ return null;
1395
+ const declaresCompaction = firstMessage['content'].some((block) => isPlainObject(block) &&
1396
+ block['type'] === 'text' &&
1397
+ typeof block['text'] === 'string' &&
1398
+ block['text'].startsWith(COMPACTION_SUMMARY_PREFIX));
1399
+ if (!declaresCompaction)
1400
+ return null;
1401
+ return sha256Canonical(promptMessagesWithoutCacheControls([firstMessage])[0]);
1402
+ }
1403
+ function hasLineageRequestPrefix(messages, previous) {
1404
+ return (messages.length >= previous.request_message_count &&
1405
+ promptMessagesHash(messages.slice(0, previous.request_message_count)) ===
1406
+ previous.request_messages_sha256);
1407
+ }
1408
+ function prepareAnthropicLineageDetails(args) {
1409
+ const targetModelId = normalizedAnthropicModelId(args.model);
1410
+ const messages = requestMessagesFromPayload(args.payload);
1411
+ const staticSha256 = conversationStaticHash(systemBlocksFromPayload(args.payload), toolsFromPayload(args.payload));
1412
+ const profileSha256 = cacheProfileHash(args.model, args.policy, args.payload, staticSha256);
1413
+ const cacheRetention = cacheRetentionFromPayload(args.payload);
1414
+ const compactionBoundarySha256 = declaredCompactionBoundarySha256(messages);
1415
+ const signatureEpoch = args.signatureEpoch;
1416
+ let previous = latestLineageForTarget(args.context, targetModelId);
1417
+ if (previous !== undefined &&
1418
+ (signatureEpoch.sha256 !== previous.signature_epoch_sha256 ||
1419
+ !hasLineageRequestPrefix(messages, previous) ||
1420
+ previous.cache_profile_sha256 !== profileSha256 ||
1421
+ previous.cache_retention !== cacheRetention)) {
1422
+ previous = undefined;
1423
+ }
1424
+ return {
1425
+ schema_version: ANTHROPIC_LINEAGE_SCHEMA,
1426
+ projection_version: ANTHROPIC_PROJECTION_VERSION,
1427
+ source_provider: 'anthropic',
1428
+ source_api: 'anthropic-messages',
1429
+ source_model: targetModelId,
1430
+ conversation_static_sha256: staticSha256,
1431
+ request_message_count: messages.length,
1432
+ request_messages_sha256: promptMessagesHash(messages),
1433
+ cache_profile_sha256: profileSha256,
1434
+ cache_retention: cacheRetention,
1435
+ compaction_boundary_sha256: compactionBoundarySha256,
1436
+ signature_epoch_sha256: signatureEpoch.sha256,
1437
+ signature_epoch_inherits_prior: signatureEpoch.inheritsPrior,
1438
+ previous_message_id: previous?.response_id ?? null,
1439
+ };
1440
+ }
1441
+ class AnthropicLineageCoordinator {
1442
+ inFlight = new Set();
1443
+ prepare(args) {
1444
+ const targetModelId = normalizedAnthropicModelId(args.model);
1445
+ const key = `${args.sessionId}\u0000${targetModelId}`;
1446
+ if (this.inFlight.has(key)) {
1447
+ throw new Error(`Anthropic cache lineage already has an in-flight request for ${targetModelId}; concurrent continuations must fork`);
1448
+ }
1449
+ const details = prepareAnthropicLineageDetails(args);
1450
+ this.inFlight.add(key);
1451
+ return { key, details };
1452
+ }
1453
+ release(prepared) {
1454
+ this.inFlight.delete(prepared.key);
1455
+ }
1456
+ }
1457
+ const anthropicLineageCoordinator = new AnthropicLineageCoordinator();
1458
+ export function createAnthropicLineageDiagnostic(args) {
1459
+ if (!isNonEmptyString(args.responseId)) {
1460
+ throw new Error('Anthropic lineage diagnostic requires a non-empty response ID');
1461
+ }
1462
+ if (args.previousMessageId !== undefined && args.previousMessageId !== null) {
1463
+ if (!isNonEmptyString(args.previousMessageId)) {
1464
+ throw new Error('Anthropic lineage diagnostic requires a non-empty previous response ID');
1465
+ }
1466
+ }
1467
+ const policy = resolveClaudeCodeModelPolicy(args.model);
1468
+ const messages = requestMessagesFromPayload(args.requestPayload);
1469
+ const staticSha256 = conversationStaticHash(systemBlocksFromPayload(args.requestPayload), toolsFromPayload(args.requestPayload));
1470
+ const cacheRetention = cacheRetentionFromPayload(args.requestPayload);
1471
+ const compactionBoundarySha256 = declaredCompactionBoundarySha256(messages);
1472
+ const signatureEpoch = initialSignatureEpochPolicy(normalizedAnthropicModelId(args.model), cacheRetention, compactionBoundarySha256);
1473
+ return {
1474
+ type: ANTHROPIC_LINEAGE_DIAGNOSTIC_TYPE,
1475
+ timestamp: Date.now(),
1476
+ details: {
1477
+ schema_version: ANTHROPIC_LINEAGE_SCHEMA,
1478
+ projection_version: ANTHROPIC_PROJECTION_VERSION,
1479
+ source_provider: 'anthropic',
1480
+ source_api: 'anthropic-messages',
1481
+ source_model: normalizedAnthropicModelId(args.model),
1482
+ response_id: args.responseId,
1483
+ assistant_content_sha256: assistantContentHash(args.assistantContent),
1484
+ conversation_static_sha256: staticSha256,
1485
+ request_message_count: messages.length,
1486
+ request_messages_sha256: promptMessagesHash(messages),
1487
+ cache_profile_sha256: cacheProfileHash(args.model, policy, args.requestPayload, staticSha256),
1488
+ cache_retention: cacheRetention,
1489
+ compaction_boundary_sha256: compactionBoundarySha256,
1490
+ signature_epoch_sha256: signatureEpoch.sha256,
1491
+ signature_epoch_inherits_prior: signatureEpoch.inheritsPrior,
1492
+ previous_message_id: args.previousMessageId ?? null,
1493
+ },
1494
+ };
1495
+ }
1496
+ function appendLineageDiagnostic(output, prepared) {
1497
+ if (!isNonEmptyString(output.responseId)) {
1498
+ throw new Error('Anthropic attribution successful response is missing responseId lineage proof');
1499
+ }
1500
+ output.diagnostics ??= [];
1501
+ output.diagnostics.push({
1502
+ type: ANTHROPIC_LINEAGE_DIAGNOSTIC_TYPE,
1503
+ timestamp: Date.now(),
1504
+ details: {
1505
+ ...prepared.details,
1506
+ response_id: output.responseId,
1507
+ assistant_content_sha256: assistantContentHash(output.content),
1508
+ },
1509
+ });
1510
+ }
1511
+ function appendProviderCacheDiagnostic(output, messageStart) {
1512
+ const diagnostics = messageStart['diagnostics'];
1513
+ const inputTransformations = messageStart['input_transformations'];
1514
+ if (diagnostics === undefined && inputTransformations === undefined)
1515
+ return;
1516
+ output.diagnostics ??= [];
1517
+ output.diagnostics.push({
1518
+ type: 'anthropic-provider-cache',
1519
+ timestamp: Date.now(),
1520
+ details: {
1521
+ cache_diagnostics: canonicalizeJson(diagnostics ?? null),
1522
+ input_transformations: canonicalizeJson(inputTransformations ?? []),
1523
+ },
1524
+ });
1525
+ }
1526
+ function headersToRecord(headers) {
1527
+ return Object.fromEntries([...headers.entries()]);
1528
+ }
1529
+ function lowerHeaderMap(headers) {
1530
+ const output = {};
1531
+ for (const [key, value] of Object.entries(headers ?? {}))
1532
+ output[key.toLowerCase()] = value;
1533
+ return output;
1534
+ }
1535
+ function resolveAnthropicBetaMessagesUrl(model) {
1536
+ const configured = typeof model.baseUrl === 'string' && model.baseUrl.trim().length > 0
1537
+ ? model.baseUrl.trim()
1538
+ : ANTHROPIC_OFFICIAL_ORIGIN;
1539
+ let parsed;
1540
+ try {
1541
+ parsed = new URL(configured);
1542
+ }
1543
+ catch (error) {
1544
+ throw new Error(`Anthropic attribution requires the official Anthropic HTTPS endpoint; invalid model.baseUrl: ${error instanceof Error ? error.message : String(error)}`);
1545
+ }
1546
+ if (parsed.origin !== ANTHROPIC_OFFICIAL_ORIGIN ||
1547
+ parsed.protocol !== 'https:' ||
1548
+ parsed.username.length > 0 ||
1549
+ parsed.password.length > 0 ||
1550
+ (parsed.pathname !== '' && parsed.pathname !== '/') ||
1551
+ parsed.search.length > 0 ||
1552
+ parsed.hash.length > 0) {
1553
+ throw new Error(`Anthropic attribution requires the official Anthropic HTTPS endpoint ${ANTHROPIC_OFFICIAL_ORIGIN}; refusing model.baseUrl ${JSON.stringify(configured)}`);
1554
+ }
1555
+ return ANTHROPIC_BETA_MESSAGES_URL;
1556
+ }
1557
+ function buildFetchHeaders(options, apiKey, sessionHeader, beta) {
1558
+ const optionHeaders = lowerHeaderMap(options?.headers);
1559
+ return {
1560
+ Accept: 'application/json',
1561
+ Authorization: `Bearer ${apiKey}`,
1562
+ 'Content-Type': 'application/json',
1563
+ 'User-Agent': optionHeaders['user-agent'] ?? CLAUDE_CODE_USER_AGENT,
1564
+ [CLAUDE_CODE_SESSION_HEADER]: sessionHeader ?? optionHeaders['x-claude-code-session-id'] ?? '',
1565
+ 'anthropic-beta': beta,
1566
+ 'anthropic-dangerous-direct-browser-access': 'true',
1567
+ 'anthropic-version': '2023-06-01',
1568
+ 'x-app': 'cli',
1569
+ };
1570
+ }
1571
+ function mapStopReason(reason) {
1572
+ switch (reason) {
1573
+ case 'end_turn':
1574
+ case 'pause_turn':
1575
+ case 'stop_sequence':
1576
+ return 'stop';
1577
+ case 'max_tokens':
1578
+ return 'length';
1579
+ case 'tool_use':
1580
+ return 'toolUse';
1581
+ default:
1582
+ return 'error';
1583
+ }
1584
+ }
1585
+ function parseStreamingJsonFragment(text) {
1586
+ try {
1587
+ return parseJsonSource(text);
1588
+ }
1589
+ catch {
1590
+ return {};
1591
+ }
1592
+ }
1593
+ function parseCompletedToolInput(text) {
1594
+ const parsed = parseJsonValue(text, 'Anthropic streamed tool input');
1595
+ if (!isPlainObject(parsed)) {
1596
+ throw new Error('Anthropic streamed tool input must complete as a JSON object');
1597
+ }
1598
+ return parsed;
1599
+ }
1600
+ function anthropicStreamErrorMessage(event) {
1601
+ const error = event['error'];
1602
+ if (isPlainObject(error) && typeof error['message'] === 'string') {
1603
+ const type = typeof error['type'] === 'string' ? `${error['type']}: ` : '';
1604
+ return `Anthropic beta messages stream error: ${type}${error['message']}`;
1605
+ }
1606
+ return 'Anthropic beta messages stream emitted an error event';
1607
+ }
1608
+ function validCostRate(value, fallback, field) {
1609
+ if (value === undefined)
1610
+ return fallback;
1611
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
1612
+ throw new Error(`Anthropic attribution model cost.${field} must be a finite non-negative number`);
1613
+ }
1614
+ return value;
1615
+ }
1616
+ function resolveModelCostRates(model, totalInputTokens) {
1617
+ let selected = model.cost;
1618
+ let matchedThreshold = -1;
1619
+ for (const tier of model.cost?.tiers ?? []) {
1620
+ const threshold = tier.inputTokensAbove;
1621
+ if (typeof threshold === 'number' &&
1622
+ Number.isFinite(threshold) &&
1623
+ threshold >= 0 &&
1624
+ totalInputTokens > threshold &&
1625
+ threshold > matchedThreshold) {
1626
+ selected = tier;
1627
+ matchedThreshold = threshold;
1628
+ }
1629
+ }
1630
+ return {
1631
+ input: validCostRate(selected?.input, 3, 'input'),
1632
+ output: validCostRate(selected?.output, 15, 'output'),
1633
+ cacheRead: validCostRate(selected?.cacheRead, 0.3, 'cacheRead'),
1634
+ cacheWrite: validCostRate(selected?.cacheWrite, 3.75, 'cacheWrite'),
1635
+ };
1636
+ }
1637
+ export function updateAnthropicUsage(output, usage, model) {
1638
+ if (!usage)
1639
+ return;
1640
+ if (typeof usage['input_tokens'] === 'number')
1641
+ output.usage.input = usage['input_tokens'];
1642
+ if (typeof usage['output_tokens'] === 'number')
1643
+ output.usage.output = usage['output_tokens'];
1644
+ if (typeof usage['cache_read_input_tokens'] === 'number')
1645
+ output.usage.cacheRead = usage['cache_read_input_tokens'];
1646
+ const cacheCreation = usage['cache_creation'];
1647
+ const reportedLongCacheWrite = isPlainObject(cacheCreation) && typeof cacheCreation['ephemeral_1h_input_tokens'] === 'number'
1648
+ ? cacheCreation['ephemeral_1h_input_tokens']
1649
+ : undefined;
1650
+ if (typeof usage['cache_creation_input_tokens'] === 'number') {
1651
+ output.usage.cacheWrite = usage['cache_creation_input_tokens'];
1652
+ output.usage.cacheWrite1h = reportedLongCacheWrite ?? 0;
1653
+ }
1654
+ else if (reportedLongCacheWrite !== undefined) {
1655
+ output.usage.cacheWrite1h = reportedLongCacheWrite;
1656
+ }
1657
+ const longCacheWrite = output.usage.cacheWrite1h ?? 0;
1658
+ if (!Number.isFinite(longCacheWrite) ||
1659
+ !Number.isInteger(longCacheWrite) ||
1660
+ longCacheWrite < 0 ||
1661
+ longCacheWrite > output.usage.cacheWrite) {
1662
+ throw new Error('Anthropic attribution received malformed 1h cache usage exceeding total cache writes');
1663
+ }
1664
+ output.usage.totalTokens =
1665
+ output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
1666
+ const rates = resolveModelCostRates(model, output.usage.input + output.usage.cacheRead + output.usage.cacheWrite);
1667
+ const shortCacheWrite = output.usage.cacheWrite - longCacheWrite;
1668
+ output.usage.cost.input = (output.usage.input * rates.input) / 1_000_000;
1669
+ output.usage.cost.output = (output.usage.output * rates.output) / 1_000_000;
1670
+ output.usage.cost.cacheRead = (output.usage.cacheRead * rates.cacheRead) / 1_000_000;
1671
+ output.usage.cost.cacheWrite =
1672
+ (shortCacheWrite * rates.cacheWrite + longCacheWrite * rates.input * 2) / 1_000_000;
1673
+ output.usage.cost.total =
1674
+ output.usage.cost.input +
1675
+ output.usage.cost.output +
1676
+ output.usage.cost.cacheRead +
1677
+ output.usage.cost.cacheWrite;
1678
+ }
1679
+ async function* iterateSseEvents(response, signal) {
1680
+ if (!response.body)
1681
+ throw new Error('Anthropic beta messages response had no body');
1682
+ const reader = response.body.getReader();
1683
+ const decoder = new TextDecoder();
1684
+ let buffer = '';
1685
+ let eventName = '';
1686
+ let dataLines = [];
1687
+ function flush() {
1688
+ if (dataLines.length === 0) {
1689
+ eventName = '';
1690
+ return undefined;
1691
+ }
1692
+ const data = dataLines.join('\n');
1693
+ const declaredEventName = eventName;
1694
+ eventName = '';
1695
+ dataLines = [];
1696
+ if (data === '[DONE]')
1697
+ return undefined;
1698
+ const event = parseJsonObject(data, 'Anthropic beta messages SSE event');
1699
+ if (declaredEventName.length > 0 &&
1700
+ (typeof event['type'] !== 'string' || event['type'] !== declaredEventName)) {
1701
+ throw new Error(`Anthropic beta messages SSE event name ${JSON.stringify(declaredEventName)} did not match data.type`);
1702
+ }
1703
+ return event;
1704
+ }
1705
+ function consumeLine(line) {
1706
+ if (line.length === 0)
1707
+ return flush();
1708
+ if (line.startsWith(':'))
1709
+ return undefined;
1710
+ const colon = line.indexOf(':');
1711
+ const field = colon === -1 ? line : line.slice(0, colon);
1712
+ let value = colon === -1 ? '' : line.slice(colon + 1);
1713
+ if (value.startsWith(' '))
1714
+ value = value.slice(1);
1715
+ if (field === 'event')
1716
+ eventName = value;
1717
+ if (field === 'data')
1718
+ dataLines.push(value);
1719
+ return undefined;
1720
+ }
1721
+ try {
1722
+ for (;;) {
1723
+ if (signal?.aborted)
1724
+ throw new Error('Request was aborted');
1725
+ const { value, done } = await reader.read();
1726
+ if (done)
1727
+ break;
1728
+ buffer += decoder.decode(value, { stream: true });
1729
+ for (;;) {
1730
+ const match = /\r\n|\n|\r/.exec(buffer);
1731
+ if (match?.index === undefined)
1732
+ break;
1733
+ const line = buffer.slice(0, match.index);
1734
+ buffer = buffer.slice(match.index + match[0].length);
1735
+ const event = consumeLine(line);
1736
+ if (event)
1737
+ yield event;
1738
+ }
1739
+ }
1740
+ buffer += decoder.decode();
1741
+ if (buffer.length > 0) {
1742
+ const event = consumeLine(buffer);
1743
+ if (event)
1744
+ yield event;
1745
+ }
1746
+ const trailing = flush();
1747
+ if (trailing)
1748
+ yield trailing;
1749
+ }
1750
+ finally {
1751
+ reader.releaseLock();
1752
+ }
1753
+ }
1754
+ function createOutput(model) {
1755
+ return {
1756
+ role: 'assistant',
1757
+ content: [],
1758
+ api: model.api,
1759
+ provider: model.provider,
1760
+ model: model.id,
1761
+ usage: {
1762
+ input: 0,
1763
+ output: 0,
1764
+ cacheRead: 0,
1765
+ cacheWrite: 0,
1766
+ totalTokens: 0,
1767
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1768
+ },
1769
+ stopReason: 'stop',
1770
+ timestamp: Date.now(),
1771
+ };
1772
+ }
1773
+ function forwardToBuiltInAnthropic(model, context, options) {
1774
+ // Pi aliases these SDK imports to its own version while loading extensions.
1775
+ // The host therefore owns both its legacy Context or normalized TranscriptContext
1776
+ // input and the complete stream/event/result shape. These intersections only mark
1777
+ // that host-owned boundary; no request, callback, event, or result is reconstructed.
1778
+ const delegated = anthropicMessagesApi().streamSimple(model, context, options);
1779
+ return delegated;
1780
+ }
1781
+ export function streamAnthropicViaBetaMessages(model, context, options, dependencies = {}) {
1782
+ if (model.provider !== 'anthropic') {
1783
+ return forwardToBuiltInAnthropic(model, context, options);
1784
+ }
1785
+ const stream = createAssistantMessageEventStream();
1786
+ const output = createOutput(model);
1787
+ void (async () => {
1788
+ let preparedLineage;
1789
+ try {
1790
+ const apiKey = options?.apiKey;
1791
+ if (typeof apiKey !== 'string' || apiKey.length === 0) {
1792
+ throw new Error('Anthropic attribution requires Pi OAuth apiKey/token; no credential was supplied');
1793
+ }
1794
+ if (!apiKey.includes('sk-ant-oat')) {
1795
+ throw new Error('Anthropic attribution refuses non-OAuth Anthropic credential; subscription OAuth token is required');
1796
+ }
1797
+ const sessionId = requireSessionId(options?.sessionId, 'options.sessionId');
1798
+ const account = requireAttributionAccount(dependencies.loadAccount?.() ?? loadClaudeAttributionAccount(undefined, options?.env));
1799
+ const url = resolveAnthropicBetaMessagesUrl(model);
1800
+ const policy = resolveClaudeCodeModelPolicy(model);
1801
+ const request = buildAnthropicRequest(model, context, options);
1802
+ let params = request.params;
1803
+ const billingSystemText = buildClaudeCodeBillingSystemText(firstUserMessageTextFromPayload(params));
1804
+ const provisionalLineage = prepareAnthropicLineageDetails({
1805
+ model,
1806
+ policy,
1807
+ context,
1808
+ payload: params,
1809
+ signatureEpoch: request.signatureEpoch,
1810
+ });
1811
+ if (policy.supportsCacheDiagnostics) {
1812
+ if (!policy.beta.split(',').includes(ANTHROPIC_CACHE_DIAGNOSTICS_BETA)) {
1813
+ throw new Error('Anthropic cache diagnostics policy is missing its required beta header');
1814
+ }
1815
+ params['diagnostics'] = {
1816
+ previous_message_id: provisionalLineage.previous_message_id,
1817
+ };
1818
+ }
1819
+ if (policy.enforcesThinkingPrefixBinding &&
1820
+ !policy.beta.split(',').includes(ANTHROPIC_THINKING_BINDING_BETA)) {
1821
+ throw new Error('Anthropic thinking-binding policy is missing its required beta header');
1822
+ }
1823
+ params = rewriteAnthropicRequestPayloadForSession({
1824
+ payload: params,
1825
+ model,
1826
+ sessionId,
1827
+ account,
1828
+ headerRegistered: true,
1829
+ ...(options?.cacheRetention === undefined
1830
+ ? {}
1831
+ : { cacheRetention: options.cacheRetention }),
1832
+ });
1833
+ const protectedCacheControlTopologySha256 = cacheControlTopologySha256(params);
1834
+ const nextParams = await options?.onPayload?.(params, model);
1835
+ if (nextParams !== undefined) {
1836
+ if (!isPlainObject(nextParams))
1837
+ throw new Error('Anthropic attribution onPayload returned a non-object payload');
1838
+ params = nextParams;
1839
+ }
1840
+ assertProtectedAnthropicAttribution({
1841
+ payload: params,
1842
+ account,
1843
+ sessionId,
1844
+ billingSystemText,
1845
+ modelId: policy.modelId,
1846
+ cacheControlTopologySha256: protectedCacheControlTopologySha256,
1847
+ });
1848
+ preparedLineage = anthropicLineageCoordinator.prepare({
1849
+ sessionId,
1850
+ model,
1851
+ policy,
1852
+ context,
1853
+ payload: params,
1854
+ signatureEpoch: request.signatureEpoch,
1855
+ });
1856
+ if (preparedLineage.details.previous_message_id !== provisionalLineage.previous_message_id ||
1857
+ preparedLineage.details.conversation_static_sha256 !==
1858
+ provisionalLineage.conversation_static_sha256 ||
1859
+ preparedLineage.details.request_message_count !==
1860
+ provisionalLineage.request_message_count ||
1861
+ preparedLineage.details.request_messages_sha256 !==
1862
+ provisionalLineage.request_messages_sha256 ||
1863
+ preparedLineage.details.cache_profile_sha256 !== provisionalLineage.cache_profile_sha256 ||
1864
+ preparedLineage.details.cache_retention !== provisionalLineage.cache_retention ||
1865
+ preparedLineage.details.compaction_boundary_sha256 !==
1866
+ provisionalLineage.compaction_boundary_sha256 ||
1867
+ preparedLineage.details.signature_epoch_sha256 !==
1868
+ provisionalLineage.signature_epoch_sha256 ||
1869
+ preparedLineage.details.signature_epoch_inherits_prior !==
1870
+ provisionalLineage.signature_epoch_inherits_prior) {
1871
+ throw new Error('Anthropic request/cache lineage changed during before_provider_request transforms');
1872
+ }
1873
+ if (policy.supportsCacheDiagnostics) {
1874
+ const diagnostics = params['diagnostics'];
1875
+ if (!isPlainObject(diagnostics) ||
1876
+ diagnostics['previous_message_id'] !== preparedLineage.details.previous_message_id) {
1877
+ throw new Error('Anthropic cache diagnostics were removed or changed during before_provider_request transforms');
1878
+ }
1879
+ }
1880
+ const headers = buildFetchHeaders(options, apiKey, sessionId, policy.beta);
1881
+ const requestInit = {
1882
+ method: 'POST',
1883
+ headers,
1884
+ body: JSON.stringify(params),
1885
+ redirect: 'error',
1886
+ };
1887
+ if (options?.signal)
1888
+ requestInit.signal = options.signal;
1889
+ const response = await fetch(url, requestInit);
1890
+ await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
1891
+ if (!response.ok) {
1892
+ throw new Error(`Anthropic beta messages request failed: HTTP ${response.status} ${response.statusText}: ${await response.text()}`);
1893
+ }
1894
+ const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
1895
+ if (!contentType.includes('text/event-stream')) {
1896
+ throw new Error(`Anthropic beta messages response must be text/event-stream; got ${JSON.stringify(contentType || '(missing)')}`);
1897
+ }
1898
+ stream.push({ type: 'start', partial: output });
1899
+ const blocks = output.content;
1900
+ const activeContentIndexes = new Set();
1901
+ let sawMessageStart = false;
1902
+ let sawMessageStop = false;
1903
+ let sawTerminalStopReason = false;
1904
+ for await (const event of iterateSseEvents(response, options?.signal)) {
1905
+ const eventType = event['type'];
1906
+ if (typeof eventType !== 'string') {
1907
+ throw new Error('Anthropic beta messages SSE event is missing string data.type');
1908
+ }
1909
+ if (eventType === 'error')
1910
+ throw new Error(anthropicStreamErrorMessage(event));
1911
+ if (eventType === 'ping')
1912
+ continue;
1913
+ if (sawMessageStop) {
1914
+ throw new Error('Anthropic beta messages stream emitted data after message_stop');
1915
+ }
1916
+ if (eventType !== 'message_start' && !sawMessageStart) {
1917
+ throw new Error('Anthropic beta messages stream emitted content before message_start');
1918
+ }
1919
+ if (eventType === 'message_start') {
1920
+ if (sawMessageStart || !isPlainObject(event['message'])) {
1921
+ throw new Error('Anthropic beta messages stream emitted malformed/duplicate message_start');
1922
+ }
1923
+ if (!isNonEmptyString(event['message']['id'])) {
1924
+ throw new Error('Anthropic beta messages message_start is missing a response id');
1925
+ }
1926
+ sawMessageStart = true;
1927
+ if (typeof event['message']['id'] === 'string')
1928
+ output.responseId = event['message']['id'];
1929
+ appendProviderCacheDiagnostic(output, event['message']);
1930
+ updateAnthropicUsage(output, isPlainObject(event['message']['usage']) ? event['message']['usage'] : undefined, model);
1931
+ }
1932
+ else if (eventType === 'content_block_start') {
1933
+ const contentIndex = event['index'];
1934
+ const contentBlock = event['content_block'];
1935
+ if (!Number.isSafeInteger(contentIndex) ||
1936
+ contentIndex < 0 ||
1937
+ !isPlainObject(contentBlock)) {
1938
+ throw new Error('Anthropic beta messages stream emitted malformed content_block_start');
1939
+ }
1940
+ if (activeContentIndexes.has(contentIndex)) {
1941
+ throw new Error('Anthropic beta messages stream duplicated an active content block');
1942
+ }
1943
+ activeContentIndexes.add(contentIndex);
1944
+ if (contentBlock['type'] === 'text') {
1945
+ output.content.push({ type: 'text', text: '', index: contentIndex });
1946
+ stream.push({
1947
+ type: 'text_start',
1948
+ contentIndex: output.content.length - 1,
1949
+ partial: output,
1950
+ });
1951
+ }
1952
+ else if (contentBlock['type'] === 'thinking') {
1953
+ output.content.push({
1954
+ type: 'thinking',
1955
+ thinking: '',
1956
+ thinkingSignature: '',
1957
+ index: contentIndex,
1958
+ });
1959
+ stream.push({
1960
+ type: 'thinking_start',
1961
+ contentIndex: output.content.length - 1,
1962
+ partial: output,
1963
+ });
1964
+ }
1965
+ else if (contentBlock['type'] === 'redacted_thinking') {
1966
+ if (typeof contentBlock['data'] !== 'string') {
1967
+ throw new Error('Anthropic redacted_thinking block is missing string data');
1968
+ }
1969
+ output.content.push({
1970
+ type: 'thinking',
1971
+ thinking: '[Reasoning redacted]',
1972
+ thinkingSignature: contentBlock['data'],
1973
+ redacted: true,
1974
+ index: contentIndex,
1975
+ });
1976
+ stream.push({
1977
+ type: 'thinking_start',
1978
+ contentIndex: output.content.length - 1,
1979
+ partial: output,
1980
+ });
1981
+ }
1982
+ else if (contentBlock['type'] === 'tool_use') {
1983
+ if (typeof contentBlock['id'] !== 'string' ||
1984
+ typeof contentBlock['name'] !== 'string' ||
1985
+ !isPlainObject(contentBlock['input'] ?? {})) {
1986
+ throw new Error('Anthropic tool_use block is malformed');
1987
+ }
1988
+ output.content.push({
1989
+ type: 'toolCall',
1990
+ id: contentBlock['id'],
1991
+ name: contentBlock['name'],
1992
+ arguments: contentBlock['input'] ?? {},
1993
+ partialJson: '',
1994
+ index: contentIndex,
1995
+ });
1996
+ stream.push({
1997
+ type: 'toolcall_start',
1998
+ contentIndex: output.content.length - 1,
1999
+ partial: output,
2000
+ });
2001
+ }
2002
+ else {
2003
+ throw new Error(`Anthropic beta messages stream emitted unsupported content block ${JSON.stringify(contentBlock['type'])}`);
2004
+ }
2005
+ }
2006
+ else if (eventType === 'content_block_delta') {
2007
+ const contentIndex = event['index'];
2008
+ const delta = event['delta'];
2009
+ if (!Number.isSafeInteger(contentIndex) ||
2010
+ !activeContentIndexes.has(contentIndex) ||
2011
+ !isPlainObject(delta)) {
2012
+ throw new Error('Anthropic beta messages stream emitted malformed/orphan content_block_delta');
2013
+ }
2014
+ const blockIndex = blocks.findIndex((block) => block.index === contentIndex);
2015
+ const block = blocks[blockIndex];
2016
+ if (!block) {
2017
+ throw new Error('Anthropic beta messages stream delta has no projected content block');
2018
+ }
2019
+ if (delta['type'] === 'text_delta' &&
2020
+ block['type'] === 'text' &&
2021
+ typeof delta['text'] === 'string') {
2022
+ block['text'] = `${String(block['text'] ?? '')}${delta['text']}`;
2023
+ stream.push({
2024
+ type: 'text_delta',
2025
+ contentIndex: blockIndex,
2026
+ delta: delta['text'],
2027
+ partial: output,
2028
+ });
2029
+ }
2030
+ else if (delta['type'] === 'thinking_delta' &&
2031
+ block['type'] === 'thinking' &&
2032
+ typeof delta['thinking'] === 'string') {
2033
+ block['thinking'] = `${String(block['thinking'] ?? '')}${delta['thinking']}`;
2034
+ stream.push({
2035
+ type: 'thinking_delta',
2036
+ contentIndex: blockIndex,
2037
+ delta: delta['thinking'],
2038
+ partial: output,
2039
+ });
2040
+ }
2041
+ else if (delta['type'] === 'input_json_delta' &&
2042
+ block['type'] === 'toolCall' &&
2043
+ typeof delta['partial_json'] === 'string') {
2044
+ block.partialJson = `${block.partialJson ?? ''}${delta['partial_json']}`;
2045
+ block['arguments'] = parseStreamingJsonFragment(block.partialJson);
2046
+ stream.push({
2047
+ type: 'toolcall_delta',
2048
+ contentIndex: blockIndex,
2049
+ delta: delta['partial_json'],
2050
+ partial: output,
2051
+ });
2052
+ }
2053
+ else if (delta['type'] === 'signature_delta' &&
2054
+ block['type'] === 'thinking' &&
2055
+ typeof delta['signature'] === 'string') {
2056
+ block['thinkingSignature'] =
2057
+ `${String(block['thinkingSignature'] ?? '')}${delta['signature']}`;
2058
+ }
2059
+ else {
2060
+ throw new Error(`Anthropic beta messages stream emitted unsupported/mismatched delta ${JSON.stringify(delta['type'])}`);
2061
+ }
2062
+ }
2063
+ else if (eventType === 'content_block_stop') {
2064
+ const contentIndex = event['index'];
2065
+ if (!Number.isSafeInteger(contentIndex) ||
2066
+ !activeContentIndexes.delete(contentIndex)) {
2067
+ throw new Error('Anthropic beta messages stream emitted malformed/orphan content_block_stop');
2068
+ }
2069
+ const blockIndex = blocks.findIndex((block) => block.index === contentIndex);
2070
+ const block = blocks[blockIndex];
2071
+ if (!block) {
2072
+ throw new Error('Anthropic beta messages stream stop has no projected content block');
2073
+ }
2074
+ delete block.index;
2075
+ if (block['type'] === 'text') {
2076
+ stream.push({
2077
+ type: 'text_end',
2078
+ contentIndex: blockIndex,
2079
+ content: String(block['text'] ?? ''),
2080
+ partial: output,
2081
+ });
2082
+ }
2083
+ else if (block['type'] === 'thinking') {
2084
+ stream.push({
2085
+ type: 'thinking_end',
2086
+ contentIndex: blockIndex,
2087
+ content: String(block['thinking'] ?? ''),
2088
+ partial: output,
2089
+ });
2090
+ }
2091
+ else if (block['type'] === 'toolCall') {
2092
+ block['arguments'] =
2093
+ block.partialJson && block.partialJson.length > 0
2094
+ ? parseCompletedToolInput(block.partialJson)
2095
+ : canonicalizeJson(block['arguments'] ?? {});
2096
+ delete block.partialJson;
2097
+ stream.push({
2098
+ type: 'toolcall_end',
2099
+ contentIndex: blockIndex,
2100
+ toolCall: block,
2101
+ partial: output,
2102
+ });
2103
+ }
2104
+ }
2105
+ else if (eventType === 'message_delta') {
2106
+ if (!isPlainObject(event['delta'])) {
2107
+ throw new Error('Anthropic beta messages stream emitted malformed message_delta');
2108
+ }
2109
+ const stopReason = event['delta']['stop_reason'];
2110
+ if (typeof stopReason === 'string') {
2111
+ const mapped = mapStopReason(stopReason);
2112
+ if (mapped === 'error') {
2113
+ throw new Error(`Anthropic beta messages stream emitted unsupported stop reason ${JSON.stringify(stopReason)}`);
2114
+ }
2115
+ output.stopReason = mapped;
2116
+ sawTerminalStopReason = true;
2117
+ }
2118
+ updateAnthropicUsage(output, isPlainObject(event['usage']) ? event['usage'] : undefined, model);
2119
+ }
2120
+ else if (eventType === 'message_stop') {
2121
+ if (activeContentIndexes.size > 0) {
2122
+ throw new Error('Anthropic beta messages message_stop arrived with open content blocks');
2123
+ }
2124
+ sawMessageStop = true;
2125
+ }
2126
+ else {
2127
+ throw new Error(`Anthropic beta messages stream emitted unsupported event ${JSON.stringify(eventType)}`);
2128
+ }
2129
+ }
2130
+ if (options?.signal?.aborted)
2131
+ throw new Error('Request was aborted');
2132
+ if (!sawMessageStart) {
2133
+ throw new Error('Anthropic beta messages stream ended without message_start');
2134
+ }
2135
+ if (!sawMessageStop) {
2136
+ throw new Error('Anthropic beta messages stream ended without message_stop');
2137
+ }
2138
+ if (!sawTerminalStopReason || output.stopReason === 'error') {
2139
+ throw new Error('Anthropic beta messages stream ended without a valid terminal stop reason');
2140
+ }
2141
+ if (preparedLineage === undefined)
2142
+ throw new Error('Anthropic attribution completed without prepared cache lineage');
2143
+ appendLineageDiagnostic(output, preparedLineage);
2144
+ stream.push({ type: 'done', reason: output.stopReason, message: output });
2145
+ stream.end();
2146
+ }
2147
+ catch (error) {
2148
+ for (const block of output.content) {
2149
+ delete block['index'];
2150
+ delete block['partialJson'];
2151
+ }
2152
+ output.stopReason = options?.signal?.aborted ? 'aborted' : 'error';
2153
+ output.errorMessage = error instanceof Error ? error.message : String(error);
2154
+ stream.push({ type: 'error', reason: output.stopReason, error: output });
2155
+ stream.end();
2156
+ }
2157
+ finally {
2158
+ if (preparedLineage !== undefined)
2159
+ anthropicLineageCoordinator.release(preparedLineage);
2160
+ }
2161
+ })();
2162
+ return stream;
2163
+ }
2164
+ function cacheRetentionLabel(retention) {
2165
+ switch (retention) {
2166
+ case 'long':
2167
+ return '1-hour';
2168
+ case 'short':
2169
+ return '5-minute';
2170
+ case 'none':
2171
+ return 'disabled';
2172
+ }
2173
+ }
2174
+ function isAnthropicAttributionClaimProbe(value) {
2175
+ return (isPlainObject(value) &&
2176
+ value['schema_version'] === ANTHROPIC_ATTRIBUTION_CLAIM_SCHEMA &&
2177
+ typeof value['acknowledge'] === 'function');
2178
+ }
2179
+ /**
2180
+ * Prevent two independently installed copies from registering duplicate provider
2181
+ * hooks and `/claude-cache` commands in one Pi runtime. Pi loads extension factories
2182
+ * sequentially and its EventBus dispatches listeners synchronously, so an existing
2183
+ * owner acknowledges this probe before emit() returns. The winning extension only
2184
+ * publishes ownership after every registration below succeeds; a factory that throws
2185
+ * cannot strand a false claim that suppresses a healthy later copy.
2186
+ */
2187
+ export default function spawnAnthropicAttribution(pi, dependencies = {}) {
2188
+ const acknowledgements = [];
2189
+ const probe = {
2190
+ schema_version: ANTHROPIC_ATTRIBUTION_CLAIM_SCHEMA,
2191
+ acknowledge: () => {
2192
+ acknowledgements.push(true);
2193
+ },
2194
+ };
2195
+ pi.events.emit(ANTHROPIC_ATTRIBUTION_CLAIM_CHANNEL, probe);
2196
+ if (acknowledgements.length > 0)
2197
+ return;
2198
+ let sessionCacheRetention;
2199
+ const getSessionOverride = () => sessionCacheRetention;
2200
+ // Registration is global but route-scoped by provider name. Keeping it at
2201
+ // factory scope avoids lifecycle-dependent provider availability; every custom
2202
+ // transport request owns attribution from its request-scoped session options.
2203
+ pi.registerProvider('anthropic', {
2204
+ api: 'anthropic-messages',
2205
+ streamSimple: (model, context, options) => streamAnthropicViaBetaMessages(model, context, {
2206
+ ...(options ?? {}),
2207
+ cacheRetention: resolveRegisteredCacheRetention(options, getSessionOverride()),
2208
+ }, dependencies),
2209
+ });
2210
+ pi.registerCommand('claude-cache', {
2211
+ description: 'Show or set Claude cache retention for this session (short, long, default)',
2212
+ handler: (args, ctx) => {
2213
+ const action = args.trim().toLowerCase();
2214
+ if (action.length === 0 || action === 'status') {
2215
+ const effective = resolveCacheRetentionPreference(undefined, sessionCacheRetention);
2216
+ ctx.ui?.notify(`Claude cache retention: ${cacheRetentionLabel(effective)}${sessionCacheRetention === undefined ? ' (default)' : ' (session override)'}`, 'info');
2217
+ return;
2218
+ }
2219
+ if (action !== 'short' && action !== 'long' && action !== 'default') {
2220
+ throw new Error('Usage: /claude-cache [status|short|long|default]');
2221
+ }
2222
+ sessionCacheRetention = action === 'default' ? undefined : action;
2223
+ pi.appendEntry(ANTHROPIC_CACHE_RETENTION_ENTRY, {
2224
+ schema_version: ANTHROPIC_CACHE_RETENTION_SCHEMA,
2225
+ retention: action,
2226
+ });
2227
+ const effective = resolveCacheRetentionPreference(undefined, sessionCacheRetention);
2228
+ ctx.ui?.notify(`Claude cache retention set to ${cacheRetentionLabel(effective)} for this session${action === 'default' ? ' (default policy)' : ''}.`, 'info');
2229
+ },
2230
+ });
2231
+ pi.on('session_start', (_event, ctx) => {
2232
+ sessionCacheRetention = restoreAnthropicSessionCacheRetention(ctx.sessionManager.getBranch());
2233
+ });
2234
+ pi.on('session_shutdown', () => {
2235
+ sessionCacheRetention = undefined;
2236
+ });
2237
+ pi.on('session_tree', (_event, ctx) => {
2238
+ sessionCacheRetention = restoreAnthropicSessionCacheRetention(ctx.sessionManager.getBranch());
2239
+ });
2240
+ // Publish ownership last. Extension loading is sequential, so later independent
2241
+ // copies probe this responder and become inert instead of registering duplicates.
2242
+ pi.events.on(ANTHROPIC_ATTRIBUTION_CLAIM_CHANNEL, (value) => {
2243
+ if (isAnthropicAttributionClaimProbe(value))
2244
+ value.acknowledge();
2245
+ });
2246
+ }
2247
+ //# sourceMappingURL=anthropic-attribution.js.map