@animalabs/membrane 0.5.79 → 0.5.81

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 (175) hide show
  1. package/dist/cache-keepalive.d.ts +115 -0
  2. package/dist/cache-keepalive.d.ts.map +1 -0
  3. package/dist/cache-keepalive.js +0 -0
  4. package/dist/cache-keepalive.js.map +1 -0
  5. package/dist/cache-keepalive.test.d.ts +2 -0
  6. package/dist/cache-keepalive.test.d.ts.map +1 -0
  7. package/dist/cache-keepalive.test.js +206 -0
  8. package/dist/cache-keepalive.test.js.map +1 -0
  9. package/dist/context/index.d.ts +1 -1
  10. package/dist/context/index.d.ts.map +1 -1
  11. package/dist/context/index.js +1 -1
  12. package/dist/context/index.js.map +1 -1
  13. package/dist/context/process.d.ts +3 -1
  14. package/dist/context/process.d.ts.map +1 -1
  15. package/dist/context/process.js +230 -49
  16. package/dist/context/process.js.map +1 -1
  17. package/dist/context/types.d.ts +30 -0
  18. package/dist/context/types.d.ts.map +1 -1
  19. package/dist/context/types.js +16 -0
  20. package/dist/context/types.js.map +1 -1
  21. package/dist/floating-cache-marker.test.d.ts +2 -0
  22. package/dist/floating-cache-marker.test.d.ts.map +1 -0
  23. package/dist/floating-cache-marker.test.js +263 -0
  24. package/dist/floating-cache-marker.test.js.map +1 -0
  25. package/dist/formatters/anthropic-xml.d.ts +2 -0
  26. package/dist/formatters/anthropic-xml.d.ts.map +1 -1
  27. package/dist/formatters/anthropic-xml.js +57 -19
  28. package/dist/formatters/anthropic-xml.js.map +1 -1
  29. package/dist/formatters/normalize-tool-pairs.d.ts +7 -0
  30. package/dist/formatters/normalize-tool-pairs.d.ts.map +1 -1
  31. package/dist/formatters/normalize-tool-pairs.js +45 -0
  32. package/dist/formatters/normalize-tool-pairs.js.map +1 -1
  33. package/dist/formatters/types.d.ts +29 -0
  34. package/dist/formatters/types.d.ts.map +1 -1
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/membrane.d.ts +142 -7
  40. package/dist/membrane.d.ts.map +1 -1
  41. package/dist/membrane.js +881 -270
  42. package/dist/membrane.js.map +1 -1
  43. package/dist/providers/anthropic.d.ts +37 -0
  44. package/dist/providers/anthropic.d.ts.map +1 -1
  45. package/dist/providers/anthropic.js +163 -26
  46. package/dist/providers/anthropic.js.map +1 -1
  47. package/dist/providers/bedrock.d.ts +8 -0
  48. package/dist/providers/bedrock.d.ts.map +1 -1
  49. package/dist/providers/bedrock.js +24 -1
  50. package/dist/providers/bedrock.js.map +1 -1
  51. package/dist/providers/gemini.d.ts +11 -0
  52. package/dist/providers/gemini.d.ts.map +1 -1
  53. package/dist/providers/gemini.js +104 -82
  54. package/dist/providers/gemini.js.map +1 -1
  55. package/dist/providers/mock.d.ts +2 -0
  56. package/dist/providers/mock.d.ts.map +1 -1
  57. package/dist/providers/mock.js +2 -0
  58. package/dist/providers/mock.js.map +1 -1
  59. package/dist/providers/openai-compatible.d.ts +7 -0
  60. package/dist/providers/openai-compatible.d.ts.map +1 -1
  61. package/dist/providers/openai-compatible.js +99 -48
  62. package/dist/providers/openai-compatible.js.map +1 -1
  63. package/dist/providers/openai-completions.d.ts +5 -0
  64. package/dist/providers/openai-completions.d.ts.map +1 -1
  65. package/dist/providers/openai-completions.js +107 -44
  66. package/dist/providers/openai-completions.js.map +1 -1
  67. package/dist/providers/openai-responses-api.d.ts +16 -0
  68. package/dist/providers/openai-responses-api.d.ts.map +1 -1
  69. package/dist/providers/openai-responses-api.js +38 -10
  70. package/dist/providers/openai-responses-api.js.map +1 -1
  71. package/dist/providers/openai-responses.d.ts +2 -0
  72. package/dist/providers/openai-responses.d.ts.map +1 -1
  73. package/dist/providers/openai-responses.js +8 -1
  74. package/dist/providers/openai-responses.js.map +1 -1
  75. package/dist/providers/openai.d.ts +7 -0
  76. package/dist/providers/openai.d.ts.map +1 -1
  77. package/dist/providers/openai.js +92 -44
  78. package/dist/providers/openai.js.map +1 -1
  79. package/dist/providers/openrouter.d.ts +9 -0
  80. package/dist/providers/openrouter.d.ts.map +1 -1
  81. package/dist/providers/openrouter.js +118 -62
  82. package/dist/providers/openrouter.js.map +1 -1
  83. package/dist/providers/utils.d.ts +74 -2
  84. package/dist/providers/utils.d.ts.map +1 -1
  85. package/dist/providers/utils.js +184 -3
  86. package/dist/providers/utils.js.map +1 -1
  87. package/dist/registry/default-pricing.d.ts +22 -0
  88. package/dist/registry/default-pricing.d.ts.map +1 -1
  89. package/dist/registry/default-pricing.js +162 -28
  90. package/dist/registry/default-pricing.js.map +1 -1
  91. package/dist/types/config.d.ts +5 -0
  92. package/dist/types/config.d.ts.map +1 -1
  93. package/dist/types/config.js.map +1 -1
  94. package/dist/types/content.d.ts +13 -0
  95. package/dist/types/content.d.ts.map +1 -1
  96. package/dist/types/content.js.map +1 -1
  97. package/dist/types/errors.d.ts +15 -0
  98. package/dist/types/errors.d.ts.map +1 -1
  99. package/dist/types/errors.js +26 -0
  100. package/dist/types/errors.js.map +1 -1
  101. package/dist/types/index.d.ts +3 -3
  102. package/dist/types/index.d.ts.map +1 -1
  103. package/dist/types/index.js +1 -1
  104. package/dist/types/index.js.map +1 -1
  105. package/dist/types/provider.d.ts +50 -0
  106. package/dist/types/provider.d.ts.map +1 -1
  107. package/dist/types/request.d.ts +13 -0
  108. package/dist/types/request.d.ts.map +1 -1
  109. package/dist/types/response.d.ts +85 -4
  110. package/dist/types/response.d.ts.map +1 -1
  111. package/dist/types/response.js.map +1 -1
  112. package/dist/types/streaming.d.ts +6 -1
  113. package/dist/types/streaming.d.ts.map +1 -1
  114. package/dist/utils/cache-marker-budget.d.ts +82 -0
  115. package/dist/utils/cache-marker-budget.d.ts.map +1 -0
  116. package/dist/utils/cache-marker-budget.js +189 -0
  117. package/dist/utils/cache-marker-budget.js.map +1 -0
  118. package/dist/utils/cost.d.ts +22 -0
  119. package/dist/utils/cost.d.ts.map +1 -1
  120. package/dist/utils/cost.js +55 -0
  121. package/dist/utils/cost.js.map +1 -1
  122. package/dist/utils/thinking-carriers.d.ts +80 -0
  123. package/dist/utils/thinking-carriers.d.ts.map +1 -0
  124. package/dist/utils/thinking-carriers.js +115 -0
  125. package/dist/utils/thinking-carriers.js.map +1 -0
  126. package/dist/utils/tool-parser.d.ts +24 -0
  127. package/dist/utils/tool-parser.d.ts.map +1 -1
  128. package/dist/utils/tool-parser.js +367 -161
  129. package/dist/utils/tool-parser.js.map +1 -1
  130. package/dist/utils/usage.d.ts +71 -0
  131. package/dist/utils/usage.d.ts.map +1 -0
  132. package/dist/utils/usage.js +161 -0
  133. package/dist/utils/usage.js.map +1 -0
  134. package/dist/yielding-stream.d.ts +8 -0
  135. package/dist/yielding-stream.d.ts.map +1 -1
  136. package/dist/yielding-stream.js +49 -3
  137. package/dist/yielding-stream.js.map +1 -1
  138. package/package.json +3 -2
  139. package/src/cache-keepalive.test.ts +244 -0
  140. package/src/cache-keepalive.ts +385 -0
  141. package/src/context/index.ts +1 -0
  142. package/src/context/process.ts +305 -59
  143. package/src/context/types.ts +38 -0
  144. package/src/floating-cache-marker.test.ts +285 -0
  145. package/src/formatters/anthropic-xml.ts +62 -20
  146. package/src/formatters/normalize-tool-pairs.ts +49 -0
  147. package/src/formatters/types.ts +32 -0
  148. package/src/index.ts +13 -0
  149. package/src/membrane.ts +1031 -279
  150. package/src/providers/anthropic.ts +205 -20
  151. package/src/providers/bedrock.ts +25 -1
  152. package/src/providers/gemini.ts +125 -87
  153. package/src/providers/mock.ts +3 -0
  154. package/src/providers/openai-compatible.ts +102 -45
  155. package/src/providers/openai-completions.ts +112 -46
  156. package/src/providers/openai-responses-api.ts +45 -15
  157. package/src/providers/openai-responses.ts +8 -1
  158. package/src/providers/openai.ts +95 -42
  159. package/src/providers/openrouter.ts +125 -63
  160. package/src/providers/utils.ts +227 -6
  161. package/src/registry/default-pricing.ts +165 -28
  162. package/src/types/config.ts +6 -0
  163. package/src/types/content.ts +14 -0
  164. package/src/types/errors.ts +28 -0
  165. package/src/types/index.ts +6 -0
  166. package/src/types/provider.ts +55 -0
  167. package/src/types/request.ts +14 -0
  168. package/src/types/response.ts +109 -8
  169. package/src/types/streaming.ts +8 -2
  170. package/src/utils/cache-marker-budget.ts +208 -0
  171. package/src/utils/cost.ts +64 -0
  172. package/src/utils/thinking-carriers.ts +125 -0
  173. package/src/utils/tool-parser.ts +473 -174
  174. package/src/utils/usage.ts +184 -0
  175. package/src/yielding-stream.ts +54 -3
