diff --git a/guides/ai-transport/.env.example b/guides/ai-transport/.env.example new file mode 100644 index 0000000000..b81e5dbda9 --- /dev/null +++ b/guides/ai-transport/.env.example @@ -0,0 +1,10 @@ +# Ably API key (required for all guides) +ABLY_API_KEY= + +# OpenAI API key (required for openai-* guides) +OPENAI_API_KEY= + +# Anthropic API key (required for anthropic-* and lang-graph-* guides) +ANTHROPIC_API_KEY= + +# Vercel AI SDK uses the underlying provider's key (e.g. OPENAI_API_KEY) diff --git a/guides/ai-transport/.gitignore b/guides/ai-transport/.gitignore new file mode 100644 index 0000000000..713d5006da --- /dev/null +++ b/guides/ai-transport/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +.env diff --git a/guides/ai-transport/README.md b/guides/ai-transport/README.md new file mode 100644 index 0000000000..49009fd285 --- /dev/null +++ b/guides/ai-transport/README.md @@ -0,0 +1,73 @@ +# AI Transport Guides - E2E Code + +Full, runnable implementations for each AI Transport guide. Each guide directory contains language-specific subdirectories with publisher (agent) and subscriber (client) code. + +## Structure + +``` +/ + javascript/ # JavaScript/TypeScript implementation + src/publisher.ts + src/subscriber.ts + test/e2e.test.ts + python/ # (future) + java/ # (future) +``` + +## Guides + +| Guide | Provider | Pattern | Languages | +|-------|----------|---------|-----------| +| `openai-message-per-token` | OpenAI | Message per token | JavaScript | +| `openai-message-per-response` | OpenAI | Message per response | JavaScript | +| `anthropic-message-per-token` | Anthropic | Message per token | JavaScript | +| `anthropic-message-per-response` | Anthropic | Message per response | JavaScript | +| `vercel-message-per-token` | Vercel AI SDK | Message per token | JavaScript | +| `vercel-message-per-response` | Vercel AI SDK | Message per response | JavaScript | +| `lang-graph-message-per-token` | LangGraph | Message per token | JavaScript | +| `lang-graph-message-per-response` | LangGraph | Message per response | JavaScript | + +## Streaming patterns + +- **Message per token**: Publisher sends discrete `start`, `token`, and `stop` events. Subscriber reconstructs the full response by appending tokens correlated by `responseId`. +- **Message per response**: Publisher creates a single message and appends each token to it. Subscriber handles `message.create` and `message.append` actions, with the full response available in message history. + +## Prerequisites + +- Node.js 20+ +- API keys for the relevant providers (see `.env.example`) + +## Setup + +```bash +cd guides/ai-transport +cp .env.example .env +# Fill in your API keys in .env + +yarn install +``` + +## Running a guide + +Each guide has a publisher (streams from LLM to Ably) and a subscriber (reads from Ably): + +```bash +# Terminal 1 - start the subscriber +npx tsx /javascript/src/subscriber.ts + +# Terminal 2 - start the publisher +npx tsx /javascript/src/publisher.ts +``` + +## Running tests + +```bash +# All guides +yarn test + +# Watch mode +yarn test:watch + +# Single guide +npx vitest run +``` diff --git a/guides/ai-transport/anthropic-citations/javascript/package.json b/guides/ai-transport/anthropic-citations/javascript/package.json new file mode 100644 index 0000000000..d051b2467f --- /dev/null +++ b/guides/ai-transport/anthropic-citations/javascript/package.json @@ -0,0 +1,14 @@ +{ + "name": "guide-anthropic-citations", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start:agent": "tsx src/agent.ts", + "start:client": "tsx src/client.ts", + "test": "vitest run" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.71" + } +} diff --git a/guides/ai-transport/anthropic-citations/javascript/src/agent.ts b/guides/ai-transport/anthropic-citations/javascript/src/agent.ts new file mode 100644 index 0000000000..908933de8d --- /dev/null +++ b/guides/ai-transport/anthropic-citations/javascript/src/agent.ts @@ -0,0 +1,140 @@ +import Anthropic from '@anthropic-ai/sdk'; +import Ably from 'ably'; + +interface Citation { + type: string; + cited_text: string; + document_index: number; + document_title: string; + start_char_index?: number; + end_char_index?: number; + start_page_number?: number; + end_page_number?: number; + source?: string; + responseStartOffset: number; + responseEndOffset: number; +} + +export async function publish( + channel: Ably.RealtimeChannel, + question: string, + documentContent: string, +): Promise<{ msgSerial: string; citations: Citation[] }> { + const anthropic = new Anthropic(); + + const response = await anthropic.messages.create({ + model: 'claude-sonnet-4-5', + max_tokens: 1024, + messages: [ + { + role: 'user', + content: [ + { + type: 'document', + source: { + type: 'text', + media_type: 'text/plain', + data: documentContent, + }, + title: 'Source Document', + citations: { enabled: true }, + }, + { + type: 'text', + text: question, + }, + ], + }, + ], + }); + + let fullText = ''; + const citations: Citation[] = []; + let currentOffset = 0; + + // Extract text and citations from response + for (const block of response.content) { + if (block.type === 'text') { + const text = block.text; + + if ('citations' in block && Array.isArray(block.citations)) { + for (const citation of block.citations as unknown as Record[]) { + citations.push({ + type: citation.type as string, + cited_text: citation.cited_text as string, + document_index: citation.document_index as number, + document_title: citation.document_title as string, + start_char_index: citation.start_char_index as number | undefined, + end_char_index: citation.end_char_index as number | undefined, + start_page_number: citation.start_page_number as number | undefined, + end_page_number: citation.end_page_number as number | undefined, + source: citation.source as string | undefined, + // Track position in the full response text + responseStartOffset: currentOffset, + responseEndOffset: currentOffset + text.length, + }); + } + } + + fullText += text; + currentOffset += text.length; + } + } + + const result = await channel.publish('response', fullText); + const msgSerial = result.serials[0]!; + + // Publish each citation as an annotation + for (const citation of citations) { + let sourceDomain; + try { + sourceDomain = citation.source ? new URL(citation.source).hostname : citation.document_title; + } catch { + sourceDomain = citation.document_title || 'document'; + } + + await channel.annotations.publish(msgSerial, { + type: 'citations:multiple.v1', + name: sourceDomain, + data: { + title: citation.document_title, + citedText: citation.cited_text, + citationType: citation.type, + startOffset: citation.responseStartOffset, + endOffset: citation.responseEndOffset, + documentIndex: citation.document_index, + ...(citation.start_char_index !== undefined && { + startCharIndex: citation.start_char_index, + endCharIndex: citation.end_char_index, + }), + ...(citation.start_page_number !== undefined && { + startPageNumber: citation.start_page_number, + endPageNumber: citation.end_page_number, + }), + }, + }); + } + + return { msgSerial, citations }; +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + const channel = realtime.channels.get('ai:anthropic-citations-guide'); + + const document = `The James Webb Space Telescope (JWST) is a space telescope designed to conduct infrared astronomy. Its high-resolution and high-sensitivity instruments allow it to view objects too old and distant for the Hubble Space Telescope. This enables investigations across many fields of astronomy and cosmology, such as observation of the first stars and the formation of the first galaxies.`; + + const { msgSerial, citations } = await publish( + channel, + 'What is the JWST designed for? Cite your sources.', + document, + ); + console.log('Message serial:', msgSerial); + console.log('Citations:', citations.length); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/anthropic-citations/javascript/src/client.ts b/guides/ai-transport/anthropic-citations/javascript/src/client.ts new file mode 100644 index 0000000000..37e93796c9 --- /dev/null +++ b/guides/ai-transport/anthropic-citations/javascript/src/client.ts @@ -0,0 +1,64 @@ +import Ably from 'ably'; + +interface CitationSummaryResult { + content: string; + citationSummary: Record; +} + +export function subscribe(channel: Ably.RealtimeChannel): Promise { + return new Promise((resolve) => { + let content = ''; + let citationSummary: Record = {}; + let doneTimer: ReturnType | null = null; + + const resetTimer = () => { + if (doneTimer) { + clearTimeout(doneTimer); + } + doneTimer = setTimeout(() => { + resolve({ content, citationSummary }); + }, 3000); + }; + + channel.subscribe((message: Ably.Message) => { + const serial = message.serial; + if (!serial) { + return; + } + + switch (message.action) { + case 'message.create': + console.log('\n[Response received]', serial); + content = message.data as string; + resetTimer(); + break; + + case 'message.summary': + if (message.annotations?.summary?.['citations:multiple.v1']) { + citationSummary = message.annotations.summary['citations:multiple.v1'] as Record< + string, + { total: number } + >; + console.log('\n[Citation summary received]', citationSummary); + } + resetTimer(); + break; + } + }); + }); +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + const channel = realtime.channels.get('ai:anthropic-citations-guide'); + console.log('Client ready, waiting for response and citations...'); + const { content, citationSummary } = await subscribe(channel); + console.log('\nFull response:', content); + console.log('Citation summary:', citationSummary); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/anthropic-citations/javascript/test/e2e.test.ts b/guides/ai-transport/anthropic-citations/javascript/test/e2e.test.ts new file mode 100644 index 0000000000..4e54a02db7 --- /dev/null +++ b/guides/ai-transport/anthropic-citations/javascript/test/e2e.test.ts @@ -0,0 +1,58 @@ +import Ably from 'ably'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { publish } from '../src/agent.js'; +import { subscribe } from '../src/client.js'; +import { waitForMessage } from '../../../test-helpers.js'; + +const document = + 'Ably provides realtime messaging infrastructure. It supports pub/sub, presence, and history features.'; + +describe('anthropic-citations', () => { + let publisherClient: Ably.Realtime; + let subscriberClient: Ably.Realtime; + let channelName: string; + + beforeAll(() => { + channelName = `ai:test-anthropic-citations-${Date.now()}`; + publisherClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + subscriberClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + }); + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + publisherClient?.close(); + subscriberClient?.close(); + }); + + it('publishes response and citation annotations', async () => { + const subChannel = subscriberClient.channels.get(channelName + '-annotations'); + const pubChannel = publisherClient.channels.get(channelName + '-annotations'); + + const responsePromise = subscribe(subChannel); + await subChannel.attach(); + + const summaryReceived = waitForMessage(subChannel, (m) => m.action === 'message.summary'); + + await publish(pubChannel, 'What features does Ably provide? Cite your sources.', document); + await summaryReceived; + + const { content, citationSummary } = await responsePromise; + + expect(content.length).toBeGreaterThan(0); + expect(Object.keys(citationSummary).length).toBeGreaterThan(0); + }); + + it('citations reference the source document', async () => { + const pubChannel = publisherClient.channels.get(channelName + '-doc-ref'); + + const { msgSerial, citations } = await publish( + pubChannel, + 'What features does Ably provide? Cite your sources.', + document, + ); + + expect(msgSerial).toBeDefined(); + expect(citations.length).toBeGreaterThan(0); + expect(citations[0].document_title).toBe('Source Document'); + }); +}); diff --git a/guides/ai-transport/anthropic-citations/javascript/tsconfig.json b/guides/ai-transport/anthropic-citations/javascript/tsconfig.json new file mode 100644 index 0000000000..6ece4d3313 --- /dev/null +++ b/guides/ai-transport/anthropic-citations/javascript/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/guides/ai-transport/anthropic-human-in-the-loop/javascript/package.json b/guides/ai-transport/anthropic-human-in-the-loop/javascript/package.json new file mode 100644 index 0000000000..c35f2f450f --- /dev/null +++ b/guides/ai-transport/anthropic-human-in-the-loop/javascript/package.json @@ -0,0 +1,14 @@ +{ + "name": "guide-anthropic-human-in-the-loop", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start:agent": "tsx src/agent.ts", + "start:approver": "tsx src/approver.ts", + "test": "vitest run" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.71" + } +} diff --git a/guides/ai-transport/anthropic-human-in-the-loop/javascript/src/agent.ts b/guides/ai-transport/anthropic-human-in-the-loop/javascript/src/agent.ts new file mode 100644 index 0000000000..c561bbf677 --- /dev/null +++ b/guides/ai-transport/anthropic-human-in-the-loop/javascript/src/agent.ts @@ -0,0 +1,98 @@ +import Anthropic from '@anthropic-ai/sdk'; +import Ably from 'ably'; + +export async function runAgent(channel: Ably.RealtimeChannel, prompt: string) { + const anthropic = new Anthropic(); + + const response = await anthropic.messages.create({ + model: 'claude-sonnet-4-5', + max_tokens: 1024, + tools: [ + { + name: 'publish_blog_post', + description: 'Publish a blog post to the website. Requires human approval.', + input_schema: { + type: 'object' as const, + properties: { + title: { + type: 'string', + description: 'Title of the blog post to publish', + }, + }, + required: ['title'], + }, + }, + ], + messages: [{ role: 'user', content: prompt }], + }); + + for (const block of response.content) { + if (block.type === 'tool_use') { + const toolUse = block; + + // Set up listener for approval response before publishing request + const approvalPromise = new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + channel.unsubscribe('approval-response', listener); + reject(new Error('Approval response timed out')); + }, 30000); + + const listener = (message: Ably.Message) => { + if (message.extras?.headers?.toolCallId === toolUse.id) { + clearTimeout(timeout); + channel.unsubscribe('approval-response', listener); + resolve(message); + } + }; + + channel.subscribe('approval-response', listener); + }); + + // Publish approval request to channel + await channel.publish({ + name: 'approval-request', + data: { + tool: toolUse.name, + arguments: toolUse.input, + }, + extras: { + headers: { + toolCallId: toolUse.id, + }, + }, + }); + + const approvalResponse = await approvalPromise; + + const decision = (approvalResponse.data as { decision: string }).decision; + + if (decision === 'approved') { + // Simulate tool execution + const input = toolUse.input as { title: string }; + return { published: true, title: input.title }; + } else { + throw new Error(`Tool call rejected: ${toolUse.name}`); + } + } + } + + // No tool use in response - return the text content + const textBlock = response.content.find((b) => b.type === 'text'); + return { published: false, text: textBlock?.type === 'text' ? textBlock.text : '' }; +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + const channel = realtime.channels.get('ai:anthropic-hitl-guide'); + try { + const result = await runAgent(channel, 'Publish a blog post called "Hello World"'); + console.log('Result:', result); + } finally { + realtime.close(); + } +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/anthropic-human-in-the-loop/javascript/src/approver.ts b/guides/ai-transport/anthropic-human-in-the-loop/javascript/src/approver.ts new file mode 100644 index 0000000000..bd578706a1 --- /dev/null +++ b/guides/ai-transport/anthropic-human-in-the-loop/javascript/src/approver.ts @@ -0,0 +1,81 @@ +import Ably from 'ably'; + +export function autoApprove(channel: Ably.RealtimeChannel): () => void { + const listener = (message: Ably.Message) => { + const toolCallId = message.extras?.headers?.toolCallId; + channel.publish({ + name: 'approval-response', + data: { decision: 'approved' }, + extras: { + headers: { + toolCallId, + }, + }, + }); + }; + + channel.subscribe('approval-request', listener); + + return () => { + channel.unsubscribe('approval-request', listener); + }; +} + +export function autoReject(channel: Ably.RealtimeChannel): () => void { + const listener = (message: Ably.Message) => { + const toolCallId = message.extras?.headers?.toolCallId; + channel.publish({ + name: 'approval-response', + data: { decision: 'rejected' }, + extras: { + headers: { + toolCallId, + }, + }, + }); + }; + + channel.subscribe('approval-request', listener); + + return () => { + channel.unsubscribe('approval-request', listener); + }; +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + const channel = realtime.channels.get('ai:anthropic-hitl-guide'); + + const { createInterface } = await import('readline'); + const rl = createInterface({ input: process.stdin, output: process.stdout }); + + console.log('Approver ready, waiting for approval requests...'); + + channel.subscribe('approval-request', (message: Ably.Message) => { + const data = message.data as { tool: string; arguments: Record }; + const toolCallId = message.extras?.headers?.toolCallId; + + console.log(`\nApproval request received:`); + console.log(` Tool: ${data.tool}`); + console.log(` Arguments: ${JSON.stringify(data.arguments)}`); + + rl.question('Approve? (y/n): ', (answer) => { + const decision = answer.toLowerCase() === 'y' ? 'approved' : 'rejected'; + channel.publish({ + name: 'approval-response', + data: { decision }, + extras: { + headers: { + toolCallId, + }, + }, + }); + console.log(`Decision sent: ${decision}`); + }); + }); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/anthropic-human-in-the-loop/javascript/test/e2e.test.ts b/guides/ai-transport/anthropic-human-in-the-loop/javascript/test/e2e.test.ts new file mode 100644 index 0000000000..dbf1535a3d --- /dev/null +++ b/guides/ai-transport/anthropic-human-in-the-loop/javascript/test/e2e.test.ts @@ -0,0 +1,74 @@ +import Ably from 'ably'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { runAgent } from '../src/agent.js'; +import { autoApprove, autoReject } from '../src/approver.js'; +import { waitForMessage } from '../../../test-helpers.js'; + +describe('anthropic-human-in-the-loop', () => { + let agentClient: Ably.Realtime; + let approverClient: Ably.Realtime; + let channelName: string; + + beforeAll(() => { + channelName = `ai:test-anthropic-hitl-${Date.now()}`; + agentClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + approverClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + }); + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + agentClient?.close(); + approverClient?.close(); + }); + + it('agent publishes approval request when tool use is triggered', async () => { + const agentChannel = agentClient.channels.get(channelName + '-request'); + const approverChannel = approverClient.channels.get(channelName + '-request'); + + const requestReceived = waitForMessage( + approverChannel, + (m) => m.name === 'approval-request', + 30000, + ); + + // Set up auto-approve so the agent can complete (otherwise it hangs waiting) + const cleanup = autoApprove(approverChannel); + + await runAgent(agentChannel, "Publish the blog post called 'Test Post'"); + + const message = await requestReceived; + cleanup(); + + expect(message.name).toBe('approval-request'); + const data = message.data as { tool: string; arguments: { title: string } }; + expect(data.tool).toBe('publish_blog_post'); + }); + + it('agent completes tool execution after approval', async () => { + const agentChannel = agentClient.channels.get(channelName + '-approve'); + const approverChannel = approverClient.channels.get(channelName + '-approve'); + + const cleanup = autoApprove(approverChannel); + await approverChannel.attach(); + + const result = await runAgent(agentChannel, "Publish the blog post called 'Approved Post'"); + cleanup(); + + expect(result).toHaveProperty('published', true); + expect(result).toHaveProperty('title'); + }); + + it('agent rejects when approval is denied', async () => { + const agentChannel = agentClient.channels.get(channelName + '-reject'); + const approverChannel = approverClient.channels.get(channelName + '-reject'); + + const cleanup = autoReject(approverChannel); + await approverChannel.attach(); + + await expect( + runAgent(agentChannel, "Publish the blog post called 'Rejected Post'"), + ).rejects.toThrow('Tool call rejected'); + + cleanup(); + }); +}); diff --git a/guides/ai-transport/anthropic-human-in-the-loop/javascript/tsconfig.json b/guides/ai-transport/anthropic-human-in-the-loop/javascript/tsconfig.json new file mode 100644 index 0000000000..6ece4d3313 --- /dev/null +++ b/guides/ai-transport/anthropic-human-in-the-loop/javascript/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/guides/ai-transport/anthropic-message-per-response/javascript/package.json b/guides/ai-transport/anthropic-message-per-response/javascript/package.json new file mode 100644 index 0000000000..ca8aaea8a2 --- /dev/null +++ b/guides/ai-transport/anthropic-message-per-response/javascript/package.json @@ -0,0 +1,14 @@ +{ + "name": "guide-anthropic-message-per-response", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start:publisher": "tsx src/publisher.ts", + "start:subscriber": "tsx src/subscriber.ts", + "test": "vitest run" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.71" + } +} diff --git a/guides/ai-transport/anthropic-message-per-response/javascript/src/publisher.ts b/guides/ai-transport/anthropic-message-per-response/javascript/src/publisher.ts new file mode 100644 index 0000000000..143e2be744 --- /dev/null +++ b/guides/ai-transport/anthropic-message-per-response/javascript/src/publisher.ts @@ -0,0 +1,53 @@ +import Anthropic from '@anthropic-ai/sdk'; +import Ably from 'ably'; + +export async function publish(channel: Ably.RealtimeChannel, prompt: string) { + const anthropic = new Anthropic(); + let msgSerial: string | null = null; + let textBlockIndex: number | null = null; + + const stream = await anthropic.messages.create({ + model: 'claude-sonnet-4-5-20250929', + max_tokens: 1024, + messages: [{ role: 'user', content: prompt }], + stream: true, + }); + + for await (const event of stream) { + switch (event.type) { + case 'message_start': { + const result = await channel.publish({ name: 'response', data: '' }); + msgSerial = result.serials[0]; + break; + } + + case 'content_block_start': + if (event.content_block.type === 'text') { + textBlockIndex = event.index; + } + break; + + case 'content_block_delta': + if (event.index === textBlockIndex && event.delta.type === 'text_delta' && msgSerial) { + channel.appendMessage({ serial: msgSerial, data: event.delta.text }); + } + break; + + case 'message_stop': + break; + } + } +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + const channel = realtime.channels.get('ai:anthropic-mpr-guide'); + await publish(channel, 'Tell me a short joke'); + console.log('Done streaming. Closing connection...'); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/anthropic-message-per-response/javascript/src/subscriber.ts b/guides/ai-transport/anthropic-message-per-response/javascript/src/subscriber.ts new file mode 100644 index 0000000000..37812c7f1c --- /dev/null +++ b/guides/ai-transport/anthropic-message-per-response/javascript/src/subscriber.ts @@ -0,0 +1,68 @@ +import Ably from 'ably'; + +// Subscribe to a channel and reconstruct streamed responses using message actions. +// Returns a promise that resolves with the full response text when no new appends +// arrive for 2 seconds (indicating the stream is complete). +export function subscribe(channel: Ably.RealtimeChannel): Promise { + return new Promise((resolve) => { + const responses = new Map(); + let lastSerial: string | null = null; + let doneTimer: ReturnType | null = null; + + const resetTimer = () => { + if (doneTimer) { + clearTimeout(doneTimer); + } + doneTimer = setTimeout(() => { + if (lastSerial) { + const finalText = responses.get(lastSerial) || ''; + resolve(finalText); + } + }, 3000); + }; + + channel.subscribe((message: Ably.Message) => { + const serial = message.serial; + if (!serial) { + return; + } + + switch (message.action) { + case 'message.create': + console.log('\n[Response started]', serial); + responses.set(serial, message.data as string); + lastSerial = serial; + resetTimer(); + break; + + case 'message.append': { + const current = responses.get(serial) || ''; + responses.set(serial, current + (message.data as string)); + process.stdout.write(message.data as string); + resetTimer(); + break; + } + + case 'message.update': + responses.set(serial, message.data as string); + console.log('\n[Response updated with full content]'); + resetTimer(); + break; + } + }); + }); +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + const channel = realtime.channels.get('ai:anthropic-mpr-guide'); + console.log('Subscriber ready, waiting for tokens...'); + const response = await subscribe(channel); + console.log('\nFull response:', response); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/anthropic-message-per-response/javascript/test/e2e.test.ts b/guides/ai-transport/anthropic-message-per-response/javascript/test/e2e.test.ts new file mode 100644 index 0000000000..c34d4f41c9 --- /dev/null +++ b/guides/ai-transport/anthropic-message-per-response/javascript/test/e2e.test.ts @@ -0,0 +1,78 @@ +import Ably from 'ably'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { publish } from '../src/publisher.js'; +import { subscribe } from '../src/subscriber.js'; +import { waitForMessage } from '../../../test-helpers.js'; + +describe('anthropic-message-per-response', () => { + let publisherClient: Ably.Realtime; + let subscriberClient: Ably.Realtime; + let channelName: string; + + beforeAll(() => { + channelName = `ai:test-anthropic-mpr-${Date.now()}`; + publisherClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + subscriberClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + }); + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + publisherClient?.close(); + subscriberClient?.close(); + }); + + it('publishes initial message and appends tokens', async () => { + const channel = subscriberClient.channels.get(channelName + '-actions'); + const pubChannel = publisherClient.channels.get(channelName + '-actions'); + const actions: { action: string; data?: string; serial?: string }[] = []; + + await channel.subscribe((message: Ably.Message) => { + actions.push({ + action: message.action!, + data: message.data as string | undefined, + serial: message.serial, + }); + }); + + const appendReceived = waitForMessage(channel, (m) => m.action === 'message.append'); + await publish(pubChannel, 'Reply with exactly: OK'); + await appendReceived; + + expect(actions[0].action).toBe('message.create'); + const appendActions = actions.filter((a) => a.action === 'message.append'); + expect(appendActions.length).toBeGreaterThan(0); + // All actions should share the same serial + const serials = new Set(actions.map((a) => a.serial)); + expect(serials.size).toBe(1); + }); + + it('subscriber reconstructs the full response from append actions', async () => { + const subChannel = subscriberClient.channels.get(channelName + '-reconstruct'); + const pubChannel = publisherClient.channels.get(channelName + '-reconstruct'); + const responsePromise = subscribe(subChannel); + await subChannel.attach(); + await publish(pubChannel, 'Reply with exactly: Hello world'); + const fullResponse = await responsePromise; + expect(fullResponse.length).toBeGreaterThan(0); + expect(fullResponse.toLowerCase()).toContain('hello'); + }); + + it('appended tokens concatenate to match the full response', async () => { + const channel = subscriberClient.channels.get(channelName + '-concat'); + const pubChannel = publisherClient.channels.get(channelName + '-concat'); + const appendedTokens: string[] = []; + + await channel.subscribe((message: Ably.Message) => { + if (message.action === 'message.append') { + appendedTokens.push(message.data as string); + } + }); + + const responsePromise = subscribe(channel); + await channel.attach(); + await publish(pubChannel, 'Reply with exactly: Test'); + const fullResponse = await responsePromise; + const concatenated = appendedTokens.join(''); + expect(concatenated).toBe(fullResponse); + }); +}); diff --git a/guides/ai-transport/anthropic-message-per-response/javascript/tsconfig.json b/guides/ai-transport/anthropic-message-per-response/javascript/tsconfig.json new file mode 100644 index 0000000000..6ece4d3313 --- /dev/null +++ b/guides/ai-transport/anthropic-message-per-response/javascript/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/guides/ai-transport/anthropic-message-per-token/javascript/package.json b/guides/ai-transport/anthropic-message-per-token/javascript/package.json new file mode 100644 index 0000000000..4170180553 --- /dev/null +++ b/guides/ai-transport/anthropic-message-per-token/javascript/package.json @@ -0,0 +1,14 @@ +{ + "name": "guide-anthropic-message-per-token", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start:publisher": "tsx src/publisher.ts", + "start:subscriber": "tsx src/subscriber.ts", + "test": "vitest run" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.71" + } +} diff --git a/guides/ai-transport/anthropic-message-per-token/javascript/src/publisher.ts b/guides/ai-transport/anthropic-message-per-token/javascript/src/publisher.ts new file mode 100644 index 0000000000..8d6a2d5407 --- /dev/null +++ b/guides/ai-transport/anthropic-message-per-token/javascript/src/publisher.ts @@ -0,0 +1,64 @@ +import Anthropic from '@anthropic-ai/sdk'; +import Ably from 'ably'; + +async function processEvent( + event: Anthropic.MessageStreamEvent, + channel: Ably.RealtimeChannel, + state: { responseId: string | null }, +) { + switch (event.type) { + case 'message_start': + state.responseId = event.message.id; + channel.publish({ + name: 'start', + extras: { headers: { responseId: state.responseId } }, + }); + break; + + case 'content_block_delta': + if (event.delta.type === 'text_delta') { + channel.publish({ + name: 'token', + data: event.delta.text, + extras: { headers: { responseId: state.responseId } }, + }); + } + break; + + case 'message_stop': + await channel.publish({ + name: 'stop', + extras: { headers: { responseId: state.responseId } }, + }); + break; + } +} + +export async function publish(channel: Ably.RealtimeChannel, prompt: string) { + const anthropic = new Anthropic(); + const state = { responseId: null as string | null }; + + const stream = await anthropic.messages.create({ + model: 'claude-sonnet-4-5-20250929', + max_tokens: 1024, + messages: [{ role: 'user', content: prompt }], + stream: true, + }); + + for await (const event of stream) { + await processEvent(event, channel, state); + } +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + const channel = realtime.channels.get('anthropic-mpt-guide'); + await publish(channel, 'Tell me a short joke'); + console.log('Done streaming. Closing connection...'); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/anthropic-message-per-token/javascript/src/subscriber.ts b/guides/ai-transport/anthropic-message-per-token/javascript/src/subscriber.ts new file mode 100644 index 0000000000..8358f4edfb --- /dev/null +++ b/guides/ai-transport/anthropic-message-per-token/javascript/src/subscriber.ts @@ -0,0 +1,42 @@ +import Ably from 'ably'; + +export function subscribe(channel: Ably.RealtimeChannel): Promise { + return new Promise((resolve) => { + const responses = new Map(); + + channel.subscribe('start', (message: Ably.Message) => { + const responseId = message.extras?.headers?.responseId; + console.log('\n[Response started]', responseId); + responses.set(responseId, ''); + }); + + channel.subscribe('token', (message: Ably.Message) => { + const responseId = message.extras?.headers?.responseId; + const token = message.data as string; + const currentText = responses.get(responseId) || ''; + responses.set(responseId, currentText + token); + process.stdout.write(token); + }); + + channel.subscribe('stop', (message: Ably.Message) => { + const responseId = message.extras?.headers?.responseId; + const finalText = responses.get(responseId) || ''; + console.log('\n[Response completed]', responseId); + resolve(finalText); + }); + }); +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + const channel = realtime.channels.get('anthropic-mpt-guide'); + console.log('Subscriber ready, waiting for tokens...'); + const response = await subscribe(channel); + console.log('\nFull response:', response); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/anthropic-message-per-token/javascript/test/e2e.test.ts b/guides/ai-transport/anthropic-message-per-token/javascript/test/e2e.test.ts new file mode 100644 index 0000000000..e88b91a98a --- /dev/null +++ b/guides/ai-transport/anthropic-message-per-token/javascript/test/e2e.test.ts @@ -0,0 +1,75 @@ +import Ably from 'ably'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { publish } from '../src/publisher.js'; +import { subscribe } from '../src/subscriber.js'; +import { waitForMessage } from '../../../test-helpers.js'; + +describe('anthropic-message-per-token', () => { + let publisherClient: Ably.Realtime; + let subscriberClient: Ably.Realtime; + let channelName: string; + + beforeAll(() => { + channelName = `test-anthropic-mpt-${Date.now()}`; + publisherClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + subscriberClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + }); + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + publisherClient?.close(); + subscriberClient?.close(); + }); + + it('publishes start, token, and stop events in order', async () => { + const channel = subscriberClient.channels.get(channelName + '-lifecycle'); + const pubChannel = publisherClient.channels.get(channelName + '-lifecycle'); + const events: { name: string; data?: string; responseId?: string }[] = []; + + await channel.subscribe((message: Ably.Message) => { + events.push({ + name: message.name!, + data: message.data as string | undefined, + responseId: message.extras?.headers?.responseId, + }); + }); + + const stopReceived = waitForMessage(channel, (m) => m.name === 'stop'); + await publish(pubChannel, 'Reply with exactly: OK'); + await stopReceived; + + expect(events[0].name).toBe('start'); + expect(events[events.length - 1].name).toBe('stop'); + const tokenEvents = events.filter((e) => e.name === 'token'); + expect(tokenEvents.length).toBeGreaterThan(0); + const responseIds = new Set(events.map((e) => e.responseId)); + expect(responseIds.size).toBe(1); + expect(responseIds.values().next().value).toBeTruthy(); + }); + + it('subscriber reconstructs the full response from token events', async () => { + const subChannel = subscriberClient.channels.get(channelName + '-reconstruct'); + const pubChannel = publisherClient.channels.get(channelName + '-reconstruct'); + const responsePromise = subscribe(subChannel); + await subChannel.attach(); + await publish(pubChannel, 'Reply with exactly: Hello world'); + const fullResponse = await responsePromise; + expect(fullResponse.length).toBeGreaterThan(0); + expect(fullResponse.toLowerCase()).toContain('hello'); + }); + + it('token data concatenates to match the complete response', async () => { + const channel = subscriberClient.channels.get(channelName + '-concat'); + const pubChannel = publisherClient.channels.get(channelName + '-concat'); + const tokens: string[] = []; + await channel.subscribe('token', (message: Ably.Message) => { + tokens.push(message.data as string); + }); + const responsePromise = subscribe(channel); + await channel.attach(); + await publish(pubChannel, 'Reply with exactly: Test'); + const fullResponse = await responsePromise; + const concatenated = tokens.join(''); + expect(concatenated).toBe(fullResponse); + }); +}); diff --git a/guides/ai-transport/anthropic-message-per-token/javascript/tsconfig.json b/guides/ai-transport/anthropic-message-per-token/javascript/tsconfig.json new file mode 100644 index 0000000000..6ece4d3313 --- /dev/null +++ b/guides/ai-transport/anthropic-message-per-token/javascript/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/guides/ai-transport/lang-graph-message-per-response/javascript/package.json b/guides/ai-transport/lang-graph-message-per-response/javascript/package.json new file mode 100644 index 0000000000..4d751fd84f --- /dev/null +++ b/guides/ai-transport/lang-graph-message-per-response/javascript/package.json @@ -0,0 +1,16 @@ +{ + "name": "guide-lang-graph-message-per-response", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start:publisher": "tsx src/publisher.ts", + "start:subscriber": "tsx src/subscriber.ts", + "test": "vitest run" + }, + "dependencies": { + "@langchain/langgraph": "^0.2", + "@langchain/anthropic": "^0.3", + "@langchain/core": "^0.3" + } +} diff --git a/guides/ai-transport/lang-graph-message-per-response/javascript/src/publisher.ts b/guides/ai-transport/lang-graph-message-per-response/javascript/src/publisher.ts new file mode 100644 index 0000000000..58ba92f0f6 --- /dev/null +++ b/guides/ai-transport/lang-graph-message-per-response/javascript/src/publisher.ts @@ -0,0 +1,56 @@ +import { ChatAnthropic } from '@langchain/anthropic'; +import { StateGraph, Annotation, START, END } from '@langchain/langgraph'; +import Ably from 'ably'; + +const model = new ChatAnthropic({ model: 'claude-sonnet-4-5-20250929' }); + +const StateAnnotation = Annotation.Root({ + messages: Annotation({ + reducer: (x, y) => x.concat(y), + default: () => [], + }), +}); + +const graph = new StateGraph(StateAnnotation) + .addNode('agent', async (state) => { + const response = await model.invoke(state.messages); + return { messages: [response] }; + }) + .addEdge(START, 'agent') + .addEdge('agent', END); + +const app = graph.compile(); + +export async function publish(channel: Ably.RealtimeChannel, prompt: string) { + let msgSerial: string | null = null; + + const stream = await app.stream( + { messages: [{ role: 'user', content: prompt }] }, + { streamMode: 'messages' }, + ); + + for await (const [messageChunk, _metadata] of stream) { + if (!msgSerial && messageChunk?.id) { + const result = await channel.publish({ name: 'response', data: '' }); + msgSerial = result.serials[0]; + } + + const content = messageChunk?.content; + if (content && msgSerial) { + channel.appendMessage({ serial: msgSerial, data: content }); + } + } +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + const channel = realtime.channels.get('ai:langgraph-mpr-guide'); + await publish(channel, 'Tell me a short joke'); + console.log('Done streaming. Closing connection...'); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/lang-graph-message-per-response/javascript/src/subscriber.ts b/guides/ai-transport/lang-graph-message-per-response/javascript/src/subscriber.ts new file mode 100644 index 0000000000..f56fdc7f7f --- /dev/null +++ b/guides/ai-transport/lang-graph-message-per-response/javascript/src/subscriber.ts @@ -0,0 +1,68 @@ +import Ably from 'ably'; + +// Subscribe to a channel and reconstruct streamed responses using message actions. +// Returns a promise that resolves with the full response text when no new appends +// arrive for 2 seconds (indicating the stream is complete). +export function subscribe(channel: Ably.RealtimeChannel): Promise { + return new Promise((resolve) => { + const responses = new Map(); + let lastSerial: string | null = null; + let doneTimer: ReturnType | null = null; + + const resetTimer = () => { + if (doneTimer) { + clearTimeout(doneTimer); + } + doneTimer = setTimeout(() => { + if (lastSerial) { + const finalText = responses.get(lastSerial) || ''; + resolve(finalText); + } + }, 3000); + }; + + channel.subscribe((message: Ably.Message) => { + const serial = message.serial; + if (!serial) { + return; + } + + switch (message.action) { + case 'message.create': + console.log('\n[Response started]', serial); + responses.set(serial, message.data as string); + lastSerial = serial; + resetTimer(); + break; + + case 'message.append': { + const current = responses.get(serial) || ''; + responses.set(serial, current + (message.data as string)); + process.stdout.write(message.data as string); + resetTimer(); + break; + } + + case 'message.update': + responses.set(serial, message.data as string); + console.log('\n[Response updated with full content]'); + resetTimer(); + break; + } + }); + }); +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + const channel = realtime.channels.get('ai:lg-mpr-guide'); + console.log('Subscriber ready, waiting for tokens...'); + const response = await subscribe(channel); + console.log('\nFull response:', response); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/lang-graph-message-per-response/javascript/test/e2e.test.ts b/guides/ai-transport/lang-graph-message-per-response/javascript/test/e2e.test.ts new file mode 100644 index 0000000000..ca5d5e9660 --- /dev/null +++ b/guides/ai-transport/lang-graph-message-per-response/javascript/test/e2e.test.ts @@ -0,0 +1,78 @@ +import Ably from 'ably'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { publish } from '../src/publisher.js'; +import { subscribe } from '../src/subscriber.js'; +import { waitForMessage } from '../../../test-helpers.js'; + +describe('lang-graph-message-per-response', () => { + let publisherClient: Ably.Realtime; + let subscriberClient: Ably.Realtime; + let channelName: string; + + beforeAll(() => { + channelName = `ai:test-lg-mpr-${Date.now()}`; + publisherClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + subscriberClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + }); + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + publisherClient?.close(); + subscriberClient?.close(); + }); + + it('publishes initial message and appends tokens', async () => { + const channel = subscriberClient.channels.get(channelName + '-actions'); + const pubChannel = publisherClient.channels.get(channelName + '-actions'); + const actions: { action: string; data?: string; serial?: string }[] = []; + + await channel.subscribe((message: Ably.Message) => { + actions.push({ + action: message.action!, + data: message.data as string | undefined, + serial: message.serial, + }); + }); + + const appendReceived = waitForMessage(channel, (m) => m.action === 'message.append'); + await publish(pubChannel, 'Reply with exactly: OK'); + await appendReceived; + + expect(actions[0].action).toBe('message.create'); + const appendActions = actions.filter((a) => a.action === 'message.append'); + expect(appendActions.length).toBeGreaterThan(0); + // All actions should share the same serial + const serials = new Set(actions.map((a) => a.serial)); + expect(serials.size).toBe(1); + }); + + it('subscriber reconstructs the full response from append actions', async () => { + const subChannel = subscriberClient.channels.get(channelName + '-reconstruct'); + const pubChannel = publisherClient.channels.get(channelName + '-reconstruct'); + const responsePromise = subscribe(subChannel); + await subChannel.attach(); + await publish(pubChannel, 'Reply with exactly: Hello world'); + const fullResponse = await responsePromise; + expect(fullResponse.length).toBeGreaterThan(0); + expect(fullResponse.toLowerCase()).toContain('hello'); + }); + + it('appended tokens concatenate to match the full response', async () => { + const channel = subscriberClient.channels.get(channelName + '-concat'); + const pubChannel = publisherClient.channels.get(channelName + '-concat'); + const appendedTokens: string[] = []; + + await channel.subscribe((message: Ably.Message) => { + if (message.action === 'message.append') { + appendedTokens.push(message.data as string); + } + }); + + const responsePromise = subscribe(channel); + await channel.attach(); + await publish(pubChannel, 'Reply with exactly: Test'); + const fullResponse = await responsePromise; + const concatenated = appendedTokens.join(''); + expect(concatenated).toBe(fullResponse); + }); +}); diff --git a/guides/ai-transport/lang-graph-message-per-response/javascript/tsconfig.json b/guides/ai-transport/lang-graph-message-per-response/javascript/tsconfig.json new file mode 100644 index 0000000000..6ece4d3313 --- /dev/null +++ b/guides/ai-transport/lang-graph-message-per-response/javascript/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/guides/ai-transport/lang-graph-message-per-token/javascript/package.json b/guides/ai-transport/lang-graph-message-per-token/javascript/package.json new file mode 100644 index 0000000000..125378263d --- /dev/null +++ b/guides/ai-transport/lang-graph-message-per-token/javascript/package.json @@ -0,0 +1,16 @@ +{ + "name": "guide-lang-graph-message-per-token", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start:publisher": "tsx src/publisher.ts", + "start:subscriber": "tsx src/subscriber.ts", + "test": "vitest run" + }, + "dependencies": { + "@langchain/langgraph": "^0.2", + "@langchain/anthropic": "^0.3", + "@langchain/core": "^0.3" + } +} diff --git a/guides/ai-transport/lang-graph-message-per-token/javascript/src/publisher.ts b/guides/ai-transport/lang-graph-message-per-token/javascript/src/publisher.ts new file mode 100644 index 0000000000..51c5a62df8 --- /dev/null +++ b/guides/ai-transport/lang-graph-message-per-token/javascript/src/publisher.ts @@ -0,0 +1,68 @@ +import { ChatAnthropic } from '@langchain/anthropic'; +import { StateGraph, Annotation, START, END } from '@langchain/langgraph'; +import Ably from 'ably'; + +const model = new ChatAnthropic({ model: 'claude-sonnet-4-5-20250929' }); + +const StateAnnotation = Annotation.Root({ + messages: Annotation({ + reducer: (x, y) => x.concat(y), + default: () => [], + }), +}); + +const graph = new StateGraph(StateAnnotation) + .addNode('agent', async (state) => { + const response = await model.invoke(state.messages); + return { messages: [response] }; + }) + .addEdge(START, 'agent') + .addEdge('agent', END); + +const app = graph.compile(); + +export async function publish(channel: Ably.RealtimeChannel, prompt: string) { + let responseId: string | null = null; + + const stream = await app.stream( + { messages: [{ role: 'user', content: prompt }] }, + { streamMode: 'messages' }, + ); + + for await (const [messageChunk, _metadata] of stream) { + if (!responseId && messageChunk?.id) { + responseId = messageChunk.id; + channel.publish({ + name: 'start', + extras: { headers: { responseId } }, + }); + } + + const content = messageChunk?.content; + if (content) { + channel.publish({ + name: 'token', + data: content, + extras: { headers: { responseId } }, + }); + } + } + + await channel.publish({ + name: 'stop', + extras: { headers: { responseId } }, + }); +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + const channel = realtime.channels.get('langgraph-mpt-guide'); + await publish(channel, 'Tell me a short joke'); + console.log('Done streaming. Closing connection...'); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/lang-graph-message-per-token/javascript/src/subscriber.ts b/guides/ai-transport/lang-graph-message-per-token/javascript/src/subscriber.ts new file mode 100644 index 0000000000..6c423d77c2 --- /dev/null +++ b/guides/ai-transport/lang-graph-message-per-token/javascript/src/subscriber.ts @@ -0,0 +1,42 @@ +import Ably from 'ably'; + +export function subscribe(channel: Ably.RealtimeChannel): Promise { + return new Promise((resolve) => { + const responses = new Map(); + + channel.subscribe('start', (message: Ably.Message) => { + const responseId = message.extras?.headers?.responseId; + console.log('\n[Response started]', responseId); + responses.set(responseId, ''); + }); + + channel.subscribe('token', (message: Ably.Message) => { + const responseId = message.extras?.headers?.responseId; + const token = message.data as string; + const currentText = responses.get(responseId) || ''; + responses.set(responseId, currentText + token); + process.stdout.write(token); + }); + + channel.subscribe('stop', (message: Ably.Message) => { + const responseId = message.extras?.headers?.responseId; + const finalText = responses.get(responseId) || ''; + console.log('\n[Response completed]', responseId); + resolve(finalText); + }); + }); +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + const channel = realtime.channels.get('langgraph-mpt-guide'); + console.log('Subscriber ready, waiting for tokens...'); + const response = await subscribe(channel); + console.log('\nFull response:', response); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/lang-graph-message-per-token/javascript/test/e2e.test.ts b/guides/ai-transport/lang-graph-message-per-token/javascript/test/e2e.test.ts new file mode 100644 index 0000000000..e3b2b4154e --- /dev/null +++ b/guides/ai-transport/lang-graph-message-per-token/javascript/test/e2e.test.ts @@ -0,0 +1,75 @@ +import Ably from 'ably'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { publish } from '../src/publisher.js'; +import { subscribe } from '../src/subscriber.js'; +import { waitForMessage } from '../../../test-helpers.js'; + +describe('lang-graph-message-per-token', () => { + let publisherClient: Ably.Realtime; + let subscriberClient: Ably.Realtime; + let channelName: string; + + beforeAll(() => { + channelName = `test-lg-mpt-${Date.now()}`; + publisherClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + subscriberClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + }); + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + publisherClient?.close(); + subscriberClient?.close(); + }); + + it('publishes start, token, and stop events in order', async () => { + const channel = subscriberClient.channels.get(channelName + '-lifecycle'); + const pubChannel = publisherClient.channels.get(channelName + '-lifecycle'); + const events: { name: string; data?: string; responseId?: string }[] = []; + + await channel.subscribe((message: Ably.Message) => { + events.push({ + name: message.name!, + data: message.data as string | undefined, + responseId: message.extras?.headers?.responseId, + }); + }); + + const stopReceived = waitForMessage(channel, (m) => m.name === 'stop'); + await publish(pubChannel, 'Reply with exactly: OK'); + await stopReceived; + + expect(events[0].name).toBe('start'); + expect(events[events.length - 1].name).toBe('stop'); + const tokenEvents = events.filter((e) => e.name === 'token'); + expect(tokenEvents.length).toBeGreaterThan(0); + const responseIds = new Set(events.map((e) => e.responseId)); + expect(responseIds.size).toBe(1); + expect(responseIds.values().next().value).toBeTruthy(); + }); + + it('subscriber reconstructs the full response from token events', async () => { + const subChannel = subscriberClient.channels.get(channelName + '-reconstruct'); + const pubChannel = publisherClient.channels.get(channelName + '-reconstruct'); + const responsePromise = subscribe(subChannel); + await subChannel.attach(); + await publish(pubChannel, 'Reply with exactly: Hello world'); + const fullResponse = await responsePromise; + expect(fullResponse.length).toBeGreaterThan(0); + expect(fullResponse.toLowerCase()).toContain('hello'); + }); + + it('token data concatenates to match the complete response', async () => { + const channel = subscriberClient.channels.get(channelName + '-concat'); + const pubChannel = publisherClient.channels.get(channelName + '-concat'); + const tokens: string[] = []; + await channel.subscribe('token', (message: Ably.Message) => { + tokens.push(message.data as string); + }); + const responsePromise = subscribe(channel); + await channel.attach(); + await publish(pubChannel, 'Reply with exactly: Test'); + const fullResponse = await responsePromise; + const concatenated = tokens.join(''); + expect(concatenated).toBe(fullResponse); + }); +}); diff --git a/guides/ai-transport/lang-graph-message-per-token/javascript/tsconfig.json b/guides/ai-transport/lang-graph-message-per-token/javascript/tsconfig.json new file mode 100644 index 0000000000..6ece4d3313 --- /dev/null +++ b/guides/ai-transport/lang-graph-message-per-token/javascript/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/guides/ai-transport/openai-citations/javascript/package.json b/guides/ai-transport/openai-citations/javascript/package.json new file mode 100644 index 0000000000..4c3bfd7681 --- /dev/null +++ b/guides/ai-transport/openai-citations/javascript/package.json @@ -0,0 +1,14 @@ +{ + "name": "guide-openai-citations", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start:agent": "tsx src/agent.ts", + "start:client": "tsx src/client.ts", + "test": "vitest run" + }, + "dependencies": { + "openai": "^4" + } +} diff --git a/guides/ai-transport/openai-citations/javascript/src/agent.ts b/guides/ai-transport/openai-citations/javascript/src/agent.ts new file mode 100644 index 0000000000..90726a42c9 --- /dev/null +++ b/guides/ai-transport/openai-citations/javascript/src/agent.ts @@ -0,0 +1,76 @@ +import OpenAI from 'openai'; +import Ably from 'ably'; + +interface Citation { + url: string; + title: string; + startIndex: number; + endIndex: number; +} + +export async function ask(channel: Ably.RealtimeChannel, question: string) { + const openai = new OpenAI(); + + const response = await openai.responses.create({ + model: 'gpt-5', + input: question, + tools: [{ type: 'web_search_preview' }], + }); + + let fullText = ''; + const citations: Citation[] = []; + + for (const item of response.output) { + if (item.type === 'message') { + for (const content of item.content) { + if (content.type === 'output_text') { + fullText = content.text; + if (content.annotations) { + for (const annotation of content.annotations) { + if (annotation.type === 'url_citation') { + citations.push({ + url: annotation.url, + title: annotation.title, + startIndex: annotation.start_index, + endIndex: annotation.end_index, + }); + } + } + } + } + } + } + } + + const result = await channel.publish('response', fullText); + const msgSerial = result.serials[0]!; + + for (const citation of citations) { + const sourceDomain = new URL(citation.url).hostname; + await channel.annotations.publish(msgSerial, { + type: 'citations:multiple.v1', + name: sourceDomain, + data: { + url: citation.url, + title: citation.title, + startIndex: citation.startIndex, + endIndex: citation.endIndex, + }, + }); + } + + return { msgSerial, citations }; +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + const channel = realtime.channels.get('ai:openai-citations-guide'); + const { citations } = await ask(channel, 'What is the latest version of Node.js as of 2025?'); + console.log(`Published response with ${citations.length} citation(s).`); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/openai-citations/javascript/src/client.ts b/guides/ai-transport/openai-citations/javascript/src/client.ts new file mode 100644 index 0000000000..e859011229 --- /dev/null +++ b/guides/ai-transport/openai-citations/javascript/src/client.ts @@ -0,0 +1,58 @@ +import Ably from 'ably'; + +interface ClientResult { + content: string; + citationSummary: Record; +} + +export function subscribe(channel: Ably.RealtimeChannel): Promise { + return new Promise((resolve) => { + let content = ''; + let citationSummary: Record = {}; + let doneTimer: ReturnType | null = null; + + const resetTimer = () => { + if (doneTimer) { + clearTimeout(doneTimer); + } + doneTimer = setTimeout(() => { + resolve({ content, citationSummary }); + }, 3000); + }; + + channel.subscribe((message: Ably.Message) => { + switch (message.action) { + case 'message.create': + console.log('\n[Response received]', message.serial); + content = message.data as string; + resetTimer(); + break; + + case 'message.summary': { + const summary = message.annotations?.summary?.['citations:multiple.v1']; + if (summary) { + citationSummary = summary as Record; + console.log('\n[Citation summary received]', Object.keys(citationSummary).length, 'source(s)'); + } + resetTimer(); + break; + } + } + }); + }); +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + const channel = realtime.channels.get('ai:openai-citations-guide'); + console.log('Client ready, waiting for response...'); + const { content, citationSummary } = await subscribe(channel); + console.log('\nFull response:', content); + console.log('\nCitation sources:', citationSummary); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/openai-citations/javascript/test/e2e.test.ts b/guides/ai-transport/openai-citations/javascript/test/e2e.test.ts new file mode 100644 index 0000000000..cee8ab550d --- /dev/null +++ b/guides/ai-transport/openai-citations/javascript/test/e2e.test.ts @@ -0,0 +1,61 @@ +import Ably from 'ably'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { ask } from '../src/agent.js'; +import { subscribe } from '../src/client.js'; +import { waitForMessage } from '../../../test-helpers.js'; + +describe('openai-citations', () => { + let publisherClient: Ably.Realtime; + let subscriberClient: Ably.Realtime; + let channelName: string; + + beforeAll(() => { + channelName = `ai:test-openai-citations-${Date.now()}`; + publisherClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + subscriberClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + }); + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + publisherClient?.close(); + subscriberClient?.close(); + }); + + it('publishes response and citation annotations', async () => { + const subChannel = subscriberClient.channels.get(channelName + '-annotations'); + const pubChannel = publisherClient.channels.get(channelName + '-annotations'); + + const resultPromise = subscribe(subChannel); + await subChannel.attach(); + + const summaryReceived = waitForMessage(subChannel, (m) => m.action === 'message.summary', 120000); + await ask(pubChannel, 'What is the latest version of Node.js as of 2025?'); + await summaryReceived; + + const { content, citationSummary } = await resultPromise; + expect(content.length).toBeGreaterThan(0); + expect(Object.keys(citationSummary).length).toBeGreaterThan(0); + }, 150000); + + it('citation summaries group by source domain', async () => { + const subChannel = subscriberClient.channels.get(channelName + '-domains'); + const pubChannel = publisherClient.channels.get(channelName + '-domains'); + + const resultPromise = subscribe(subChannel); + await subChannel.attach(); + + const summaryReceived = waitForMessage(subChannel, (m) => m.action === 'message.summary', 120000); + await ask(pubChannel, 'What is the latest version of Node.js as of 2025?'); + await summaryReceived; + + const { citationSummary } = await resultPromise; + const domains = Object.keys(citationSummary); + expect(domains.length).toBeGreaterThan(0); + + for (const domain of domains) { + // Verify each key is a valid domain name (contains at least one dot) + expect(domain).toMatch(/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/); + expect(citationSummary[domain].total).toBeGreaterThan(0); + } + }, 150000); +}); diff --git a/guides/ai-transport/openai-citations/javascript/tsconfig.json b/guides/ai-transport/openai-citations/javascript/tsconfig.json new file mode 100644 index 0000000000..6ece4d3313 --- /dev/null +++ b/guides/ai-transport/openai-citations/javascript/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/guides/ai-transport/openai-human-in-the-loop/javascript/package.json b/guides/ai-transport/openai-human-in-the-loop/javascript/package.json new file mode 100644 index 0000000000..82bdfd1d85 --- /dev/null +++ b/guides/ai-transport/openai-human-in-the-loop/javascript/package.json @@ -0,0 +1,14 @@ +{ + "name": "guide-openai-human-in-the-loop", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start:agent": "tsx src/agent.ts", + "start:approver": "tsx src/approver.ts", + "test": "vitest run" + }, + "dependencies": { + "openai": "^4" + } +} diff --git a/guides/ai-transport/openai-human-in-the-loop/javascript/src/agent.ts b/guides/ai-transport/openai-human-in-the-loop/javascript/src/agent.ts new file mode 100644 index 0000000000..bd683d9327 --- /dev/null +++ b/guides/ai-transport/openai-human-in-the-loop/javascript/src/agent.ts @@ -0,0 +1,88 @@ +import OpenAI from 'openai'; +import Ably from 'ably'; + +export async function runAgent(channel: Ably.RealtimeChannel, prompt: string) { + const openai = new OpenAI(); + + const response = await openai.responses.create({ + model: 'gpt-4o', + input: prompt, + tools: [ + { + type: 'function', + name: 'publish_blog_post', + description: 'Publish a blog post to the website. Requires human approval.', + parameters: { + type: 'object', + properties: { + title: { + type: 'string', + description: 'Title of the blog post to publish', + }, + }, + required: ['title'], + }, + } as unknown as OpenAI.Responses.FunctionTool, + ], + }); + + const toolCalls = response.output.filter((item) => item.type === 'function_call'); + + for (const toolCall of toolCalls) { + // Set up listener for approval response before publishing request + const decisionPromise = new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + channel.unsubscribe('approval-response', listener); + reject(new Error('Timed out waiting for approval response')); + }, 30000); + + const listener = (message: Ably.Message) => { + const messageToolCallId = message.extras?.headers?.toolCallId; + if (messageToolCallId === toolCall.call_id) { + clearTimeout(timeout); + channel.unsubscribe('approval-response', listener); + resolve((message.data as { decision: string }).decision); + } + }; + + channel.subscribe('approval-response', listener); + }); + + await channel.publish({ + name: 'approval-request', + data: { + tool: toolCall.name, + arguments: toolCall.arguments, + }, + extras: { + headers: { + toolCallId: toolCall.call_id, + }, + }, + }); + + const decision = await decisionPromise; + + if (decision !== 'approved') { + throw new Error(`Tool call rejected: ${toolCall.name}`); + } + + const { title } = JSON.parse(toolCall.arguments); + return { published: true, title }; + } + + return { message: 'No tool calls were made by the model.' }; +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + const channel = realtime.channels.get('ai:openai-hitl-guide'); + const result = await runAgent(channel, 'Publish a blog post called "Hello World"'); + console.log('Result:', result); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/openai-human-in-the-loop/javascript/src/approver.ts b/guides/ai-transport/openai-human-in-the-loop/javascript/src/approver.ts new file mode 100644 index 0000000000..8c608dbc3b --- /dev/null +++ b/guides/ai-transport/openai-human-in-the-loop/javascript/src/approver.ts @@ -0,0 +1,80 @@ +import Ably from 'ably'; + +export function autoApprove(channel: Ably.RealtimeChannel): () => void { + const listener = (message: Ably.Message) => { + const toolCallId = message.extras?.headers?.toolCallId; + channel.publish({ + name: 'approval-response', + data: { decision: 'approved' }, + extras: { + headers: { + toolCallId, + }, + }, + }); + }; + + channel.subscribe('approval-request', listener); + + return () => { + channel.unsubscribe('approval-request', listener); + }; +} + +export function autoReject(channel: Ably.RealtimeChannel): () => void { + const listener = (message: Ably.Message) => { + const toolCallId = message.extras?.headers?.toolCallId; + channel.publish({ + name: 'approval-response', + data: { decision: 'rejected' }, + extras: { + headers: { + toolCallId, + }, + }, + }); + }; + + channel.subscribe('approval-request', listener); + + return () => { + channel.unsubscribe('approval-request', listener); + }; +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + const channel = realtime.channels.get('ai:openai-hitl-guide'); + + const { createInterface } = await import('readline'); + const rl = createInterface({ input: process.stdin, output: process.stdout }); + + console.log('Approver ready, waiting for approval requests...'); + + channel.subscribe('approval-request', (message: Ably.Message) => { + const data = message.data as { tool: string; arguments: string }; + const toolCallId = message.extras?.headers?.toolCallId; + + console.log(`\nApproval requested for tool: ${data.tool}`); + console.log(`Arguments: ${data.arguments}`); + + rl.question('Approve? (y/n): ', (answer) => { + const decision = answer.toLowerCase() === 'y' ? 'approved' : 'rejected'; + channel.publish({ + name: 'approval-response', + data: { decision }, + extras: { + headers: { + toolCallId, + }, + }, + }); + console.log(`Decision sent: ${decision}`); + }); + }); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/openai-human-in-the-loop/javascript/test/e2e.test.ts b/guides/ai-transport/openai-human-in-the-loop/javascript/test/e2e.test.ts new file mode 100644 index 0000000000..00662f0c57 --- /dev/null +++ b/guides/ai-transport/openai-human-in-the-loop/javascript/test/e2e.test.ts @@ -0,0 +1,71 @@ +import Ably from 'ably'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { runAgent } from '../src/agent.js'; +import { autoApprove, autoReject } from '../src/approver.js'; +import { waitForMessage } from '../../../test-helpers.js'; + +describe('openai-human-in-the-loop', () => { + let agentClient: Ably.Realtime; + let approverClient: Ably.Realtime; + let channelName: string; + + beforeAll(() => { + channelName = `ai:test-openai-hitl-${Date.now()}`; + agentClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + approverClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + }); + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + agentClient?.close(); + approverClient?.close(); + }); + + it('agent publishes approval request when tool use is triggered', async () => { + const agentChannel = agentClient.channels.get(channelName + '-request'); + const approverChannel = approverClient.channels.get(channelName + '-request'); + + const requestReceived = waitForMessage(approverChannel, (m) => m.name === 'approval-request', 30000); + + // Set up auto-approve so the agent doesn't hang waiting for a response + const cleanup = autoApprove(approverChannel); + + await runAgent(agentChannel, "Publish the blog post called 'Test Post'"); + const message = await requestReceived; + + cleanup(); + + const data = message.data as { tool: string; arguments: string }; + expect(data.tool).toBe('publish_blog_post'); + expect(data.arguments).toBeDefined(); + }); + + it('agent completes tool execution after approval', async () => { + const agentChannel = agentClient.channels.get(channelName + '-approve'); + const approverChannel = approverClient.channels.get(channelName + '-approve'); + + const cleanup = autoApprove(approverChannel); + await approverChannel.attach(); + + const result = await runAgent(agentChannel, "Publish the blog post called 'Approved Post'"); + + cleanup(); + + expect(result).toHaveProperty('published', true); + expect(result).toHaveProperty('title'); + }); + + it('agent rejects when approval is denied', async () => { + const agentChannel = agentClient.channels.get(channelName + '-reject'); + const approverChannel = approverClient.channels.get(channelName + '-reject'); + + const cleanup = autoReject(approverChannel); + await approverChannel.attach(); + + await expect(runAgent(agentChannel, "Publish the blog post called 'Rejected Post'")).rejects.toThrow( + 'Tool call rejected', + ); + + cleanup(); + }); +}); diff --git a/guides/ai-transport/openai-human-in-the-loop/javascript/tsconfig.json b/guides/ai-transport/openai-human-in-the-loop/javascript/tsconfig.json new file mode 100644 index 0000000000..6ece4d3313 --- /dev/null +++ b/guides/ai-transport/openai-human-in-the-loop/javascript/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/guides/ai-transport/openai-message-per-response/javascript/package.json b/guides/ai-transport/openai-message-per-response/javascript/package.json new file mode 100644 index 0000000000..2b741fd2ce --- /dev/null +++ b/guides/ai-transport/openai-message-per-response/javascript/package.json @@ -0,0 +1,14 @@ +{ + "name": "guide-openai-message-per-response", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start:publisher": "tsx src/publisher.ts", + "start:subscriber": "tsx src/subscriber.ts", + "test": "vitest run" + }, + "dependencies": { + "openai": "^4" + } +} diff --git a/guides/ai-transport/openai-message-per-response/javascript/src/publisher.ts b/guides/ai-transport/openai-message-per-response/javascript/src/publisher.ts new file mode 100644 index 0000000000..9d9d91604d --- /dev/null +++ b/guides/ai-transport/openai-message-per-response/javascript/src/publisher.ts @@ -0,0 +1,52 @@ +import OpenAI from 'openai'; +import Ably from 'ably'; + +export async function publish(channel: Ably.RealtimeChannel, prompt: string) { + const openai = new OpenAI(); + let msgSerial: string | null = null; + let messageItemId: string | null = null; + + const stream = await openai.responses.create({ + model: 'gpt-4o-mini', + input: prompt, + stream: true, + }); + + for await (const event of stream) { + switch (event.type) { + case 'response.created': { + const result = await channel.publish({ name: 'response', data: '' }); + msgSerial = result.serials[0]; + break; + } + + case 'response.output_item.added': + if (event.item.type === 'message') { + messageItemId = event.item.id; + } + break; + + case 'response.output_text.delta': + if (event.item_id === messageItemId && msgSerial) { + channel.appendMessage({ serial: msgSerial, data: event.delta }); + } + break; + + case 'response.completed': + break; + } + } +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + const channel = realtime.channels.get('ai:openai-mpr-guide'); + await publish(channel, 'Tell me a short joke'); + console.log('Done streaming. Closing connection...'); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/openai-message-per-response/javascript/src/subscriber.ts b/guides/ai-transport/openai-message-per-response/javascript/src/subscriber.ts new file mode 100644 index 0000000000..44744a5812 --- /dev/null +++ b/guides/ai-transport/openai-message-per-response/javascript/src/subscriber.ts @@ -0,0 +1,68 @@ +import Ably from 'ably'; + +// Subscribe to a channel and reconstruct streamed responses using message actions. +// Returns a promise that resolves with the full response text when no new appends +// arrive for 2 seconds (indicating the stream is complete). +export function subscribe(channel: Ably.RealtimeChannel): Promise { + return new Promise((resolve) => { + const responses = new Map(); + let lastSerial: string | null = null; + let doneTimer: ReturnType | null = null; + + const resetTimer = () => { + if (doneTimer) { + clearTimeout(doneTimer); + } + doneTimer = setTimeout(() => { + if (lastSerial) { + const finalText = responses.get(lastSerial) || ''; + resolve(finalText); + } + }, 3000); + }; + + channel.subscribe((message: Ably.Message) => { + const serial = message.serial; + if (!serial) { + return; + } + + switch (message.action) { + case 'message.create': + console.log('\n[Response started]', serial); + responses.set(serial, message.data as string); + lastSerial = serial; + resetTimer(); + break; + + case 'message.append': { + const current = responses.get(serial) || ''; + responses.set(serial, current + (message.data as string)); + process.stdout.write(message.data as string); + resetTimer(); + break; + } + + case 'message.update': + responses.set(serial, message.data as string); + console.log('\n[Response updated with full content]'); + resetTimer(); + break; + } + }); + }); +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + const channel = realtime.channels.get('ai:openai-mpr-guide'); + console.log('Subscriber ready, waiting for tokens...'); + const response = await subscribe(channel); + console.log('\nFull response:', response); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/openai-message-per-response/javascript/test/e2e.test.ts b/guides/ai-transport/openai-message-per-response/javascript/test/e2e.test.ts new file mode 100644 index 0000000000..3565e618ba --- /dev/null +++ b/guides/ai-transport/openai-message-per-response/javascript/test/e2e.test.ts @@ -0,0 +1,78 @@ +import Ably from 'ably'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { publish } from '../src/publisher.js'; +import { subscribe } from '../src/subscriber.js'; +import { waitForMessage } from '../../../test-helpers.js'; + +describe('openai-message-per-response', () => { + let publisherClient: Ably.Realtime; + let subscriberClient: Ably.Realtime; + let channelName: string; + + beforeAll(() => { + channelName = `ai:test-openai-mpr-${Date.now()}`; + publisherClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + subscriberClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + }); + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + publisherClient?.close(); + subscriberClient?.close(); + }); + + it('publishes initial message and appends tokens', async () => { + const channel = subscriberClient.channels.get(channelName + '-actions'); + const pubChannel = publisherClient.channels.get(channelName + '-actions'); + const actions: { action: string; data?: string; serial?: string }[] = []; + + await channel.subscribe((message: Ably.Message) => { + actions.push({ + action: message.action!, + data: message.data as string | undefined, + serial: message.serial, + }); + }); + + const appendReceived = waitForMessage(channel, (m) => m.action === 'message.append'); + await publish(pubChannel, 'Reply with exactly: OK'); + await appendReceived; + + expect(actions[0].action).toBe('message.create'); + const appendActions = actions.filter((a) => a.action === 'message.append'); + expect(appendActions.length).toBeGreaterThan(0); + // All actions should share the same serial + const serials = new Set(actions.map((a) => a.serial)); + expect(serials.size).toBe(1); + }); + + it('subscriber reconstructs the full response from append actions', async () => { + const subChannel = subscriberClient.channels.get(channelName + '-reconstruct'); + const pubChannel = publisherClient.channels.get(channelName + '-reconstruct'); + const responsePromise = subscribe(subChannel); + await subChannel.attach(); + await publish(pubChannel, 'Reply with exactly: Hello world'); + const fullResponse = await responsePromise; + expect(fullResponse.length).toBeGreaterThan(0); + expect(fullResponse.toLowerCase()).toContain('hello'); + }); + + it('appended tokens concatenate to match the full response', async () => { + const channel = subscriberClient.channels.get(channelName + '-concat'); + const pubChannel = publisherClient.channels.get(channelName + '-concat'); + const appendedTokens: string[] = []; + + await channel.subscribe((message: Ably.Message) => { + if (message.action === 'message.append') { + appendedTokens.push(message.data as string); + } + }); + + const responsePromise = subscribe(channel); + await channel.attach(); + await publish(pubChannel, 'Reply with exactly: Test'); + const fullResponse = await responsePromise; + const concatenated = appendedTokens.join(''); + expect(concatenated).toBe(fullResponse); + }); +}); diff --git a/guides/ai-transport/openai-message-per-response/javascript/tsconfig.json b/guides/ai-transport/openai-message-per-response/javascript/tsconfig.json new file mode 100644 index 0000000000..6ece4d3313 --- /dev/null +++ b/guides/ai-transport/openai-message-per-response/javascript/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/guides/ai-transport/openai-message-per-token/javascript/package.json b/guides/ai-transport/openai-message-per-token/javascript/package.json new file mode 100644 index 0000000000..37b29bff0e --- /dev/null +++ b/guides/ai-transport/openai-message-per-token/javascript/package.json @@ -0,0 +1,14 @@ +{ + "name": "guide-openai-message-per-token", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start:publisher": "tsx src/publisher.ts", + "start:subscriber": "tsx src/subscriber.ts", + "test": "vitest run" + }, + "dependencies": { + "openai": "^4" + } +} diff --git a/guides/ai-transport/openai-message-per-token/javascript/src/publisher.ts b/guides/ai-transport/openai-message-per-token/javascript/src/publisher.ts new file mode 100644 index 0000000000..5f3d10fe75 --- /dev/null +++ b/guides/ai-transport/openai-message-per-token/javascript/src/publisher.ts @@ -0,0 +1,87 @@ +import OpenAI from 'openai'; +import Ably from 'ably'; +import 'dotenv/config'; + +// Process each streaming event and publish to Ably. +// Returns a promise for the 'stop' event so the caller can await it. +async function processEvent( + event: OpenAI.Responses.ResponseStreamEvent, + channel: Ably.RealtimeChannel, + state: { responseId: string | null; messageItemId: string | null }, +) { + switch (event.type) { + case 'response.created': + state.responseId = event.response.id; + channel.publish({ + name: 'start', + extras: { + headers: { responseId: state.responseId }, + }, + }); + break; + + case 'response.output_item.added': + if (event.item.type === 'message') { + state.messageItemId = event.item.id; + } + break; + + case 'response.output_text.delta': + if (event.item_id === state.messageItemId) { + channel.publish({ + name: 'token', + data: event.delta, + extras: { + headers: { responseId: state.responseId }, + }, + }); + } + break; + + case 'response.completed': + // Await the final publish so all messages are flushed before returning + await channel.publish({ + name: 'stop', + extras: { + headers: { responseId: state.responseId }, + }, + }); + break; + } +} + +// Stream a response from OpenAI and publish tokens to Ably +export async function publish(channel: Ably.RealtimeChannel, prompt: string) { + const openai = new OpenAI(); + const state = { responseId: null as string | null, messageItemId: null as string | null }; + + const stream = await openai.responses.create({ + model: 'gpt-4o-mini', + input: prompt, + stream: true, + }); + + for await (const event of stream) { + await processEvent(event, channel, state); + } +} + +// Run as a standalone script +async function main() { + const realtime = new Ably.Realtime({ + key: process.env.ABLY_API_KEY, + echoMessages: false, + }); + + const channel = realtime.channels.get('openai-mpt-guide'); + + await publish(channel, 'Tell me a short joke'); + + console.log('Done streaming. Closing connection...'); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/openai-message-per-token/javascript/src/subscriber.ts b/guides/ai-transport/openai-message-per-token/javascript/src/subscriber.ts new file mode 100644 index 0000000000..4d19ef17d9 --- /dev/null +++ b/guides/ai-transport/openai-message-per-token/javascript/src/subscriber.ts @@ -0,0 +1,53 @@ +import Ably from 'ably'; +import 'dotenv/config'; + +// Subscribe to a channel and reconstruct streamed responses. +// Returns a promise that resolves with the full response text when a stop event is received. +export function subscribe(channel: Ably.RealtimeChannel): Promise { + return new Promise((resolve) => { + const responses = new Map(); + + channel.subscribe('start', (message: Ably.Message) => { + const responseId = message.extras?.headers?.responseId; + console.log('\n[Response started]', responseId); + responses.set(responseId, ''); + }); + + channel.subscribe('token', (message: Ably.Message) => { + const responseId = message.extras?.headers?.responseId; + const token = message.data as string; + + const currentText = responses.get(responseId) || ''; + responses.set(responseId, currentText + token); + + process.stdout.write(token); + }); + + channel.subscribe('stop', (message: Ably.Message) => { + const responseId = message.extras?.headers?.responseId; + const finalText = responses.get(responseId) || ''; + console.log('\n[Response completed]', responseId); + resolve(finalText); + }); + }); +} + +// Run as a standalone script +async function main() { + const realtime = new Ably.Realtime({ + key: process.env.ABLY_API_KEY, + }); + + const channel = realtime.channels.get('openai-mpt-guide'); + + console.log('Subscriber ready, waiting for tokens...'); + const response = await subscribe(channel); + console.log('\nFull response:', response); + + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/openai-message-per-token/javascript/test/e2e.test.ts b/guides/ai-transport/openai-message-per-token/javascript/test/e2e.test.ts new file mode 100644 index 0000000000..8fcbe5ede9 --- /dev/null +++ b/guides/ai-transport/openai-message-per-token/javascript/test/e2e.test.ts @@ -0,0 +1,75 @@ +import Ably from 'ably'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { publish } from '../src/publisher.js'; +import { subscribe } from '../src/subscriber.js'; +import { waitForMessage } from '../../../test-helpers.js'; + +describe('openai-message-per-token', () => { + let publisherClient: Ably.Realtime; + let subscriberClient: Ably.Realtime; + let channelName: string; + + beforeAll(() => { + channelName = `test-openai-mpt-${Date.now()}`; + publisherClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + subscriberClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + }); + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + publisherClient?.close(); + subscriberClient?.close(); + }); + + it('publishes start, token, and stop events in order', async () => { + const channel = subscriberClient.channels.get(channelName + '-lifecycle'); + const pubChannel = publisherClient.channels.get(channelName + '-lifecycle'); + const events: { name: string; data?: string; responseId?: string }[] = []; + + await channel.subscribe((message: Ably.Message) => { + events.push({ + name: message.name!, + data: message.data as string | undefined, + responseId: message.extras?.headers?.responseId, + }); + }); + + const stopReceived = waitForMessage(channel, (m) => m.name === 'stop'); + await publish(pubChannel, 'Reply with exactly: OK'); + await stopReceived; + + expect(events[0].name).toBe('start'); + expect(events[events.length - 1].name).toBe('stop'); + const tokenEvents = events.filter((e) => e.name === 'token'); + expect(tokenEvents.length).toBeGreaterThan(0); + const responseIds = new Set(events.map((e) => e.responseId)); + expect(responseIds.size).toBe(1); + expect(responseIds.values().next().value).toBeTruthy(); + }); + + it('subscriber reconstructs the full response from token events', async () => { + const subChannel = subscriberClient.channels.get(channelName + '-reconstruct'); + const pubChannel = publisherClient.channels.get(channelName + '-reconstruct'); + const responsePromise = subscribe(subChannel); + await subChannel.attach(); + await publish(pubChannel, 'Reply with exactly: Hello world'); + const fullResponse = await responsePromise; + expect(fullResponse.length).toBeGreaterThan(0); + expect(fullResponse.toLowerCase()).toContain('hello'); + }); + + it('token data concatenates to match the complete response', async () => { + const channel = subscriberClient.channels.get(channelName + '-concat'); + const pubChannel = publisherClient.channels.get(channelName + '-concat'); + const tokens: string[] = []; + await channel.subscribe('token', (message: Ably.Message) => { + tokens.push(message.data as string); + }); + const responsePromise = subscribe(channel); + await channel.attach(); + await publish(pubChannel, 'Reply with exactly: Test'); + const fullResponse = await responsePromise; + const concatenated = tokens.join(''); + expect(concatenated).toBe(fullResponse); + }); +}); diff --git a/guides/ai-transport/openai-message-per-token/javascript/tsconfig.json b/guides/ai-transport/openai-message-per-token/javascript/tsconfig.json new file mode 100644 index 0000000000..6ece4d3313 --- /dev/null +++ b/guides/ai-transport/openai-message-per-token/javascript/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/guides/ai-transport/package-lock.json b/guides/ai-transport/package-lock.json new file mode 100644 index 0000000000..526e4440cb --- /dev/null +++ b/guides/ai-transport/package-lock.json @@ -0,0 +1,3346 @@ +{ + "name": "ai-transport-guides", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ai-transport-guides", + "version": "0.1.0", + "workspaces": [ + "openai-message-per-token/javascript", + "openai-message-per-response/javascript", + "anthropic-message-per-token/javascript", + "anthropic-message-per-response/javascript", + "vercel-message-per-token/javascript", + "vercel-message-per-response/javascript", + "lang-graph-message-per-token/javascript", + "lang-graph-message-per-response/javascript" + ], + "dependencies": { + "ably": "^2.17.0", + "dotenv": "^16.4.5" + }, + "devDependencies": { + "tsx": "^4.19.0", + "typescript": "^5", + "vitest": "^3.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "anthropic-message-per-response/javascript": { + "name": "guide-anthropic-message-per-response", + "version": "0.1.0", + "dependencies": { + "@anthropic-ai/sdk": "^0.71" + } + }, + "anthropic-message-per-token/javascript": { + "name": "guide-anthropic-message-per-token", + "version": "0.1.0", + "dependencies": { + "@anthropic-ai/sdk": "^0.71" + } + }, + "lang-graph-message-per-response/javascript": { + "name": "guide-lang-graph-message-per-response", + "version": "0.1.0", + "dependencies": { + "@langchain/anthropic": "^0.3", + "@langchain/core": "^0.3", + "@langchain/langgraph": "^0.2" + } + }, + "lang-graph-message-per-token/javascript": { + "name": "guide-lang-graph-message-per-token", + "version": "0.1.0", + "dependencies": { + "@langchain/anthropic": "^0.3", + "@langchain/core": "^0.3", + "@langchain/langgraph": "^0.2" + } + }, + "node_modules/@ably/msgpack-js": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@ably/msgpack-js/-/msgpack-js-0.4.1.tgz", + "integrity": "sha512-Sjxj6SOr17hExAVrsycN7u6oV4PhZcK7Z2S8dM71CH/butgO47cSo/TL6FJPCXUyDAzKkOWjMUpJGyZkEpyu4Q==", + "license": "Apache-2.0", + "dependencies": { + "bops": "^1.0.1" + } + }, + "node_modules/@ai-sdk/gateway": { + "version": "3.0.46", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.46.tgz", + "integrity": "sha512-zH1UbNRjG5woOXXFOrVCZraqZuFTtmPvLardMGcgLkzpxKV0U3tAGoyWKSZ862H+eBJfI/Hf2yj/zzGJcCkycg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.15", + "@vercel/oidc": "3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.15.tgz", + "integrity": "sha512-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/openai": { + "version": "2.0.91", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-2.0.91.tgz", + "integrity": "sha512-lozfRHfSTHg5/UliQjTDcOtISYGbEpt4FS/6QM5PcLmhdT0HmROllaBmG7+JaK+uqFtDXZGgMIpz3bqB9nzqCQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.1", + "@ai-sdk/provider-utils": "3.0.21" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.1.tgz", + "integrity": "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.21.tgz", + "integrity": "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.1", + "@standard-schema/spec": "^1.0.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.71.2", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.71.2.tgz", + "integrity": "sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ==", + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@langchain/anthropic": { + "version": "0.3.34", + "resolved": "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-0.3.34.tgz", + "integrity": "sha512-8bOW1A2VHRCjbzdYElrjxutKNs9NSIxYRGtR+OJWVzluMqoKKh2NmmFrpPizEyqCUEG2tTq5xt6XA1lwfqMJRA==", + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "^0.65.0", + "fast-xml-parser": "^4.4.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.58 <0.4.0" + } + }, + "node_modules/@langchain/anthropic/node_modules/@anthropic-ai/sdk": { + "version": "0.65.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.65.0.tgz", + "integrity": "sha512-zIdPOcrCVEI8t3Di40nH4z9EoeyGZfXbYSvWdDLsB/KkaSYMnEgC7gmcgWu83g2NTn1ZTpbMvpdttWDGGIk6zw==", + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@langchain/core": { + "version": "0.3.80", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.80.tgz", + "integrity": "sha512-vcJDV2vk1AlCwSh3aBm/urQ1ZrlXFFBocv11bz/NBUfLWD5/UDNMzwPdaAd2dKvNmTWa9FM2lirLU3+JCf4cRA==", + "license": "MIT", + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": "^0.3.67", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^10.0.0", + "zod": "^3.25.32", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/langgraph": { + "version": "0.2.74", + "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.2.74.tgz", + "integrity": "sha512-oHpEi5sTZTPaeZX1UnzfM2OAJ21QGQrwReTV6+QnX7h8nDCBzhtipAw1cK616S+X8zpcVOjgOtJuaJhXa4mN8w==", + "license": "MIT", + "dependencies": { + "@langchain/langgraph-checkpoint": "~0.0.17", + "@langchain/langgraph-sdk": "~0.0.32", + "uuid": "^10.0.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.36 <0.3.0 || >=0.3.40 < 0.4.0", + "zod-to-json-schema": "^3.x" + }, + "peerDependenciesMeta": { + "zod-to-json-schema": { + "optional": true + } + } + }, + "node_modules/@langchain/langgraph-checkpoint": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.0.18.tgz", + "integrity": "sha512-IS7zJj36VgY+4pf8ZjsVuUWef7oTwt1y9ylvwu0aLuOn1d0fg05Om9DLm3v2GZ2Df6bhLV1kfWAM0IAl9O5rQQ==", + "license": "MIT", + "dependencies": { + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.31 <0.4.0" + } + }, + "node_modules/@langchain/langgraph-sdk": { + "version": "0.0.112", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.112.tgz", + "integrity": "sha512-/9W5HSWCqYgwma6EoOspL4BGYxGxeJP6lIquPSF4FA0JlKopaUv58ucZC3vAgdJyCgg6sorCIV/qg7SGpEcCLw==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.15", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^9.0.0" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.31 <0.4.0", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@langchain/core": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, + "node_modules/@vercel/oidc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz", + "integrity": "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==", + "license": "Apache-2.0", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ably": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/ably/-/ably-2.17.1.tgz", + "integrity": "sha512-70yfXHoM7JtJD/8FCtPD1gkWW0f+AJqbJp0PsqDAqiyxFB8cPFY+FuKHgNTYb8eRHKXq8hT1xiDphUcY0+GHnA==", + "license": "Apache-2.0", + "dependencies": { + "@ably/msgpack-js": "^0.4.0", + "dequal": "^2.0.3", + "fastestsmallesttextencoderdecoder": "^1.0.22", + "got": "^11.8.5", + "ulid": "^2.3.0", + "ws": "^8.17.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ai": { + "version": "6.0.86", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.86.tgz", + "integrity": "sha512-U2W2LBCHA/pr0Ui7vmmsjBiLEzBbZF3yVHNy7Rbzn7IX+SvoQPFM5rN74hhfVzZoE8zBuGD4nLLk+j0elGacvQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/gateway": "3.0.46", + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.15", + "@opentelemetry/api": "1.9.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/ai/node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ai/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.15.tgz", + "integrity": "sha512-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.2.tgz", + "integrity": "sha512-ZXBDPMt/v/8fsIqn+Z5VwrhdR6jVka0bYobHdGia0Nxi7BJ9i/Uvml3AocHIBtIIBhZjBw5MR0aR4ROs/8+SNg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bops": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bops/-/bops-1.0.1.tgz", + "integrity": "sha512-qCMBuZKP36tELrrgXpAfM+gHzqa0nLsWZ+L37ncsb8txYlnAoxOPpVp+g7fK0sGkMXfA0wl8uQkESqw3v4HNag==", + "license": "MIT", + "dependencies": { + "base64-js": "1.0.2", + "to-utf8": "0.0.1" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/console-table-printer": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.15.0.tgz", + "integrity": "sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==", + "license": "MIT", + "dependencies": { + "simple-wcswidth": "^1.1.2" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastestsmallesttextencoderdecoder": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", + "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", + "license": "CC0-1.0" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/guide-anthropic-message-per-response": { + "resolved": "anthropic-message-per-response/javascript", + "link": true + }, + "node_modules/guide-anthropic-message-per-token": { + "resolved": "anthropic-message-per-token/javascript", + "link": true + }, + "node_modules/guide-lang-graph-message-per-response": { + "resolved": "lang-graph-message-per-response/javascript", + "link": true + }, + "node_modules/guide-lang-graph-message-per-token": { + "resolved": "lang-graph-message-per-token/javascript", + "link": true + }, + "node_modules/guide-openai-message-per-response": { + "resolved": "openai-message-per-response/javascript", + "link": true + }, + "node_modules/guide-openai-message-per-token": { + "resolved": "openai-message-per-token/javascript", + "link": true + }, + "node_modules/guide-vercel-message-per-response": { + "resolved": "vercel-message-per-response/javascript", + "link": true + }, + "node_modules/guide-vercel-message-per-token": { + "resolved": "vercel-message-per-token/javascript", + "link": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/js-tiktoken": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz", + "integrity": "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.5.1" + } + }, + "node_modules/js-tiktoken/node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/langsmith": { + "version": "0.3.87", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.87.tgz", + "integrity": "sha512-XXR1+9INH8YX96FKWc5tie0QixWz6tOqAsAKfcJyPkE0xPep+NDz0IQLR32q4bn10QK3LqD2HN6T3n6z1YLW7Q==", + "license": "MIT", + "dependencies": { + "@types/uuid": "^10.0.0", + "chalk": "^4.1.2", + "console-table-printer": "^2.12.1", + "p-queue": "^6.6.2", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "*", + "@opentelemetry/exporter-trace-otlp-proto": "*", + "@opentelemetry/sdk-trace-base": "*", + "openai": "*" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "openai": { + "optional": true + } + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-wcswidth": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz", + "integrity": "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-utf8": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz", + "integrity": "sha512-zks18/TWT1iHO3v0vFp5qLKOG27m67ycq/Y7a7cTiRuUNlc4gf3HGnkRgMv0NyhnfTamtkYBJl+YeD1/j07gBQ==", + "license": "MIT" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ulid": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.4.0.tgz", + "integrity": "sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==", + "license": "MIT", + "bin": { + "ulid": "bin/cli.js" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, + "openai-message-per-response/javascript": { + "name": "guide-openai-message-per-response", + "version": "0.1.0", + "dependencies": { + "openai": "^4" + } + }, + "openai-message-per-token/javascript": { + "name": "guide-openai-message-per-token", + "version": "0.1.0", + "dependencies": { + "openai": "^4" + } + }, + "vercel-message-per-response/javascript": { + "name": "guide-vercel-message-per-response", + "version": "0.1.0", + "dependencies": { + "@ai-sdk/openai": "^2", + "ai": "^6" + } + }, + "vercel-message-per-token/javascript": { + "name": "guide-vercel-message-per-token", + "version": "0.1.0", + "dependencies": { + "@ai-sdk/openai": "^2", + "ai": "^6" + } + } + } +} diff --git a/guides/ai-transport/package.json b/guides/ai-transport/package.json new file mode 100644 index 0000000000..1ed7256d8f --- /dev/null +++ b/guides/ai-transport/package.json @@ -0,0 +1,38 @@ +{ + "name": "ai-transport-guides", + "version": "0.1.0", + "private": true, + "type": "module", + "engines": { + "node": ">=20.0.0" + }, + "workspaces": [ + "openai-message-per-token/javascript", + "openai-message-per-response/javascript", + "openai-citations/javascript", + "openai-human-in-the-loop/javascript", + "anthropic-message-per-token/javascript", + "anthropic-message-per-response/javascript", + "anthropic-citations/javascript", + "anthropic-human-in-the-loop/javascript", + "vercel-message-per-token/javascript", + "vercel-message-per-response/javascript", + "lang-graph-message-per-token/javascript", + "lang-graph-message-per-response/javascript" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "tsc --noEmit", + "lint:fix": "tsc --noEmit" + }, + "dependencies": { + "ably": "^2.17.0", + "dotenv": "^16.4.5" + }, + "devDependencies": { + "tsx": "^4.19.0", + "typescript": "^5", + "vitest": "^3.0.0" + } +} diff --git a/guides/ai-transport/test-helpers.ts b/guides/ai-transport/test-helpers.ts new file mode 100644 index 0000000000..e402cd5767 --- /dev/null +++ b/guides/ai-transport/test-helpers.ts @@ -0,0 +1,30 @@ +import Ably from 'ably'; + +type MessagePredicate = (message: Ably.Message) => boolean; + +/** + * Wait for a message matching the predicate on the given channel. + * Resolves with the first matching message, or rejects on timeout. + */ +export async function waitForMessage( + channel: Ably.RealtimeChannel, + predicate: MessagePredicate = () => true, + timeoutMs = 5000, +): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + channel.unsubscribe(listener); + reject(new Error(`waitForMessage timed out after ${timeoutMs}ms`)); + }, timeoutMs); + + const listener = (message: Ably.Message) => { + if (predicate(message)) { + clearTimeout(timer); + channel.unsubscribe(listener); + resolve(message); + } + }; + + channel.subscribe(listener); + }); +} diff --git a/guides/ai-transport/tsconfig.base.json b/guides/ai-transport/tsconfig.base.json new file mode 100644 index 0000000000..10dc38349c --- /dev/null +++ b/guides/ai-transport/tsconfig.base.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "isolatedModules": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/guides/ai-transport/tsconfig.json b/guides/ai-transport/tsconfig.json new file mode 100644 index 0000000000..d637c70af6 --- /dev/null +++ b/guides/ai-transport/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.base.json", + "include": [ + "test-helpers.ts", + "*/javascript/src/**/*.ts", + "*/javascript/test/**/*.ts" + ] +} diff --git a/guides/ai-transport/vercel-message-per-response/javascript/package.json b/guides/ai-transport/vercel-message-per-response/javascript/package.json new file mode 100644 index 0000000000..9f4184b334 --- /dev/null +++ b/guides/ai-transport/vercel-message-per-response/javascript/package.json @@ -0,0 +1,15 @@ +{ + "name": "guide-vercel-message-per-response", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start:publisher": "tsx src/publisher.ts", + "start:subscriber": "tsx src/subscriber.ts", + "test": "vitest run" + }, + "dependencies": { + "ai": "^6", + "@ai-sdk/openai": "^2" + } +} diff --git a/guides/ai-transport/vercel-message-per-response/javascript/src/publisher.ts b/guides/ai-transport/vercel-message-per-response/javascript/src/publisher.ts new file mode 100644 index 0000000000..79dd60b720 --- /dev/null +++ b/guides/ai-transport/vercel-message-per-response/javascript/src/publisher.ts @@ -0,0 +1,44 @@ +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import Ably from 'ably'; + +export async function publish(channel: Ably.RealtimeChannel, prompt: string) { + let msgSerial: string | null = null; + + const result = streamText({ + model: openai('gpt-4o-mini'), + prompt, + }); + + for await (const event of result.fullStream) { + switch (event.type) { + case 'text-start': { + const publishResult = await channel.publish({ name: 'response', data: '' }); + msgSerial = publishResult.serials[0]; + break; + } + + case 'text-delta': + if (msgSerial) { + channel.appendMessage({ serial: msgSerial, data: (event as any).text }); + } + break; + + case 'text-end': + break; + } + } +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + const channel = realtime.channels.get('ai:vercel-mpr-guide'); + await publish(channel, 'Tell me a short joke'); + console.log('Done streaming. Closing connection...'); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/vercel-message-per-response/javascript/src/subscriber.ts b/guides/ai-transport/vercel-message-per-response/javascript/src/subscriber.ts new file mode 100644 index 0000000000..f6b6dca1f7 --- /dev/null +++ b/guides/ai-transport/vercel-message-per-response/javascript/src/subscriber.ts @@ -0,0 +1,68 @@ +import Ably from 'ably'; + +// Subscribe to a channel and reconstruct streamed responses using message actions. +// Returns a promise that resolves with the full response text when no new appends +// arrive for 2 seconds (indicating the stream is complete). +export function subscribe(channel: Ably.RealtimeChannel): Promise { + return new Promise((resolve) => { + const responses = new Map(); + let lastSerial: string | null = null; + let doneTimer: ReturnType | null = null; + + const resetTimer = () => { + if (doneTimer) { + clearTimeout(doneTimer); + } + doneTimer = setTimeout(() => { + if (lastSerial) { + const finalText = responses.get(lastSerial) || ''; + resolve(finalText); + } + }, 3000); + }; + + channel.subscribe((message: Ably.Message) => { + const serial = message.serial; + if (!serial) { + return; + } + + switch (message.action) { + case 'message.create': + console.log('\n[Response started]', serial); + responses.set(serial, message.data as string); + lastSerial = serial; + resetTimer(); + break; + + case 'message.append': { + const current = responses.get(serial) || ''; + responses.set(serial, current + (message.data as string)); + process.stdout.write(message.data as string); + resetTimer(); + break; + } + + case 'message.update': + responses.set(serial, message.data as string); + console.log('\n[Response updated with full content]'); + resetTimer(); + break; + } + }); + }); +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + const channel = realtime.channels.get('ai:vercel-mpr-guide'); + console.log('Subscriber ready, waiting for tokens...'); + const response = await subscribe(channel); + console.log('\nFull response:', response); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/vercel-message-per-response/javascript/test/e2e.test.ts b/guides/ai-transport/vercel-message-per-response/javascript/test/e2e.test.ts new file mode 100644 index 0000000000..9200e5e589 --- /dev/null +++ b/guides/ai-transport/vercel-message-per-response/javascript/test/e2e.test.ts @@ -0,0 +1,78 @@ +import Ably from 'ably'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { publish } from '../src/publisher.js'; +import { subscribe } from '../src/subscriber.js'; +import { waitForMessage } from '../../../test-helpers.js'; + +describe('vercel-message-per-response', () => { + let publisherClient: Ably.Realtime; + let subscriberClient: Ably.Realtime; + let channelName: string; + + beforeAll(() => { + channelName = `ai:test-vercel-mpr-${Date.now()}`; + publisherClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + subscriberClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + }); + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + publisherClient?.close(); + subscriberClient?.close(); + }); + + it('publishes initial message and appends tokens', async () => { + const channel = subscriberClient.channels.get(channelName + '-actions'); + const pubChannel = publisherClient.channels.get(channelName + '-actions'); + const actions: { action: string; data?: string; serial?: string }[] = []; + + await channel.subscribe((message: Ably.Message) => { + actions.push({ + action: message.action!, + data: message.data as string | undefined, + serial: message.serial, + }); + }); + + const appendReceived = waitForMessage(channel, (m) => m.action === 'message.append'); + await publish(pubChannel, 'Reply with exactly: OK'); + await appendReceived; + + expect(actions[0].action).toBe('message.create'); + const appendActions = actions.filter((a) => a.action === 'message.append'); + expect(appendActions.length).toBeGreaterThan(0); + // All actions should share the same serial + const serials = new Set(actions.map((a) => a.serial)); + expect(serials.size).toBe(1); + }); + + it('subscriber reconstructs the full response from append actions', async () => { + const subChannel = subscriberClient.channels.get(channelName + '-reconstruct'); + const pubChannel = publisherClient.channels.get(channelName + '-reconstruct'); + const responsePromise = subscribe(subChannel); + await subChannel.attach(); + await publish(pubChannel, 'Reply with exactly: Hello world'); + const fullResponse = await responsePromise; + expect(fullResponse.length).toBeGreaterThan(0); + expect(fullResponse.toLowerCase()).toContain('hello'); + }); + + it('appended tokens concatenate to match the full response', async () => { + const channel = subscriberClient.channels.get(channelName + '-concat'); + const pubChannel = publisherClient.channels.get(channelName + '-concat'); + const appendedTokens: string[] = []; + + await channel.subscribe((message: Ably.Message) => { + if (message.action === 'message.append') { + appendedTokens.push(message.data as string); + } + }); + + const responsePromise = subscribe(channel); + await channel.attach(); + await publish(pubChannel, 'Reply with exactly: Test'); + const fullResponse = await responsePromise; + const concatenated = appendedTokens.join(''); + expect(concatenated).toBe(fullResponse); + }); +}); diff --git a/guides/ai-transport/vercel-message-per-response/javascript/tsconfig.json b/guides/ai-transport/vercel-message-per-response/javascript/tsconfig.json new file mode 100644 index 0000000000..6ece4d3313 --- /dev/null +++ b/guides/ai-transport/vercel-message-per-response/javascript/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/guides/ai-transport/vercel-message-per-token/javascript/package.json b/guides/ai-transport/vercel-message-per-token/javascript/package.json new file mode 100644 index 0000000000..9bd0df936a --- /dev/null +++ b/guides/ai-transport/vercel-message-per-token/javascript/package.json @@ -0,0 +1,15 @@ +{ + "name": "guide-vercel-message-per-token", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start:publisher": "tsx src/publisher.ts", + "start:subscriber": "tsx src/subscriber.ts", + "test": "vitest run" + }, + "dependencies": { + "ai": "^6", + "@ai-sdk/openai": "^2" + } +} diff --git a/guides/ai-transport/vercel-message-per-token/javascript/src/publisher.ts b/guides/ai-transport/vercel-message-per-token/javascript/src/publisher.ts new file mode 100644 index 0000000000..b96a0644c3 --- /dev/null +++ b/guides/ai-transport/vercel-message-per-token/javascript/src/publisher.ts @@ -0,0 +1,60 @@ +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import Ably from 'ably'; + +async function processEvent( + event: { type: string; id?: string; text?: string }, + channel: Ably.RealtimeChannel, + state: { responseId: string | null }, +) { + switch (event.type) { + case 'text-start': + state.responseId = event.id || null; + channel.publish({ + name: 'start', + extras: { headers: { responseId: state.responseId } }, + }); + break; + + case 'text-delta': + channel.publish({ + name: 'token', + data: event.text, + extras: { headers: { responseId: state.responseId } }, + }); + break; + + case 'text-end': + await channel.publish({ + name: 'stop', + extras: { headers: { responseId: state.responseId } }, + }); + break; + } +} + +export async function publish(channel: Ably.RealtimeChannel, prompt: string) { + const state = { responseId: null as string | null }; + + const result = streamText({ + model: openai('gpt-4o-mini'), + prompt, + }); + + for await (const event of result.fullStream) { + await processEvent(event as { type: string; id?: string; text?: string }, channel, state); + } +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + const channel = realtime.channels.get('vercel-mpt-guide'); + await publish(channel, 'Tell me a short joke'); + console.log('Done streaming. Closing connection...'); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/vercel-message-per-token/javascript/src/subscriber.ts b/guides/ai-transport/vercel-message-per-token/javascript/src/subscriber.ts new file mode 100644 index 0000000000..07e6981332 --- /dev/null +++ b/guides/ai-transport/vercel-message-per-token/javascript/src/subscriber.ts @@ -0,0 +1,42 @@ +import Ably from 'ably'; + +export function subscribe(channel: Ably.RealtimeChannel): Promise { + return new Promise((resolve) => { + const responses = new Map(); + + channel.subscribe('start', (message: Ably.Message) => { + const responseId = message.extras?.headers?.responseId; + console.log('\n[Response started]', responseId); + responses.set(responseId, ''); + }); + + channel.subscribe('token', (message: Ably.Message) => { + const responseId = message.extras?.headers?.responseId; + const token = message.data as string; + const currentText = responses.get(responseId) || ''; + responses.set(responseId, currentText + token); + process.stdout.write(token); + }); + + channel.subscribe('stop', (message: Ably.Message) => { + const responseId = message.extras?.headers?.responseId; + const finalText = responses.get(responseId) || ''; + console.log('\n[Response completed]', responseId); + resolve(finalText); + }); + }); +} + +async function main() { + const realtime = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + const channel = realtime.channels.get('vercel-mpt-guide'); + console.log('Subscriber ready, waiting for tokens...'); + const response = await subscribe(channel); + console.log('\nFull response:', response); + realtime.close(); +} + +import { fileURLToPath } from 'url'; +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch(console.error); +} diff --git a/guides/ai-transport/vercel-message-per-token/javascript/test/e2e.test.ts b/guides/ai-transport/vercel-message-per-token/javascript/test/e2e.test.ts new file mode 100644 index 0000000000..4795fbd6e6 --- /dev/null +++ b/guides/ai-transport/vercel-message-per-token/javascript/test/e2e.test.ts @@ -0,0 +1,75 @@ +import Ably from 'ably'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { publish } from '../src/publisher.js'; +import { subscribe } from '../src/subscriber.js'; +import { waitForMessage } from '../../../test-helpers.js'; + +describe('vercel-message-per-token', () => { + let publisherClient: Ably.Realtime; + let subscriberClient: Ably.Realtime; + let channelName: string; + + beforeAll(() => { + channelName = `test-vercel-mpt-${Date.now()}`; + publisherClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY, echoMessages: false }); + subscriberClient = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); + }); + + afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + publisherClient?.close(); + subscriberClient?.close(); + }); + + it('publishes start, token, and stop events in order', async () => { + const channel = subscriberClient.channels.get(channelName + '-lifecycle'); + const pubChannel = publisherClient.channels.get(channelName + '-lifecycle'); + const events: { name: string; data?: string; responseId?: string }[] = []; + + await channel.subscribe((message: Ably.Message) => { + events.push({ + name: message.name!, + data: message.data as string | undefined, + responseId: message.extras?.headers?.responseId, + }); + }); + + const stopReceived = waitForMessage(channel, (m) => m.name === 'stop'); + await publish(pubChannel, 'Reply with exactly: OK'); + await stopReceived; + + expect(events[0].name).toBe('start'); + expect(events[events.length - 1].name).toBe('stop'); + const tokenEvents = events.filter((e) => e.name === 'token'); + expect(tokenEvents.length).toBeGreaterThan(0); + const responseIds = new Set(events.map((e) => e.responseId)); + expect(responseIds.size).toBe(1); + expect(responseIds.values().next().value).toBeTruthy(); + }); + + it('subscriber reconstructs the full response from token events', async () => { + const subChannel = subscriberClient.channels.get(channelName + '-reconstruct'); + const pubChannel = publisherClient.channels.get(channelName + '-reconstruct'); + const responsePromise = subscribe(subChannel); + await subChannel.attach(); + await publish(pubChannel, 'Reply with exactly: Hello world'); + const fullResponse = await responsePromise; + expect(fullResponse.length).toBeGreaterThan(0); + expect(fullResponse.toLowerCase()).toContain('hello'); + }); + + it('token data concatenates to match the complete response', async () => { + const channel = subscriberClient.channels.get(channelName + '-concat'); + const pubChannel = publisherClient.channels.get(channelName + '-concat'); + const tokens: string[] = []; + await channel.subscribe('token', (message: Ably.Message) => { + tokens.push(message.data as string); + }); + const responsePromise = subscribe(channel); + await channel.attach(); + await publish(pubChannel, 'Reply with exactly: Test'); + const fullResponse = await responsePromise; + const concatenated = tokens.join(''); + expect(concatenated).toBe(fullResponse); + }); +}); diff --git a/guides/ai-transport/vercel-message-per-token/javascript/tsconfig.json b/guides/ai-transport/vercel-message-per-token/javascript/tsconfig.json new file mode 100644 index 0000000000..6ece4d3313 --- /dev/null +++ b/guides/ai-transport/vercel-message-per-token/javascript/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/guides/ai-transport/vitest.config.ts b/guides/ai-transport/vitest.config.ts new file mode 100644 index 0000000000..e785262498 --- /dev/null +++ b/guides/ai-transport/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + testTimeout: 30_000, + include: ['*/*/test/**/*.test.ts'], + setupFiles: ['./vitest.setup.ts'], + }, +}); diff --git a/guides/ai-transport/vitest.setup.ts b/guides/ai-transport/vitest.setup.ts new file mode 100644 index 0000000000..3c83d9f599 --- /dev/null +++ b/guides/ai-transport/vitest.setup.ts @@ -0,0 +1,2 @@ +import dotenv from 'dotenv'; +dotenv.config(); diff --git a/guides/ai-transport/yarn.lock b/guides/ai-transport/yarn.lock new file mode 100644 index 0000000000..16a95a0e86 --- /dev/null +++ b/guides/ai-transport/yarn.lock @@ -0,0 +1,1556 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ably/msgpack-js@^0.4.0": + version "0.4.1" + resolved "https://registry.npmjs.org/@ably/msgpack-js/-/msgpack-js-0.4.1.tgz" + integrity sha512-Sjxj6SOr17hExAVrsycN7u6oV4PhZcK7Z2S8dM71CH/butgO47cSo/TL6FJPCXUyDAzKkOWjMUpJGyZkEpyu4Q== + dependencies: + bops "^1.0.1" + +"@ai-sdk/gateway@3.0.46": + version "3.0.46" + resolved "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.46.tgz" + integrity sha512-zH1UbNRjG5woOXXFOrVCZraqZuFTtmPvLardMGcgLkzpxKV0U3tAGoyWKSZ862H+eBJfI/Hf2yj/zzGJcCkycg== + dependencies: + "@ai-sdk/provider" "3.0.8" + "@ai-sdk/provider-utils" "4.0.15" + "@vercel/oidc" "3.1.0" + +"@ai-sdk/openai@^2": + version "2.0.91" + resolved "https://registry.npmjs.org/@ai-sdk/openai/-/openai-2.0.91.tgz" + integrity sha512-lozfRHfSTHg5/UliQjTDcOtISYGbEpt4FS/6QM5PcLmhdT0HmROllaBmG7+JaK+uqFtDXZGgMIpz3bqB9nzqCQ== + dependencies: + "@ai-sdk/provider" "2.0.1" + "@ai-sdk/provider-utils" "3.0.21" + +"@ai-sdk/provider-utils@3.0.21": + version "3.0.21" + resolved "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.21.tgz" + integrity sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q== + dependencies: + "@ai-sdk/provider" "2.0.1" + "@standard-schema/spec" "^1.0.0" + eventsource-parser "^3.0.6" + +"@ai-sdk/provider-utils@4.0.15": + version "4.0.15" + resolved "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.15.tgz" + integrity sha512-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w== + dependencies: + "@ai-sdk/provider" "3.0.8" + "@standard-schema/spec" "^1.1.0" + eventsource-parser "^3.0.6" + +"@ai-sdk/provider@2.0.1": + version "2.0.1" + resolved "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.1.tgz" + integrity sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng== + dependencies: + json-schema "^0.4.0" + +"@ai-sdk/provider@3.0.8": + version "3.0.8" + resolved "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz" + integrity sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ== + dependencies: + json-schema "^0.4.0" + +"@anthropic-ai/sdk@^0.65.0": + version "0.65.0" + resolved "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.65.0.tgz" + integrity sha512-zIdPOcrCVEI8t3Di40nH4z9EoeyGZfXbYSvWdDLsB/KkaSYMnEgC7gmcgWu83g2NTn1ZTpbMvpdttWDGGIk6zw== + dependencies: + json-schema-to-ts "^3.1.1" + +"@anthropic-ai/sdk@^0.71": + version "0.71.2" + resolved "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.71.2.tgz" + integrity sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ== + dependencies: + json-schema-to-ts "^3.1.1" + +"@babel/runtime@^7.18.3": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz" + integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA== + +"@cfworker/json-schema@^4.0.2": + version "4.1.1" + resolved "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz" + integrity sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og== + +"@esbuild/aix-ppc64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz#815b39267f9bffd3407ea6c376ac32946e24f8d2" + integrity sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg== + +"@esbuild/android-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz#19b882408829ad8e12b10aff2840711b2da361e8" + integrity sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg== + +"@esbuild/android-arm@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz#90be58de27915efa27b767fcbdb37a4470627d7b" + integrity sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA== + +"@esbuild/android-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz#d7dcc976f16e01a9aaa2f9b938fbec7389f895ac" + integrity sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ== + +"@esbuild/darwin-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz" + integrity sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg== + +"@esbuild/darwin-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz#ac61d645faa37fd650340f1866b0812e1fb14d6a" + integrity sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg== + +"@esbuild/freebsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz#b8625689d73cf1830fe58c39051acdc12474ea1b" + integrity sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w== + +"@esbuild/freebsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz#07be7dd3c9d42fe0eccd2ab9f9ded780bc53bead" + integrity sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA== + +"@esbuild/linux-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz#bf31918fe5c798586460d2b3d6c46ed2c01ca0b6" + integrity sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg== + +"@esbuild/linux-arm@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz#28493ee46abec1dc3f500223cd9f8d2df08f9d11" + integrity sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw== + +"@esbuild/linux-ia32@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz#750752a8b30b43647402561eea764d0a41d0ee29" + integrity sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg== + +"@esbuild/linux-loong64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz#a5a92813a04e71198c50f05adfaf18fc1e95b9ed" + integrity sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA== + +"@esbuild/linux-mips64el@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz#deb45d7fd2d2161eadf1fbc593637ed766d50bb1" + integrity sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw== + +"@esbuild/linux-ppc64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz#6f39ae0b8c4d3d2d61a65b26df79f6e12a1c3d78" + integrity sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA== + +"@esbuild/linux-riscv64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz#4c5c19c3916612ec8e3915187030b9df0b955c1d" + integrity sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ== + +"@esbuild/linux-s390x@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz#9ed17b3198fa08ad5ccaa9e74f6c0aff7ad0156d" + integrity sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw== + +"@esbuild/linux-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz#12383dcbf71b7cf6513e58b4b08d95a710bf52a5" + integrity sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA== + +"@esbuild/netbsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz#dd0cb2fa543205fcd931df44f4786bfcce6df7d7" + integrity sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA== + +"@esbuild/netbsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz#028ad1807a8e03e155153b2d025b506c3787354b" + integrity sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA== + +"@esbuild/openbsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz#e3c16ff3490c9b59b969fffca87f350ffc0e2af5" + integrity sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw== + +"@esbuild/openbsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz#c5a4693fcb03d1cbecbf8b422422468dfc0d2a8b" + integrity sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ== + +"@esbuild/openharmony-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz#082082444f12db564a0775a41e1991c0e125055e" + integrity sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g== + +"@esbuild/sunos-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz#5ab036c53f929e8405c4e96e865a424160a1b537" + integrity sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA== + +"@esbuild/win32-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz#38de700ef4b960a0045370c171794526e589862e" + integrity sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA== + +"@esbuild/win32-ia32@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz#451b93dc03ec5d4f38619e6cd64d9f9eff06f55c" + integrity sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q== + +"@esbuild/win32-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz#0eaf705c941a218a43dba8e09f1df1d6cd2f1f17" + integrity sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA== + +"@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@langchain/anthropic@^0.3": + version "0.3.34" + resolved "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-0.3.34.tgz" + integrity sha512-8bOW1A2VHRCjbzdYElrjxutKNs9NSIxYRGtR+OJWVzluMqoKKh2NmmFrpPizEyqCUEG2tTq5xt6XA1lwfqMJRA== + dependencies: + "@anthropic-ai/sdk" "^0.65.0" + fast-xml-parser "^4.4.1" + +"@langchain/core@^0.3": + version "0.3.80" + resolved "https://registry.npmjs.org/@langchain/core/-/core-0.3.80.tgz" + integrity sha512-vcJDV2vk1AlCwSh3aBm/urQ1ZrlXFFBocv11bz/NBUfLWD5/UDNMzwPdaAd2dKvNmTWa9FM2lirLU3+JCf4cRA== + dependencies: + "@cfworker/json-schema" "^4.0.2" + ansi-styles "^5.0.0" + camelcase "6" + decamelize "1.2.0" + js-tiktoken "^1.0.12" + langsmith "^0.3.67" + mustache "^4.2.0" + p-queue "^6.6.2" + p-retry "4" + uuid "^10.0.0" + zod "^3.25.32" + zod-to-json-schema "^3.22.3" + +"@langchain/langgraph-checkpoint@~0.0.17": + version "0.0.18" + resolved "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.0.18.tgz" + integrity sha512-IS7zJj36VgY+4pf8ZjsVuUWef7oTwt1y9ylvwu0aLuOn1d0fg05Om9DLm3v2GZ2Df6bhLV1kfWAM0IAl9O5rQQ== + dependencies: + uuid "^10.0.0" + +"@langchain/langgraph-sdk@~0.0.32": + version "0.0.112" + resolved "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.112.tgz" + integrity sha512-/9W5HSWCqYgwma6EoOspL4BGYxGxeJP6lIquPSF4FA0JlKopaUv58ucZC3vAgdJyCgg6sorCIV/qg7SGpEcCLw== + dependencies: + "@types/json-schema" "^7.0.15" + p-queue "^6.6.2" + p-retry "4" + uuid "^9.0.0" + +"@langchain/langgraph@^0.2": + version "0.2.74" + resolved "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.2.74.tgz" + integrity sha512-oHpEi5sTZTPaeZX1UnzfM2OAJ21QGQrwReTV6+QnX7h8nDCBzhtipAw1cK616S+X8zpcVOjgOtJuaJhXa4mN8w== + dependencies: + "@langchain/langgraph-checkpoint" "~0.0.17" + "@langchain/langgraph-sdk" "~0.0.32" + uuid "^10.0.0" + zod "^3.23.8" + +"@opentelemetry/api@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz" + integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== + +"@rollup/rollup-android-arm-eabi@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz#add5e608d4e7be55bc3ca3d962490b8b1890e088" + integrity sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg== + +"@rollup/rollup-android-arm64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz#10bd0382b73592beee6e9800a69401a29da625c4" + integrity sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w== + +"@rollup/rollup-darwin-arm64@4.57.1": + version "4.57.1" + resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz" + integrity sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg== + +"@rollup/rollup-darwin-x64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz#69e741aeb2839d2e8f0da2ce7a33d8bd23632423" + integrity sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w== + +"@rollup/rollup-freebsd-arm64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz#3736c232a999c7bef7131355d83ebdf9651a0839" + integrity sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug== + +"@rollup/rollup-freebsd-x64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz#227dcb8f466684070169942bd3998901c9bfc065" + integrity sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q== + +"@rollup/rollup-linux-arm-gnueabihf@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz#ba004b30df31b724f99ce66e7128248bea17cb0c" + integrity sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw== + +"@rollup/rollup-linux-arm-musleabihf@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz#6929f3e07be6b6da5991f63c6b68b3e473d0a65a" + integrity sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw== + +"@rollup/rollup-linux-arm64-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz#06e89fd4a25d21fe5575d60b6f913c0e65297bfa" + integrity sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g== + +"@rollup/rollup-linux-arm64-musl@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz#fddabf395b90990d5194038e6cd8c00156ed8ac0" + integrity sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q== + +"@rollup/rollup-linux-loong64-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz#04c10bb764bbf09a3c1bd90432e92f58d6603c36" + integrity sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA== + +"@rollup/rollup-linux-loong64-musl@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz#f2450361790de80581d8687ea19142d8a4de5c0f" + integrity sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw== + +"@rollup/rollup-linux-ppc64-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz#0474f4667259e407eee1a6d38e29041b708f6a30" + integrity sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w== + +"@rollup/rollup-linux-ppc64-musl@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz#9f32074819eeb1ddbe51f50ea9dcd61a6745ec33" + integrity sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw== + +"@rollup/rollup-linux-riscv64-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz#3fdb9d4b1e29fb6b6a6da9f15654d42eb77b99b2" + integrity sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A== + +"@rollup/rollup-linux-riscv64-musl@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz#1de780d64e6be0e3e8762035c22e0d8ea68df8ed" + integrity sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw== + +"@rollup/rollup-linux-s390x-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz#1da022ffd2d9e9f0fd8344ea49e113001fbcac64" + integrity sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg== + +"@rollup/rollup-linux-x64-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz#78c16eef9520bd10e1ea7a112593bb58e2842622" + integrity sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg== + +"@rollup/rollup-linux-x64-musl@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz#a7598591b4d9af96cb3167b50a5bf1e02dfea06c" + integrity sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw== + +"@rollup/rollup-openbsd-x64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz#c51d48c07cd6c466560e5bed934aec688ce02614" + integrity sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw== + +"@rollup/rollup-openharmony-arm64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz#f09921d0b2a0b60afbf3586d2a7a7f208ba6df17" + integrity sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ== + +"@rollup/rollup-win32-arm64-msvc@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz#08d491717135376e4a99529821c94ecd433d5b36" + integrity sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ== + +"@rollup/rollup-win32-ia32-msvc@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz#b0c12aac1104a8b8f26a5e0098e5facbb3e3964a" + integrity sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew== + +"@rollup/rollup-win32-x64-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz#b9cccef26f5e6fdc013bf3c0911a3c77428509d0" + integrity sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ== + +"@rollup/rollup-win32-x64-msvc@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz#a03348e7b559c792b6277cc58874b89ef46e1e72" + integrity sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA== + +"@sindresorhus/is@^4.0.0": + version "4.6.0" + resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + +"@standard-schema/spec@^1.0.0", "@standard-schema/spec@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== + +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + +"@types/cacheable-request@^6.0.1": + version "6.0.3" + resolved "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz" + integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "^3.1.4" + "@types/node" "*" + "@types/responselike" "^1.0.0" + +"@types/chai@^5.2.2": + version "5.2.3" + resolved "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz" + integrity sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA== + dependencies: + "@types/deep-eql" "*" + assertion-error "^2.0.1" + +"@types/deep-eql@*": + version "4.0.2" + resolved "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz" + integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== + +"@types/estree@1.0.8", "@types/estree@^1.0.0": + version "1.0.8" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@types/http-cache-semantics@*": + version "4.2.0" + resolved "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz" + integrity sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q== + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/keyv@^3.1.4": + version "3.1.4" + resolved "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz" + integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== + dependencies: + "@types/node" "*" + +"@types/node-fetch@^2.6.4": + version "2.6.13" + resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz" + integrity sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw== + dependencies: + "@types/node" "*" + form-data "^4.0.4" + +"@types/node@*": + version "25.2.3" + resolved "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz" + integrity sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ== + dependencies: + undici-types "~7.16.0" + +"@types/node@^18.11.18": + version "18.19.130" + resolved "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz" + integrity sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg== + dependencies: + undici-types "~5.26.4" + +"@types/responselike@^1.0.0": + version "1.0.3" + resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz" + integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== + dependencies: + "@types/node" "*" + +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/uuid@^10.0.0": + version "10.0.0" + resolved "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz" + integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ== + +"@vercel/oidc@3.1.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz" + integrity sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w== + +"@vitest/expect@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz" + integrity sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig== + dependencies: + "@types/chai" "^5.2.2" + "@vitest/spy" "3.2.4" + "@vitest/utils" "3.2.4" + chai "^5.2.0" + tinyrainbow "^2.0.0" + +"@vitest/mocker@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz" + integrity sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ== + dependencies: + "@vitest/spy" "3.2.4" + estree-walker "^3.0.3" + magic-string "^0.30.17" + +"@vitest/pretty-format@3.2.4", "@vitest/pretty-format@^3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz" + integrity sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA== + dependencies: + tinyrainbow "^2.0.0" + +"@vitest/runner@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz" + integrity sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ== + dependencies: + "@vitest/utils" "3.2.4" + pathe "^2.0.3" + strip-literal "^3.0.0" + +"@vitest/snapshot@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz" + integrity sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ== + dependencies: + "@vitest/pretty-format" "3.2.4" + magic-string "^0.30.17" + pathe "^2.0.3" + +"@vitest/spy@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz" + integrity sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw== + dependencies: + tinyspy "^4.0.3" + +"@vitest/utils@3.2.4": + version "3.2.4" + resolved "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz" + integrity sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA== + dependencies: + "@vitest/pretty-format" "3.2.4" + loupe "^3.1.4" + tinyrainbow "^2.0.0" + +ably@^2.17.0: + version "2.17.1" + resolved "https://registry.npmjs.org/ably/-/ably-2.17.1.tgz" + integrity sha512-70yfXHoM7JtJD/8FCtPD1gkWW0f+AJqbJp0PsqDAqiyxFB8cPFY+FuKHgNTYb8eRHKXq8hT1xiDphUcY0+GHnA== + dependencies: + "@ably/msgpack-js" "^0.4.0" + dequal "^2.0.3" + fastestsmallesttextencoderdecoder "^1.0.22" + got "^11.8.5" + ulid "^2.3.0" + ws "^8.17.1" + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +agentkeepalive@^4.2.1: + version "4.6.0" + resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== + dependencies: + humanize-ms "^1.2.1" + +ai@^6: + version "6.0.86" + resolved "https://registry.npmjs.org/ai/-/ai-6.0.86.tgz" + integrity sha512-U2W2LBCHA/pr0Ui7vmmsjBiLEzBbZF3yVHNy7Rbzn7IX+SvoQPFM5rN74hhfVzZoE8zBuGD4nLLk+j0elGacvQ== + dependencies: + "@ai-sdk/gateway" "3.0.46" + "@ai-sdk/provider" "3.0.8" + "@ai-sdk/provider-utils" "4.0.15" + "@opentelemetry/api" "1.9.0" + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +base64-js@1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.0.2.tgz" + integrity sha512-ZXBDPMt/v/8fsIqn+Z5VwrhdR6jVka0bYobHdGia0Nxi7BJ9i/Uvml3AocHIBtIIBhZjBw5MR0aR4ROs/8+SNg== + +base64-js@^1.5.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bops@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/bops/-/bops-1.0.1.tgz" + integrity sha512-qCMBuZKP36tELrrgXpAfM+gHzqa0nLsWZ+L37ncsb8txYlnAoxOPpVp+g7fK0sGkMXfA0wl8uQkESqw3v4HNag== + dependencies: + base64-js "1.0.2" + to-utf8 "0.0.1" + +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.2: + version "7.0.4" + resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz" + integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +camelcase@6: + version "6.3.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +chai@^5.2.0: + version "5.3.3" + resolved "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz" + integrity sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +check-error@^2.1.1: + version "2.1.3" + resolved "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz" + integrity sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA== + +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +console-table-printer@^2.12.1: + version "2.15.0" + resolved "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.15.0.tgz" + integrity sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw== + dependencies: + simple-wcswidth "^1.1.2" + +debug@^4.4.1: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +decamelize@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +dotenv@^16.4.5: + version "16.6.1" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +end-of-stream@^1.1.0: + version "1.4.5" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz" + integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== + dependencies: + once "^1.4.0" + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-module-lexer@^1.7.0: + version "1.7.0" + resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +esbuild@^0.27.0, esbuild@~0.27.0: + version "0.27.3" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz" + integrity sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.27.3" + "@esbuild/android-arm" "0.27.3" + "@esbuild/android-arm64" "0.27.3" + "@esbuild/android-x64" "0.27.3" + "@esbuild/darwin-arm64" "0.27.3" + "@esbuild/darwin-x64" "0.27.3" + "@esbuild/freebsd-arm64" "0.27.3" + "@esbuild/freebsd-x64" "0.27.3" + "@esbuild/linux-arm" "0.27.3" + "@esbuild/linux-arm64" "0.27.3" + "@esbuild/linux-ia32" "0.27.3" + "@esbuild/linux-loong64" "0.27.3" + "@esbuild/linux-mips64el" "0.27.3" + "@esbuild/linux-ppc64" "0.27.3" + "@esbuild/linux-riscv64" "0.27.3" + "@esbuild/linux-s390x" "0.27.3" + "@esbuild/linux-x64" "0.27.3" + "@esbuild/netbsd-arm64" "0.27.3" + "@esbuild/netbsd-x64" "0.27.3" + "@esbuild/openbsd-arm64" "0.27.3" + "@esbuild/openbsd-x64" "0.27.3" + "@esbuild/openharmony-arm64" "0.27.3" + "@esbuild/sunos-x64" "0.27.3" + "@esbuild/win32-arm64" "0.27.3" + "@esbuild/win32-ia32" "0.27.3" + "@esbuild/win32-x64" "0.27.3" + +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +eventemitter3@^4.0.4: + version "4.0.7" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +eventsource-parser@^3.0.6: + version "3.0.6" + resolved "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz" + integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== + +expect-type@^1.2.1: + version "1.3.0" + resolved "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz" + integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA== + +fast-xml-parser@^4.4.1: + version "4.5.3" + resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz" + integrity sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig== + dependencies: + strnum "^1.1.1" + +fastestsmallesttextencoderdecoder@^1.0.22: + version "1.0.22" + resolved "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz" + integrity sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw== + +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + +form-data-encoder@1.7.2: + version "1.7.2" + resolved "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz" + integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== + +form-data@^4.0.4: + version "4.0.5" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz" + integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.12" + +formdata-node@^4.3.2: + version "4.4.1" + resolved "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz" + integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.3" + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-tsconfig@^4.7.5: + version "4.13.6" + resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz" + integrity sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw== + dependencies: + resolve-pkg-maps "^1.0.0" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +got@^11.8.5: + version "11.8.6" + resolved "https://registry.npmjs.org/got/-/got-11.8.6.tgz" + integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +http-cache-semantics@^4.0.0: + version "4.2.0" + resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz" + integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ== + +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +js-tiktoken@^1.0.12: + version "1.0.21" + resolved "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz" + integrity sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g== + dependencies: + base64-js "^1.5.1" + +js-tokens@^9.0.1: + version "9.0.1" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz" + integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-to-ts@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz" + integrity sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g== + dependencies: + "@babel/runtime" "^7.18.3" + ts-algebra "^2.0.0" + +json-schema@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +keyv@^4.0.0: + version "4.5.4" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +langsmith@^0.3.67: + version "0.3.87" + resolved "https://registry.npmjs.org/langsmith/-/langsmith-0.3.87.tgz" + integrity sha512-XXR1+9INH8YX96FKWc5tie0QixWz6tOqAsAKfcJyPkE0xPep+NDz0IQLR32q4bn10QK3LqD2HN6T3n6z1YLW7Q== + dependencies: + "@types/uuid" "^10.0.0" + chalk "^4.1.2" + console-table-printer "^2.12.1" + p-queue "^6.6.2" + semver "^7.6.3" + uuid "^10.0.0" + +loupe@^3.1.0, loupe@^3.1.4: + version "3.2.1" + resolved "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz" + integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + +magic-string@^0.30.17: + version "0.30.21" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +ms@^2.0.0, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mustache@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz" + integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== + +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +node-domexception@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +openai@^4: + version "4.104.0" + resolved "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz" + integrity sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-queue@^6.6.2: + version "6.6.2" + resolved "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + +p-retry@4: + version "4.6.2" + resolved "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + +pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + +pathval@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz" + integrity sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^4.0.2, picomatch@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + +postcss@^8.5.6: + version "8.5.6" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +pump@^3.0.0: + version "3.0.3" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz" + integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +responselike@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz" + integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== + dependencies: + lowercase-keys "^2.0.0" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +rollup@^4.43.0: + version "4.57.1" + resolved "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz" + integrity sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A== + dependencies: + "@types/estree" "1.0.8" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.57.1" + "@rollup/rollup-android-arm64" "4.57.1" + "@rollup/rollup-darwin-arm64" "4.57.1" + "@rollup/rollup-darwin-x64" "4.57.1" + "@rollup/rollup-freebsd-arm64" "4.57.1" + "@rollup/rollup-freebsd-x64" "4.57.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.57.1" + "@rollup/rollup-linux-arm-musleabihf" "4.57.1" + "@rollup/rollup-linux-arm64-gnu" "4.57.1" + "@rollup/rollup-linux-arm64-musl" "4.57.1" + "@rollup/rollup-linux-loong64-gnu" "4.57.1" + "@rollup/rollup-linux-loong64-musl" "4.57.1" + "@rollup/rollup-linux-ppc64-gnu" "4.57.1" + "@rollup/rollup-linux-ppc64-musl" "4.57.1" + "@rollup/rollup-linux-riscv64-gnu" "4.57.1" + "@rollup/rollup-linux-riscv64-musl" "4.57.1" + "@rollup/rollup-linux-s390x-gnu" "4.57.1" + "@rollup/rollup-linux-x64-gnu" "4.57.1" + "@rollup/rollup-linux-x64-musl" "4.57.1" + "@rollup/rollup-openbsd-x64" "4.57.1" + "@rollup/rollup-openharmony-arm64" "4.57.1" + "@rollup/rollup-win32-arm64-msvc" "4.57.1" + "@rollup/rollup-win32-ia32-msvc" "4.57.1" + "@rollup/rollup-win32-x64-gnu" "4.57.1" + "@rollup/rollup-win32-x64-msvc" "4.57.1" + fsevents "~2.3.2" + +semver@^7.6.3: + version "7.7.4" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + +simple-wcswidth@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz" + integrity sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw== + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.9.0: + version "3.10.0" + resolved "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz" + integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== + +strip-literal@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz" + integrity sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg== + dependencies: + js-tokens "^9.0.1" + +strnum@^1.1.1: + version "1.1.2" + resolved "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz" + integrity sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinyglobby@^0.2.14, tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + +tinypool@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz" + integrity sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== + +tinyrainbow@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz" + integrity sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw== + +tinyspy@^4.0.3: + version "4.0.4" + resolved "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz" + integrity sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q== + +to-utf8@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz" + integrity sha512-zks18/TWT1iHO3v0vFp5qLKOG27m67ycq/Y7a7cTiRuUNlc4gf3HGnkRgMv0NyhnfTamtkYBJl+YeD1/j07gBQ== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +ts-algebra@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz" + integrity sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw== + +tsx@^4.19.0: + version "4.21.0" + resolved "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz" + integrity sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw== + dependencies: + esbuild "~0.27.0" + get-tsconfig "^4.7.5" + optionalDependencies: + fsevents "~2.3.3" + +typescript@^5: + version "5.9.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +ulid@^2.3.0: + version "2.4.0" + resolved "https://registry.npmjs.org/ulid/-/ulid-2.4.0.tgz" + integrity sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + +uuid@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +vite-node@3.2.4: + version "3.2.4" + resolved "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz" + integrity sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg== + dependencies: + cac "^6.7.14" + debug "^4.4.1" + es-module-lexer "^1.7.0" + pathe "^2.0.3" + vite "^5.0.0 || ^6.0.0 || ^7.0.0-0" + +"vite@^5.0.0 || ^6.0.0 || ^7.0.0-0": + version "7.3.1" + resolved "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz" + integrity sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA== + dependencies: + esbuild "^0.27.0" + fdir "^6.5.0" + picomatch "^4.0.3" + postcss "^8.5.6" + rollup "^4.43.0" + tinyglobby "^0.2.15" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^3.0.0: + version "3.2.4" + resolved "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz" + integrity sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A== + dependencies: + "@types/chai" "^5.2.2" + "@vitest/expect" "3.2.4" + "@vitest/mocker" "3.2.4" + "@vitest/pretty-format" "^3.2.4" + "@vitest/runner" "3.2.4" + "@vitest/snapshot" "3.2.4" + "@vitest/spy" "3.2.4" + "@vitest/utils" "3.2.4" + chai "^5.2.0" + debug "^4.4.1" + expect-type "^1.2.1" + magic-string "^0.30.17" + pathe "^2.0.3" + picomatch "^4.0.2" + std-env "^3.9.0" + tinybench "^2.9.0" + tinyexec "^0.3.2" + tinyglobby "^0.2.14" + tinypool "^1.1.1" + tinyrainbow "^2.0.0" + vite "^5.0.0 || ^6.0.0 || ^7.0.0-0" + vite-node "3.2.4" + why-is-node-running "^2.3.0" + +web-streams-polyfill@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz" + integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^8.17.1: + version "8.19.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz" + integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== + +zod-to-json-schema@^3.22.3: + version "3.25.1" + resolved "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz" + integrity sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA== + +zod@^3.23.8, zod@^3.25.32: + version "3.25.76" + resolved "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz" + integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/jest.config.js b/jest.config.js index 09d4f81d9f..9e3892d5ab 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,7 +12,7 @@ module.exports = { }, coveragePathIgnorePatterns: ['src/styles/svg'], testPathIgnorePatterns: [`node_modules`, `\\.cache`, `.*/public`], - modulePathIgnorePatterns: ['/examples/'], + modulePathIgnorePatterns: ['/examples/', '/guides/'], // NOTE: This is a workaround for compilation issues with .d.ts files transformIgnorePatterns: [ `node_modules/(?!(gatsby|gatsby-script|use-keyboard-shortcut|react-medium-image-zoom|@react-hook/media-query|@mdx-js/react|@ably/ui/core|until-async)/)`,