@iinm/plain-agent 1.0.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 (79) hide show
  1. package/.config/agents.library/code-simplifier.md +5 -0
  2. package/.config/agents.library/qa-engineer.md +74 -0
  3. package/.config/agents.library/software-architect.md +278 -0
  4. package/.config/agents.predefined/worker.md +3 -0
  5. package/.config/config.predefined.json +825 -0
  6. package/.config/prompts.library/code-review.md +8 -0
  7. package/.config/prompts.library/feature-dev.md +6 -0
  8. package/.config/prompts.predefined/shortcuts/commit-by-user.md +9 -0
  9. package/.config/prompts.predefined/shortcuts/commit.md +10 -0
  10. package/.config/prompts.predefined/shortcuts/general-question.md +6 -0
  11. package/LICENSE +21 -0
  12. package/README.md +624 -0
  13. package/bin/plain +3 -0
  14. package/bin/plain-interrupt +6 -0
  15. package/bin/plain-notify-desktop +19 -0
  16. package/bin/plain-notify-terminal-bell +3 -0
  17. package/package.json +57 -0
  18. package/sandbox/bin/plain-sandbox +972 -0
  19. package/src/agent.d.ts +48 -0
  20. package/src/agent.mjs +159 -0
  21. package/src/agentLoop.mjs +369 -0
  22. package/src/agentState.mjs +41 -0
  23. package/src/cliArgs.mjs +45 -0
  24. package/src/cliFormatter.mjs +217 -0
  25. package/src/cliInteractive.mjs +739 -0
  26. package/src/config.d.ts +48 -0
  27. package/src/config.mjs +168 -0
  28. package/src/context/consumeInterruptMessage.mjs +30 -0
  29. package/src/context/loadAgentRoles.mjs +272 -0
  30. package/src/context/loadPrompts.mjs +312 -0
  31. package/src/context/loadUserMessageContext.mjs +147 -0
  32. package/src/env.mjs +46 -0
  33. package/src/main.mjs +202 -0
  34. package/src/mcp.mjs +202 -0
  35. package/src/model.d.ts +109 -0
  36. package/src/modelCaller.mjs +29 -0
  37. package/src/modelDefinition.d.ts +73 -0
  38. package/src/prompt.mjs +128 -0
  39. package/src/providers/anthropic.d.ts +248 -0
  40. package/src/providers/anthropic.mjs +596 -0
  41. package/src/providers/gemini.d.ts +208 -0
  42. package/src/providers/gemini.mjs +752 -0
  43. package/src/providers/openai.d.ts +281 -0
  44. package/src/providers/openai.mjs +551 -0
  45. package/src/providers/openaiCompatible.d.ts +147 -0
  46. package/src/providers/openaiCompatible.mjs +658 -0
  47. package/src/providers/platform/azure.mjs +42 -0
  48. package/src/providers/platform/bedrock.mjs +74 -0
  49. package/src/providers/platform/googleCloud.mjs +34 -0
  50. package/src/subagent.mjs +247 -0
  51. package/src/tmpfile.mjs +27 -0
  52. package/src/tool.d.ts +74 -0
  53. package/src/toolExecutor.mjs +236 -0
  54. package/src/toolInputValidator.mjs +183 -0
  55. package/src/toolUseApprover.mjs +98 -0
  56. package/src/tools/askGoogle.mjs +135 -0
  57. package/src/tools/delegateToSubagent.d.ts +4 -0
  58. package/src/tools/delegateToSubagent.mjs +48 -0
  59. package/src/tools/execCommand.d.ts +22 -0
  60. package/src/tools/execCommand.mjs +200 -0
  61. package/src/tools/fetchWebPage.mjs +96 -0
  62. package/src/tools/patchFile.d.ts +4 -0
  63. package/src/tools/patchFile.mjs +96 -0
  64. package/src/tools/reportAsSubagent.d.ts +3 -0
  65. package/src/tools/reportAsSubagent.mjs +44 -0
  66. package/src/tools/tavilySearch.d.ts +6 -0
  67. package/src/tools/tavilySearch.mjs +57 -0
  68. package/src/tools/tmuxCommand.d.ts +14 -0
  69. package/src/tools/tmuxCommand.mjs +194 -0
  70. package/src/tools/writeFile.d.ts +4 -0
  71. package/src/tools/writeFile.mjs +56 -0
  72. package/src/utils/evalJSONConfig.mjs +48 -0
  73. package/src/utils/matchValue.d.ts +6 -0
  74. package/src/utils/matchValue.mjs +40 -0
  75. package/src/utils/noThrow.mjs +31 -0
  76. package/src/utils/notify.mjs +28 -0
  77. package/src/utils/parseFileRange.mjs +18 -0
  78. package/src/utils/readFileRange.mjs +33 -0
  79. package/src/utils/retryOnError.mjs +41 -0