@@ -1,66 +1,203 @@
1
1
  import type { ModelPricing } from '../types/provider.js';
2
2
 
3
3
  /**
4
- * Built-in pricing table for known models.
5
- * Prices in USD per million tokens. Last updated: 2025-07.
4
+ * Built-in pricing table for known models, in USD per million tokens.
5
+ * Used as fallback when no ModelRegistry is configured; registry pricing
6
+ * (if available) takes precedence.
6
7
  *
7
- * Used as fallback when no ModelRegistry is configured.
8
- * Registry pricing (if available) takes precedence.
8
+ * Every row carries its OWN verification date through `ModelPricing.asOf`, out
9
+ * to `CostBreakdown.pricingAsOf`, so a caller can see how old the number it is
10
+ * billing against is instead of trusting a prose header. Currently-published
11
+ * rows were read off the provider's own price page on the date in
12
+ * {@link DEFAULT_PRICING_LAST_VERIFIED} (the per-section comments name the
13
+ * page) and carry it. A row for a RETIRED model carries the date its rate was
14
+ * last published instead — claude-3-5-sonnet is dated 2025-07-01 because it is
15
+ * no longer listed anywhere to verify against, and stamping it with the sweep
16
+ * date would claim a check that cannot be performed. The previous header
17
+ * claimed "Last updated: 2025-07" for the whole table while it carried Claude
18
+ * 4.6 rows, and three rows were simply wrong by the time they were checked.
19
+ *
20
+ * Matching is longest-prefix (see {@link getDefaultPricing}), so a more
21
+ * specific row always wins over a more general one and rows may be listed in
22
+ * any order.
9
23
  */
