gsd-pi 2.37.1-dev.193bd3d → 2.37.1-dev.3bbb0a9

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 (70) hide show
  1. package/README.md +1 -1
  2. package/dist/onboarding.js +1 -0
  3. package/dist/resources/extensions/gsd/auto-dispatch.js +67 -1
  4. package/dist/resources/extensions/gsd/auto-post-unit.js +14 -0
  5. package/dist/resources/extensions/gsd/auto-prompts.js +91 -2
  6. package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
  7. package/dist/resources/extensions/gsd/doctor-providers.js +35 -1
  8. package/dist/resources/extensions/gsd/files.js +41 -0
  9. package/dist/resources/extensions/gsd/observability-validator.js +24 -0
  10. package/dist/resources/extensions/gsd/preferences-types.js +2 -1
  11. package/dist/resources/extensions/gsd/preferences-validation.js +42 -0
  12. package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  13. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
  14. package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
  15. package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
  16. package/package.json +2 -1
  17. package/packages/pi-ai/dist/env-api-keys.js +13 -0
  18. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  19. package/packages/pi-ai/dist/models.generated.d.ts +172 -0
  20. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  21. package/packages/pi-ai/dist/models.generated.js +172 -0
  22. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  23. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
  24. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
  25. package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
  26. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
  27. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
  28. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
  29. package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
  30. package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
  31. package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
  32. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  33. package/packages/pi-ai/dist/providers/anthropic.js +47 -764
  34. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  35. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  36. package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
  37. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  38. package/packages/pi-ai/dist/types.d.ts +2 -2
  39. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  40. package/packages/pi-ai/dist/types.js.map +1 -1
  41. package/packages/pi-ai/package.json +1 -0
  42. package/packages/pi-ai/src/env-api-keys.ts +14 -0
  43. package/packages/pi-ai/src/models.generated.ts +172 -0
  44. package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
  45. package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
  46. package/packages/pi-ai/src/providers/anthropic.ts +76 -868
  47. package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
  48. package/packages/pi-ai/src/types.ts +2 -0
  49. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  50. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  51. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  52. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  53. package/src/resources/extensions/gsd/auto-dispatch.ts +93 -0
  54. package/src/resources/extensions/gsd/auto-post-unit.ts +14 -0
  55. package/src/resources/extensions/gsd/auto-prompts.ts +125 -3
  56. package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
  57. package/src/resources/extensions/gsd/doctor-providers.ts +38 -1
  58. package/src/resources/extensions/gsd/files.ts +45 -0
  59. package/src/resources/extensions/gsd/observability-validator.ts +27 -0
  60. package/src/resources/extensions/gsd/preferences-types.ts +5 -1
  61. package/src/resources/extensions/gsd/preferences-validation.ts +41 -0
  62. package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  63. package/src/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
  64. package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
  65. package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
  66. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +108 -3
  67. package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
  68. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
  69. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
  70. package/src/resources/extensions/gsd/types.ts +43 -0
