dsh-plugin-subscription-usage 0.0.0-stage → 0.2.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 (169) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +50 -2
  3. package/cordis.patch.yml +5 -0
  4. package/icon.svg +9 -0
  5. package/lib/auth/claude-code-creds.d.ts +23 -0
  6. package/lib/auth/claude-code-creds.js +172 -0
  7. package/lib/auth/device-flow.d.ts +55 -0
  8. package/lib/auth/device-flow.js +177 -0
  9. package/lib/auth/jwt.d.ts +10 -0
  10. package/lib/auth/jwt.js +25 -0
  11. package/lib/auth/oauth-flow.d.ts +91 -0
  12. package/lib/auth/oauth-flow.js +227 -0
  13. package/lib/auth/pkce.d.ts +31 -0
  14. package/lib/auth/pkce.js +35 -0
  15. package/lib/auth/rpc.d.ts +233 -0
  16. package/lib/auth/rpc.js +501 -0
  17. package/lib/auth/store.d.ts +198 -0
  18. package/lib/auth/store.js +473 -0
  19. package/lib/client/Footer.d.ts +3 -0
  20. package/lib/client/Footer.js +73 -0
  21. package/lib/client/Gauge.d.ts +28 -0
  22. package/lib/client/Gauge.js +139 -0
  23. package/lib/client/ImageGallery.d.ts +54 -0
  24. package/lib/client/ImageGallery.js +112 -0
  25. package/lib/client/ImageGenerateToolview.d.ts +48 -0
  26. package/lib/client/ImageGenerateToolview.js +130 -0
  27. package/lib/client/Notice.d.ts +15 -0
  28. package/lib/client/Notice.js +93 -0
  29. package/lib/client/ProviderCard.d.ts +35 -0
  30. package/lib/client/ProviderCard.js +240 -0
  31. package/lib/client/SpeedSelect.d.ts +72 -0
  32. package/lib/client/SpeedSelect.js +214 -0
  33. package/lib/client/Usage.d.ts +7 -0
  34. package/lib/client/Usage.js +83 -0
  35. package/lib/client/VideoGenerateToolview.d.ts +42 -0
  36. package/lib/client/VideoGenerateToolview.js +172 -0
  37. package/lib/client/context.d.ts +16 -0
  38. package/lib/client/context.js +11 -0
  39. package/lib/client/fast-command.d.ts +2 -0
  40. package/lib/client/fast-command.js +4 -0
  41. package/lib/client/hooks.d.ts +2 -0
  42. package/lib/client/hooks.js +10 -0
  43. package/lib/client/index.d.ts +3 -0
  44. package/lib/client/index.js +113 -0
  45. package/lib/client/locales.d.ts +139 -0
  46. package/lib/client/locales.js +267 -0
  47. package/lib/client/meter.d.ts +91 -0
  48. package/lib/client/meter.js +196 -0
  49. package/lib/client/prefs.d.ts +51 -0
  50. package/lib/client/prefs.js +71 -0
  51. package/lib/client/rpc.d.ts +161 -0
  52. package/lib/client/rpc.js +31 -0
  53. package/lib/client/store.d.ts +67 -0
  54. package/lib/client/store.js +129 -0
  55. package/lib/client/styles.d.ts +7 -0
  56. package/lib/client/styles.js +176 -0
  57. package/lib/client.js +3933 -0
  58. package/lib/client.js.map +1 -0
  59. package/lib/compat.d.ts +55 -0
  60. package/lib/compat.js +20 -0
  61. package/lib/http.d.ts +134 -0
  62. package/lib/http.js +431 -0
  63. package/lib/index.d.ts +121 -0
  64. package/lib/index.js +13399 -0
  65. package/lib/json-text.d.ts +2 -0
  66. package/lib/json-text.js +4 -0
  67. package/lib/model-defaults.d.ts +23 -0
  68. package/lib/model-defaults.js +238 -0
  69. package/lib/model-speeds.d.ts +10 -0
  70. package/lib/model-speeds.js +48 -0
  71. package/lib/models-directory.d.ts +17 -0
  72. package/lib/models-directory.js +25 -0
  73. package/lib/provider-settings.d.ts +55 -0
  74. package/lib/provider-settings.js +215 -0
  75. package/lib/providers/account-preferences.d.ts +46 -0
  76. package/lib/providers/account-preferences.js +188 -0
  77. package/lib/providers/accounts.d.ts +113 -0
  78. package/lib/providers/accounts.js +142 -0
  79. package/lib/providers/antigravity-oauth-client.d.ts +7 -0
  80. package/lib/providers/antigravity-oauth-client.js +7 -0
  81. package/lib/providers/antigravity.d.ts +119 -0
  82. package/lib/providers/antigravity.js +552 -0
  83. package/lib/providers/catalog-refresh.d.ts +4 -0
  84. package/lib/providers/catalog-refresh.js +30 -0
  85. package/lib/providers/catalog-store.d.ts +47 -0
  86. package/lib/providers/catalog-store.js +283 -0
  87. package/lib/providers/claude-cli-version.d.ts +15 -0
  88. package/lib/providers/claude-cli-version.js +24 -0
  89. package/lib/providers/claude.d.ts +212 -0
  90. package/lib/providers/claude.js +726 -0
  91. package/lib/providers/codex-client-version.d.ts +8 -0
  92. package/lib/providers/codex-client-version.js +17 -0
  93. package/lib/providers/codex-search.d.ts +29 -0
  94. package/lib/providers/codex-search.js +198 -0
  95. package/lib/providers/codex.d.ts +253 -0
  96. package/lib/providers/codex.js +930 -0
  97. package/lib/providers/common.d.ts +415 -0
  98. package/lib/providers/common.js +679 -0
  99. package/lib/providers/copilot.d.ts +365 -0
  100. package/lib/providers/copilot.js +956 -0
  101. package/lib/providers/grok.d.ts +214 -0
  102. package/lib/providers/grok.js +750 -0
  103. package/lib/providers/image-pool.d.ts +30 -0
  104. package/lib/providers/image-pool.js +89 -0
  105. package/lib/providers/npm-cli-version.d.ts +56 -0
  106. package/lib/providers/npm-cli-version.js +98 -0
  107. package/lib/providers/pool-family.d.ts +56 -0
  108. package/lib/providers/pool-family.js +45 -0
  109. package/lib/providers/pool-health.d.ts +91 -0
  110. package/lib/providers/pool-health.js +164 -0
  111. package/lib/providers/pool-usage.d.ts +106 -0
  112. package/lib/providers/pool-usage.js +302 -0
  113. package/lib/providers/pool.d.ts +115 -0
  114. package/lib/providers/pool.js +461 -0
  115. package/lib/providers/rate-limit.d.ts +192 -0
  116. package/lib/providers/rate-limit.js +338 -0
  117. package/lib/providers/replay.d.ts +4 -0
  118. package/lib/providers/replay.js +37 -0
  119. package/lib/providers/reset-redemption.d.ts +20 -0
  120. package/lib/providers/reset-redemption.js +70 -0
  121. package/lib/tools/image-generate.d.ts +128 -0
  122. package/lib/tools/image-generate.js +438 -0
  123. package/lib/tools/registration.d.ts +16 -0
  124. package/lib/tools/registration.js +25 -0
  125. package/lib/tools/usage.d.ts +62 -0
  126. package/lib/tools/usage.js +89 -0
  127. package/lib/tools/video-generate.d.ts +89 -0
  128. package/lib/tools/video-generate.js +257 -0
  129. package/lib/tools/x-search.d.ts +58 -0
  130. package/lib/tools/x-search.js +197 -0
  131. package/lib/translate/anthropic.d.ts +180 -0
  132. package/lib/translate/anthropic.js +534 -0
  133. package/lib/translate/antigravity-images.d.ts +11 -0
  134. package/lib/translate/antigravity-images.js +62 -0
  135. package/lib/translate/antigravity-schema.d.ts +4 -0
  136. package/lib/translate/antigravity-schema.js +145 -0
  137. package/lib/translate/antigravity-thinking.d.ts +5 -0
  138. package/lib/translate/antigravity-thinking.js +44 -0
  139. package/lib/translate/antigravity.d.ts +120 -0
  140. package/lib/translate/antigravity.js +412 -0
  141. package/lib/translate/chat-completions.d.ts +120 -0
  142. package/lib/translate/chat-completions.js +382 -0
  143. package/lib/translate/resolved.d.ts +98 -0
  144. package/lib/translate/resolved.js +186 -0
  145. package/lib/translate/responses.d.ts +192 -0
  146. package/lib/translate/responses.js +443 -0
  147. package/lib/translate/sse.d.ts +21 -0
  148. package/lib/translate/sse.js +62 -0
  149. package/lib/usage/headers.d.ts +21 -0
  150. package/lib/usage/headers.js +64 -0
  151. package/lib/usage/pace.d.ts +53 -0
  152. package/lib/usage/pace.js +48 -0
  153. package/lib/usage/service.d.ts +64 -0
  154. package/lib/usage/service.js +104 -0
  155. package/lib/usage-providers/common.d.ts +23 -0
  156. package/lib/usage-providers/common.js +28 -0
  157. package/lib/usage-providers/index.d.ts +42 -0
  158. package/lib/usage-providers/index.js +71 -0
  159. package/lib/usage-providers/keys.d.ts +11 -0
  160. package/lib/usage-providers/keys.js +52 -0
  161. package/lib/usage-providers/kimi.d.ts +4 -0
  162. package/lib/usage-providers/kimi.js +52 -0
  163. package/lib/usage-providers/ollama-cloud.d.ts +4 -0
  164. package/lib/usage-providers/ollama-cloud.js +42 -0
  165. package/lib/usage-providers/zai.d.ts +5 -0
  166. package/lib/usage-providers/zai.js +53 -0
  167. package/locale/en.json +6 -0
  168. package/locale/zh.json +6 -0
  169. package/package.json +115 -4
