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.
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/cordis.patch.yml +12 -0
- package/lib/auth/claude-code-creds.d.ts +23 -0
- package/lib/auth/claude-code-creds.js +172 -0
- package/lib/auth/device-flow.d.ts +55 -0
- package/lib/auth/device-flow.js +177 -0
- package/lib/auth/jwt.d.ts +10 -0
- package/lib/auth/jwt.js +25 -0
- package/lib/auth/oauth-flow.d.ts +91 -0
- package/lib/auth/oauth-flow.js +227 -0
- package/lib/auth/pkce.d.ts +31 -0
- package/lib/auth/pkce.js +35 -0
- package/lib/auth/rpc.d.ts +209 -0
- package/lib/auth/rpc.js +386 -0
- package/lib/auth/store.d.ts +196 -0
- package/lib/auth/store.js +468 -0
- package/lib/client/CursorCard.d.ts +9 -0
- package/lib/client/CursorCard.js +113 -0
- package/lib/client/ExternalUsageCards.d.ts +9 -0
- package/lib/client/ExternalUsageCards.js +72 -0
- package/lib/client/ImageGallery.d.ts +54 -0
- package/lib/client/ImageGallery.js +112 -0
- package/lib/client/ImageGenerateToolview.d.ts +48 -0
- package/lib/client/ImageGenerateToolview.js +133 -0
- package/lib/client/ProviderAccountManager.d.ts +13 -0
- package/lib/client/ProviderAccountManager.js +147 -0
- package/lib/client/ProviderModelEditor.d.ts +39 -0
- package/lib/client/ProviderModelEditor.js +107 -0
- package/lib/client/SpeedSelect.d.ts +71 -0
- package/lib/client/SpeedSelect.js +177 -0
- package/lib/client/SubscriptionUsageBadge.d.ts +116 -0
- package/lib/client/SubscriptionUsageBadge.js +543 -0
- package/lib/client/SubscriptionsSection.d.ts +140 -0
- package/lib/client/SubscriptionsSection.js +544 -0
- package/lib/client/UsageBadgeDisplaySetting.d.ts +7 -0
- package/lib/client/UsageBadgeDisplaySetting.js +36 -0
- package/lib/client/VideoGenerateToolview.d.ts +42 -0
- package/lib/client/VideoGenerateToolview.js +175 -0
- package/lib/client/account-preferences.d.ts +24 -0
- package/lib/client/account-preferences.js +21 -0
- package/lib/client/fast-command.d.ts +2 -0
- package/lib/client/fast-command.js +4 -0
- package/lib/client/host-contract.d.ts +90 -0
- package/lib/client/host-contract.js +101 -0
- package/lib/client/host-icons.d.ts +24 -0
- package/lib/client/host-icons.js +21 -0
- package/lib/client/index.d.ts +27 -0
- package/lib/client/index.js +136 -0
- package/lib/client/locales.d.ts +297 -0
- package/lib/client/locales.js +295 -0
- package/lib/client/subscription-card-styles.d.ts +3 -0
- package/lib/client/subscription-card-styles.js +53 -0
- package/lib/client/subscriptions-rpc.d.ts +14 -0
- package/lib/client/subscriptions-rpc.js +34 -0
- package/lib/client/usage-badge-preferences.d.ts +11 -0
- package/lib/client/usage-badge-preferences.js +41 -0
- package/lib/client.js +4472 -0
- package/lib/client.js.map +1 -0
- package/lib/compat.d.ts +36 -0
- package/lib/compat.js +20 -0
- package/lib/http.d.ts +6 -0
- package/lib/http.js +15 -0
- package/lib/index.d.ts +80 -0
- package/lib/index.js +14018 -0
- package/lib/model-defaults.d.ts +23 -0
- package/lib/model-defaults.js +237 -0
- package/lib/provider-settings.d.ts +39 -0
- package/lib/provider-settings.js +151 -0
- package/lib/providers/account-preferences.d.ts +41 -0
- package/lib/providers/account-preferences.js +153 -0
- package/lib/providers/accounts.d.ts +112 -0
- package/lib/providers/accounts.js +142 -0
- package/lib/providers/antigravity-oauth-client.d.ts +7 -0
- package/lib/providers/antigravity-oauth-client.js +7 -0
- package/lib/providers/antigravity.d.ts +110 -0
- package/lib/providers/antigravity.js +516 -0
- package/lib/providers/catalog-store.d.ts +48 -0
- package/lib/providers/catalog-store.js +243 -0
- package/lib/providers/claude-cli-version.d.ts +15 -0
- package/lib/providers/claude-cli-version.js +24 -0
- package/lib/providers/claude.d.ts +181 -0
- package/lib/providers/claude.js +680 -0
- package/lib/providers/codex-client-version.d.ts +8 -0
- package/lib/providers/codex-client-version.js +17 -0
- package/lib/providers/codex-search.d.ts +29 -0
- package/lib/providers/codex-search.js +198 -0
- package/lib/providers/codex.d.ts +229 -0
- package/lib/providers/codex.js +821 -0
- package/lib/providers/common.d.ts +389 -0
- package/lib/providers/common.js +656 -0
- package/lib/providers/copilot.d.ts +344 -0
- package/lib/providers/copilot.js +888 -0
- package/lib/providers/cursor-adapter.d.ts +15 -0
- package/lib/providers/cursor-adapter.js +39 -0
- package/lib/providers/cursor-auth.d.ts +43 -0
- package/lib/providers/cursor-auth.js +169 -0
- package/lib/providers/cursor-model-settings.d.ts +15 -0
- package/lib/providers/cursor-model-settings.js +51 -0
- package/lib/providers/cursor-usage.d.ts +9 -0
- package/lib/providers/cursor-usage.js +112 -0
- package/lib/providers/external-usage-controller.d.ts +19 -0
- package/lib/providers/external-usage-controller.js +32 -0
- package/lib/providers/external-usage.d.ts +8 -0
- package/lib/providers/external-usage.js +82 -0
- package/lib/providers/grok.d.ts +205 -0
- package/lib/providers/grok.js +713 -0
- package/lib/providers/image-pool.d.ts +30 -0
- package/lib/providers/image-pool.js +89 -0
- package/lib/providers/npm-cli-version.d.ts +56 -0
- package/lib/providers/npm-cli-version.js +98 -0
- package/lib/providers/pool-family.d.ts +56 -0
- package/lib/providers/pool-family.js +45 -0
- package/lib/providers/pool-health.d.ts +74 -0
- package/lib/providers/pool-health.js +150 -0
- package/lib/providers/pool-usage.d.ts +86 -0
- package/lib/providers/pool-usage.js +223 -0
- package/lib/providers/pool.d.ts +108 -0
- package/lib/providers/pool.js +368 -0
- package/lib/providers/rate-limit.d.ts +192 -0
- package/lib/providers/rate-limit.js +338 -0
- package/lib/tools/image-generate.d.ts +128 -0
- package/lib/tools/image-generate.js +438 -0
- package/lib/tools/registration.d.ts +15 -0
- package/lib/tools/registration.js +24 -0
- package/lib/tools/video-generate.d.ts +89 -0
- package/lib/tools/video-generate.js +257 -0
- package/lib/tools/x-search.d.ts +58 -0
- package/lib/tools/x-search.js +197 -0
- package/lib/translate/anthropic.d.ts +161 -0
- package/lib/translate/anthropic.js +515 -0
- package/lib/translate/antigravity-schema.d.ts +4 -0
- package/lib/translate/antigravity-schema.js +145 -0
- package/lib/translate/antigravity-thinking.d.ts +5 -0
- package/lib/translate/antigravity-thinking.js +44 -0
- package/lib/translate/antigravity.d.ts +114 -0
- package/lib/translate/antigravity.js +366 -0
- package/lib/translate/chat-completions.d.ts +120 -0
- package/lib/translate/chat-completions.js +382 -0
- package/lib/translate/resolved.d.ts +80 -0
- package/lib/translate/resolved.js +118 -0
- package/lib/translate/responses.d.ts +179 -0
- package/lib/translate/responses.js +408 -0
- package/lib/translate/sse.d.ts +21 -0
- package/lib/translate/sse.js +56 -0
- package/package.json +114 -0
- package/vendor/cursor/LICENSE +21 -0
- package/vendor/cursor/index.d.ts +18 -0
- package/vendor/cursor/index.js +3574 -0
- 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,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;
|