@@ -0,0 +1,761 @@
1
+ /**
2
+ * Shared utilities for Anthropic providers (direct API and Vertex AI).
3
+ */
4
+ import type Anthropic from "@anthropic-ai/sdk";
5
+ import type {
6
+ ContentBlockParam,
7
+ MessageCreateParamsStreaming,
8
+ MessageParam,
9
+ } from "@anthropic-ai/sdk/resources/messages.js";
10
+ import { calculateCost } from "../models.js";
11
+ import type {
12
+ Api,
13
+ AssistantMessage,
14
+ CacheRetention,
15
+ Context,
16
+ ImageContent,
17
+ Message,
18
+ Model,
19
+ ServerToolUseContent,
20
+ StopReason,
21
+ StreamOptions,
22
+ TextContent,
23
+ ThinkingContent,
24
+ Tool,
25
+ ToolCall,
26
+ ToolResultMessage,
27
+ WebSearchResultContent,
28
+ } from "../types.js";
29
+
30
+ /** API types that use the Anthropic Messages protocol */
31
+ export type AnthropicApi = "anthropic-messages" | "anthropic-vertex";
32
+ import type { AssistantMessageEventStream } from "../utils/event-stream.js";
33
+ import { parseStreamingJson } from "../utils/json-parse.js";
34
+ import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
35
+ import { transformMessages } from "./transform-messages.js";
36
+
37
+ export type AnthropicEffort = "low" | "medium" | "high" | "max";
38
+
39
+ export interface AnthropicOptions extends StreamOptions {
40
+ thinkingEnabled?: boolean;
41
+ thinkingBudgetTokens?: number;
42
+ effort?: AnthropicEffort;
43
+ interleavedThinking?: boolean;
44
+ toolChoice?: "auto" | "any" | "none" | { type: "tool"; name: string };
45
+ }
46
+
47
+ const claudeCodeTools = [
48
+ "Read",
49
+ "Write",
50
+ "Edit",
51
+ "Bash",
52
+ "Grep",
53
+ "Glob",
54
+ "AskUserQuestion",
55
+ "EnterPlanMode",
56
+ "ExitPlanMode",
57
+ "KillShell",
58
+ "NotebookEdit",
59
+ "Skill",
60
+ "Task",
61
+ "TaskOutput",
62
+ "TodoWrite",
63
+ "WebFetch",
64
+ "WebSearch",
65
+ ];
66
+
67
+ const ccToolLookup = new Map(claudeCodeTools.map((t) => [t.toLowerCase(), t]));
68
+
69
+ export const toClaudeCodeName = (name: string) => ccToolLookup.get(name.toLowerCase()) ?? name;
70
+ export const fromClaudeCodeName = (name: string, tools?: Tool[]) => {
71
+ if (tools && tools.length > 0) {
72
+ const lowerName = name.toLowerCase();
73
+ const matchedTool = tools.find((tool) => tool.name.toLowerCase() === lowerName);
74
+ if (matchedTool) return matchedTool.name;
75
+ }
76
+ return name;
77
+ };
78
+
79
+ function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRetention {
80
+ if (cacheRetention) {
81
+ return cacheRetention;
82
+ }
83
+ if (typeof process !== "undefined" && process.env.PI_CACHE_RETENTION === "long") {
84
+ return "long";
85
+ }
86
+ return "short";
87
+ }
88
+
89
+ export function getCacheControl(
90
+ baseUrl: string,
91
+ cacheRetention?: CacheRetention,
92
+ ): { retention: CacheRetention; cacheControl?: { type: "ephemeral"; ttl?: "1h" } } {
93
+ const retention = resolveCacheRetention(cacheRetention);
94
+ if (retention === "none") {
95
+ return { retention };
96
+ }
97
+ const ttl = retention === "long" && baseUrl.includes("api.anthropic.com") ? "1h" : undefined;
98
+ return {
99
+ retention,
100
+ cacheControl: { type: "ephemeral", ...(ttl && { ttl }) },
101
+ };
102
+ }
103
+
104
+ export function convertContentBlocks(content: (TextContent | ImageContent)[]):
105
+ | string
106
+ | Array<
107
+ | { type: "text"; text: string }
108
+ | {
109
+ type: "image";
110
+ source: {
111
+ type: "base64";
112
+ media_type: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
113
+ data: string;
114
+ };
115
+ }
116
+ > {
117
+ const hasImages = content.some((c) => c.type === "image");
118
+ if (!hasImages) {
119
+ return sanitizeSurrogates(content.map((c) => (c as TextContent).text).join("\n"));
120
+ }
121
+
122
+ const blocks = content.map((block) => {
123
+ if (block.type === "text") {
124
+ return {
125
+ type: "text" as const,
126
+ text: sanitizeSurrogates(block.text),
127
+ };
128
+ }
129
+ return {
130
+ type: "image" as const,
131
+ source: {
132
+ type: "base64" as const,
133
+ media_type: block.mimeType as "image/jpeg" | "image/png" | "image/gif" | "image/webp",
134
+ data: block.data,
135
+ },
136
+ };
137
+ });
138
+
139
+ const hasText = blocks.some((b) => b.type === "text");
140
+ if (!hasText) {
141
+ blocks.unshift({
142
+ type: "text" as const,
143
+ text: "(see attached image)",
144
+ });
145
+ }
146
+
147
+ return blocks;
148
+ }
149
+
150
+ export function supportsAdaptiveThinking(modelId: string): boolean {
151
+ return (
152
+ modelId.includes("opus-4-6") ||
153
+ modelId.includes("opus-4.6") ||
154
+ modelId.includes("sonnet-4-6") ||
155
+ modelId.includes("sonnet-4.6")
156
+ );
157
+ }
158
+
159
+ export function mapThinkingLevelToEffort(level: string | undefined, modelId: string): AnthropicEffort {
160
+ switch (level) {
161
+ case "minimal":
162
+ return "low";
163
+ case "low":
164
+ return "low";
165
+ case "medium":
166
+ return "medium";
167
+ case "high":
168
+ return "high";
169
+ case "xhigh":
170
+ return modelId.includes("opus-4-6") || modelId.includes("opus-4.6") ? "max" : "high";
171
+ default:
172
+ return "high";
173
+ }
174
+ }
175
+
176
+ export function isTransientNetworkError(error: unknown): boolean {
177
+ if (!(error instanceof Error)) return false;
178
+ const msg = error.message.toLowerCase();
179
+ const code = (error as NodeJS.ErrnoException).code;
180
+ return (
181
+ code === 'ECONNRESET' ||
182
+ code === 'EPIPE' ||
183
+ code === 'ETIMEDOUT' ||
184
+ code === 'ENOTFOUND' ||
185
+ code === 'EAI_AGAIN' ||
186
+ msg.includes('connector_closed') ||
187
+ msg.includes('socket hang up') ||
188
+ msg.includes('network') ||
189
+ msg.includes('connection') && msg.includes('closed') ||
190
+ msg.includes('fetch failed')
191
+ );
192
+ }
193
+
194
+ export function extractRetryAfterMs(headers: Headers | { get(name: string): string | null }, errorText = ""): number | undefined {
195
+ const normalizeDelay = (ms: number): number | undefined => (ms > 0 ? Math.ceil(ms + 1000) : undefined);
196
+
197
+ const retryAfter = headers.get("retry-after");
198
+ if (retryAfter) {
199
+ const seconds = Number(retryAfter);
200
+ if (Number.isFinite(seconds)) {
201
+ const delay = normalizeDelay(seconds * 1000);
202
+ if (delay !== undefined) return delay;
203
+ }
204
+ const asDate = new Date(retryAfter).getTime();
205
+ if (!Number.isNaN(asDate)) {
206
+ const delay = normalizeDelay(asDate - Date.now());
207
+ if (delay !== undefined) return delay;
208
+ }
209
+ }
210
+
211
+ for (const header of ["x-ratelimit-reset-requests", "x-ratelimit-reset-tokens"]) {
212
+ const value = headers.get(header);
213
+ if (value) {
214
+ const resetSeconds = Number(value);
215
+ if (Number.isFinite(resetSeconds)) {
216
+ const delay = normalizeDelay(resetSeconds * 1000 - Date.now());
217
+ if (delay !== undefined) return delay;
218
+ }
219
+ }
220
+ }
221
+
222
+ return undefined;
223
+ }
224
+
225
+ export function normalizeToolCallId(id: string): string {
226
+ return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
227
+ }
228
+
229
+ export function convertMessages(
230
+ messages: Message[],
231
+ model: Model<AnthropicApi>,
232
+ isOAuthToken: boolean,
233
+ cacheControl?: { type: "ephemeral"; ttl?: "1h" },
234
+ ): MessageParam[] {
235
+ const params: MessageParam[] = [];
236
+
237
+ const transformedMessages = transformMessages(messages, model, normalizeToolCallId);
238
+
239
+ for (let i = 0; i < transformedMessages.length; i++) {
240
+ const msg = transformedMessages[i];
241
+
242
+ if (msg.role === "user") {
243
+ if (typeof msg.content === "string") {
244
+ if (msg.content.trim().length > 0) {
245
+ params.push({
246
+ role: "user",
247
+ content: sanitizeSurrogates(msg.content),
248
+ });
249
+ }
250
+ } else {
251
+ const blocks: ContentBlockParam[] = msg.content.map((item) => {
252
+ if (item.type === "text") {
253
+ return {
254
+ type: "text",
255
+ text: sanitizeSurrogates(item.text),
256
+ };
257
+ } else {
258
+ return {
259
+ type: "image",
260
+ source: {
261
+ type: "base64",
262
+ media_type: item.mimeType as "image/jpeg" | "image/png" | "image/gif" | "image/webp",
263
+ data: item.data,
264
+ },
265
+ };
266
+ }
267
+ });
268
+ let filteredBlocks = !model?.input.includes("image") ? blocks.filter((b) => b.type !== "image") : blocks;
269
+ filteredBlocks = filteredBlocks.filter((b) => {
270
+ if (b.type === "text") {
271
+ return b.text.trim().length > 0;
272
+ }
273
+ return true;
274
+ });
275
+ if (filteredBlocks.length === 0) continue;
276
+ params.push({
277
+ role: "user",
278
+ content: filteredBlocks,
279
+ });
280
+ }
281
+ } else if (msg.role === "assistant") {
282
+ const blocks: ContentBlockParam[] = [];
283
+
284
+ for (const block of msg.content) {
285
+ if (block.type === "text") {
286
+ if (block.text.trim().length === 0) continue;
287
+ blocks.push({
288
+ type: "text",
289
+ text: sanitizeSurrogates(block.text),
290
+ });
291
+ } else if (block.type === "thinking") {
292
+ if (block.redacted) {
293
+ blocks.push({
294
+ type: "redacted_thinking",
295
+ data: block.thinkingSignature!,
296
+ });
297
+ continue;
298
+ }
299
+ if (block.thinking.trim().length === 0) continue;
300
+ if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {
301
+ blocks.push({
302
+ type: "text",
303
+ text: sanitizeSurrogates(block.thinking),
304
+ });
305
+ } else {
306
+ blocks.push({
307
+ type: "thinking",
308
+ thinking: sanitizeSurrogates(block.thinking),
309
+ signature: block.thinkingSignature,
310
+ });
311
+ }
312
+ } else if (block.type === "toolCall") {
313
+ blocks.push({
314
+ type: "tool_use",
315
+ id: block.id,
316
+ name: isOAuthToken ? toClaudeCodeName(block.name) : block.name,
317
+ input: block.arguments ?? {},
318
+ });
319
+ } else if (block.type === "serverToolUse") {
320
+ blocks.push({
321
+ type: "server_tool_use",
322
+ id: block.id,
323
+ name: block.name,
324
+ input: block.input ?? {},
325
+ } as any);
326
+ } else if (block.type === "webSearchResult") {
327
+ blocks.push({
328
+ type: "web_search_tool_result",
329
+ tool_use_id: block.toolUseId,
330
+ content: block.content,
331
+ } as any);
332
+ }
333
+ }
334
+ if (blocks.length === 0) continue;
335
+ params.push({
336
+ role: "assistant",
337
+ content: blocks,
338
+ });
339
+ } else if (msg.role === "toolResult") {
340
+ const toolResults: ContentBlockParam[] = [];
341
+
342
+ toolResults.push({
343
+ type: "tool_result",
344
+ tool_use_id: msg.toolCallId,
345
+ content: convertContentBlocks(msg.content),
346
+ is_error: msg.isError,
347
+ });
348
+
349
+ let j = i + 1;
350
+ while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
351
+ const nextMsg = transformedMessages[j] as ToolResultMessage;
352
+ toolResults.push({
353
+ type: "tool_result",
354
+ tool_use_id: nextMsg.toolCallId,
355
+ content: convertContentBlocks(nextMsg.content),
356
+ is_error: nextMsg.isError,
357
+ });
358
+ j++;
359
+ }
360
+
361
+ i = j - 1;
362
+
363
+ params.push({
364
+ role: "user",
365
+ content: toolResults,
366
+ });
367
+ }
368
+ }
369
+
370
+ if (cacheControl && params.length > 0) {
371
+ const lastMessage = params[params.length - 1];
372
+ if (lastMessage.role === "user") {
373
+ if (Array.isArray(lastMessage.content)) {
374
+ const lastBlock = lastMessage.content[lastMessage.content.length - 1];
375
+ if (
376
+ lastBlock &&
377
+ (lastBlock.type === "text" || lastBlock.type === "image" || lastBlock.type === "tool_result")
378
+ ) {
379
+ (lastBlock as any).cache_control = cacheControl;
380
+ }
381
+ } else if (typeof lastMessage.content === "string") {
382
+ lastMessage.content = [
383
+ {
384
+ type: "text",
385
+ text: lastMessage.content,
386
+ cache_control: cacheControl,
387
+ },
388
+ ] as any;
389
+ }
390
+ }
391
+ }
392
+
393
+ return params;
394
+ }
395
+
396
+ export function convertTools(tools: Tool[], isOAuthToken: boolean): Anthropic.Messages.Tool[] {
397
+ if (!tools) return [];
398
+
399
+ return tools.map((tool) => {
400
+ const jsonSchema = tool.parameters as any;
401
+
402
+ return {
403
+ name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,
404
+ description: tool.description,
405
+ input_schema: {
406
+ type: "object" as const,
407
+ properties: jsonSchema.properties || {},
408
+ required: jsonSchema.required || [],
409
+ },
410
+ };
411
+ });
412
+ }
413
+
414
+ export function buildParams(
415
+ model: Model<AnthropicApi>,
416
+ context: Context,
417
+ isOAuthToken: boolean,
418
+ options?: AnthropicOptions,
419
+ ): MessageCreateParamsStreaming {
420
+ const { cacheControl } = getCacheControl(model.baseUrl, options?.cacheRetention);
421
+ const apiModelId = model.id.replace(/\[.*\]$/, "");
422
+ const params: MessageCreateParamsStreaming = {
423
+ model: apiModelId,
424
+ messages: convertMessages(context.messages, model, isOAuthToken, cacheControl),
425
+ max_tokens: options?.maxTokens || (model.maxTokens / 3) | 0,
426
+ stream: true,
427
+ };
428
+
429
+ if (isOAuthToken) {
430
+ params.system = [
431
+ {
432
+ type: "text",
433
+ text: "You are Claude Code, Anthropic's official CLI for Claude.",
434
+ ...(cacheControl ? { cache_control: cacheControl } : {}),
435
+ },
436
+ ];
437
+ if (context.systemPrompt) {
438
+ params.system.push({
439
+ type: "text",
440
+ text: sanitizeSurrogates(context.systemPrompt),
441
+ ...(cacheControl ? { cache_control: cacheControl } : {}),
442
+ });
443
+ }
444
+ } else if (context.systemPrompt) {
445
+ params.system = [
446
+ {
447
+ type: "text",
448
+ text: sanitizeSurrogates(context.systemPrompt),
449
+ ...(cacheControl ? { cache_control: cacheControl } : {}),
450
+ },
451
+ ];
452
+ }
453
+
454
+ if (options?.temperature !== undefined && !options?.thinkingEnabled) {
455
+ params.temperature = options.temperature;
456
+ }
457
+
458
+ if (context.tools) {
459
+ params.tools = convertTools(context.tools, isOAuthToken);
460
+ }
461
+
462
+ if (options?.thinkingEnabled && model.reasoning) {
463
+ if (supportsAdaptiveThinking(model.id)) {
464
+ params.thinking = { type: "adaptive" };
465
+ if (options.effort) {
466
+ params.output_config = { effort: options.effort };
467
+ }
468
+ } else {
469
+ params.thinking = {
470
+ type: "enabled",
471
+ budget_tokens: options.thinkingBudgetTokens || 1024,
472
+ };
473
+ }
474
+ }
475
+
476
+ if (options?.metadata) {
477
+ const userId = options.metadata.user_id;
478
+ if (typeof userId === "string") {
479
+ params.metadata = { user_id: userId };
480
+ }
481
+ }
482
+
483
+ if (options?.toolChoice) {
484
+ if (typeof options.toolChoice === "string") {
485
+ params.tool_choice = { type: options.toolChoice };
486
+ } else {
487
+ params.tool_choice = options.toolChoice;
488
+ }
489
+ }
490
+
491
+ return params;
492
+ }
493
+
494
+ export function mapStopReason(reason: string): StopReason {
495
+ switch (reason) {
496
+ case "end_turn":
497
+ return "stop";
498
+ case "max_tokens":
499
+ return "length";
500
+ case "tool_use":
501
+ return "toolUse";
502
+ case "refusal":
503
+ return "error";
504
+ case "pause_turn":
505
+ return "stop";
506
+ case "stop_sequence":
507
+ return "stop";
508
+ case "sensitive":
509
+ return "error";
510
+ default:
511
+ throw new Error(`Unhandled stop reason: ${reason}`);
512
+ }
513
+ }
514
+
515
+ export interface StreamAnthropicArgs {
516
+ client: Anthropic;
517
+ model: Model<AnthropicApi>;
518
+ context: Context;
519
+ isOAuthToken: boolean;
520
+ options?: AnthropicOptions;
521
+ AnthropicSdkClass?: typeof Anthropic;
522
+ }
523
+
524
+ export function processAnthropicStream(
525
+ stream: AssistantMessageEventStream,
526
+ args: StreamAnthropicArgs,
527
+ ): void {
528
+ const { client, model, context, isOAuthToken, options, AnthropicSdkClass } = args;
529
+
530
+ (async () => {
531
+ const output: AssistantMessage = {
532
+ role: "assistant",
533
+ content: [],
534
+ api: model.api as Api,
535
+ provider: model.provider,
536
+ model: model.id,
537
+ usage: {
538
+ input: 0,
539
+ output: 0,
540
+ cacheRead: 0,
541
+ cacheWrite: 0,
542
+ totalTokens: 0,
543
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
544
+ },
545
+ stopReason: "stop",
546
+ timestamp: Date.now(),
547
+ };
548
+
549
+ try {
550
+ let params = buildParams(model, context, isOAuthToken, options);
551
+ const nextParams = await options?.onPayload?.(params, model);
552
+ if (nextParams !== undefined) {
553
+ params = nextParams as MessageCreateParamsStreaming;
554
+ }
555
+ const anthropicStream = client.messages.stream({ ...params, stream: true }, { signal: options?.signal });
556
+ stream.push({ type: "start", partial: output });
557
+
558
+ type Block = (ThinkingContent | TextContent | (ToolCall & { partialJson: string }) | ServerToolUseContent | WebSearchResultContent) & { index: number };
559
+ const blocks = output.content as Block[];
560
+
561
+ for await (const event of anthropicStream) {
562
+ if (event.type === "message_start") {
563
+ output.usage.input = event.message.usage.input_tokens || 0;
564
+ output.usage.output = event.message.usage.output_tokens || 0;
565
+ output.usage.cacheRead = event.message.usage.cache_read_input_tokens || 0;
566
+ output.usage.cacheWrite = event.message.usage.cache_creation_input_tokens || 0;
567
+ output.usage.totalTokens =
568
+ output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
569
+ calculateCost(model, output.usage);
570
+ } else if (event.type === "content_block_start") {
571
+ if (event.content_block.type === "text") {
572
+ const block: Block = {
573
+ type: "text",
574
+ text: "",
575
+ index: event.index,
576
+ };
577
+ output.content.push(block);
578
+ stream.push({ type: "text_start", contentIndex: output.content.length - 1, partial: output });
579
+ } else if (event.content_block.type === "thinking") {
580
+ const block: Block = {
581
+ type: "thinking",
582
+ thinking: "",
583
+ thinkingSignature: "",
584
+ index: event.index,
585
+ };
586
+ output.content.push(block);
587
+ stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
588
+ } else if (event.content_block.type === "redacted_thinking") {
589
+ const block: Block = {
590
+ type: "thinking",
591
+ thinking: "[Reasoning redacted]",
592
+ thinkingSignature: event.content_block.data,
593
+ redacted: true,
594
+ index: event.index,
595
+ };
596
+ output.content.push(block);
597
+ stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
598
+ } else if (event.content_block.type === "tool_use") {
599
+ const block: Block = {
600
+ type: "toolCall",
601
+ id: event.content_block.id,
602
+ name: isOAuthToken
603
+ ? fromClaudeCodeName(event.content_block.name, context.tools)
604
+ : event.content_block.name,
605
+ arguments: (event.content_block.input as Record<string, any>) ?? {},
606
+ partialJson: "",
607
+ index: event.index,
608
+ };
609
+ output.content.push(block);
610
+ stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
611
+ } else if ((event.content_block as any).type === "server_tool_use") {
612
+ const serverBlock = event.content_block as any;
613
+ const block: Block = {
614
+ type: "serverToolUse",
615
+ id: serverBlock.id,
616
+ name: serverBlock.name,
617
+ input: serverBlock.input,
618
+ index: event.index,
619
+ };
620
+ output.content.push(block);
621
+ stream.push({ type: "server_tool_use", contentIndex: output.content.length - 1, partial: output });
622
+ } else if ((event.content_block as any).type === "web_search_tool_result") {
623
+ const resultBlock = event.content_block as any;
624
+ const block: Block = {
625
+ type: "webSearchResult",
626
+ toolUseId: resultBlock.tool_use_id,
627
+ content: resultBlock.content,
628
+ index: event.index,
629
+ };
630
+ output.content.push(block);
631
+ stream.push({ type: "web_search_result", contentIndex: output.content.length - 1, partial: output });
632
+ }
633
+ } else if (event.type === "content_block_delta") {
634
+ if (event.delta.type === "text_delta") {
635
+ const index = blocks.findIndex((b) => b.index === event.index);
636
+ const block = blocks[index];
637
+ if (block && block.type === "text") {
638
+ block.text += event.delta.text;
639
+ stream.push({
640
+ type: "text_delta",
641
+ contentIndex: index,
642
+ delta: event.delta.text,
643
+ partial: output,
644
+ });
645
+ }
646
+ } else if (event.delta.type === "thinking_delta") {
647
+ const index = blocks.findIndex((b) => b.index === event.index);
648
+ const block = blocks[index];
649
+ if (block && block.type === "thinking") {
650
+ block.thinking += event.delta.thinking;
651
+ stream.push({
652
+ type: "thinking_delta",
653
+ contentIndex: index,
654
+ delta: event.delta.thinking,
655
+ partial: output,
656
+ });
657
+ }
658
+ } else if (event.delta.type === "input_json_delta") {
659
+ const index = blocks.findIndex((b) => b.index === event.index);
660
+ const block = blocks[index];
661
+ if (block && block.type === "toolCall") {
662
+ block.partialJson += event.delta.partial_json;
663
+ block.arguments = parseStreamingJson(block.partialJson);
664
+ stream.push({
665
+ type: "toolcall_delta",
666
+ contentIndex: index,
667
+ delta: event.delta.partial_json,
668
+ partial: output,
669
+ });
670
+ }
671
+ } else if (event.delta.type === "signature_delta") {
672
+ const index = blocks.findIndex((b) => b.index === event.index);
673
+ const block = blocks[index];
674
+ if (block && block.type === "thinking") {
675
+ block.thinkingSignature = block.thinkingSignature || "";
676
+ block.thinkingSignature += event.delta.signature;
677
+ }
678
+ }
679
+ } else if (event.type === "content_block_stop") {
680
+ const index = blocks.findIndex((b) => b.index === event.index);
681
+ const block = blocks[index];
682
+ if (block) {
683
+ delete (block as any).index;
684
+ if (block.type === "text") {
685
+ stream.push({
686
+ type: "text_end",
687
+ contentIndex: index,
688
+ content: block.text,
689
+ partial: output,
690
+ });
691
+ } else if (block.type === "thinking") {
692
+ stream.push({
693
+ type: "thinking_end",
694
+ contentIndex: index,
695
+ content: block.thinking,
696
+ partial: output,
697
+ });
698
+ } else if (block.type === "toolCall") {
699
+ block.arguments = parseStreamingJson(block.partialJson);
700
+ delete (block as any).partialJson;
701
+ stream.push({
702
+ type: "toolcall_end",
703
+ contentIndex: index,
704
+ toolCall: block,
705
+ partial: output,
706
+ });
707
+ }
708
+ }
709
+ } else if (event.type === "message_delta") {
710
+ if (event.delta.stop_reason) {
711
+ output.stopReason = mapStopReason(event.delta.stop_reason);
712
+ }
713
+ if (event.usage.input_tokens != null) {
714
+ output.usage.input = event.usage.input_tokens;
715
+ }
716
+ if (event.usage.output_tokens != null) {
717
+ output.usage.output = event.usage.output_tokens;
718
+ }
719
+ if (event.usage.cache_read_input_tokens != null) {
720
+ output.usage.cacheRead = event.usage.cache_read_input_tokens;
721
+ }
722
+ if (event.usage.cache_creation_input_tokens != null) {
723
+ output.usage.cacheWrite = event.usage.cache_creation_input_tokens;
724
+ }
725
+ output.usage.totalTokens =
726
+ output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
727
+ calculateCost(model, output.usage);
728
+ }
729
+ }
730
+
731
+ if (options?.signal?.aborted) {
732
+ throw new Error("Request was aborted");
733
+ }
734
+
735
+ if (output.stopReason === "aborted" || output.stopReason === "error") {
736
+ throw new Error("An unknown error occurred");
737
+ }
738
+
739
+ stream.push({ type: "done", reason: output.stopReason, message: output });
740
+ stream.end();
741
+ } catch (error) {
742
+ for (const block of output.content) delete (block as any).index;
743
+ output.stopReason = options?.signal?.aborted ? "aborted" : "error";
744
+ output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
745
+ if (model.provider === "alibaba-coding-plan") {
746
+ output.errorMessage = `[alibaba-coding-plan] ${output.errorMessage}`;
747
+ }
748
+ if (AnthropicSdkClass && error instanceof AnthropicSdkClass.APIError && error.headers) {
749
+ const retryAfterMs = extractRetryAfterMs(error.headers, error.message);
750
+ if (retryAfterMs !== undefined) {
751
+ output.retryAfterMs = retryAfterMs;
752
+ }
753
+ }
754
+ if (isTransientNetworkError(error)) {
755
+ output.retryAfterMs = output.retryAfterMs ?? 5000;
756
+ }
757
+ stream.push({ type: "error", reason: output.stopReason, error: output });
758
+ stream.end();
759
+ }
760
+ })();
761
+ }