dsh-subscriptions 0.1.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 (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +129 -0
  3. package/cordis.patch.yml +12 -0
  4. package/lib/auth/claude-code-creds.d.ts +23 -0
  5. package/lib/auth/claude-code-creds.js +172 -0
  6. package/lib/auth/device-flow.d.ts +55 -0
  7. package/lib/auth/device-flow.js +177 -0
  8. package/lib/auth/jwt.d.ts +10 -0
  9. package/lib/auth/jwt.js +25 -0
  10. package/lib/auth/oauth-flow.d.ts +91 -0
  11. package/lib/auth/oauth-flow.js +227 -0
  12. package/lib/auth/pkce.d.ts +31 -0
  13. package/lib/auth/pkce.js +35 -0
  14. package/lib/auth/rpc.d.ts +209 -0
  15. package/lib/auth/rpc.js +386 -0
  16. package/lib/auth/store.d.ts +196 -0
  17. package/lib/auth/store.js +468 -0
  18. package/lib/client/CursorCard.d.ts +9 -0
  19. package/lib/client/CursorCard.js +113 -0
  20. package/lib/client/ExternalUsageCards.d.ts +9 -0
  21. package/lib/client/ExternalUsageCards.js +72 -0
  22. package/lib/client/ImageGallery.d.ts +54 -0
  23. package/lib/client/ImageGallery.js +112 -0
  24. package/lib/client/ImageGenerateToolview.d.ts +48 -0
  25. package/lib/client/ImageGenerateToolview.js +133 -0
  26. package/lib/client/ProviderAccountManager.d.ts +13 -0
  27. package/lib/client/ProviderAccountManager.js +147 -0
  28. package/lib/client/ProviderModelEditor.d.ts +39 -0
  29. package/lib/client/ProviderModelEditor.js +107 -0
  30. package/lib/client/SpeedSelect.d.ts +71 -0
  31. package/lib/client/SpeedSelect.js +177 -0
  32. package/lib/client/SubscriptionUsageBadge.d.ts +116 -0
  33. package/lib/client/SubscriptionUsageBadge.js +543 -0
  34. package/lib/client/SubscriptionsSection.d.ts +140 -0
  35. package/lib/client/SubscriptionsSection.js +544 -0
  36. package/lib/client/UsageBadgeDisplaySetting.d.ts +7 -0
  37. package/lib/client/UsageBadgeDisplaySetting.js +36 -0
  38. package/lib/client/VideoGenerateToolview.d.ts +42 -0
  39. package/lib/client/VideoGenerateToolview.js +175 -0
  40. package/lib/client/account-preferences.d.ts +24 -0
  41. package/lib/client/account-preferences.js +21 -0
  42. package/lib/client/fast-command.d.ts +2 -0
  43. package/lib/client/fast-command.js +4 -0
  44. package/lib/client/host-contract.d.ts +90 -0
  45. package/lib/client/host-contract.js +101 -0
  46. package/lib/client/host-icons.d.ts +24 -0
  47. package/lib/client/host-icons.js +21 -0
  48. package/lib/client/index.d.ts +27 -0
  49. package/lib/client/index.js +136 -0
  50. package/lib/client/locales.d.ts +297 -0
  51. package/lib/client/locales.js +295 -0
  52. package/lib/client/subscription-card-styles.d.ts +3 -0
  53. package/lib/client/subscription-card-styles.js +53 -0
  54. package/lib/client/subscriptions-rpc.d.ts +14 -0
  55. package/lib/client/subscriptions-rpc.js +34 -0
  56. package/lib/client/usage-badge-preferences.d.ts +11 -0
  57. package/lib/client/usage-badge-preferences.js +41 -0
  58. package/lib/client.js +4472 -0
  59. package/lib/client.js.map +1 -0
  60. package/lib/compat.d.ts +36 -0
  61. package/lib/compat.js +20 -0
  62. package/lib/http.d.ts +6 -0
  63. package/lib/http.js +15 -0
  64. package/lib/index.d.ts +80 -0
  65. package/lib/index.js +14018 -0
  66. package/lib/model-defaults.d.ts +23 -0
  67. package/lib/model-defaults.js +237 -0
  68. package/lib/provider-settings.d.ts +39 -0
  69. package/lib/provider-settings.js +151 -0
  70. package/lib/providers/account-preferences.d.ts +41 -0
  71. package/lib/providers/account-preferences.js +153 -0
  72. package/lib/providers/accounts.d.ts +112 -0
  73. package/lib/providers/accounts.js +142 -0
  74. package/lib/providers/antigravity-oauth-client.d.ts +7 -0
  75. package/lib/providers/antigravity-oauth-client.js +7 -0
  76. package/lib/providers/antigravity.d.ts +110 -0
  77. package/lib/providers/antigravity.js +516 -0
  78. package/lib/providers/catalog-store.d.ts +48 -0
  79. package/lib/providers/catalog-store.js +243 -0
  80. package/lib/providers/claude-cli-version.d.ts +15 -0
  81. package/lib/providers/claude-cli-version.js +24 -0
  82. package/lib/providers/claude.d.ts +181 -0
  83. package/lib/providers/claude.js +680 -0
  84. package/lib/providers/codex-client-version.d.ts +8 -0
  85. package/lib/providers/codex-client-version.js +17 -0
  86. package/lib/providers/codex-search.d.ts +29 -0
  87. package/lib/providers/codex-search.js +198 -0
  88. package/lib/providers/codex.d.ts +229 -0
  89. package/lib/providers/codex.js +821 -0
  90. package/lib/providers/common.d.ts +389 -0
  91. package/lib/providers/common.js +656 -0
  92. package/lib/providers/copilot.d.ts +344 -0
  93. package/lib/providers/copilot.js +888 -0
  94. package/lib/providers/cursor-adapter.d.ts +15 -0
  95. package/lib/providers/cursor-adapter.js +39 -0
  96. package/lib/providers/cursor-auth.d.ts +43 -0
  97. package/lib/providers/cursor-auth.js +169 -0
  98. package/lib/providers/cursor-model-settings.d.ts +15 -0
  99. package/lib/providers/cursor-model-settings.js +51 -0
  100. package/lib/providers/cursor-usage.d.ts +9 -0
  101. package/lib/providers/cursor-usage.js +112 -0
  102. package/lib/providers/external-usage-controller.d.ts +19 -0
  103. package/lib/providers/external-usage-controller.js +32 -0
  104. package/lib/providers/external-usage.d.ts +8 -0
  105. package/lib/providers/external-usage.js +82 -0
  106. package/lib/providers/grok.d.ts +205 -0
  107. package/lib/providers/grok.js +713 -0
  108. package/lib/providers/image-pool.d.ts +30 -0
  109. package/lib/providers/image-pool.js +89 -0
  110. package/lib/providers/npm-cli-version.d.ts +56 -0
  111. package/lib/providers/npm-cli-version.js +98 -0
  112. package/lib/providers/pool-family.d.ts +56 -0
  113. package/lib/providers/pool-family.js +45 -0
  114. package/lib/providers/pool-health.d.ts +74 -0
  115. package/lib/providers/pool-health.js +150 -0
  116. package/lib/providers/pool-usage.d.ts +86 -0
  117. package/lib/providers/pool-usage.js +223 -0
  118. package/lib/providers/pool.d.ts +108 -0
  119. package/lib/providers/pool.js +368 -0
  120. package/lib/providers/rate-limit.d.ts +192 -0
  121. package/lib/providers/rate-limit.js +338 -0
  122. package/lib/tools/image-generate.d.ts +128 -0
  123. package/lib/tools/image-generate.js +438 -0
  124. package/lib/tools/registration.d.ts +15 -0
  125. package/lib/tools/registration.js +24 -0
  126. package/lib/tools/video-generate.d.ts +89 -0
  127. package/lib/tools/video-generate.js +257 -0
  128. package/lib/tools/x-search.d.ts +58 -0
  129. package/lib/tools/x-search.js +197 -0
  130. package/lib/translate/anthropic.d.ts +161 -0
  131. package/lib/translate/anthropic.js +515 -0
  132. package/lib/translate/antigravity-schema.d.ts +4 -0
  133. package/lib/translate/antigravity-schema.js +145 -0
  134. package/lib/translate/antigravity-thinking.d.ts +5 -0
  135. package/lib/translate/antigravity-thinking.js +44 -0
  136. package/lib/translate/antigravity.d.ts +114 -0
  137. package/lib/translate/antigravity.js +366 -0
  138. package/lib/translate/chat-completions.d.ts +120 -0
  139. package/lib/translate/chat-completions.js +382 -0
  140. package/lib/translate/resolved.d.ts +80 -0
  141. package/lib/translate/resolved.js +118 -0
  142. package/lib/translate/responses.d.ts +179 -0
  143. package/lib/translate/responses.js +408 -0
  144. package/lib/translate/sse.d.ts +21 -0
  145. package/lib/translate/sse.js +56 -0
  146. package/package.json +114 -0
  147. package/vendor/cursor/LICENSE +21 -0
  148. package/vendor/cursor/index.d.ts +18 -0
  149. package/vendor/cursor/index.js +3574 -0
  150. package/vendor/cursor/proto.js +254 -0
@@ -0,0 +1,515 @@
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
+ /** Preserve native image blocks, retaining the existing text-only wire shape. */
36
+ function toolResultContent(block) {
37
+ if (!block.content.some(part => part.type === 'image' && 'dataBase64' in part)) {
38
+ return block.content.map(part => (part.type === 'text' ? part.text : '')).join('');
39
+ }
40
+ const content = [];
41
+ for (const part of block.content) {
42
+ if (part.type === 'text' && part.text.length > 0)
43
+ content.push({ type: 'text', text: part.text });
44
+ if (part.type === 'image' && 'dataBase64' in part) {
45
+ content.push({ type: 'image', source: { type: 'base64', media_type: part.mediaType, data: part.dataBase64 } });
46
+ }
47
+ }
48
+ return content;
49
+ }
50
+ /** Parse a tool call's raw JSON arguments into Anthropic's object-shaped `input`. */
51
+ function parseToolInput(raw) {
52
+ try {
53
+ const parsed = JSON.parse(raw);
54
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
55
+ return parsed;
56
+ }
57
+ return {};
58
+ }
59
+ catch {
60
+ // The model produced malformed JSON; an empty object keeps the request valid.
61
+ return {};
62
+ }
63
+ }
64
+ /**
65
+ * Move a user message's `tool_result` blocks into one contiguous run at the
66
+ * front, preserving the relative order of both groups.
67
+ *
68
+ * Anthropic answers every `tool_use` against the blocks that *lead* the next
69
+ * message, so a block of any other kind before or between the results reads
70
+ * as a call left unanswered and the request is rejected. The harness merges
71
+ * everything queued for one user turn into a single message, and a parallel
72
+ * tool batch arrives as one result message per call, so any context spliced
73
+ * mid-batch lands between two results. Restoring the run here keeps that
74
+ * independent of delivery order. Order *among* the results does not matter.
75
+ * @param message - one assembled user message, reordered in place.
76
+ */
77
+ function leadWithToolResults(message) {
78
+ const firstOther = message.content.findIndex(block => block.type !== 'tool_result');
79
+ if (firstOther === -1)
80
+ return;
81
+ if (!message.content.slice(firstOther).some(block => block.type === 'tool_result'))
82
+ return;
83
+ message.content = [
84
+ ...message.content.filter(block => block.type === 'tool_result'),
85
+ ...message.content.filter(block => block.type !== 'tool_result'),
86
+ ];
87
+ }
88
+ /**
89
+ * Index of the first non-system message; `messages.length` when every message
90
+ * is a system one.
91
+ *
92
+ * A system message before the conversation starts is the operator's opening
93
+ * instruction and belongs in the `system` slot. One that arrives later is
94
+ * mid-conversation context, and hoisting it into `system` would move bytes in
95
+ * front of the whole history — invalidating every cached turn behind it — so
96
+ * it stays where it is, as a reminder block in `messages`.
97
+ * @param messages - ordered conversation messages.
98
+ * @returns the boundary index separating the two.
99
+ */
100
+ function conversationStart(messages) {
101
+ const index = messages.findIndex(message => message.role !== 'system');
102
+ return index === -1 ? messages.length : index;
103
+ }
104
+ /**
105
+ * Convert harness messages into Anthropic messages. Consecutive same-role
106
+ * messages merge into one message with multiple content blocks; tool results
107
+ * arrive as user messages with `tool_result` blocks, which a merged user
108
+ * message keeps in one leading run ({@link leadWithToolResults}); system-role
109
+ * messages before the conversation starts are handled by
110
+ * {@link toAnthropicSystem} and skipped here, while a later one rides in
111
+ * place as a user-role `<system-reminder>` block.
112
+ * Reasoning blocks are not replayed (v1). Images must arrive pre-resolved
113
+ * ({@link TranslatableMessage}); an unresolved ImageBlock is skipped because
114
+ * its bytes are unreachable here.
115
+ * @param messages - ordered conversation messages with resolved images.
116
+ * @returns Anthropic messages in conversation order.
117
+ */
118
+ export function toAnthropicMessages(messages) {
119
+ const out = [];
120
+ const start = conversationStart(messages);
121
+ for (const [index, message] of messages.entries()) {
122
+ // A leading system message is an opening instruction; toAnthropicSystem
123
+ // owns those. A later one rides here so the cached prefix ahead of it
124
+ // stays byte-identical.
125
+ if (message.role === 'system' && index < start)
126
+ continue;
127
+ const role = message.role === 'assistant' ? 'assistant' : 'user';
128
+ const blocks = [];
129
+ if (message.role === 'tool') {
130
+ const id = message.toolCallId ?? message.tool_call_id
131
+ ?? (message.source?.kind === 'tool' ? String(message.source.callId) : undefined);
132
+ if (id === undefined)
133
+ throw new LlmError('tool result has no call id', 'INVALID_REQUEST');
134
+ blocks.push({
135
+ type: 'tool_result',
136
+ tool_use_id: id,
137
+ content: toolResultContent({ type: 'tool-result', toolCallId: ToolCallId(id), content: message.content }),
138
+ ...message.isError === true ? { is_error: true } : {},
139
+ });
140
+ const last = out[out.length - 1];
141
+ if (last?.role === 'user')
142
+ last.content.push(...blocks);
143
+ else
144
+ out.push({ role: 'user', content: blocks });
145
+ continue;
146
+ }
147
+ for (const block of message.content) {
148
+ switch (block.type) {
149
+ case 'text':
150
+ blocks.push({
151
+ type: 'text',
152
+ text: message.role === 'system'
153
+ ? `${SYSTEM_REMINDER_OPEN}${block.text}${SYSTEM_REMINDER_CLOSE}`
154
+ : block.text,
155
+ });
156
+ break;
157
+ case 'tool-call':
158
+ // Anthropic accepts `tool_use` only in assistant messages, and only
159
+ // when a matching `tool_result` follows. A tool call in any other
160
+ // role is replayed narrative — a settled subagent's closing message
161
+ // spliced into the parent as a user-role notice carries the calls it
162
+ // died holding, which no result will ever answer — so it rides as
163
+ // descriptive text instead of a call the API would reject.
164
+ blocks.push(role === 'assistant'
165
+ ? {
166
+ type: 'tool_use',
167
+ id: String(block.id),
168
+ name: block.name,
169
+ input: parseToolInput(block.arguments),
170
+ }
171
+ : { type: 'text', text: `[tool call ${block.name}: ${block.arguments}]` });
172
+ break;
173
+ case 'tool-result':
174
+ blocks.push({
175
+ type: 'tool_result',
176
+ tool_use_id: String(block.toolCallId),
177
+ content: toolResultContent(block),
178
+ ...block.isError === true ? { is_error: true } : {},
179
+ });
180
+ break;
181
+ case 'image':
182
+ if ('dataBase64' in block) {
183
+ blocks.push({
184
+ type: 'image',
185
+ source: { type: 'base64', media_type: block.mediaType, data: block.dataBase64 },
186
+ });
187
+ }
188
+ // An unresolved ImageBlock carries only an attachment reference; the
189
+ // adapter resolves images before translation, so this is skipped.
190
+ break;
191
+ default:
192
+ // reasoning (not replayed), unknown blocks.
193
+ break;
194
+ }
195
+ }
196
+ if (blocks.length === 0)
197
+ continue;
198
+ const last = out[out.length - 1];
199
+ if (last !== undefined && last.role === role)
200
+ last.content.push(...blocks);
201
+ else
202
+ out.push({ role, content: blocks });
203
+ }
204
+ for (const message of out) {
205
+ if (message.role === 'user')
206
+ leadWithToolResults(message);
207
+ }
208
+ return out;
209
+ }
210
+ /**
211
+ * Mark the conversation's cache breakpoints in place: the last content block,
212
+ * then one every {@link CACHE_BLOCK_STRIDE} blocks backwards, {@link
213
+ * MESSAGE_CACHE_BREAKPOINTS} in total.
214
+ *
215
+ * The history is append-only, so the block one request marks last is
216
+ * byte-identical in the next — that entry is what the next request reads.
217
+ * Marks are counted across the flattened block sequence, not per message,
218
+ * because the lookback window Anthropic walks counts blocks the same way.
219
+ * @param messages - assembled Anthropic messages, marked in place.
220
+ */
221
+ export function markMessageCache(messages) {
222
+ const blocks = messages.flatMap(message => message.content);
223
+ for (let mark = 0; mark < MESSAGE_CACHE_BREAKPOINTS; mark++) {
224
+ const at = blocks.length - 1 - mark * CACHE_BLOCK_STRIDE;
225
+ if (at < 0)
226
+ return;
227
+ blocks[at].cache_control = { type: 'ephemeral' };
228
+ }
229
+ }
230
+ /**
231
+ * Build the Anthropic `system` array: the mandatory Claude Code identity
232
+ * block, then the explicit system prompt, then any system-role messages.
233
+ * @param system - explicit system prompt, when set.
234
+ * @param messages - conversation messages; the system-role text preceding the
235
+ * conversation is appended, and a later one is left to {@link toAnthropicMessages}.
236
+ * @returns the system content blocks.
237
+ */
238
+ export function toAnthropicSystem(system, messages) {
239
+ const blocks = [{ type: 'text', text: CLAUDE_CODE_IDENTITY }];
240
+ if (system !== undefined && system.length > 0)
241
+ blocks.push({ type: 'text', text: system });
242
+ const history = messages ?? [];
243
+ for (const message of history.slice(0, conversationStart(history))) {
244
+ for (const block of message.content) {
245
+ if (block.type === 'text')
246
+ blocks.push({ type: 'text', text: block.text });
247
+ }
248
+ }
249
+ // `tools` renders ahead of `system`, so this one marker caches both. It is
250
+ // deliberately separate from the message marks: a tool_choice or thinking
251
+ // change invalidates the messages tier only, and this entry survives it.
252
+ blocks[blocks.length - 1].cache_control = { type: 'ephemeral' };
253
+ return blocks;
254
+ }
255
+ /**
256
+ * Map harness tool schemas to Anthropic tools, in name order.
257
+ *
258
+ * `tools` renders at position 0 of the cached prefix, so any reordering
259
+ * invalidates every cache entry behind it — `system` and the whole
260
+ * conversation included. Registration order belongs to the caller and plugin
261
+ * load order can differ between processes, so the wire order is fixed here
262
+ * instead. Anthropic selects a tool by name; the array order carries nothing.
263
+ * @param tools - tool schemas from the request.
264
+ * @returns Anthropic `tools` array entries, ordered by tool name.
265
+ */
266
+ export function toAnthropicTools(tools) {
267
+ return [...tools]
268
+ .sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0))
269
+ .map(tool => ({
270
+ name: tool.name,
271
+ description: tool.description,
272
+ input_schema: tool.parameters,
273
+ }));
274
+ }
275
+ /** Assemble the final ContentBlock for one open block. */
276
+ function closeBlock(block) {
277
+ switch (block.kind) {
278
+ case 'text':
279
+ return { type: 'text', text: block.text };
280
+ case 'reasoning':
281
+ return { type: 'reasoning', text: block.text };
282
+ case 'tool-call':
283
+ return {
284
+ type: 'tool-call',
285
+ id: ToolCallId(block.callId),
286
+ name: block.name ?? '',
287
+ arguments: block.text,
288
+ };
289
+ }
290
+ }
291
+ /**
292
+ * Classify an Anthropic `error` event into a thrown LlmError.
293
+ * @param error - the wire error object.
294
+ * @returns the mapped error.
295
+ */
296
+ export function anthropicFailure(error) {
297
+ const type = error?.type ?? 'unknown_error';
298
+ const message = error?.message ?? `Anthropic reported ${type}`;
299
+ if (type === 'invalid_request_error' && /prompt is too long/i.test(message)) {
300
+ return new LlmError(message, CONTEXT_WINDOW_EXCEEDED_CODE);
301
+ }
302
+ if (type === 'rate_limit_error')
303
+ return new LlmError(message, 'RATE_LIMIT');
304
+ if (type === 'authentication_error')
305
+ return new LlmError(message, 'AUTH');
306
+ return new LlmError(message, 'SERVER');
307
+ }
308
+ /**
309
+ * Push-model Anthropic SSE translator: feed each parsed event object to
310
+ * {@link push} and collect the emitted harness StreamChunks. Block indexes
311
+ * are allocated in first-seen order; `usage` is emitted before the terminal
312
+ * `finish`, and nothing is emitted after it. `error` events throw
313
+ * {@link LlmError}.
314
+ */
315
+ export class AnthropicStreamTranslator {
316
+ blocks = new Map();
317
+ nextIndex = 0;
318
+ sawAnyBlock = false;
319
+ pendingUsage;
320
+ outputTokens;
321
+ stopReason = 'stop';
322
+ usageEmitted = false;
323
+ /** Set once `message_stop` produced the terminal finish chunk. */
324
+ terminated = false;
325
+ open(wireIndex, kind, chunks, callId = '', name) {
326
+ const block = {
327
+ index: this.nextIndex++,
328
+ kind,
329
+ text: '',
330
+ callId,
331
+ ...name === undefined ? {} : { name },
332
+ };
333
+ this.blocks.set(wireIndex, block);
334
+ this.sawAnyBlock = true;
335
+ chunks.push({ type: 'block-start', index: block.index, blockType: kind });
336
+ return block;
337
+ }
338
+ emitUsage(chunks) {
339
+ if (this.usageEmitted)
340
+ return;
341
+ this.usageEmitted = true;
342
+ const usage = {
343
+ inputTokens: this.pendingUsage?.inputTokens ?? 0,
344
+ outputTokens: this.outputTokens ?? 0,
345
+ ...this.pendingUsage?.cacheReadTokens !== undefined
346
+ ? { cacheReadTokens: this.pendingUsage.cacheReadTokens }
347
+ : {},
348
+ ...this.pendingUsage?.cacheWriteTokens !== undefined
349
+ ? { cacheWriteTokens: this.pendingUsage.cacheWriteTokens }
350
+ : {},
351
+ };
352
+ chunks.push({ type: 'usage', usage });
353
+ }
354
+ /**
355
+ * Process one parsed Anthropic SSE event.
356
+ * @param event - the parsed event object.
357
+ * @returns the StreamChunks this event produced (possibly none).
358
+ */
359
+ push(event) {
360
+ if (this.terminated)
361
+ return [];
362
+ const chunks = [];
363
+ switch (event.type) {
364
+ case 'message_start': {
365
+ const usage = event.message?.usage;
366
+ if (usage !== undefined) {
367
+ this.pendingUsage = {
368
+ inputTokens: usage.input_tokens ?? 0,
369
+ ...usage.cache_read_input_tokens !== undefined
370
+ ? { cacheReadTokens: usage.cache_read_input_tokens }
371
+ : {},
372
+ ...usage.cache_creation_input_tokens !== undefined
373
+ ? { cacheWriteTokens: usage.cache_creation_input_tokens }
374
+ : {},
375
+ };
376
+ this.outputTokens = usage.output_tokens ?? this.outputTokens;
377
+ }
378
+ return chunks;
379
+ }
380
+ case 'content_block_start': {
381
+ const wireIndex = event.index ?? 0;
382
+ const block = event.content_block;
383
+ switch (block?.type) {
384
+ case 'text':
385
+ this.open(wireIndex, 'text', chunks);
386
+ break;
387
+ case 'thinking':
388
+ this.open(wireIndex, 'reasoning', chunks);
389
+ break;
390
+ case 'tool_use': {
391
+ const opened = this.open(wireIndex, 'tool-call', chunks, block.id ?? '', block.name);
392
+ chunks.push({
393
+ type: 'tool-call-delta',
394
+ index: opened.index,
395
+ id: ToolCallId(opened.callId),
396
+ ...block.name === undefined ? {} : { name: block.name },
397
+ argumentsDelta: '',
398
+ });
399
+ break;
400
+ }
401
+ default:
402
+ break;
403
+ }
404
+ return chunks;
405
+ }
406
+ case 'content_block_delta': {
407
+ const wireIndex = event.index ?? 0;
408
+ const block = this.blocks.get(wireIndex);
409
+ const delta = event.delta;
410
+ if (block === undefined || delta === undefined)
411
+ return chunks;
412
+ switch (delta.type) {
413
+ case 'text_delta':
414
+ block.text += delta.text ?? '';
415
+ chunks.push({ type: 'text-delta', index: block.index, text: delta.text ?? '' });
416
+ break;
417
+ case 'thinking_delta':
418
+ block.text += delta.thinking ?? '';
419
+ chunks.push({ type: 'reasoning-delta', index: block.index, text: delta.thinking ?? '' });
420
+ break;
421
+ case 'input_json_delta':
422
+ block.text += delta.partial_json ?? '';
423
+ chunks.push({
424
+ type: 'tool-call-delta',
425
+ index: block.index,
426
+ id: ToolCallId(block.callId),
427
+ ...block.name === undefined ? {} : { name: block.name },
428
+ argumentsDelta: delta.partial_json ?? '',
429
+ });
430
+ break;
431
+ default:
432
+ // signature_delta and future deltas carry no harness content.
433
+ break;
434
+ }
435
+ return chunks;
436
+ }
437
+ case 'content_block_stop': {
438
+ const wireIndex = event.index ?? 0;
439
+ const block = this.blocks.get(wireIndex);
440
+ if (block === undefined)
441
+ return chunks;
442
+ this.blocks.delete(wireIndex);
443
+ chunks.push({ type: 'block-end', index: block.index, block: closeBlock(block) });
444
+ return chunks;
445
+ }
446
+ case 'message_delta': {
447
+ if (event.usage?.output_tokens !== undefined)
448
+ this.outputTokens = event.usage.output_tokens;
449
+ switch (event.delta?.stop_reason) {
450
+ case 'end_turn':
451
+ case 'stop_sequence':
452
+ this.stopReason = 'stop';
453
+ break;
454
+ case 'tool_use':
455
+ this.stopReason = 'tool-calls';
456
+ break;
457
+ case 'max_tokens':
458
+ this.stopReason = 'max-tokens';
459
+ break;
460
+ default:
461
+ break;
462
+ }
463
+ return chunks;
464
+ }
465
+ case 'message_stop': {
466
+ this.terminated = true;
467
+ for (const [wireIndex, block] of [...this.blocks]) {
468
+ this.blocks.delete(wireIndex);
469
+ chunks.push({ type: 'block-end', index: block.index, block: closeBlock(block) });
470
+ }
471
+ this.emitUsage(chunks);
472
+ if (this.stopReason === 'stop' && !this.sawAnyBlock) {
473
+ chunks.push({
474
+ type: 'finish',
475
+ reason: {
476
+ kind: 'error',
477
+ failure: { message: 'model returned a completed response with no content', code: EMPTY_RESPONSE_CODE },
478
+ },
479
+ });
480
+ }
481
+ else {
482
+ chunks.push({ type: 'finish', reason: { kind: this.stopReason } });
483
+ }
484
+ return chunks;
485
+ }
486
+ case 'error':
487
+ throw anthropicFailure(event.error);
488
+ default:
489
+ // ping and future event types carry no harness content.
490
+ return chunks;
491
+ }
492
+ }
493
+ }
494
+ /**
495
+ * Consume an Anthropic SSE byte stream and yield harness StreamChunks.
496
+ * @param stream - raw response body.
497
+ * @param onActivity - transport-activity callback for the idle watchdog.
498
+ * @returns the chunk stream; throws when the stream ends before `message_stop`.
499
+ */
500
+ export async function* streamAnthropic(stream, onActivity) {
501
+ const translator = new AnthropicStreamTranslator();
502
+ for await (const sseEvent of parseSse(stream, onActivity)) {
503
+ let event;
504
+ try {
505
+ event = JSON.parse(sseEvent.data);
506
+ }
507
+ catch {
508
+ throw new LlmError(`malformed SSE payload: ${sseEvent.data.slice(0, 120)}`, 'MALFORMED_RESPONSE');
509
+ }
510
+ yield* translator.push(event);
511
+ if (translator.terminated)
512
+ return;
513
+ }
514
+ throw new LlmError('Anthropic SSE stream ended before message_stop', 'STREAM_CLOSED');
515
+ }
@@ -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 {};
@@ -0,0 +1,145 @@
1
+ /** Model-specific tool schemas for Antigravity's Gemini and custom-tool bridges. */
2
+ import { LlmError } from '@deepseek-ai/dsh-llm';
3
+ const CUSTOM_KEYS = new Set(['type', 'description', 'properties', 'required', 'items', 'enum']);
4
+ function object(value) {
5
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ }
7
+ /** Resolve local references before removing definitions; reject cycles and remote references. */
8
+ function resolve(value, root, refs = new Set()) {
9
+ if (Array.isArray(value))
10
+ return value.map(item => resolve(item, root, refs));
11
+ if (!object(value))
12
+ return value;
13
+ if (typeof value.$ref === 'string') {
14
+ const ref = value.$ref;
15
+ if (!ref.startsWith('#/') || refs.has(ref)) {
16
+ throw new LlmError(`Antigravity tool schema has an unsupported reference: ${ref}`, 'INVALID_REQUEST');
17
+ }
18
+ let target = root;
19
+ for (const segment of ref.slice(2).split('/')) {
20
+ const key = segment.replaceAll('~1', '/').replaceAll('~0', '~');
21
+ target = object(target) && Object.hasOwn(target, key) ? target[key] : undefined;
22
+ }
23
+ if (!object(target))
24
+ throw new LlmError(`Antigravity tool schema reference is missing: ${ref}`, 'INVALID_REQUEST');
25
+ const { $ref: _ref, ...siblings } = value;
26
+ return resolve({ ...target, ...siblings }, root, new Set([...refs, ref]));
27
+ }
28
+ return Object.fromEntries(Object.entries(value)
29
+ .filter(([key]) => !['$schema', '$id', '$defs', 'definitions'].includes(key))
30
+ .map(([key, item]) => [key, key === 'properties' && object(item)
31
+ ? Object.fromEntries(Object.entries(item).map(([name, property]) => [name, resolve(property, root, refs)]))
32
+ : resolve(item, root, refs)]));
33
+ }
34
+ /** Numeric types widen to `number`: every integer is also a number. */
35
+ const NUMERIC = new Set(['integer', 'number']);
36
+ /** The strings one alternative is limited to (`enum` or `const`), or undefined when unconstrained. */
37
+ function stringChoices(schema) {
38
+ if (typeof schema.const === 'string')
39
+ return [schema.const];
40
+ return Array.isArray(schema.enum) && schema.enum.every(entry => typeof entry === 'string') ? schema.enum : undefined;
41
+ }
42
+ /**
43
+ * Fold schema alternatives into one shape this bridge can express, never
44
+ * narrower than any alternative: picking one branch would make the tool
45
+ * uncallable with the others' values, which is worse than a looser contract
46
+ * the tool still validates. Objects keep the union of their properties and
47
+ * only the fields every alternative requires; scalars of one type keep that
48
+ * type (and a string choice union); anything heterogeneous is unconstrained.
49
+ */
50
+ function widen(variants) {
51
+ const members = variants.filter(object);
52
+ if (members.length === 0 || members.length !== variants.length)
53
+ return {};
54
+ const types = new Set(members.map(member => member.type));
55
+ const type = types.size === 1 ? [...types][0]
56
+ : [...types].every(entry => typeof entry === 'string' && NUMERIC.has(entry)) ? 'number' : undefined;
57
+ if (typeof type !== 'string')
58
+ return {};
59
+ if (type === 'object') {
60
+ const properties = {};
61
+ for (const member of members) {
62
+ if (!object(member.properties))
63
+ continue;
64
+ for (const [name, property] of Object.entries(member.properties)) {
65
+ properties[name] = !Object.hasOwn(properties, name) || JSON.stringify(properties[name]) === JSON.stringify(property)
66
+ ? property
67
+ : widen([properties[name], property]);
68
+ }
69
+ }
70
+ const required = members
71
+ .map(member => Array.isArray(member.required) ? member.required.filter(entry => typeof entry === 'string') : [])
72
+ .reduce((shared, next) => shared.filter(name => next.includes(name)));
73
+ return {
74
+ type,
75
+ ...Object.keys(properties).length === 0 ? {} : { properties },
76
+ ...required.length === 0 ? {} : { required },
77
+ };
78
+ }
79
+ if (type === 'array') {
80
+ return { type, items: widen(members.map(member => member.items ?? {})) };
81
+ }
82
+ const choices = members.map(stringChoices);
83
+ return choices.every(entry => entry !== undefined)
84
+ ? { type, enum: [...new Set(choices.flat())] }
85
+ : { type };
86
+ }
87
+ /** Merge `extra` into `schema`: properties and required accumulate, other keywords override. */
88
+ function absorb(schema, extra) {
89
+ const { properties, required, ...rest } = extra;
90
+ Object.assign(schema, rest);
91
+ if (object(properties))
92
+ schema.properties = { ...object(schema.properties) ? schema.properties : {}, ...properties };
93
+ if (Array.isArray(required)) {
94
+ schema.required = [...new Set([...Array.isArray(schema.required) ? schema.required : [], ...required])];
95
+ }
96
+ }
97
+ /** Keep property names intact while reducing schema keywords for Claude/GPT-OSS. */
98
+ function custom(value) {
99
+ if (!object(value))
100
+ return value;
101
+ const schema = { ...value };
102
+ // An intersection holds every member's constraints at once.
103
+ if (Array.isArray(schema.allOf)) {
104
+ const members = schema.allOf.filter(object);
105
+ delete schema.allOf;
106
+ for (const member of members)
107
+ absorb(schema, member);
108
+ }
109
+ for (const keyword of ['anyOf', 'oneOf']) {
110
+ if (!Array.isArray(schema[keyword]))
111
+ continue;
112
+ const variants = schema[keyword].filter(item => !object(item) || item.type !== 'null');
113
+ delete schema[keyword];
114
+ absorb(schema, variants.length === 1 && object(variants[0]) ? variants[0] : widen(variants));
115
+ }
116
+ const out = {};
117
+ for (const [key, value] of Object.entries(schema)) {
118
+ if (!CUSTOM_KEYS.has(key))
119
+ continue;
120
+ if (key === 'properties' && object(value)) {
121
+ out[key] = Object.fromEntries(Object.entries(value).map(([name, property]) => [name, custom(property)]));
122
+ }
123
+ else if (key === 'items')
124
+ out[key] = custom(value);
125
+ else if (key === 'type' && Array.isArray(value)) {
126
+ const types = value.filter(type => type !== 'null');
127
+ const type = types.length === 1 ? types[0]
128
+ : types.length > 0 && types.every(entry => NUMERIC.has(entry)) ? 'number' : undefined;
129
+ if (type !== undefined)
130
+ out[key] = type;
131
+ }
132
+ else if (key !== 'enum' || Array.isArray(value) && value.every(entry => typeof entry === 'string')) {
133
+ out[key] = value;
134
+ }
135
+ }
136
+ return out;
137
+ }
138
+ /** Detach and normalize one tool's root object without changing the registry schema. */
139
+ export function antigravityToolParameters(parameters, legacy) {
140
+ const resolved = resolve(parameters, parameters);
141
+ const root = { ...resolved, type: resolved.type ?? 'object' };
142
+ if (root.type !== 'object')
143
+ throw new LlmError('Antigravity tool parameters must be an object schema', 'INVALID_REQUEST');
144
+ return (legacy ? custom(root) : root);
145
+ }
@@ -0,0 +1,5 @@
1
+ import type { LlmResolvedModelInfo } from '@deepseek-ai/dsh-llm';
2
+ /** Efforts supported by the known Antigravity runtime families. Unknown models keep upstream defaults. */
3
+ export declare function antigravityReasoning(model: string): LlmResolvedModelInfo['reasoning'];
4
+ /** Build the v1internal thinking config; do not forward arbitrary DSH effort strings. */
5
+ export declare function antigravityThinking(model: string, effort: string | undefined, maxTokens?: number): Record<string, unknown> | undefined;