@@ -0,0 +1,534 @@
1
+ /**
2
+ * Translate between the harness message vocabulary and the Anthropic Messages
3
+ * API wire format used by the claude provider: request message assembly, tool
4
+ * schema mapping, and a push-model SSE-event → StreamChunk state machine
5
+ * ({@link AnthropicStreamTranslator}) so tests need no streams.
6
+ */
7
+ import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, LlmError, } from '@deepseek-ai/dsh-llm';
8
+ import { ToolCallId } from '../compat.js';
9
+ import { parseSse } from './sse.js';
10
+ /**
11
+ * The Claude Code identity block. The subscription endpoint rejects requests
12
+ * that do not present as Claude Code, so this block is REQUIRED as the first
13
+ * system entry on every request.
14
+ */
15
+ export const CLAUDE_CODE_IDENTITY = 'You are Claude Code, Anthropic\'s official CLI for Claude.';
16
+ /** Tags wrapping a mid-conversation system message where it sits in the history. */
17
+ export const SYSTEM_REMINDER_OPEN = '<system-reminder>';
18
+ export const SYSTEM_REMINDER_CLOSE = '</system-reminder>';
19
+ /**
20
+ * How far apart consecutive message breakpoints sit, in content blocks.
21
+ *
22
+ * A breakpoint looks back at most 20 blocks for an entry an earlier request
23
+ * wrote, so marks must stay closer than that: one agentic turn can append a
24
+ * dozen tool_use/tool_result blocks at once, and a single trailing mark would
25
+ * silently fall out of range and rebuild the whole prefix.
26
+ */
27
+ export const CACHE_BLOCK_STRIDE = 15;
28
+ /**
29
+ * Message breakpoints per request. Anthropic allows four in total and the
30
+ * last `system` block takes the fourth, so three are left for the history —
31
+ * enough to tolerate a turn appending roughly {@link CACHE_BLOCK_STRIDE} × 3
32
+ * blocks before a read is lost.
33
+ */
34
+ export const MESSAGE_CACHE_BREAKPOINTS = 3;
35
+ /** The API default; a marker at this TTL carries no `ttl` field at all. */
36
+ export const DEFAULT_PROMPT_CACHE_TTL = '5m';
37
+ /**
38
+ * Build one `cache_control` marker.
39
+ *
40
+ * Five minutes is spelled by omitting `ttl`, so a request that does not opt in
41
+ * stays byte-identical to what earlier releases sent.
42
+ * @param ttl - cache lifetime; defaults to five minutes.
43
+ * @returns a fresh marker object (callers attach it to one block each).
44
+ */
45
+ export function cacheControl(ttl = DEFAULT_PROMPT_CACHE_TTL) {
46
+ return ttl === '1h' ? { type: 'ephemeral', ttl: '1h' } : { type: 'ephemeral' };
47
+ }
48
+ /** Preserve native image blocks, retaining the existing text-only wire shape. */
49
+ function toolResultContent(block) {
50
+ if (!block.content.some(part => part.type === 'image' && 'dataBase64' in part)) {
51
+ return block.content.map(part => (part.type === 'text' ? part.text : '')).join('');
52
+ }
53
+ const content = [];
54
+ for (const part of block.content) {
55
+ if (part.type === 'text' && part.text.length > 0)
56
+ content.push({ type: 'text', text: part.text });
57
+ if (part.type === 'image' && 'dataBase64' in part) {
58
+ content.push({ type: 'image', source: { type: 'base64', media_type: part.mediaType, data: part.dataBase64 } });
59
+ }
60
+ }
61
+ return content;
62
+ }
63
+ /** Parse a tool call's raw JSON arguments into Anthropic's object-shaped `input`. */
64
+ function parseToolInput(raw) {
65
+ try {
66
+ const parsed = JSON.parse(raw);
67
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
68
+ return parsed;
69
+ }
70
+ return {};
71
+ }
72
+ catch {
73
+ // The model produced malformed JSON; an empty object keeps the request valid.
74
+ return {};
75
+ }
76
+ }
77
+ /**
78
+ * Move a user message's `tool_result` blocks into one contiguous run at the
79
+ * front, preserving the relative order of both groups.
80
+ *
81
+ * Anthropic answers every `tool_use` against the blocks that *lead* the next
82
+ * message, so a block of any other kind before or between the results reads
83
+ * as a call left unanswered and the request is rejected. The harness merges
84
+ * everything queued for one user turn into a single message, and a parallel
85
+ * tool batch arrives as one result message per call, so any context spliced
86
+ * mid-batch lands between two results. Restoring the run here keeps that
87
+ * independent of delivery order. Order *among* the results does not matter.
88
+ * @param message - one assembled user message, reordered in place.
89
+ */
90
+ function leadWithToolResults(message) {
91
+ const firstOther = message.content.findIndex(block => block.type !== 'tool_result');
92
+ if (firstOther === -1)
93
+ return;
94
+ if (!message.content.slice(firstOther).some(block => block.type === 'tool_result'))
95
+ return;
96
+ message.content = [
97
+ ...message.content.filter(block => block.type === 'tool_result'),
98
+ ...message.content.filter(block => block.type !== 'tool_result'),
99
+ ];
100
+ }
101
+ /**
102
+ * Index of the first non-system message; `messages.length` when every message
103
+ * is a system one.
104
+ *
105
+ * A system message before the conversation starts is the operator's opening
106
+ * instruction and belongs in the `system` slot. One that arrives later is
107
+ * mid-conversation context, and hoisting it into `system` would move bytes in
108
+ * front of the whole history — invalidating every cached turn behind it — so
109
+ * it stays where it is, as a reminder block in `messages`.
110
+ * @param messages - ordered conversation messages.
111
+ * @returns the boundary index separating the two.
112
+ */
113
+ function conversationStart(messages) {
114
+ const index = messages.findIndex(message => message.role !== 'system');
115
+ return index === -1 ? messages.length : index;
116
+ }
117
+ /**
118
+ * Convert harness messages into Anthropic messages. Consecutive same-role
119
+ * messages merge into one message with multiple content blocks; tool results
120
+ * arrive as user messages with `tool_result` blocks, which a merged user
121
+ * message keeps in one leading run ({@link leadWithToolResults}); system-role
122
+ * messages before the conversation starts are handled by
123
+ * {@link toAnthropicSystem} and skipped here, while a later one rides in
124
+ * place as a user-role `<system-reminder>` block.
125
+ * Reasoning blocks are not replayed (v1). Images must arrive pre-resolved
126
+ * ({@link TranslatableMessage}); an unresolved ImageBlock is skipped because
127
+ * its bytes are unreachable here.
128
+ * @param messages - ordered conversation messages with resolved images.
129
+ * @returns Anthropic messages in conversation order.
130
+ */
131
+ export function toAnthropicMessages(messages) {
132
+ const out = [];
133
+ const start = conversationStart(messages);
134
+ for (const [index, message] of messages.entries()) {
135
+ // A leading system message is an opening instruction; toAnthropicSystem
136
+ // owns those. A later one rides here so the cached prefix ahead of it
137
+ // stays byte-identical.
138
+ if (message.role === 'system' && index < start)
139
+ continue;
140
+ const role = message.role === 'assistant' ? 'assistant' : 'user';
141
+ const blocks = [];
142
+ if (message.role === 'tool') {
143
+ const id = message.toolCallId ?? message.tool_call_id
144
+ ?? (message.source?.kind === 'tool' ? String(message.source.callId) : undefined);
145
+ if (id === undefined)
146
+ throw new LlmError('tool result has no call id', 'INVALID_REQUEST');
147
+ blocks.push({
148
+ type: 'tool_result',
149
+ tool_use_id: id,
150
+ content: toolResultContent({ type: 'tool-result', toolCallId: ToolCallId(id), content: message.content }),
151
+ ...message.isError === true ? { is_error: true } : {},
152
+ });
153
+ const last = out[out.length - 1];
154
+ if (last?.role === 'user')
155
+ last.content.push(...blocks);
156
+ else
157
+ out.push({ role: 'user', content: blocks });
158
+ continue;
159
+ }
160
+ for (const block of message.content) {
161
+ switch (block.type) {
162
+ case 'text':
163
+ blocks.push({
164
+ type: 'text',
165
+ text: message.role === 'system'
166
+ ? `${SYSTEM_REMINDER_OPEN}${block.text}${SYSTEM_REMINDER_CLOSE}`
167
+ : block.text,
168
+ });
169
+ break;
170
+ case 'tool-call':
171
+ // Anthropic accepts `tool_use` only in assistant messages, and only
172
+ // when a matching `tool_result` follows. A tool call in any other
173
+ // role is replayed narrative — a settled subagent's closing message
174
+ // spliced into the parent as a user-role notice carries the calls it
175
+ // died holding, which no result will ever answer — so it rides as
176
+ // descriptive text instead of a call the API would reject.
177
+ blocks.push(role === 'assistant'
178
+ ? {
179
+ type: 'tool_use',
180
+ id: String(block.id),
181
+ name: block.name,
182
+ input: parseToolInput(block.arguments),
183
+ }
184
+ : { type: 'text', text: `[tool call ${block.name}: ${block.arguments}]` });
185
+ break;
186
+ case 'tool-result':
187
+ blocks.push({
188
+ type: 'tool_result',
189
+ tool_use_id: String(block.toolCallId),
190
+ content: toolResultContent(block),
191
+ ...block.isError === true ? { is_error: true } : {},
192
+ });
193
+ break;
194
+ case 'image':
195
+ if ('dataBase64' in block) {
196
+ blocks.push({
197
+ type: 'image',
198
+ source: { type: 'base64', media_type: block.mediaType, data: block.dataBase64 },
199
+ });
200
+ }
201
+ // An unresolved ImageBlock carries only an attachment reference; the
202
+ // adapter resolves images before translation, so this is skipped.
203
+ break;
204
+ default:
205
+ // reasoning (not replayed), unknown blocks.
206
+ break;
207
+ }
208
+ }
209
+ if (blocks.length === 0)
210
+ continue;
211
+ const last = out[out.length - 1];
212
+ if (last !== undefined && last.role === role)
213
+ last.content.push(...blocks);
214
+ else
215
+ out.push({ role, content: blocks });
216
+ }
217
+ for (const message of out) {
218
+ if (message.role === 'user')
219
+ leadWithToolResults(message);
220
+ }
221
+ return out;
222
+ }
223
+ /**
224
+ * Mark the conversation's cache breakpoints in place: the last content block,
225
+ * then one every {@link CACHE_BLOCK_STRIDE} blocks backwards, {@link
226
+ * MESSAGE_CACHE_BREAKPOINTS} in total.
227
+ *
228
+ * The history is append-only, so the block one request marks last is
229
+ * byte-identical in the next — that entry is what the next request reads.
230
+ * Marks are counted across the flattened block sequence, not per message,
231
+ * because the lookback window Anthropic walks counts blocks the same way.
232
+ * @param messages - assembled Anthropic messages, marked in place.
233
+ * @param ttl - cache lifetime for every mark; defaults to five minutes.
234
+ */
235
+ export function markMessageCache(messages, ttl) {
236
+ const blocks = messages.flatMap(message => message.content);
237
+ for (let mark = 0; mark < MESSAGE_CACHE_BREAKPOINTS; mark++) {
238
+ const at = blocks.length - 1 - mark * CACHE_BLOCK_STRIDE;
239
+ if (at < 0)
240
+ return;
241
+ blocks[at].cache_control = cacheControl(ttl);
242
+ }
243
+ }
244
+ /**
245
+ * Build the Anthropic `system` array: the mandatory Claude Code identity
246
+ * block, then the explicit system prompt, then any system-role messages.
247
+ * @param system - explicit system prompt, when set.
248
+ * @param messages - conversation messages; the system-role text preceding the
249
+ * conversation is appended, and a later one is left to {@link toAnthropicMessages}.
250
+ * @param ttl - cache lifetime for the tools+system mark; defaults to five minutes.
251
+ * @returns the system content blocks.
252
+ */
253
+ export function toAnthropicSystem(system, messages, ttl) {
254
+ const blocks = [{ type: 'text', text: CLAUDE_CODE_IDENTITY }];
255
+ if (system !== undefined && system.length > 0)
256
+ blocks.push({ type: 'text', text: system });
257
+ const history = messages ?? [];
258
+ for (const message of history.slice(0, conversationStart(history))) {
259
+ for (const block of message.content) {
260
+ if (block.type === 'text')
261
+ blocks.push({ type: 'text', text: block.text });
262
+ }
263
+ }
264
+ // `tools` renders ahead of `system`, so this one marker caches both. It is
265
+ // deliberately separate from the message marks: a tool_choice or thinking
266
+ // change invalidates the messages tier only, and this entry survives it.
267
+ //
268
+ // Anthropic processes `tools`, `system`, then `messages`, and rejects a
269
+ // one-hour mark that follows a five-minute one (HTTP 400). This mark and the
270
+ // message marks therefore share one TTL, which can never hit that ordering.
271
+ blocks[blocks.length - 1].cache_control = cacheControl(ttl);
272
+ return blocks;
273
+ }
274
+ /**
275
+ * Map harness tool schemas to Anthropic tools, in name order.
276
+ *
277
+ * `tools` renders at position 0 of the cached prefix, so any reordering
278
+ * invalidates every cache entry behind it — `system` and the whole
279
+ * conversation included. Registration order belongs to the caller and plugin
280
+ * load order can differ between processes, so the wire order is fixed here
281
+ * instead. Anthropic selects a tool by name; the array order carries nothing.
282
+ * @param tools - tool schemas from the request.
283
+ * @returns Anthropic `tools` array entries, ordered by tool name.
284
+ */
285
+ export function toAnthropicTools(tools) {
286
+ return [...tools]
287
+ .sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0))
288
+ .map(tool => ({
289
+ name: tool.name,
290
+ description: tool.description,
291
+ input_schema: tool.parameters,
292
+ }));
293
+ }
294
+ /** Assemble the final ContentBlock for one open block. */
295
+ function closeBlock(block) {
296
+ switch (block.kind) {
297
+ case 'text':
298
+ return { type: 'text', text: block.text };
299
+ case 'reasoning':
300
+ return { type: 'reasoning', text: block.text };
301
+ case 'tool-call':
302
+ return {
303
+ type: 'tool-call',
304
+ id: ToolCallId(block.callId),
305
+ name: block.name ?? '',
306
+ arguments: block.text,
307
+ };
308
+ }
309
+ }
310
+ /**
311
+ * Classify an Anthropic `error` event into a thrown LlmError.
312
+ * @param error - the wire error object.
313
+ * @returns the mapped error.
314
+ */
315
+ export function anthropicFailure(error) {
316
+ const type = error?.type ?? 'unknown_error';
317
+ const message = error?.message ?? `Anthropic reported ${type}`;
318
+ if (type === 'invalid_request_error' && /prompt is too long/i.test(message)) {
319
+ return new LlmError(message, CONTEXT_WINDOW_EXCEEDED_CODE);
320
+ }
321
+ if (type === 'rate_limit_error')
322
+ return new LlmError(message, 'RATE_LIMIT');
323
+ if (type === 'authentication_error')
324
+ return new LlmError(message, 'AUTH');
325
+ return new LlmError(message, 'SERVER');
326
+ }
327
+ /**
328
+ * Push-model Anthropic SSE translator: feed each parsed event object to
329
+ * {@link push} and collect the emitted harness StreamChunks. Block indexes
330
+ * are allocated in first-seen order; `usage` is emitted before the terminal
331
+ * `finish`, and nothing is emitted after it. `error` events throw
332
+ * {@link LlmError}.
333
+ */
334
+ export class AnthropicStreamTranslator {
335
+ blocks = new Map();
336
+ nextIndex = 0;
337
+ sawAnyBlock = false;
338
+ pendingUsage;
339
+ outputTokens;
340
+ stopReason = 'stop';
341
+ usageEmitted = false;
342
+ /** Set once `message_stop` produced the terminal finish chunk. */
343
+ terminated = false;
344
+ open(wireIndex, kind, chunks, callId = '', name) {
345
+ const block = {
346
+ index: this.nextIndex++,
347
+ kind,
348
+ text: '',
349
+ callId,
350
+ ...name === undefined ? {} : { name },
351
+ };
352
+ this.blocks.set(wireIndex, block);
353
+ this.sawAnyBlock = true;
354
+ chunks.push({ type: 'block-start', index: block.index, blockType: kind });
355
+ return block;
356
+ }
357
+ emitUsage(chunks) {
358
+ if (this.usageEmitted)
359
+ return;
360
+ this.usageEmitted = true;
361
+ const usage = {
362
+ inputTokens: this.pendingUsage?.inputTokens ?? 0,
363
+ outputTokens: this.outputTokens ?? 0,
364
+ ...this.pendingUsage?.cacheReadTokens !== undefined
365
+ ? { cacheReadTokens: this.pendingUsage.cacheReadTokens }
366
+ : {},
367
+ ...this.pendingUsage?.cacheWriteTokens !== undefined
368
+ ? { cacheWriteTokens: this.pendingUsage.cacheWriteTokens }
369
+ : {},
370
+ };
371
+ chunks.push({ type: 'usage', usage });
372
+ }
373
+ /**
374
+ * Process one parsed Anthropic SSE event.
375
+ * @param event - the parsed event object.
376
+ * @returns the StreamChunks this event produced (possibly none).
377
+ */
378
+ push(event) {
379
+ if (this.terminated)
380
+ return [];
381
+ const chunks = [];
382
+ switch (event.type) {
383
+ case 'message_start': {
384
+ const usage = event.message?.usage;
385
+ if (usage !== undefined) {
386
+ this.pendingUsage = {
387
+ inputTokens: usage.input_tokens ?? 0,
388
+ ...usage.cache_read_input_tokens !== undefined
389
+ ? { cacheReadTokens: usage.cache_read_input_tokens }
390
+ : {},
391
+ ...usage.cache_creation_input_tokens !== undefined
392
+ ? { cacheWriteTokens: usage.cache_creation_input_tokens }
393
+ : {},
394
+ };
395
+ this.outputTokens = usage.output_tokens ?? this.outputTokens;
396
+ }
397
+ return chunks;
398
+ }
399
+ case 'content_block_start': {
400
+ const wireIndex = event.index ?? 0;
401
+ const block = event.content_block;
402
+ switch (block?.type) {
403
+ case 'text':
404
+ this.open(wireIndex, 'text', chunks);
405
+ break;
406
+ case 'thinking':
407
+ this.open(wireIndex, 'reasoning', chunks);
408
+ break;
409
+ case 'tool_use': {
410
+ const opened = this.open(wireIndex, 'tool-call', chunks, block.id ?? '', block.name);
411
+ chunks.push({
412
+ type: 'tool-call-delta',
413
+ index: opened.index,
414
+ id: ToolCallId(opened.callId),
415
+ ...block.name === undefined ? {} : { name: block.name },
416
+ argumentsDelta: '',
417
+ });
418
+ break;
419
+ }
420
+ default:
421
+ break;
422
+ }
423
+ return chunks;
424
+ }
425
+ case 'content_block_delta': {
426
+ const wireIndex = event.index ?? 0;
427
+ const block = this.blocks.get(wireIndex);
428
+ const delta = event.delta;
429
+ if (block === undefined || delta === undefined)
430
+ return chunks;
431
+ switch (delta.type) {
432
+ case 'text_delta':
433
+ block.text += delta.text ?? '';
434
+ chunks.push({ type: 'text-delta', index: block.index, text: delta.text ?? '' });
435
+ break;
436
+ case 'thinking_delta':
437
+ block.text += delta.thinking ?? '';
438
+ chunks.push({ type: 'reasoning-delta', index: block.index, text: delta.thinking ?? '' });
439
+ break;
440
+ case 'input_json_delta':
441
+ block.text += delta.partial_json ?? '';
442
+ chunks.push({
443
+ type: 'tool-call-delta',
444
+ index: block.index,
445
+ id: ToolCallId(block.callId),
446
+ ...block.name === undefined ? {} : { name: block.name },
447
+ argumentsDelta: delta.partial_json ?? '',
448
+ });
449
+ break;
450
+ default:
451
+ // signature_delta and future deltas carry no harness content.
452
+ break;
453
+ }
454
+ return chunks;
455
+ }
456
+ case 'content_block_stop': {
457
+ const wireIndex = event.index ?? 0;
458
+ const block = this.blocks.get(wireIndex);
459
+ if (block === undefined)
460
+ return chunks;
461
+ this.blocks.delete(wireIndex);
462
+ chunks.push({ type: 'block-end', index: block.index, block: closeBlock(block) });
463
+ return chunks;
464
+ }
465
+ case 'message_delta': {
466
+ if (event.usage?.output_tokens !== undefined)
467
+ this.outputTokens = event.usage.output_tokens;
468
+ switch (event.delta?.stop_reason) {
469
+ case 'end_turn':
470
+ case 'stop_sequence':
471
+ this.stopReason = 'stop';
472
+ break;
473
+ case 'tool_use':
474
+ this.stopReason = 'tool-calls';
475
+ break;
476
+ case 'max_tokens':
477
+ this.stopReason = 'max-tokens';
478
+ break;
479
+ default:
480
+ break;
481
+ }
482
+ return chunks;
483
+ }
484
+ case 'message_stop': {
485
+ this.terminated = true;
486
+ for (const [wireIndex, block] of [...this.blocks]) {
487
+ this.blocks.delete(wireIndex);
488
+ chunks.push({ type: 'block-end', index: block.index, block: closeBlock(block) });
489
+ }
490
+ this.emitUsage(chunks);
491
+ if (this.stopReason === 'stop' && !this.sawAnyBlock) {
492
+ chunks.push({
493
+ type: 'finish',
494
+ reason: {
495
+ kind: 'error',
496
+ failure: { message: 'model returned a completed response with no content', code: EMPTY_RESPONSE_CODE },
497
+ },
498
+ });
499
+ }
500
+ else {
501
+ chunks.push({ type: 'finish', reason: { kind: this.stopReason } });
502
+ }
503
+ return chunks;
504
+ }
505
+ case 'error':
506
+ throw anthropicFailure(event.error);
507
+ default:
508
+ // ping and future event types carry no harness content.
509
+ return chunks;
510
+ }
511
+ }
512
+ }
513
+ /**
514
+ * Consume an Anthropic SSE byte stream and yield harness StreamChunks.
515
+ * @param stream - raw response body.
516
+ * @param onActivity - transport-activity callback for the idle watchdog.
517
+ * @returns the chunk stream; throws when the stream ends before `message_stop`.
518
+ */
519
+ export async function* streamAnthropic(stream, onActivity) {
520
+ const translator = new AnthropicStreamTranslator();
521
+ for await (const sseEvent of parseSse(stream, onActivity)) {
522
+ let event;
523
+ try {
524
+ event = JSON.parse(sseEvent.data);
525
+ }
526
+ catch {
527
+ throw new LlmError(`malformed SSE payload: ${sseEvent.data.slice(0, 120)}`, 'MALFORMED_RESPONSE');
528
+ }
529
+ yield* translator.push(event);
530
+ if (translator.terminated)
531
+ return;
532
+ }
533
+ throw new LlmError('Anthropic SSE stream ended before message_stop', 'STREAM_CLOSED');
534
+ }
@@ -0,0 +1,11 @@
1
+ import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
2
+ import type { ImageBlock } from '@deepseek-ai/dsh-llm';
3
+ /** Response-scoped admission: persist bytes before publishing a host image block. */
4
+ export declare class AntigravityOutputImages {
5
+ private readonly attachments?;
6
+ private readonly signal?;
7
+ private count;
8
+ private bytes;
9
+ constructor(attachments?: AttachmentStore | undefined, signal?: AbortSignal | undefined);
10
+ save(value: unknown): Promise<ImageBlock>;
11
+ }
@@ -0,0 +1,62 @@
1
+ import { LlmError } from '@deepseek-ai/dsh-llm';
2
+ const MEDIA_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'image/gif'];
3
+ /** Response-scoped admission: persist bytes before publishing a host image block. */
4
+ export class AntigravityOutputImages {
5
+ attachments;
6
+ signal;
7
+ count = 0;
8
+ bytes = 0;
9
+ constructor(attachments, signal) {
10
+ this.attachments = attachments;
11
+ this.signal = signal;
12
+ }
13
+ async save(value) {
14
+ this.signal?.throwIfAborted();
15
+ if (value === null || typeof value !== 'object') {
16
+ throw new LlmError('Antigravity returned invalid inlineData', 'INVALID_IMAGE_RESPONSE');
17
+ }
18
+ const { mimeType, data } = value;
19
+ if (typeof mimeType !== 'string' || !MEDIA_TYPES.includes(mimeType)
20
+ || typeof data !== 'string' || data.length === 0) {
21
+ throw new LlmError('Antigravity returned an unsupported image MIME type or empty image', 'INVALID_IMAGE_RESPONSE');
22
+ }
23
+ const store = this.attachments;
24
+ if (store === undefined) {
25
+ throw new LlmError('Antigravity image output requires the DSH attachment store', 'UNSUPPORTED');
26
+ }
27
+ const limits = store.imageLimits;
28
+ const byteBudget = Math.min(limits.maxImageBytes, limits.maxMessageImageBytes - this.bytes);
29
+ if (this.count >= limits.maxImagesPerMessage || data.length > Math.ceil(byteBudget / 3) * 4) {
30
+ throw new LlmError('Antigravity image output exceeds host attachment limits', 'IMAGE_OUTPUT_LIMIT');
31
+ }
32
+ // Buffer's decoder is permissive; require canonical, padded base64 and reject
33
+ // data URLs, whitespace, invalid padding and truncated encodings explicitly.
34
+ if (data.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(data)) {
35
+ throw new LlmError('Antigravity returned invalid image base64', 'INVALID_IMAGE_RESPONSE');
36
+ }
37
+ const bytes = Buffer.from(data, 'base64');
38
+ if (bytes.toString('base64') !== data) {
39
+ throw new LlmError('Antigravity returned non-canonical image base64', 'INVALID_IMAGE_RESPONSE');
40
+ }
41
+ if (bytes.length > byteBudget) {
42
+ throw new LlmError('Antigravity image output exceeds host attachment limits', 'IMAGE_OUTPUT_LIMIT');
43
+ }
44
+ if (!limits.mediaTypes.includes(mimeType)) {
45
+ throw new LlmError('Antigravity image MIME type is disabled by the host', 'INVALID_IMAGE_RESPONSE');
46
+ }
47
+ // The store verifies MIME against decoded pixels, dimensions and integrity.
48
+ // Local admission/storage failures must not penalize a subscription account.
49
+ let attachment;
50
+ try {
51
+ attachment = await store.saveImage({ data: bytes, mediaType: mimeType });
52
+ }
53
+ catch {
54
+ this.signal?.throwIfAborted();
55
+ throw new LlmError('Could not validate or persist Antigravity output image', 'IMAGE_OUTPUT_STORAGE');
56
+ }
57
+ this.signal?.throwIfAborted();
58
+ this.count += 1;
59
+ this.bytes += bytes.length;
60
+ return { type: 'image', attachment };
61
+ }
62
+ }
@@ -0,0 +1,4 @@
1
+ type Schema = Record<string, unknown>;
2
+ /** Detach and normalize one tool's root object without changing the registry schema. */
3
+ export declare function antigravityToolParameters(parameters: Schema, legacy: boolean): Schema;
4
+ export {};