@@ -0,0 +1,596 @@
1
+ /**
2
+ * @import { ModelInput, Message, AssistantMessage, ModelOutput, PartialMessageContent } from "../model";
3
+ * @import { AnthropicChatCompletion, AnthropicMessage, AnthropicToolDefinition, AnthropicModelConfig, AnthropicAssistantMessage, AnthropicStreamEvent, AnthropicAssistantMessageContent, AnthropicChatCompletionUsage, AnthropicRequestInput } from "./anthropic";
4
+ * @import { ToolDefinition } from "../tool";
5
+ */
6
+
7
+ import { styleText } from "node:util";
8
+ import { Sha256 } from "@aws-crypto/sha256-js";
9
+ import { fromIni } from "@aws-sdk/credential-providers";
10
+ import { HttpRequest } from "@smithy/protocol-http";
11
+ import { SignatureV4 } from "@smithy/signature-v4";
12
+ import { noThrow } from "../utils/noThrow.mjs";
13
+ import { readBedrockStreamEvents } from "./platform/bedrock.mjs";
14
+ import { getGoogleCloudAccessToken } from "./platform/googleCloud.mjs";
15
+
16
+ /**
17
+ * @param {import("../modelDefinition").PlatformConfig} platformConfig
18
+ * @param {AnthropicModelConfig} modelConfig
19
+ * @param {ModelInput} input
20
+ * @param {number} [retryCount]
21
+ * @returns {Promise<ModelOutput | Error>}
22
+ */
23
+ export async function callAnthropicModel(
24
+ platformConfig,
25
+ modelConfig,
26
+ input,
27
+ retryCount = 0,
28
+ ) {
29
+ return await noThrow(async () => {
30
+ const messages = convertGenericMessageToAnthropicFormat(input.messages);
31
+ const cacheEnabledMessages = enableContextCaching(messages);
32
+ const tools = convertGenericToolDefinitionToAnthropicFormat(
33
+ input.tools || [],
34
+ );
35
+
36
+ const url = (() => {
37
+ const baseURL = platformConfig.baseURL;
38
+
39
+ switch (platformConfig.name) {
40
+ case "anthropic":
41
+ return `${baseURL}/v1/messages`;
42
+ case "bedrock":
43
+ return `${baseURL}/model/${modelConfig.model}/invoke-with-response-stream`;
44
+ case "vertex-ai":
45
+ return `${baseURL}/publishers/anthropic/models/${modelConfig.model}:streamRawPredict`;
46
+ default:
47
+ throw new Error(`Unsupported platform: ${platformConfig.name}`);
48
+ }
49
+ })();
50
+
51
+ /** @type {Record<string,string>} */
52
+ const headers = await (async () => {
53
+ switch (platformConfig.name) {
54
+ case "anthropic":
55
+ return {
56
+ ...platformConfig.customHeaders,
57
+ "anthropic-version": "2023-06-01",
58
+ "x-api-key": `${platformConfig.apiKey}`,
59
+ };
60
+ case "bedrock":
61
+ return platformConfig.customHeaders ?? {};
62
+ case "vertex-ai":
63
+ return {
64
+ ...platformConfig.customHeaders,
65
+ Authorization: `Bearer ${await getGoogleCloudAccessToken()}`,
66
+ };
67
+ }
68
+ })();
69
+
70
+ const { model: _, ...modelConfigWithoutName } = modelConfig;
71
+ const platformRequest = (() => {
72
+ switch (platformConfig.name) {
73
+ case "anthropic":
74
+ return {
75
+ ...modelConfig,
76
+ stream: true,
77
+ };
78
+ case "bedrock":
79
+ return {
80
+ anthropic_version: "bedrock-2023-05-31",
81
+ ...modelConfigWithoutName,
82
+ };
83
+ case "vertex-ai":
84
+ return {
85
+ anthropic_version: "vertex-2023-10-16",
86
+ stream: true,
87
+ ...modelConfigWithoutName,
88
+ };
89
+ }
90
+ })();
91
+
92
+ /** @type {AnthropicRequestInput} */
93
+ const request = {
94
+ ...platformRequest,
95
+ system: messages
96
+ .filter((m) => m.role === "system")
97
+ .flatMap((m) => m.content),
98
+ messages: cacheEnabledMessages.filter((m) => m.role !== "system"),
99
+ tools: tools.length ? tools : undefined,
100
+ };
101
+
102
+ const runFetchDefault = async () =>
103
+ fetch(url, {
104
+ method: "POST",
105
+ headers: {
106
+ ...headers,
107
+ "Content-Type": "application/json",
108
+ },
109
+ body: JSON.stringify(request),
110
+ signal: AbortSignal.timeout(4 * 60 * 1000),
111
+ });
112
+
113
+ // bedrock + sso profile
114
+ const runFetchForBedrock = async () => {
115
+ const region =
116
+ url.match(/bedrock-runtime\.([\w-]+)\.amazonaws\.com/)?.[1] ?? "";
117
+ const urlParsed = new URL(url);
118
+ const { hostname, pathname } = urlParsed;
119
+
120
+ const signer = new SignatureV4({
121
+ credentials: fromIni({
122
+ profile:
123
+ platformConfig.name === "bedrock" ? platformConfig.awsProfile : "",
124
+ }),
125
+ region,
126
+ service: "bedrock",
127
+ sha256: Sha256,
128
+ });
129
+
130
+ const req = new HttpRequest({
131
+ protocol: "https:",
132
+ method: "POST",
133
+ hostname,
134
+ path: pathname,
135
+ headers: {
136
+ host: hostname,
137
+ "Content-Type": "application/json",
138
+ },
139
+ body: JSON.stringify(request),
140
+ });
141
+
142
+ const signed = await signer.sign(req);
143
+
144
+ return fetch(url, {
145
+ method: signed.method,
146
+ headers: signed.headers,
147
+ body: signed.body,
148
+ signal: AbortSignal.timeout(4 * 60 * 1000),
149
+ });
150
+ };
151
+
152
+ const runFetch =
153
+ platformConfig.name === "bedrock" ? runFetchForBedrock : runFetchDefault;
154
+
155
+ const response = await runFetch();
156
+
157
+ if (response.status === 429 || response.status >= 500) {
158
+ const interval = Math.min(2 * 2 ** retryCount, 16);
159
+ console.error(
160
+ styleText(
161
+ "yellow",
162
+ `Anthropic rate limit exceeded. Retry in ${interval} seconds...`,
163
+ ),
164
+ );
165
+ await new Promise((resolve) => setTimeout(resolve, interval * 1000));
166
+ return callAnthropicModel(
167
+ platformConfig,
168
+ modelConfig,
169
+ input,
170
+ retryCount + 1,
171
+ );
172
+ }
173
+
174
+ if (response.status !== 200) {
175
+ return new Error(
176
+ `Failed to call Anthropic model: status=${response.status}, body=${await response.text()}`,
177
+ );
178
+ }
179
+
180
+ if (!response.body) {
181
+ throw new Error("Response body is empty");
182
+ }
183
+
184
+ const reader = response.body.getReader();
185
+ const eventStreamReader =
186
+ platformConfig.name === "bedrock"
187
+ ? /** @type {typeof readAnthropicStreamEvents} */ (
188
+ readBedrockStreamEvents
189
+ )
190
+ : readAnthropicStreamEvents;
191
+
192
+ /** @type {AnthropicStreamEvent[]} */
193
+ const events = [];
194
+ /** @type {PartialMessageContent | undefined} */
195
+ let previousPartialContent;
196
+ for await (const event of eventStreamReader(reader)) {
197
+ events.push(event);
198
+
199
+ const partialContent = convertAnthropicStreamEventToAgentPartialContent(
200
+ event,
201
+ previousPartialContent,
202
+ );
203
+
204
+ if (partialContent) {
205
+ previousPartialContent = partialContent;
206
+ }
207
+
208
+ if (input.onPartialMessageContent && partialContent) {
209
+ input.onPartialMessageContent(partialContent);
210
+ }
211
+ }
212
+
213
+ /** @type {AnthropicChatCompletion} */
214
+ const chatCompletion = convertAnthropicStreamEventsToChatCompletion(events);
215
+
216
+ return {
217
+ message: convertAnthropicAssistantMessageToGenericFormat(chatCompletion),
218
+ providerTokenUsage: chatCompletion.usage,
219
+ };
220
+ });
221
+ }
222
+
223
+ /**
224
+ * @param {Message[]} genericMessages
225
+ * @returns {AnthropicMessage[]}
226
+ */
227
+ function convertGenericMessageToAnthropicFormat(genericMessages) {
228
+ /** @type {AnthropicMessage[]} */
229
+ const anthropicMessages = [];
230
+ for (const genericMessage of genericMessages) {
231
+ switch (genericMessage.role) {
232
+ case "system": {
233
+ anthropicMessages.push({
234
+ role: "system",
235
+ content: genericMessage.content.map((part) => {
236
+ if (part.type === "text") {
237
+ return { type: "text", text: part.text };
238
+ }
239
+ throw new Error(
240
+ `Unsupported content part: ${JSON.stringify(part)}`,
241
+ );
242
+ }),
243
+ });
244
+ break;
245
+ }
246
+ case "user": {
247
+ anthropicMessages.push({
248
+ role: "user",
249
+ content: genericMessage.content.map((part) => {
250
+ if (part.type === "text") {
251
+ return { type: "text", text: part.text };
252
+ }
253
+ if (part.type === "image") {
254
+ return {
255
+ type: "image",
256
+ source: {
257
+ type: "base64",
258
+ media_type: part.mimeType,
259
+ data: part.data,
260
+ },
261
+ };
262
+ }
263
+ if (part.type === "tool_result") {
264
+ return {
265
+ type: "tool_result",
266
+ tool_use_id: part.toolUseId,
267
+ content: part.content.map((contentPart) => {
268
+ switch (contentPart.type) {
269
+ case "text":
270
+ return { type: "text", text: contentPart.text };
271
+ case "image":
272
+ return {
273
+ type: "image",
274
+ source: {
275
+ type: "base64",
276
+ media_type: contentPart.mimeType,
277
+ data: contentPart.data,
278
+ },
279
+ };
280
+ default:
281
+ throw new Error(
282
+ `Unsupported content part: ${JSON.stringify(contentPart)}`,
283
+ );
284
+ }
285
+ }),
286
+ is_error: part.isError,
287
+ };
288
+ }
289
+ throw new Error(
290
+ `Unsupported content part: ${JSON.stringify(part)}`,
291
+ );
292
+ }),
293
+ });
294
+ break;
295
+ }
296
+ case "assistant": {
297
+ anthropicMessages.push({
298
+ role: "assistant",
299
+ content: genericMessage.content.map((part) => {
300
+ if (part.type === "thinking") {
301
+ const signature = /** @type {string} */ (
302
+ part.provider?.fields?.signature
303
+ );
304
+ return {
305
+ type: "thinking",
306
+ thinking: part.thinking,
307
+ signature,
308
+ };
309
+ }
310
+ if (part.type === "redacted_thinking") {
311
+ const data = /** @type {string} */ (part.provider?.fields?.data);
312
+ return {
313
+ type: "redacted_thinking",
314
+ data,
315
+ };
316
+ }
317
+ if (part.type === "text") {
318
+ return { type: "text", text: part.text };
319
+ }
320
+ if (part.type === "tool_use") {
321
+ return {
322
+ type: "tool_use",
323
+ id: part.toolUseId,
324
+ name: part.toolName,
325
+ input: part.input,
326
+ };
327
+ }
328
+ throw new Error(`Unknown message part type: ${part}`);
329
+ }),
330
+ });
331
+ break;
332
+ }
333
+ }
334
+ }
335
+
336
+ return anthropicMessages;
337
+ }
338
+
339
+ /**
340
+ * @param {ToolDefinition[]} genericToolDefs
341
+ * @returns {AnthropicToolDefinition[]}
342
+ */
343
+ function convertGenericToolDefinitionToAnthropicFormat(genericToolDefs) {
344
+ /** @type {AnthropicToolDefinition[]} */
345
+ const anthropicToolDefs = [];
346
+ for (const tool of genericToolDefs) {
347
+ anthropicToolDefs.push({
348
+ name: tool.name,
349
+ description: tool.description,
350
+ input_schema: tool.inputSchema,
351
+ });
352
+ }
353
+ return anthropicToolDefs;
354
+ }
355
+
356
+ /**
357
+ * @param {AnthropicAssistantMessage} anthropicAssistantMessage
358
+ * @returns {AssistantMessage}
359
+ */
360
+ function convertAnthropicAssistantMessageToGenericFormat(
361
+ anthropicAssistantMessage,
362
+ ) {
363
+ /** @type {AssistantMessage["content"]} */
364
+ const content = [];
365
+ for (const part of anthropicAssistantMessage.content) {
366
+ if (part.type === "thinking") {
367
+ content.push({
368
+ type: "thinking",
369
+ thinking: part.thinking,
370
+ provider: { fields: { signature: part.signature } },
371
+ });
372
+ } else if (part.type === "redacted_thinking") {
373
+ content.push({
374
+ type: "redacted_thinking",
375
+ provider: { fields: { data: part.data } },
376
+ });
377
+ } else if (part.type === "text") {
378
+ content.push({ type: "text", text: part.text });
379
+ } else if (part.type === "tool_use") {
380
+ content.push({
381
+ type: "tool_use",
382
+ toolUseId: part.id,
383
+ toolName: part.name,
384
+ input: part.input,
385
+ });
386
+ }
387
+ }
388
+
389
+ return {
390
+ role: "assistant",
391
+ content,
392
+ };
393
+ }
394
+
395
+ /**
396
+ * @param {AnthropicStreamEvent[]} events
397
+ * @returns {AnthropicChatCompletion}
398
+ */
399
+ function convertAnthropicStreamEventsToChatCompletion(events) {
400
+ /** @type {Partial<AnthropicChatCompletion>} */
401
+ let chatCompletion = {};
402
+ /** @type {string[]} */
403
+ const toolUseInputJsonBuffer = [];
404
+ for (const event of events) {
405
+ if (event.type === "message_start") {
406
+ chatCompletion = Object.assign(chatCompletion, event.message);
407
+ } else if (event.type === "message_delta") {
408
+ Object.assign(chatCompletion, event.delta);
409
+ if (event.usage?.output_tokens) {
410
+ const usage = /** @type {AnthropicChatCompletionUsage} */ (
411
+ chatCompletion.usage || {}
412
+ );
413
+ usage.output_tokens += event.usage.output_tokens;
414
+ chatCompletion.usage = usage;
415
+ }
416
+ } else if (event.type === "content_block_start") {
417
+ chatCompletion.content = chatCompletion.content || [];
418
+ chatCompletion.content.push(
419
+ /** @type {AnthropicAssistantMessageContent} */ (event.content_block),
420
+ );
421
+ } else if (event.type === "content_block_delta") {
422
+ const lastContentPart = chatCompletion.content?.at(-1);
423
+ if (lastContentPart) {
424
+ switch (event.delta.type) {
425
+ case "text_delta": {
426
+ if (lastContentPart.type === "text") {
427
+ lastContentPart.text = lastContentPart.text + event.delta.text;
428
+ }
429
+ break;
430
+ }
431
+ case "thinking_delta": {
432
+ if (lastContentPart.type === "thinking") {
433
+ lastContentPart.thinking =
434
+ lastContentPart.thinking + event.delta.thinking;
435
+ }
436
+ break;
437
+ }
438
+ case "signature_delta": {
439
+ if (lastContentPart.type === "thinking") {
440
+ lastContentPart.signature = event.delta.signature;
441
+ }
442
+ break;
443
+ }
444
+ case "input_json_delta": {
445
+ if (lastContentPart.type === "tool_use") {
446
+ toolUseInputJsonBuffer.push(event.delta.partial_json);
447
+ }
448
+ break;
449
+ }
450
+ }
451
+ } else {
452
+ console.error(
453
+ `Received content block delta without a content block: ${JSON.stringify(event)}`,
454
+ );
455
+ }
456
+ } else if (event.type === "content_block_stop") {
457
+ const lastContentPart = chatCompletion.content?.at(-1);
458
+ if (lastContentPart?.type === "tool_use") {
459
+ lastContentPart.input = JSON.parse(
460
+ toolUseInputJsonBuffer.join("") || "{}",
461
+ );
462
+ toolUseInputJsonBuffer.length = 0;
463
+ }
464
+ }
465
+ }
466
+
467
+ return /** @type {AnthropicChatCompletion} */ (chatCompletion);
468
+ }
469
+
470
+ /**
471
+ * @param {AnthropicStreamEvent} event
472
+ * @param {PartialMessageContent | undefined} previousPartialContent
473
+ * @returns {PartialMessageContent | undefined}
474
+ */
475
+ function convertAnthropicStreamEventToAgentPartialContent(
476
+ event,
477
+ previousPartialContent,
478
+ ) {
479
+ switch (event.type) {
480
+ case "content_block_start":
481
+ return {
482
+ type: event.content_block.type,
483
+ position: "start",
484
+ };
485
+ case "content_block_delta":
486
+ switch (event.delta.type) {
487
+ case "text_delta":
488
+ return {
489
+ type: "text",
490
+ content: event.delta.text,
491
+ position: "delta",
492
+ };
493
+ case "thinking_delta":
494
+ return {
495
+ type: "thinking",
496
+ content: event.delta.thinking,
497
+ position: "delta",
498
+ };
499
+ case "input_json_delta":
500
+ return {
501
+ type: "tool_use",
502
+ content: event.delta.partial_json,
503
+ position: "delta",
504
+ };
505
+ }
506
+ break;
507
+ case "content_block_stop":
508
+ return {
509
+ type: previousPartialContent?.type || "unknown",
510
+ position: "stop",
511
+ };
512
+ }
513
+ }
514
+
515
+ /**
516
+ * @param {AnthropicMessage[]} messages
517
+ * @returns {AnthropicMessage[]}
518
+ */
519
+ function enableContextCaching(messages) {
520
+ /** @type {number[]} */
521
+ const userMessageIndices = [];
522
+ for (let i = 0; i < messages.length; i++) {
523
+ if (messages[i].role === "user") {
524
+ userMessageIndices.push(i);
525
+ }
526
+ }
527
+ const cacheTargetIndices = [
528
+ // last user message
529
+ userMessageIndices.at(-1),
530
+ // second last user message
531
+ userMessageIndices.at(-2),
532
+ ].filter((index) => index !== undefined);
533
+
534
+ const contextCachingEnabledMessages = messages.map((message, index) => {
535
+ if (
536
+ (index === 0 && message.role === "system") ||
537
+ cacheTargetIndices.includes(index)
538
+ ) {
539
+ return {
540
+ ...message,
541
+ content: message.content.map((part, partIndex) =>
542
+ partIndex === message.content.length - 1
543
+ ? { ...part, cache_control: { type: "ephemeral" } }
544
+ : part,
545
+ ),
546
+ };
547
+ }
548
+ return message;
549
+ });
550
+
551
+ return /** @type {AnthropicMessage[]} */ (contextCachingEnabledMessages);
552
+ }
553
+
554
+ /**
555
+ * @param {ReadableStreamDefaultReader<Uint8Array>} reader
556
+ */
557
+ async function* readAnthropicStreamEvents(reader) {
558
+ let buffer = new Uint8Array();
559
+
560
+ while (true) {
561
+ const { done, value } = await reader.read();
562
+ if (done) {
563
+ break;
564
+ }
565
+
566
+ const nextBuffer = new Uint8Array(buffer.length + value.length);
567
+ nextBuffer.set(buffer);
568
+ nextBuffer.set(value, buffer.length);
569
+ buffer = nextBuffer;
570
+
571
+ const lineFeed = "\n".charCodeAt(0);
572
+ const eventEndIndices = [];
573
+ for (let i = 0; i < buffer.length - 1; i++) {
574
+ if (buffer[i] === lineFeed && buffer[i + 1] === lineFeed) {
575
+ eventEndIndices.push(i);
576
+ }
577
+ }
578
+
579
+ for (let i = 0; i < eventEndIndices.length; i++) {
580
+ const eventStartIndex = i === 0 ? 0 : eventEndIndices[i - 1] + 2;
581
+ const eventEndIndex = eventEndIndices[i];
582
+ const event = buffer.slice(eventStartIndex, eventEndIndex);
583
+ const decodedEvent = new TextDecoder().decode(event);
584
+ const data = decodedEvent.split("\n").at(-1);
585
+ if (data?.startsWith("data: ")) {
586
+ /** @type {AnthropicStreamEvent} */
587
+ const parsedData = JSON.parse(data.slice("data: ".length));
588
+ yield parsedData;
589
+ }
590
+ }
591
+
592
+ if (eventEndIndices.length) {
593
+ buffer = buffer.slice(eventEndIndices[eventEndIndices.length - 1] + 2);
594
+ }
595
+ }
596
+ }