24
+ export const DEFAULT_PRICING_LAST_VERIFIED = '2026-08-25';
25
+
10
26
  const PRICING_TABLE: Array<{ prefix: string; pricing: ModelPricing }> = [
11
- // Anthropic — Claude 4.6
27
+ // --------------------------------------------------------------------------
28
+ // Anthropic — https://docs.claude.com/en/docs/about-claude/pricing
29
+ // Retrieved 2026-08-25. Cache-write column is the 5-minute TTL bucket; the
30
+ // 1-hour bucket costs more and this table has no field for it.
31
+ // --------------------------------------------------------------------------
12
32
  {
13
- prefix: 'claude-opus-4-6',
14
- pricing: { inputPerMillion: 15, outputPerMillion: 75, cacheWritePerMillion: 18.75, cacheReadPerMillion: 1.50, currency: 'USD' },
33
+ prefix: 'claude-fable-5',
34
+ pricing: { inputPerMillion: 10, outputPerMillion: 50, cacheWritePerMillion: 12.50, cacheReadPerMillion: 1, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
15
35
  },
16
36
  {
17
- prefix: 'claude-sonnet-4-6',
18
- pricing: { inputPerMillion: 3, outputPerMillion: 15, cacheWritePerMillion: 3.75, cacheReadPerMillion: 0.30, currency: 'USD' },
37
+ prefix: 'claude-opus-5',
38
+ pricing: { inputPerMillion: 5, outputPerMillion: 25, cacheWritePerMillion: 6.25, cacheReadPerMillion: 0.50, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
19
39
  },
20
- // Anthropic — Claude 4.5
21
40
  {
22
- prefix: 'claude-haiku-4-5',
23
- pricing: { inputPerMillion: 0.80, outputPerMillion: 4, cacheWritePerMillion: 1.00, cacheReadPerMillion: 0.08, currency: 'USD' },
41
+ prefix: 'claude-sonnet-5',
42
+ pricing: { inputPerMillion: 2, outputPerMillion: 10, cacheWritePerMillion: 2.50, cacheReadPerMillion: 0.20, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
43
+ },
44
+ // Opus 4.5 through 4.8 all price identically to Opus 5.
45
+ {
46
+ prefix: 'claude-opus-4-8',
47
+ pricing: { inputPerMillion: 5, outputPerMillion: 25, cacheWritePerMillion: 6.25, cacheReadPerMillion: 0.50, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
24
48
  },
25
- // Anthropic — Claude 4
26
49
  {
50
+ prefix: 'claude-opus-4-7',
51
+ pricing: { inputPerMillion: 5, outputPerMillion: 25, cacheWritePerMillion: 6.25, cacheReadPerMillion: 0.50, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
52
+ },
53
+ {
54
+ // Was priced here at 15/75 — the retired Opus 4 rate, 3x the real one.
55
+ prefix: 'claude-opus-4-6',
56
+ pricing: { inputPerMillion: 5, outputPerMillion: 25, cacheWritePerMillion: 6.25, cacheReadPerMillion: 0.50, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
57
+ },
58
+ {
59
+ prefix: 'claude-opus-4-5',
60
+ pricing: { inputPerMillion: 5, outputPerMillion: 25, cacheWritePerMillion: 6.25, cacheReadPerMillion: 0.50, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
61
+ },
62
+ {
63
+ // Retired except on Bedrock and Google Cloud; also covers Opus 4.1, which
64
+ // is priced identically.
27
65
  prefix: 'claude-opus-4',
28
- pricing: { inputPerMillion: 15, outputPerMillion: 75, cacheWritePerMillion: 18.75, cacheReadPerMillion: 1.50, currency: 'USD' },
66
+ pricing: { inputPerMillion: 15, outputPerMillion: 75, cacheWritePerMillion: 18.75, cacheReadPerMillion: 1.50, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
29
67
  },
30
68
  {
69
+ prefix: 'claude-sonnet-4-6',
70
+ pricing: { inputPerMillion: 3, outputPerMillion: 15, cacheWritePerMillion: 3.75, cacheReadPerMillion: 0.30, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
71
+ },
72
+ {
73
+ prefix: 'claude-sonnet-4-5',
74
+ pricing: { inputPerMillion: 3, outputPerMillion: 15, cacheWritePerMillion: 3.75, cacheReadPerMillion: 0.30, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
75
+ },
76
+ {
77
+ // Retired except on Bedrock and Google Cloud.
31
78
  prefix: 'claude-sonnet-4',
32
- pricing: { inputPerMillion: 3, outputPerMillion: 15, cacheWritePerMillion: 3.75, cacheReadPerMillion: 0.30, currency: 'USD' },
79
+ pricing: { inputPerMillion: 3, outputPerMillion: 15, cacheWritePerMillion: 3.75, cacheReadPerMillion: 0.30, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
33
80
  },
34
- // Anthropic — Claude 3.5
35
81
  {
36
- prefix: 'claude-3-5-sonnet',
37
- pricing: { inputPerMillion: 3, outputPerMillion: 15, cacheWritePerMillion: 3.75, cacheReadPerMillion: 0.30, currency: 'USD' },
82
+ // Was priced here at 0.80/4 — the retired Haiku 3.5 rate.
83
+ prefix: 'claude-haiku-4-5',
84
+ pricing: { inputPerMillion: 1, outputPerMillion: 5, cacheWritePerMillion: 1.25, cacheReadPerMillion: 0.10, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
38
85
  },
39
86
  {
87
+ // Retired except on Bedrock and Google Cloud.
40
88
  prefix: 'claude-3-5-haiku',
41
- pricing: { inputPerMillion: 0.80, outputPerMillion: 4, cacheWritePerMillion: 1.00, cacheReadPerMillion: 0.08, currency: 'USD' },
89
+ pricing: { inputPerMillion: 0.80, outputPerMillion: 4, cacheWritePerMillion: 1, cacheReadPerMillion: 0.08, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
90
+ },
91
+ {
92
+ // Fully retired: no longer listed on the price page as of 2026-08-25, so
93
+ // this is the last published rate rather than a current one.
94
+ prefix: 'claude-3-5-sonnet',
95
+ pricing: { inputPerMillion: 3, outputPerMillion: 15, cacheWritePerMillion: 3.75, cacheReadPerMillion: 0.30, currency: 'USD', asOf: '2025-07-01' },
42
96
  },
43
- // OpenAI — GPT-4o
97
+
98
+ // --------------------------------------------------------------------------
99
+ // OpenAI — https://platform.openai.com/docs/pricing (standard tier)
100
+ // Retrieved 2026-08-25. "Cached input" is OpenAI's discounted rate for the
101
+ // automatically-cached prompt prefix; note that OpenAI's prompt_tokens
102
+ // INCLUDES that span (see UsageCacheConvention), which membrane normalizes
103
+ // away before pricing, so cacheReadPerMillion is applied to a disjoint count.
104
+ // --------------------------------------------------------------------------
44
105
  {
45
- prefix: 'gpt-4o-2024',
46
- pricing: { inputPerMillion: 2.50, outputPerMillion: 10, cacheReadPerMillion: 1.25, currency: 'USD' },
106
+ prefix: 'gpt-5.6-sol',
107
+ pricing: { inputPerMillion: 4, outputPerMillion: 20, cacheReadPerMillion: 0.40, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
47
108
  },
48
109
  {
49
- prefix: 'gpt-4o',
50
- pricing: { inputPerMillion: 2.50, outputPerMillion: 10, cacheReadPerMillion: 1.25, currency: 'USD' },
110
+ prefix: 'gpt-5.6-terra',
111
+ pricing: { inputPerMillion: 2, outputPerMillion: 12, cacheReadPerMillion: 0.20, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
112
+ },
113
+ {
114
+ prefix: 'gpt-5.6-luna',
115
+ pricing: { inputPerMillion: 0.20, outputPerMillion: 1.20, cacheReadPerMillion: 0.02, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
116
+ },
117
+ {
118
+ // Under 272K context; the long-context tier costs more and this table has
119
+ // no field for context-dependent rates.
120
+ prefix: 'gpt-5.5',
121
+ pricing: { inputPerMillion: 5, outputPerMillion: 30, cacheReadPerMillion: 0.50, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
122
+ },
123
+ {
124
+ prefix: 'gpt-5.4-mini',
125
+ pricing: { inputPerMillion: 0.75, outputPerMillion: 4.50, cacheReadPerMillion: 0.075, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
126
+ },
127
+ {
128
+ prefix: 'gpt-5.4-nano',
129
+ pricing: { inputPerMillion: 0.20, outputPerMillion: 1.25, cacheReadPerMillion: 0.02, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
130
+ },
131
+ {
132
+ // Under 272K context.
133
+ prefix: 'gpt-5.4',
134
+ pricing: { inputPerMillion: 2.50, outputPerMillion: 15, cacheReadPerMillion: 0.25, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
135
+ },
136
+ {
137
+ prefix: 'gpt-5.2',
138
+ pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
139
+ },
140
+ {
141
+ prefix: 'gpt-5.1',
142
+ pricing: { inputPerMillion: 1.25, outputPerMillion: 10, cacheReadPerMillion: 0.125, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
143
+ },
144
+ {
145
+ prefix: 'gpt-5-mini',
146
+ pricing: { inputPerMillion: 0.25, outputPerMillion: 2, cacheReadPerMillion: 0.025, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
147
+ },
148
+ {
149
+ prefix: 'gpt-5-nano',
150
+ pricing: { inputPerMillion: 0.05, outputPerMillion: 0.40, cacheReadPerMillion: 0.005, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
151
+ },
152
+ {
153
+ prefix: 'gpt-5',
154
+ pricing: { inputPerMillion: 1.25, outputPerMillion: 10, cacheReadPerMillion: 0.125, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
51
155
  },
52
156
  {
53
157
  prefix: 'gpt-4o-mini',
54
- pricing: { inputPerMillion: 0.15, outputPerMillion: 0.60, cacheReadPerMillion: 0.075, currency: 'USD' },
158
+ pricing: { inputPerMillion: 0.15, outputPerMillion: 0.60, cacheReadPerMillion: 0.075, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
159
+ },
160
+ {
161
+ // The one gpt-4o snapshot priced differently from plain gpt-4o. Replaces a
162
+ // `gpt-4o-2024` row that was byte-identical to gpt-4o and so could never
163
+ // change any answer — while masking this genuine difference.
164
+ prefix: 'gpt-4o-2024-05-13',
165
+ pricing: { inputPerMillion: 5, outputPerMillion: 15, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
166
+ },
167
+ {
168
+ prefix: 'gpt-4o',
169
+ pricing: { inputPerMillion: 2.50, outputPerMillion: 10, cacheReadPerMillion: 1.25, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
170
+ },
171
+
172
+ // --------------------------------------------------------------------------
173
+ // Google — https://ai.google.dev/gemini-api/docs/pricing (paid tier)
174
+ // Retrieved 2026-08-25. Google's published output price INCLUDES thinking
175
+ // tokens, which is why the gemini adapter folds thoughtsTokenCount into
176
+ // outputTokens. Cache rates are the context-caching per-token price and
177
+ // exclude Google's separate per-hour storage charge, which membrane does not
178
+ // model. Where a model has a >200k-token tier the smaller-prompt rate is used.
179
+ // --------------------------------------------------------------------------
180
+ {
181
+ prefix: 'gemini-3.5-flash-lite',
182
+ pricing: { inputPerMillion: 0.30, outputPerMillion: 2.50, cacheReadPerMillion: 0.03, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
183
+ },
184
+ {
185
+ prefix: 'gemini-3.5-flash',
186
+ pricing: { inputPerMillion: 1.50, outputPerMillion: 9, cacheReadPerMillion: 0.15, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
55
187
  },
56
- // Google — Gemini 2.5
57
188
  {
58
189
  prefix: 'gemini-2.5-pro',
59
- pricing: { inputPerMillion: 1.25, outputPerMillion: 10, currency: 'USD' },
190
+ pricing: { inputPerMillion: 1.25, outputPerMillion: 10, cacheReadPerMillion: 0.125, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
191
+ },
192
+ {
193
+ // Was absent, so the gemini-2.5-flash row priced it at 3x input / 6x output.
194
+ prefix: 'gemini-2.5-flash-lite',
195
+ pricing: { inputPerMillion: 0.10, outputPerMillion: 0.40, cacheReadPerMillion: 0.01, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
60
196
  },
61
197
  {
198
+ // Was priced here at 0.15/0.60 — text rate; audio input costs more.
62
199
  prefix: 'gemini-2.5-flash',
63
- pricing: { inputPerMillion: 0.15, outputPerMillion: 0.60, currency: 'USD' },
200
+ pricing: { inputPerMillion: 0.30, outputPerMillion: 2.50, cacheReadPerMillion: 0.03, currency: 'USD', asOf: DEFAULT_PRICING_LAST_VERIFIED },
64
201
  },
65
202
  ];
66
203
 
@@ -180,6 +180,12 @@ export interface MembraneConfig {
180
180
  */
181
181
  defaultPromptCaching?: boolean;
182
182
 
183
+ /**
184
+ * Default for request.floatingCacheMarker when the request doesn't set it.
185
+ * Default: true. See NormalizedRequest.floatingCacheMarker.
186
+ */
187
+ defaultFloatingCacheMarker?: boolean;
188
+
183
189
  /**
184
190
  * Prefill formatter for message serialization and response parsing.
185
191
  * Controls how messages are formatted for the API and how responses are parsed.
@@ -121,6 +121,20 @@ export interface ToolUseContent {
121
121
  * block. Absent on native-tools blocks and on legacy stored blocks.
122
122
  */
123
123
  rawXml?: string;
124
+ /**
125
+ * Raw accumulated argument text that FAILED to parse as JSON, kept verbatim.
126
+ *
127
+ * Present only when the provider's streamed `input_json_delta` fragments did
128
+ * not assemble into valid JSON — a call truncated mid-arguments (max_tokens)
129
+ * is the usual cause. `input` then holds whatever the provider's
130
+ * content_block_start carried, which for Anthropic is `{}`: a wire-valid
131
+ * tool call with empty arguments, indistinguishable from a genuine no-arg
132
+ * call once it is written to durable history. Presence of this field means
133
+ * `input` is NOT the model's arguments, so a consumer can refuse the block
134
+ * (or attempt its own repair) instead of trusting a plausible `{}`.
135
+ */
136
+ unparseableInput?: string;
137
+
124
138
  /** See {@link TextContent.rawItem}. */
125
139
  rawItem?: unknown;
126
140
  }
@@ -215,6 +215,34 @@ export function abortError(message: string = 'Request was aborted', rawRequest?:
215
215
  });
216
216
  }
217
217
 
218
+ /**
219
+ * A request cancelled by the adapter's OWN deadline (`timeoutMs`), as opposed
220
+ * to a caller's signal or a stray abort.
221
+ *
222
+ * It is both facts at once, and callers need both: a timeout by `type` (so
223
+ * `classifyError` and the abort-reason ladder report `'timeout'`), and an
224
+ * abort by provenance (so the streaming paths still hand back an
225
+ * `AbortedResponse` with whatever partial content arrived, rather than
226
+ * throwing). Non-retryable: the deadline that fired belongs to this call, and
227
+ * retrying inside it would only spend the caller's budget again.
228
+ */
229
+ export class TimeoutAbortError extends MembraneError {
230
+ constructor(message: string = 'Request timed out', raw?: unknown, rawRequest?: unknown) {
231
+ super({
232
+ type: 'timeout',
233
+ message,
234
+ retryable: false,
235
+ rawError: raw,
236
+ rawRequest,
237
+ });
238
+ this.name = 'TimeoutAbortError';
239
+ }
240
+ }
241
+
242
+ export function isTimeoutAbortError(error: unknown): error is TimeoutAbortError {
243
+ return error instanceof TimeoutAbortError;
244
+ }
245
+
218
246
  export function safetyError(message: string, raw?: unknown, rawRequest?: unknown): MembraneError {
219
247
  return new MembraneError({
220
248
  type: 'safety',
@@ -73,7 +73,10 @@ export type {
73
73
  export type {
74
74
  StopReason,
75
75
  BasicUsage,
76
+ CallUsage,
76
77
  DetailedUsage,
78
+ TurnRoundUsage,
79
+ DiscardedAttemptsUsage,
77
80
  CostBreakdown,
78
81
  StopInfo,
79
82
  ModelInfo,
@@ -101,6 +104,7 @@ export type {
101
104
  ProviderRequestOptions,
102
105
  ProviderResponse,
103
106
  StreamCallbacks,
107
+ UsageCacheConvention,
104
108
  } from './provider.js';
105
109
 
106
110
  // Streaming
@@ -162,6 +166,8 @@ export {
162
166
  networkError,
163
167
  timeoutError,
164
168
  abortError,
169
+ TimeoutAbortError,
170
+ isTimeoutAbortError,
165
171
  safetyError,
166
172
  unsupportedError,
167
173
  classifyError,
@@ -82,6 +82,21 @@ export interface ProviderCapabilities {
82
82
  // Model Pricing
83
83
  // ============================================================================
84
84
 
85
+ /**
86
+ * Whether a provider's prompt-token count INCLUDES the span served from cache.
87
+ *
88
+ * Measured live 2026-08-25 — Anthropic (`cache-excluded`): a 4,650-token cached
89
+ * system prompt returned `input_tokens: 8` with `cache_read_input_tokens: 4650`.
90
+ * OpenAI (`cache-inclusive`): `prompt_tokens` stayed at 1732 across a cache hit
91
+ * that reported `cached_tokens: 1664`, so cached is a SUBSET of the prompt.
92
+ *
93
+ * `unknown` is a real epistemic state, not a default to lean on: it means no
94
+ * one has established this adapter's convention, and membrane will pass the
95
+ * counts through unchanged and warn the first time a cache read makes the
96
+ * ambiguity bite.
97
+ */
98
+ export type UsageCacheConvention = 'cache-excluded' | 'cache-inclusive' | 'unknown';
99
+
85
100
  export interface ModelPricing {
86
101
  /** Cost per million input tokens */
87
102
  inputPerMillion: number;
@@ -97,6 +112,14 @@ export interface ModelPricing {
97
112
 
98
113
  /** Currency code */
99
114
  currency: string;
115
+
116
+ /**
117
+ * ISO date these rates were last checked against the provider's published
118
+ * price page, surfaced to callers as {@link CostBreakdown.pricingAsOf}. A
119
+ * pricing source that cannot vouch for a date leaves it unset — better an
120
+ * absent freshness signal than a fabricated one.
121
+ */
122
+ asOf?: string;
100
123
  }
101
124
 
102
125
  // ============================================================================
@@ -169,6 +192,21 @@ export interface ProviderAdapter {
169
192
  /** Provider name */
170
193
  readonly name: string;
171
194
 
195
+ /**
196
+ * Which convention this adapter's `usage.inputTokens` carries. Membrane
197
+ * normalizes every response onto `cache-excluded` before any ratio or cost is
198
+ * computed, and it can only do that if the adapter says what it is reporting.
199
+ *
200
+ * OPTIONAL, defaulting to `'unknown'`: an adapter that declares nothing is in
201
+ * exactly the state `'unknown'` names, and treating it that way — pass the
202
+ * counts through untouched, warn once when a cache read makes the ambiguity
203
+ * bite — is the honest reading of silence. Requiring it would also stop every
204
+ * external custom adapter compiling for a fact membrane can already say it
205
+ * does not know. Declare it: `'unknown'` is a real epistemic state, not a
206
+ * resting place.
207
+ */
208
+ usageCacheConvention?: UsageCacheConvention;
209
+
172
210
  /** Check if this adapter handles a model */
173
211
  supportsModel(modelId: string): boolean;
174
212
 
@@ -272,6 +310,23 @@ export interface ProviderResponse {
272
310
  outputTokens: number;
273
311
  cacheCreationTokens?: number;
274
312
  cacheReadTokens?: number;
313
+
314
+ /**
315
+ * Thinking/reasoning tokens reported separately from the visible-output
316
+ * count and already folded INTO `outputTokens`.
317
+ * See {@link DetailedUsage.thinkingTokens}.
318
+ */
319
+ thinkingTokens?: number;
320
+
321
+ /**
322
+ * Overrides {@link ProviderAdapter.usageCacheConvention} for THIS response.
323
+ * Needed where one adapter fronts several upstream conventions: OpenRouter
324
+ * reads `cache_read_input_tokens` (Anthropic, cache-excluded) OR
325
+ * `prompt_tokens_details.cached_tokens` (OpenAI, cache-inclusive) depending
326
+ * on which provider it routed to, so the convention is a per-response fact
327
+ * there rather than a per-adapter one.
328
+ */
329
+ cacheConvention?: UsageCacheConvention;
275
330
  };
276
331
 
277
332
  /** Model that actually ran */
@@ -159,6 +159,20 @@ export interface NormalizedRequest {
159
159
  */
160
160
  cacheTtl?: '5m' | '1h';
161
161
 
162
+ /**
163
+ * Float a trailing cache_control marker onto the newest message when the
164
+ * native tool loop rebuilds the request between tool-execution rounds, so
165
+ * the growing tool-round suffix caches incrementally (each round writes
166
+ * only its delta and cache-reads everything before it). Placed only from
167
+ * the request's *residual* breakpoint budget — the marker is withheld when
168
+ * upstream markers already occupy all 4 Anthropic cache_control slots —
169
+ * so upstream breakpoints are never displaced or stripped.
170
+ * Defaults to true (when promptCaching is enabled). Set false for context
171
+ * strategies whose request prefix churns between rounds, where a trailing
172
+ * marker would be pure cache-write cost.
173
+ */
174
+ floatingCacheMarker?: boolean;
175
+
162
176
  /**
163
177
  * Context prefix for simulacrum seeding.
164
178
  * Injected as first assistant message (before conversation history).
@@ -30,20 +30,77 @@ export interface BasicUsage {
30
30
  outputTokens: number;
31
31
  }
32
32
 
33
- export interface DetailedUsage extends BasicUsage {
33
+ /**
34
+ * What ONE priced unit of work cost — a turn, or the discarded attempts summed
35
+ * together. Held apart from `DetailedUsage` so the discarded-spend record can
36
+ * carry every token field without also inheriting `discardedAttempts`, which
37
+ * would let the type describe discarded spend nested inside discarded spend:
38
+ * a shape nothing produces and nothing could read sensibly.
39
+ */
40
+ export interface CallUsage extends BasicUsage {
34
41
  /** Tokens used for cache creation */
35
42
  cacheCreationTokens?: number;
36
-
43
+
37
44
  /** Tokens read from cache */
38
45
  cacheReadTokens?: number;
39
-
40
- /** Tokens used for thinking/reasoning */
46
+
47
+ /**
48
+ * Thinking/reasoning tokens the provider reported separately from its
49
+ * visible-output count, already INCLUDED in `outputTokens` (they are billed
50
+ * at the output rate). Surfaced so a caller can attribute spend to thinking;
51
+ * summing it with `outputTokens` would double-count. Gemini's
52
+ * `thoughtsTokenCount` is the current source.
53
+ */
41
54
  thinkingTokens?: number;
42
-
55
+
43
56
  /** Estimated cost breakdown */
44
57
  estimatedCost?: CostBreakdown;
45
58
  }
46
59
 
60
+ export interface DetailedUsage extends CallUsage {
61
+ /**
62
+ * Spend on provider calls whose output was thrown away — today, refusal
63
+ * retries. Those attempts were completed, billed HTTP calls; the response
64
+ * describes only the attempt that STANDS, so without this the real cost of
65
+ * a turn is invisible. Absent when nothing was discarded.
66
+ *
67
+ * Reported on `details.usage` only: the top-level `usage` stays the
68
+ * surviving attempt's, so existing consumers keep their meaning.
69
+ */
70
+ discardedAttempts?: DiscardedAttemptsUsage;
71
+ }
72
+
73
+ export interface DiscardedAttemptsUsage extends CallUsage {
74
+ /** How many billed-but-abandoned provider calls are summed here. */
75
+ attempts: number;
76
+ }
77
+
78
+ type Assert<TCondition extends true> = TCondition;
79
+
80
+ /**
81
+ * Erased at build; checked by `tsc --noEmit`, which covers src/ and not the
82
+ * test suite — so this is where a type-level guarantee can actually fail the
83
+ * build. Re-widening the discarded record to `DetailedUsage` reintroduces
84
+ * discarded-spend-inside-discarded-spend and turns this line red.
85
+ */
86
+ type DiscardedSpendDoesNotNest = Assert<
87
+ 'discardedAttempts' extends keyof DiscardedAttemptsUsage ? false : true
88
+ >;
89
+
90
+ /**
91
+ * One provider round of a turn: the model that served it and what that round
92
+ * alone used and cost. `usage.estimatedCost` here is priced at THIS round's
93
+ * model, which is why the rounds can be summed into a turn total that a
94
+ * multi-model turn's bill actually matches.
95
+ */
96
+ export interface TurnRoundUsage {
97
+ /** Model the provider named as having served this round; the requested id when it named none. */
98
+ model: string;
99
+
100
+ /** This round's own tokens and its own cost. */
101
+ usage: DetailedUsage;
102
+ }
103
+
47
104
  export interface CostBreakdown {
48
105
  input: number;
49
106
  output: number;
@@ -51,6 +108,13 @@ export interface CostBreakdown {
51
108
  cacheRead?: number;
52
109
  total: number;
53
110
  currency: string;
111
+
112
+ /**
113
+ * ISO date the rates behind this breakdown were last verified against the
114
+ * provider's published prices, when the pricing source records one. Unset
115
+ * means the source vouches for no date, NOT that the numbers are current.
116
+ */
117
+ pricingAsOf?: string;
54
118
  }
55
119
 
56
120
  // ============================================================================
@@ -65,6 +129,16 @@ export interface StopInfo {
65
129
 
66
130
  /** Whether output was truncated */
67
131
  wasTruncated: boolean;
132
+
133
+ /**
134
+ * XML tool mode: the turn ended with a tool block still open — a
135
+ * `<function_calls>` opener with no closer, or text cut mid-tag. The loop
136
+ * does not resume on a length stop, so this is the shape a max_tokens
137
+ * truncation leaves behind. A consumer persisting the turn must not write it
138
+ * back bare: on the next round the stale opener would be read as part of that
139
+ * round's block.
140
+ */
141
+ unclosedToolBlock?: boolean;
68
142
  }
69
143
 
70
144
  // ============================================================================
@@ -75,11 +149,26 @@ export interface ModelInfo {
75
149
  /** Model ID that was requested */
76
150
  requested: string;
77
151
 
78
- /** Model ID that actually ran (may differ due to routing/fallback) */
152
+ /** Model ID that actually ran (may differ due to routing/fallback). On a
153
+ * multi-round turn this is the model that served the LAST round; see
154
+ * {@link perRound} for the whole roster. */
79
155
  actual: string;
80
156
 
81
157
  /** Provider that served the request */
82
158
  provider: string;
159
+
160
+ /**
161
+ * Every provider round of this turn in order, each naming the model that
162
+ * served it and what that round alone used and cost — the audit trail behind
163
+ * `usage.estimatedCost`, which is their sum. A routed turn can change models
164
+ * mid-turn (OpenRouter re-picks a provider per call), so `actual` alone
165
+ * cannot say what was billed at which rate.
166
+ *
167
+ * Set on the streaming/tool-loop paths, which are the ones that sum. Unset
168
+ * on `complete()`, which makes exactly one call: `actual` is the whole story
169
+ * there.
170
+ */
171
+ perRound?: TurnRoundUsage[];
83
172
  }
84
173
 
85
174
  // ============================================================================
@@ -96,9 +185,21 @@ export interface TimingInfo {
96
185
  /** Tokens per second (streaming only) */
97
186
  tokensPerSecond?: number;
98
187
 
99
- /** Number of retry attempts */
188
+ /**
189
+ * Provider calls this turn actually cost: retries plus, on the streaming
190
+ * paths, every continuation round and refusal re-issue. A stitched
191
+ * multi-call turn used to report 1 here, indistinguishable in durable
192
+ * logs from a single-shot one.
193
+ */
100
194
  attempts: number;
101
-
195
+
196
+ /**
197
+ * Continuation rounds that made up the turn — tool rounds and automatic
198
+ * resumptions. 1 for a single-round turn; lower than `attempts` whenever a
199
+ * round was re-issued. Streaming paths only.
200
+ */
201
+ rounds?: number;
202
+
102
203
  /** Delay between retries */
103
204
  retryDelaysMs?: number[];
104
205
  }
@@ -194,9 +194,15 @@ export interface StreamOptions {
194
194
  /** Abort signal for cancellation */
195
195
  signal?: AbortSignal;
196
196
 
197
- /** Request timeout */
197
+ /** Request timeout (per provider call, not per turn) */
198
198
  timeoutMs?: number;
199
-
199
+
200
+ /**
201
+ * Abort a call whose stream goes silent for this many ms. Parity with
202
+ * YieldingStreamOptions.idleTimeoutMs; the adapter owns the default.
203
+ */
204
+ idleTimeoutMs?: number;
205
+
200
206
  /** Request ID for correlation */
201
207
  requestId?: string;
202
208