diff --git a/.gitignore b/.gitignore index ae16bb43e..2d5204ff6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ node_modules .vscode **/mastra.db* + +.pnpm-store + +**/.poetry-cache diff --git a/apps/dojo/package.json b/apps/dojo/package.json index 74805947b..8a8cd3d68 100644 --- a/apps/dojo/package.json +++ b/apps/dojo/package.json @@ -12,6 +12,8 @@ "run-everything": "./scripts/prep-dojo-everything.js && ./scripts/run-dojo-everything.js" }, "dependencies": { + "@a2a-js/sdk": "0.2.5", + "@ag-ui/a2a": "workspace:*", "@ag-ui/a2a-middleware": "workspace:*", "@ag-ui/adk": "workspace:*", "@ag-ui/agno": "workspace:*", @@ -20,10 +22,10 @@ "@ag-ui/llamaindex": "workspace:*", "@ag-ui/mastra": "workspace:*", "@ag-ui/middleware-starter": "workspace:*", - "@ag-ui/spring-ai": "workspace:*", "@ag-ui/pydantic-ai": "workspace:*", "@ag-ui/server-starter": "workspace:*", "@ag-ui/server-starter-all-features": "workspace:*", + "@ag-ui/spring-ai": "workspace:*", "@ag-ui/vercel-ai-sdk": "workspace:*", "@ai-sdk/openai": "^2.0.42", "@copilotkit/react-core": "1.10.6", @@ -31,6 +33,9 @@ "@copilotkit/runtime": "1.10.6", "@copilotkit/runtime-client-gql": "1.10.6", "@copilotkit/shared": "1.10.6", + "@copilotkitnext/react": "0.0.19-alpha.0", + "@copilotkitnext/runtime": "0.0.19-alpha.0", + "@copilotkitnext/agent": "0.0.19-alpha.0", "@mastra/client-js": "^0.15.2", "@mastra/core": "^0.20.2", "@mastra/dynamodb": "^0.15.6", @@ -58,6 +63,7 @@ "diff": "^7.0.0", "embla-carousel-react": "^8.6.0", "fast-json-patch": "^3.1.1", + "hono": "^4.10.3", "lucide-react": "^0.477.0", "markdown-it": "^14.1.0", "markdown-it-ins": "^4.0.0", diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 18d7e90e8..02db44e28 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -16,9 +16,11 @@ import getEnvVars from "./env"; import { mastra } from "./mastra"; import { PydanticAIAgent } from "@ag-ui/pydantic-ai"; import { ADKAgent } from "@ag-ui/adk"; -import { SpringAiAgent } from '@ag-ui/spring-ai'; +import { SpringAiAgent } from "@ag-ui/spring-ai"; import { HttpAgent } from "@ag-ui/client"; import { A2AMiddlewareAgent } from "@ag-ui/a2a-middleware"; +import { A2AAgent } from "@ag-ui/a2a"; +import { A2AClient } from "@a2a-js/sdk/client"; const envVars = getEnvVars(); export const agentsIntegrations: AgentIntegrationConfig[] = [ @@ -81,7 +83,9 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ backend_tool_rendering: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/backend_tool_rendering`, }), - shared_state: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/adk-shared-state-agent` }), + shared_state: new ADKAgent({ + url: `${envVars.adkMiddlewareUrl}/adk-shared-state-agent`, + }), // predictive_state_updates: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/adk-predictive-state-agent` }), }; }, @@ -273,26 +277,26 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ }, }, { - id: 'spring-ai', + id: "spring-ai", agents: async () => { return { agentic_chat: new SpringAiAgent({ - url: `${envVars.springAiUrl}/agentic_chat/agui` + url: `${envVars.springAiUrl}/agentic_chat/agui`, }), shared_state: new SpringAiAgent({ - url: `${envVars.springAiUrl}/shared_state/agui` + url: `${envVars.springAiUrl}/shared_state/agui`, }), tool_based_generative_ui: new SpringAiAgent({ - url: `${envVars.springAiUrl}/tool_based_generative_ui/agui` + url: `${envVars.springAiUrl}/tool_based_generative_ui/agui`, }), human_in_the_loop: new SpringAiAgent({ - url: `${envVars.springAiUrl}/human_in_the_loop/agui` + url: `${envVars.springAiUrl}/human_in_the_loop/agui`, }), agentic_generative_ui: new SpringAiAgent({ - url: `${envVars.springAiUrl}/agentic_generative_ui/agui` - }) - } - } + url: `${envVars.springAiUrl}/agentic_generative_ui/agui`, + }), + }; + }, }, { id: "llama-index", @@ -341,6 +345,19 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ }; }, }, + { + id: "a2a-basic", + agents: async () => { + const a2aClient = new A2AClient(envVars.a2aUrl); + return { + agentic_chat: new A2AAgent({ + description: "Direct A2A agent", + a2aClient, + debug: process.env.NODE_ENV !== "production", + }), + }; + }, + }, { id: "a2a", agents: async () => { diff --git a/apps/dojo/src/app/[integrationId]/feature/vnext_chat/page.tsx b/apps/dojo/src/app/[integrationId]/feature/vnext_chat/page.tsx new file mode 100644 index 000000000..b81ab7ecb --- /dev/null +++ b/apps/dojo/src/app/[integrationId]/feature/vnext_chat/page.tsx @@ -0,0 +1,39 @@ +"use client"; + +import React from "react"; +import "@copilotkitnext/react/styles.css"; +import { CopilotChat, CopilotKitProvider } from "@copilotkitnext/react"; + +export const dynamic = "force-dynamic"; + +interface PageProps { + params: Promise<{ + integrationId: string; + }>; +} + +export default function Page({ params }: PageProps) { + const { integrationId } = React.use(params); + + return ( + +
+ +
+
+ ); +} + +function Chat({ threadId }: { threadId: string }) { + return ( +
+ +
+ ); +} diff --git a/apps/dojo/src/app/api/copilotkit/[integrationId]/route.ts b/apps/dojo/src/app/api/copilotkit/[integrationId]/route.ts index 5a0641441..a1c04b856 100644 --- a/apps/dojo/src/app/api/copilotkit/[integrationId]/route.ts +++ b/apps/dojo/src/app/api/copilotkit/[integrationId]/route.ts @@ -3,10 +3,10 @@ import { ExperimentalEmptyAdapter, copilotRuntimeNextJSAppRouterEndpoint, } from "@copilotkit/runtime"; -import { agentsIntegrations } from "@/agents"; - import { NextRequest } from "next/server"; +import { agentsIntegrations } from "@/agents"; + export async function POST(request: NextRequest) { const integrationId = request.url.split("/").pop(); @@ -14,6 +14,7 @@ export async function POST(request: NextRequest) { if (!integration) { return new Response("Integration not found", { status: 404 }); } + const agents = await integration.agents(); const runtime = new CopilotRuntime({ // @ts-ignore for now @@ -27,3 +28,4 @@ export async function POST(request: NextRequest) { return handleRequest(request); } + diff --git a/apps/dojo/src/app/api/copilotkit/route.ts b/apps/dojo/src/app/api/copilotkit/route.ts new file mode 100644 index 000000000..80f03f2c1 --- /dev/null +++ b/apps/dojo/src/app/api/copilotkit/route.ts @@ -0,0 +1,22 @@ +import { + CopilotRuntime, + InMemoryAgentRunner, + createCopilotEndpoint, +} from "@copilotkitnext/runtime"; +import { handle } from "hono/vercel"; + +const runtime = new CopilotRuntime({ + agents: { + default: null as any, + }, + runner: new InMemoryAgentRunner(), +}); + +const app = createCopilotEndpoint({ + runtime, + basePath: "/api/copilotkit", +}); + +export const GET = handle(app); +export const POST = handle(app); + diff --git a/apps/dojo/src/app/api/copilotkitnext/[integrationId]/[[...slug]]/route.ts b/apps/dojo/src/app/api/copilotkitnext/[integrationId]/[[...slug]]/route.ts new file mode 100644 index 000000000..f1b05e9d3 --- /dev/null +++ b/apps/dojo/src/app/api/copilotkitnext/[integrationId]/[[...slug]]/route.ts @@ -0,0 +1,54 @@ +import { + CopilotRuntime, + InMemoryAgentRunner, + createCopilotEndpoint, +} from "@copilotkitnext/runtime"; +import { handle } from "hono/vercel"; +import type { NextRequest } from "next/server"; +import { BasicAgent } from "@copilotkitnext/agent"; + +type RouteParams = { + params: Promise<{ + integrationId: string; + slug?: string[]; + }>; +}; + +const handlerCache = new Map>(); + +function getHandler(integrationId: string) { + const cached = handlerCache.get(integrationId); + if (cached) { + return cached; + } + + const runtime = new CopilotRuntime({ + agents: { + default: new BasicAgent({ + model: "openai/gpt-4o", + }), + }, + runner: new InMemoryAgentRunner(), + }); + + const app = createCopilotEndpoint({ + runtime, + basePath: `/api/copilotkitnext/${integrationId}`, + }); + + const handler = handle(app); + handlerCache.set(integrationId, handler); + return handler; +} + +export async function GET(request: NextRequest, context: RouteParams) { + const { integrationId } = await context.params; + const handler = getHandler(integrationId); + return handler(request); +} + +export async function POST(request: NextRequest, context: RouteParams) { + const { integrationId } = await context.params; + const handler = getHandler(integrationId); + return handler(request); +} diff --git a/apps/dojo/src/config.ts b/apps/dojo/src/config.ts index e27f3a4b8..df7e0f25e 100644 --- a/apps/dojo/src/config.ts +++ b/apps/dojo/src/config.ts @@ -32,13 +32,15 @@ export const featureConfig: FeatureConfig[] = [ createFeatureConfig({ id: "human_in_the_loop", name: "Human in the loop", - description: "Plan a task together and direct the Copilot to take the right steps", + description: + "Plan a task together and direct the Copilot to take the right steps", tags: ["HITL", "Interactivity"], }), createFeatureConfig({ id: "agentic_generative_ui", name: "Agentic Generative UI", - description: "Assign a long running task to your Copilot and see how it performs!", + description: + "Assign a long running task to your Copilot and see how it performs!", tags: ["Generative ui (agent)", "Long running task"], }), createFeatureConfig({ @@ -56,7 +58,8 @@ export const featureConfig: FeatureConfig[] = [ createFeatureConfig({ id: "predictive_state_updates", name: "Predictive State Updates", - description: "Use collaboration to edit a document in real time with your Copilot", + description: + "Use collaboration to edit a document in real time with your Copilot", tags: ["State", "Streaming", "Tools"], }), createFeatureConfig({ @@ -68,7 +71,8 @@ export const featureConfig: FeatureConfig[] = [ createFeatureConfig({ id: "subgraphs", name: "Subgraphs", - description: "Have your tasks performed by multiple agents, working together", + description: + "Have your tasks performed by multiple agents, working together", tags: ["Chat", "Multi-agent architecture", "Streaming", "Subgraphs"], }), createFeatureConfig({ @@ -77,6 +81,12 @@ export const featureConfig: FeatureConfig[] = [ description: "Chat with your Copilot and call frontend tools", tags: ["Chat", "Tools", "Streaming"], }), + createFeatureConfig({ + id: "vnext_chat", + name: "VNext Chat", + description: "Chat based on CopilotKit vnext", + tags: ["Chat", "VNext", "Streaming"], + }), ]; export default featureConfig; diff --git a/apps/dojo/src/env.ts b/apps/dojo/src/env.ts index c63344897..569c6cfdc 100644 --- a/apps/dojo/src/env.ts +++ b/apps/dojo/src/env.ts @@ -11,6 +11,7 @@ type envVars = { crewAiUrl: string; pydanticAIUrl: string; adkMiddlewareUrl: string; + a2aUrl: string; a2aMiddlewareBuildingsManagementUrl: string; a2aMiddlewareFinanceUrl: string; a2aMiddlewareItUrl: string; @@ -40,10 +41,11 @@ export default function getEnvVars(): envVars { pydanticAIUrl: process.env.PYDANTIC_AI_URL || 'http://localhost:9000', adkMiddlewareUrl: process.env.ADK_MIDDLEWARE_URL || 'http://localhost:8000', springAiUrl: process.env.SPRING_AI_URL || 'http://localhost:8080', + a2aUrl: process.env.A2A_URL || 'http://localhost:10002', a2aMiddlewareBuildingsManagementUrl: process.env.A2A_MIDDLEWARE_BUILDINGS_MANAGEMENT_URL || 'http://localhost:9001', a2aMiddlewareFinanceUrl: process.env.A2A_MIDDLEWARE_FINANCE_URL || 'http://localhost:9002', a2aMiddlewareItUrl: process.env.A2A_MIDDLEWARE_IT_URL || 'http://localhost:9003', a2aMiddlewareOrchestratorUrl: process.env.A2A_MIDDLEWARE_ORCHESTRATOR_URL || 'http://localhost:9000', customDomainTitle: customDomainTitle, } -} \ No newline at end of file +} diff --git a/apps/dojo/src/menu.ts b/apps/dojo/src/menu.ts index 25d02f283..36959a218 100644 --- a/apps/dojo/src/menu.ts +++ b/apps/dojo/src/menu.ts @@ -47,7 +47,11 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ { id: "mastra", name: "Mastra", - features: ["agentic_chat", "backend_tool_rendering", "tool_based_generative_ui"], + features: [ + "agentic_chat", + "backend_tool_rendering", + "tool_based_generative_ui", + ], }, { id: "mastra-agent-local", @@ -60,15 +64,15 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ ], }, { - id: 'spring-ai', - name: 'Spring AI', + id: "spring-ai", + name: "Spring AI", features: [ - 'agentic_chat', - 'shared_state', - 'tool_based_generative_ui', - 'human_in_the_loop', - 'agentic_generative_ui' - ] + "agentic_chat", + "shared_state", + "tool_based_generative_ui", + "human_in_the_loop", + "agentic_generative_ui", + ], }, { id: "pydantic-ai", @@ -99,7 +103,11 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ { id: "agno", name: "Agno", - features: ["agentic_chat", "backend_tool_rendering", "tool_based_generative_ui"], + features: [ + "agentic_chat", + "backend_tool_rendering", + "tool_based_generative_ui", + ], }, { id: "llama-index", @@ -125,6 +133,11 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ "tool_based_generative_ui", ], }, + { + id: "a2a-basic", + name: "A2A (Direct)", + features: ["vnext_chat"], + }, // Disabled until we can support Vercel AI SDK v5 // { // id: "vercel-ai-sdk", diff --git a/apps/dojo/src/types/integration.ts b/apps/dojo/src/types/integration.ts index 59d9b3adb..182933a6f 100644 --- a/apps/dojo/src/types/integration.ts +++ b/apps/dojo/src/types/integration.ts @@ -10,7 +10,8 @@ export type Feature = | "backend_tool_rendering" | "agentic_chat_reasoning" | "subgraphs" - | "a2a_chat"; + | "a2a_chat" + | "vnext_chat"; export interface MenuIntegrationConfig { id: string; diff --git a/apps/dojo/tsconfig.json b/apps/dojo/tsconfig.json index af157911a..a57e78c43 100644 --- a/apps/dojo/tsconfig.json +++ b/apps/dojo/tsconfig.json @@ -19,9 +19,9 @@ } ], "paths": { - "@/*": ["./src/*", "../../packages/client/src/*"], - "@ag-ui/client": ["../../packages/client/src"], - "@ag-ui/client/*": ["../../packages/client/src/*"] + "@/*": ["./src/*", "../../sdks/typescript/packages/client/src/*"], + "@ag-ui/client": ["../../sdks/typescript/packages/client/src"], + "@ag-ui/client/*": ["../../sdks/typescript/packages/client/src/*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], diff --git a/docs/concepts/events.mdx b/docs/concepts/events.mdx index 920b203b9..6ba6c9cc4 100644 --- a/docs/concepts/events.mdx +++ b/docs/concepts/events.mdx @@ -19,6 +19,7 @@ Events in the protocol are categorized by their purpose: | Text Message Events | Handle streaming textual content | | Tool Call Events | Manage tool executions by agents | | State Management Events | Synchronize state between agents and UI | +| Activity Events | Represent ongoing activity progress | | Special Events | Support custom functionality | | Draft Events | Proposed events under development | @@ -83,10 +84,12 @@ elements such as progress indicators or loading states. It also provides crucial identifiers that can be used to associate subsequent events with this specific run. -| Property | Description | -| ---------- | ----------------------------- | -| `threadId` | ID of the conversation thread | -| `runId` | ID of the agent run | +| Property | Description | +| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `threadId` | ID of the conversation thread | +| `runId` | ID of the agent run | +| `parentRunId` | (Optional) Lineage pointer for branching/time travel. If present, refers to a prior run within the same thread, creating a git-like append-only log | +| `input` | (Optional) The exact agent input payload that was sent to the agent for this run. May omit messages already present in history; compactEvents() will normalize | ### RunFinished @@ -445,6 +448,40 @@ displayed to users. | ---------- | ------------------------ | | `messages` | Array of message objects | +## Activity Events + +Activity Events expose structured, in-progress activity updates that occur +between chat messages. They follow the same snapshot/delta pattern as the state +system so that UIs can render a complete activity view immediately and then +incrementally update it as new information arrives. + +### ActivitySnapshot + +Delivers a complete snapshot of an activity message. + +| Property | Description | +| --------------- | ---------------------------------------------------------------- | +| `messageId` | Identifier for the `ActivityMessage` this event updates | +| `activityType` | Activity discriminator (for example `"PLAN"`, `"SEARCH"`) | +| `content` | Structured JSON payload representing the full activity state | + +Frontends should either create a new `ActivityMessage` or replace the existing +one with the payload supplied by the snapshot. + +### ActivityDelta + +Applies incremental updates to an existing activity using JSON Patch operations. + +| Property | Description | +| --------------- | -------------------------------------------------------------------- | +| `messageId` | Identifier for the target activity message | +| `activityType` | Activity discriminator (mirrors the value from the most recent snapshot) | +| `patch` | Array of RFC 6902 JSON Patch operations to apply to the activity data | + +Activity deltas should be applied in order to the previously synchronized +activity content. If an application detects divergence, it can request or emit a +fresh `ActivitySnapshot` to resynchronize. + ## Special Events Special events provide flexibility in the protocol by allowing for @@ -504,9 +541,10 @@ Provides the complete activity state at a point in time. | Property | Description | | -------------- | ---------------------------------------------------- | -| `messageId` | Unique identifier for the ActivityMessage | -| `activityType` | Activity type (e.g., "PLAN", "SEARCH", "SCRAPE") | -| `content` | Complete activity state at this point | +| `messageId` | Unique identifier for the ActivityMessage | +| `activityType` | Activity type (e.g., "PLAN", "SEARCH", "SCRAPE") | +| `content` | Complete activity state at this point | +| `replace` | `true` (default) replaces an existing activity message; `false` only creates a new one if none exists | #### ActivityDeltaEvent diff --git a/docs/concepts/messages.mdx b/docs/concepts/messages.mdx index 4777cbfe1..c37011d1b 100644 --- a/docs/concepts/messages.mdx +++ b/docs/concepts/messages.mdx @@ -28,6 +28,10 @@ interface BaseMessage { } ``` +The `role` discriminator can be `"user"`, `"assistant"`, `"system"`, +`"tool"`, `"developer"`, or `"activity"`. Concrete message types extend this +shape with the fields they need. + ## Message Types AG-UI supports several message types to accommodate different participants in a @@ -41,11 +45,31 @@ Messages from the end user to the agent: interface UserMessage { id: string role: "user" - content: string // Text input from the user + content: string | InputContent[] // Text or multimodal input from the user name?: string // Optional user identifier } + +type InputContent = TextInputContent | BinaryInputContent + +interface TextInputContent { + type: "text" + text: string +} + +interface BinaryInputContent { + type: "binary" + mimeType: string + id?: string + url?: string + data?: string + filename?: string +} ``` +> For `BinaryInputContent`, provide at least one of `id`, `url`, or `data` to reference the payload. + +This structure keeps traditional plain-text inputs working while enabling richer payloads such as images, audio clips, or uploaded files in the same message. + ### Assistant Messages Messages from the AI assistant to the user: @@ -86,6 +110,24 @@ interface ToolMessage { } ``` +### Activity Messages + +Structured progress updates that appear between chat messages: + +```typescript +interface ActivityMessage { + id: string + role: "activity" + activityType: string // e.g. "PLAN", "SEARCH", "SCRAPE" + content: Record // Structured payload rendered by the frontend +} +``` + +Activity messages are populated by `ACTIVITY_SNAPSHOT` and `ACTIVITY_DELTA` +events. The structured `content` object gives frontends everything they need to +render bespoke status views, such as checklists, workflow progress, or search +results in flight. + ### Developer Messages Internal messages used for development or debugging: diff --git a/docs/drafts/multimodal-messages.mdx b/docs/drafts/multimodal-messages.mdx index 969032fc9..6b375ff89 100644 --- a/docs/drafts/multimodal-messages.mdx +++ b/docs/drafts/multimodal-messages.mdx @@ -21,7 +21,7 @@ apps. Inputs may include text, images, audio, and files. ## Status -- **Status**: Draft +- **Status**: Implemented — October 16, 2025 - **Author(s)**: Markus Ecker (mail@mme.xyz) ## Detailed Specification diff --git a/docs/sdk/js/client/abstract-agent.mdx b/docs/sdk/js/client/abstract-agent.mdx index 3738b0aae..cf322d214 100644 --- a/docs/sdk/js/client/abstract-agent.mdx +++ b/docs/sdk/js/client/abstract-agent.mdx @@ -111,6 +111,33 @@ Creates a deep copy of the agent instance. clone(): AbstractAgent ``` +### connectAgent() + +Establishes a persistent connection with an agent that implements the +`connect()` method. + +```typescript +connectAgent(parameters?: RunAgentParameters, subscriber?: AgentSubscriber): Promise +``` + +Similar to `runAgent()` but uses the `connect()` method internally. The agent +must implement `connect()` or this functionality must be provided by a framework +like [CopilotKit](https://copilotkit.ai). + +## Observable Properties + +### events$ + +An observable stream of all events emitted during agent execution. + +```typescript +events$: Observable +``` + +This property provides direct access to the agent's event stream. Events are +stored using a `ReplaySubject`, allowing late subscribers will receive all +historical events. + ## Properties - `agentId`: Unique identifier for the agent instance @@ -118,6 +145,8 @@ clone(): AbstractAgent - `threadId`: Conversation thread identifier - `messages`: Array of conversation messages - `state`: Current agent state object +- `events$`: Observable stream of all `BaseEvent` objects emitted during agent + execution (replayed for late subscribers) ## Protected Methods @@ -131,6 +160,17 @@ Executes the agent and returns an observable event stream. protected abstract run(input: RunAgentInput): RunAgent ``` +### connect() + +Establishes a persistent connection and returns an observable event stream. + +```typescript +protected connect(input: RunAgentInput): RunAgent +``` + +Override this method to implement persistent connections. Default implementation +throws `ConnectNotImplementedError`. + ### apply() Processes events from the run and updates the agent state. diff --git a/docs/sdk/js/client/subscriber.mdx b/docs/sdk/js/client/subscriber.mdx index ee010488f..f97ab1389 100644 --- a/docs/sdk/js/client/subscriber.mdx +++ b/docs/sdk/js/client/subscriber.mdx @@ -271,6 +271,32 @@ Called when a complete message history snapshot is provided. onMessagesSnapshotEvent?(params: { event: MessagesSnapshotEvent } & AgentSubscriberParams): MaybePromise ``` +#### onActivitySnapshotEvent() + +Called when an activity snapshot is received. The handler receives both the raw +event and any existing `ActivityMessage` (if present) so you can inspect or +replace it before the default client logic runs. + +```typescript +onActivitySnapshotEvent?(params: { + event: ActivitySnapshotEvent + activityMessage?: ActivityMessage + existingMessage?: Message +} & AgentSubscriberParams): MaybePromise +``` + +#### onActivityDeltaEvent() + +Triggered for each activity delta. Use this hook to transform or debounce the +incoming JSON Patch operations before they update the conversation transcript. + +```typescript +onActivityDeltaEvent?(params: { + event: ActivityDeltaEvent + activityMessage?: ActivityMessage +} & AgentSubscriberParams): MaybePromise +``` + #### onRawEvent() Handler for raw, unprocessed events. diff --git a/docs/sdk/js/core/events.mdx b/docs/sdk/js/core/events.mdx index ea119090c..df4d8646e 100644 --- a/docs/sdk/js/core/events.mdx +++ b/docs/sdk/js/core/events.mdx @@ -26,6 +26,8 @@ enum EventType { STATE_SNAPSHOT = "STATE_SNAPSHOT", STATE_DELTA = "STATE_DELTA", MESSAGES_SNAPSHOT = "MESSAGES_SNAPSHOT", + ACTIVITY_SNAPSHOT = "ACTIVITY_SNAPSHOT", + ACTIVITY_DELTA = "ACTIVITY_DELTA", RAW = "RAW", CUSTOM = "CUSTOM", RUN_STARTED = "RUN_STARTED", @@ -68,13 +70,17 @@ type RunStartedEvent = BaseEvent & { type: EventType.RUN_STARTED threadId: string runId: string + parentRunId?: string + input?: RunAgentInput } ``` -| Property | Type | Description | -| ---------- | -------- | ----------------------------- | -| `threadId` | `string` | ID of the conversation thread | -| `runId` | `string` | ID of the agent run | +| Property | Type | Description | +| -------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `threadId` | `string` | ID of the conversation thread | +| `runId` | `string` | ID of the agent run | +| `parentRunId` | `string` (optional) | (Optional) Lineage pointer for branching/time travel. If present, refers to a prior run within the same thread | +| `input` | `RunAgentInput` (optional) | (Optional) The exact agent input payload sent to the agent for this run. May omit messages already in history | ### RunFinishedEvent @@ -320,6 +326,46 @@ type MessagesSnapshotEvent = BaseEvent & { | ---------- | ----------- | ------------------------ | | `messages` | `Message[]` | Array of message objects | +### ActivitySnapshotEvent + +Delivers a complete snapshot of an activity message. + +```typescript +type ActivitySnapshotEvent = BaseEvent & { + type: EventType.ACTIVITY_SNAPSHOT + messageId: string + activityType: string + content: Record + replace?: boolean +} +``` + +| Property | Type | Description | +| -------------- | ---------------------- | ------------------------------------------------------- | +| `messageId` | `string` | Identifier for the target `ActivityMessage` | +| `activityType` | `string` | Activity discriminator such as `"PLAN"` or `"SEARCH"` | +| `content` | `Record` | Structured payload describing the full activity state | +| `replace` | `boolean` (optional) | Defaults to `true`; when `false` the snapshot is ignored if a message with the same ID already exists | + +### ActivityDeltaEvent + +Provides incremental updates to an activity snapshot using JSON Patch. + +```typescript +type ActivityDeltaEvent = BaseEvent & { + type: EventType.ACTIVITY_DELTA + messageId: string + activityType: string + patch: any[] // RFC 6902 JSON Patch operations +} +``` + +| Property | Type | Description | +| -------------- | ------------- | ----------------------------------------------------------------- | +| `messageId` | `string` | Identifier for the target `ActivityMessage` | +| `activityType` | `string` | Activity discriminator mirroring the most recent snapshot | +| `patch` | `any[]` | JSON Patch operations applied to the structured activity payload | + ## Special Events ### RawEvent diff --git a/docs/sdk/js/core/types.mdx b/docs/sdk/js/core/types.mdx index afc011609..758549ed0 100644 --- a/docs/sdk/js/core/types.mdx +++ b/docs/sdk/js/core/types.mdx @@ -20,6 +20,7 @@ Input parameters for running an agent. In the HTTP API, this is the body of the type RunAgentInput = { threadId: string runId: string + parentRunId?: string state: any messages: Message[] tools: Tool[] @@ -32,6 +33,7 @@ type RunAgentInput = { | ---------------- | ----------- | ---------------------------------------------- | | `threadId` | `string` | ID of the conversation thread | | `runId` | `string` | ID of the current run | +| `parentRunId` | `string (optional)` | ID of the run that spawned this run | | `state` | `any` | Current state of the agent | | `messages` | `Message[]` | Array of messages in the conversation | | `tools` | `Tool[]` | Array of tools available to the agent | @@ -48,7 +50,7 @@ messages in the system. Represents the possible roles a message sender can have. ```typescript -type Role = "developer" | "system" | "assistant" | "user" | "tool" +type Role = "developer" | "system" | "assistant" | "user" | "tool" | "activity" ``` ### DeveloperMessage @@ -121,17 +123,49 @@ Represents a message from a user. type UserMessage = { id: string role: "user" - content: string + content: string | InputContent[] name?: string } ``` -| Property | Type | Description | -| --------- | -------- | ------------------------------------------- | -| `id` | `string` | Unique identifier for the message | -| `role` | `"user"` | Role of the message sender, fixed as "user" | -| `content` | `string` | Text content of the message (required) | -| `name` | `string` | Optional name of the sender | +| Property | Type | Description | +| --------- | --------------------------- | --------------------------------------------------------------------- | +| `id` | `string` | Unique identifier for the message | +| `role` | `"user"` | Role of the message sender, fixed as "user" | +| `content` | `string \| InputContent[]` | Either plain text or an ordered array of multimodal content fragments | +| `name` | `string` | Optional name of the sender | + +### InputContent + +Union of supported multimodal fragments. + +```typescript +type InputContent = TextInputContent | BinaryInputContent +``` + +### TextInputContent + +```typescript +type TextInputContent = { + type: "text" + text: string +} +``` + +### BinaryInputContent + +```typescript +type BinaryInputContent = { + type: "binary" + mimeType: string + id?: string + url?: string + data?: string + filename?: string +} +``` + +> At least one of `id`, `url`, or `data` must be provided. ### ToolMessage @@ -155,6 +189,26 @@ type ToolMessage = { | `toolCallId` | `string` | ID of the tool call this message responds to | | `error` | `string` | Error message if the tool call failed | +### ActivityMessage + +Represents structured activity progress emitted between chat messages. + +```typescript +type ActivityMessage = { + id: string + role: "activity" + activityType: string + content: Record +} +``` + +| Property | Type | Description | +| -------------- | --------------------- | ------------------------------------------------------- | +| `id` | `string` | Unique identifier for the activity message | +| `role` | `"activity"` | Fixed discriminator identifying the message as activity | +| `activityType` | `string` | Activity discriminator used for renderer selection | +| `content` | `Record` | Structured payload representing the activity state | + ### Message A union type representing any type of message in the system. @@ -166,6 +220,7 @@ type Message = | AssistantMessage | UserMessage | ToolMessage + | ActivityMessage ``` ### ToolCall diff --git a/docs/sdk/python/core/events.mdx b/docs/sdk/python/core/events.mdx index 6d5cdc934..463b21734 100644 --- a/docs/sdk/python/core/events.mdx +++ b/docs/sdk/python/core/events.mdx @@ -29,6 +29,8 @@ class EventType(str, Enum): STATE_SNAPSHOT = "STATE_SNAPSHOT" STATE_DELTA = "STATE_DELTA" MESSAGES_SNAPSHOT = "MESSAGES_SNAPSHOT" + ACTIVITY_SNAPSHOT = "ACTIVITY_SNAPSHOT" + ACTIVITY_DELTA = "ACTIVITY_DELTA" RAW = "RAW" CUSTOM = "CUSTOM" RUN_STARTED = "RUN_STARTED" @@ -73,12 +75,16 @@ class RunStartedEvent(BaseEvent): type: Literal[EventType.RUN_STARTED] thread_id: str run_id: str + parent_run_id: Optional[str] = None + input: Optional[RunAgentInput] = None ``` -| Property | Type | Description | -| ----------- | ----- | ----------------------------- | -| `thread_id` | `str` | ID of the conversation thread | -| `run_id` | `str` | ID of the agent run | +| Property | Type | Description | +| ---------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `thread_id` | `str` | ID of the conversation thread | +| `run_id` | `str` | ID of the agent run | +| `parent_run_id` | `Optional[str]` | (Optional) Lineage pointer for branching/time travel. If present, refers to a prior run within the same thread | +| `input` | `Optional[RunAgentInput]` | (Optional) The exact agent input payload sent to the agent for this run. May omit messages already in history | ### RunFinishedEvent @@ -342,6 +348,48 @@ class MessagesSnapshotEvent(BaseEvent): | ---------- | --------------- | ------------------------ | | `messages` | `List[Message]` | Array of message objects | +### ActivitySnapshotEvent + +`from ag_ui.core import ActivitySnapshotEvent` + +Delivers a complete snapshot of an activity message. + +```python +class ActivitySnapshotEvent(BaseEvent): + type: Literal[EventType.ACTIVITY_SNAPSHOT] + message_id: str + activity_type: str + content: Any + replace: bool = True +``` + +| Property | Type | Description | +| --------------- | ----- | ----------------------------------------------------- | +| `message_id` | `str` | Identifier for the target `ActivityMessage` | +| `activity_type` | `str` | Activity discriminator such as `"PLAN"` or `"SEARCH"` | +| `content` | `Any` | Structured payload describing the full activity state | +| `replace` | `bool` (default `True`) | When `False`, the snapshot is ignored if a message with the same ID already exists | + +### ActivityDeltaEvent + +`from ag_ui.core import ActivityDeltaEvent` + +Provides incremental updates to an activity snapshot using JSON Patch. + +```python +class ActivityDeltaEvent(BaseEvent): + type: Literal[EventType.ACTIVITY_DELTA] + message_id: str + activity_type: str + patch: List[Any] +``` + +| Property | Type | Description | +| --------------- | ------------ | -------------------------------------------------------------------- | +| `message_id` | `str` | Identifier for the target `ActivityMessage` | +| `activity_type` | `str` | Activity discriminator mirroring the most recent snapshot | +| `patch` | `List[Any]` | JSON Patch operations applied to the structured activity content | + ## Special Events ### RawEvent diff --git a/docs/sdk/python/core/types.mdx b/docs/sdk/python/core/types.mdx index 4f9180455..70275c4e2 100644 --- a/docs/sdk/python/core/types.mdx +++ b/docs/sdk/python/core/types.mdx @@ -22,6 +22,7 @@ Input parameters for running an agent. In the HTTP API, this is the body of the class RunAgentInput(ConfiguredBaseModel): thread_id: str run_id: str + parent_run_id: Optional[str] = None state: Any messages: List[Message] tools: List[Tool] @@ -33,6 +34,7 @@ class RunAgentInput(ConfiguredBaseModel): | ----------------- | --------------- | --------------------------------------------- | | `thread_id` | `str` | ID of the conversation thread | | `run_id` | `str` | ID of the current run | +| `parent_run_id` | `Optional[str]` | (Optional) ID of the run that spawned this run| | `state` | `Any` | Current state of the agent | | `messages` | `List[Message]` | List of messages in the conversation | | `tools` | `List[Tool]` | List of tools available to the agent | @@ -51,7 +53,7 @@ messages in the system. Represents the possible roles a message sender can have. ```python -Role = Literal["developer", "system", "assistant", "user", "tool"] +Role = Literal["developer", "system", "assistant", "user", "tool", "activity"] ``` ### DeveloperMessage @@ -122,15 +124,55 @@ Represents a message from a user. ```python class UserMessage(BaseMessage): role: Literal["user"] - content: str + content: Union[str, List["InputContent"]] +``` + +| Property | Type | Description | +| --------- | ---------------------------------- | --------------------------------------------------------------------- | +| `id` | `str` | Unique identifier for the message | +| `role` | `Literal["user"]` | Role of the message sender, fixed as "user" | +| `content` | `Union[str, List["InputContent"]]` | Either a plain text string or an ordered list of multimodal fragments | +| `name` | `Optional[str]` | Optional name of the sender | + +### TextInputContent + +Represents a text fragment inside a multimodal user message. + +```python +class TextInputContent(ConfiguredBaseModel): + type: Literal["text"] + text: str +``` + +| Property | Type | Description | +| -------- | --------------- | ---------------------------- | +| `type` | `Literal["text"]` | Identifies the fragment type | +| `text` | `str` | Text content | + +### BinaryInputContent + +Represents binary data such as images, audio, or files. + +```python +class BinaryInputContent(ConfiguredBaseModel): + type: Literal["binary"] + mime_type: str + id: Optional[str] = None + url: Optional[str] = None + data: Optional[str] = None + filename: Optional[str] = None ``` -| Property | Type | Description | -| --------- | ----------------- | ------------------------------------------- | -| `id` | `str` | Unique identifier for the message | -| `role` | `Literal["user"]` | Role of the message sender, fixed as "user" | -| `content` | `str` | Text content of the message (required) | -| `name` | `Optional[str]` | Optional name of the sender | +| Property | Type | Description | +| ---------- | ----------------- | ------------------------------------------------------------- | +| `type` | `Literal["binary"]` | Identifies the fragment type | +| `mime_type`| `str` | MIME type, for example `"image/png"` | +| `id` | `Optional[str]` | Reference to previously uploaded content | +| `url` | `Optional[str]` | Remote URL where the content can be retrieved | +| `data` | `Optional[str]` | Base64 encoded content | +| `filename` | `Optional[str]` | Optional filename hint | + +> **Validation:** At least one of `id`, `url`, or `data` must be provided. ### ToolMessage @@ -155,6 +197,27 @@ class ToolMessage(ConfiguredBaseModel): | `tool_call_id` | `str` | ID of the tool call this message responds to | | `error` | `Optional[str]` | Error message if the tool call failed | +### ActivityMessage + +`from ag_ui.core import ActivityMessage` + +Represents structured activity progress emitted between chat messages. + +```python +class ActivityMessage(ConfiguredBaseModel): + id: str + role: Literal["activity"] + activity_type: str + content: Dict[str, Any] +``` + +| Property | Type | Description | +| --------------- | ----------------------- | ------------------------------------------------------- | +| `id` | `str` | Unique identifier for the activity message | +| `role` | `Literal["activity"]` | Fixed discriminator identifying the message as activity | +| `activity_type` | `str` | Activity discriminator used for renderer selection | +| `content` | `Dict[str, Any]` | Structured payload representing the activity state | + ### Message `from ag_ui.core import Message` @@ -163,7 +226,14 @@ A union type representing any type of message in the system. ```python Message = Annotated[ - Union[DeveloperMessage, SystemMessage, AssistantMessage, UserMessage, ToolMessage], + Union[ + DeveloperMessage, + SystemMessage, + AssistantMessage, + UserMessage, + ToolMessage, + ActivityMessage, + ], Field(discriminator="role") ] ``` diff --git a/integrations/a2a/typescript/.gitignore b/integrations/a2a/typescript/.gitignore new file mode 100644 index 000000000..de4d1f007 --- /dev/null +++ b/integrations/a2a/typescript/.gitignore @@ -0,0 +1,2 @@ +dist +node_modules diff --git a/integrations/a2a/typescript/.npmrc b/integrations/a2a/typescript/.npmrc new file mode 100644 index 000000000..3e775efb0 --- /dev/null +++ b/integrations/a2a/typescript/.npmrc @@ -0,0 +1 @@ +auto-install-peers=true diff --git a/integrations/a2a/typescript/README.md b/integrations/a2a/typescript/README.md new file mode 100644 index 000000000..e1f689eb1 --- /dev/null +++ b/integrations/a2a/typescript/README.md @@ -0,0 +1,86 @@ +# @ag-ui/a2a + +A TypeScript integration that connects AG-UI agents with remote services that expose the [A2A protocol](https://a2a.dev/). It converts AG-UI conversations into A2A payloads, forwards them through the official A2A SDK, and replays the responses back into AG-UI event streams. + +> **Status:** Experimental. APIs may change while the integration stabilises. + +## Features + +- Message conversion helpers between AG-UI and A2A formats (user, assistant, tool, binary payloads). +- `A2AAgent` implementation that streams or performs blocking requests against A2A endpoints. +- Optional fallback from streaming to blocking requests when an agent does not support SSE. +- Event conversion utilities that surface A2A messages, task status updates, and artifact chunks as AG-UI events. +- Helper tool schema (`send_message_to_a2a_agent`) for orchestration scenarios. +- Example client and Jest tests to validate conversions and streaming flows. + +## Installation + +Once dependencies are installed in the monorepo: + +```bash +pnpm install +pnpm --filter @ag-ui/a2a build +``` + +## Quick start + +```ts +import { A2AAgent } from "@ag-ui/a2a"; + +import { A2AClient } from "@a2a-js/sdk/client"; + +const client = new A2AClient("https://my-a2a-agent"); + +const agent = new A2AAgent({ + a2aClient: client, + initialMessages: [ + { id: "user-1", role: "user", content: "Plan a team offsite" } as any, + ], +}); + +const { result, newMessages } = await agent.runAgent(); +console.log(result); +console.log(newMessages); +``` + +You can inject your own `A2AClient` instance via the `client` option, override default instructions, or force blocking mode by setting `strategy: "blocking"`. + +## Configuration reference + +| Option | Description | +| ------ | ----------- | +| `a2aClient` | Required. Provide an `A2AClient` instance (with any auth headers or custom fetch logic you need). | + +## Environment variables & authentication + +The integration relies on the underlying A2A agent for authentication. Common patterns include: + +- `A2A_AGENT_URL` – set in deployment environments to point to the remote agent base URL. +- `A2A_API_KEY` or `A2A_BEARER_TOKEN` – consumed by a wrapped `fetch` inside a custom `A2AClient` instance if the remote agent enforces API key or bearer authentication. + +Pass any credentials to the `A2AClient` you provide to `A2AAgent`, or configure an HTTP proxy that injects the correct headers. + +## Utilities + +- `convertAGUIMessagesToA2A(messages, options)` — reshapes AG-UI history into A2A message objects, forwarding only user/assistant/tool turns and preserving the tool payloads. +- `convertA2AEventToAGUIEvents(event, options)` — maps an A2A stream event to AG-UI text and tool events (`TEXT_MESSAGE_CHUNK`, `TOOL_CALL_*`, `TOOL_CALL_RESULT`). +- `sendMessageToA2AAgentTool` — JSON schema describing a `send_message_to_a2a_agent` tool for orchestration agents. + +## Testing + +```bash +pnpm --filter @ag-ui/a2a test +``` + +The suite covers conversion edge cases and streaming / fallback behaviour using mocked A2A clients. + +## Examples + +- `examples/basic.ts` – minimal script. If you set `A2A_AGENT_URL`, it will connect to that agent through the real `A2AClient`. Otherwise it falls back to a tiny in-memory mock client so you can observe the integration without hitting a remote endpoint. + +## Release checklist + +1. `pnpm --filter @ag-ui/a2a build` +2. `pnpm --filter @ag-ui/a2a test` +3. Update CHANGELOG / release notes. +4. Publish with `pnpm publish --filter @ag-ui/a2a`. diff --git a/integrations/a2a/typescript/jest.config.js b/integrations/a2a/typescript/jest.config.js new file mode 100644 index 000000000..0521f8d91 --- /dev/null +++ b/integrations/a2a/typescript/jest.config.js @@ -0,0 +1,10 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + testMatch: ["**/*.test.ts"], + passWithNoTests: true, + moduleNameMapper: { + "^@/(.*)$": "/src/$1", + }, +}; diff --git a/integrations/a2a/typescript/package.json b/integrations/a2a/typescript/package.json new file mode 100644 index 000000000..d939bf829 --- /dev/null +++ b/integrations/a2a/typescript/package.json @@ -0,0 +1,52 @@ +{ + "name": "@ag-ui/a2a", + "author": "Markus Ecker ", + "version": "0.0.5", + "license": "Apache-2.0", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "sideEffects": false, + "private": false, + "publishConfig": { + "access": "public" + }, + "files": [ + "dist/**", + "README.md" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "clean": "rm -rf dist .turbo node_modules", + "typecheck": "tsc --noEmit", + "test": "jest", + "link:global": "pnpm link --global", + "unlink:global": "pnpm unlink --global" + }, + "dependencies": { + "@a2a-js/sdk": "^0.2.2", + "rxjs": "7.8.1" + }, + "peerDependencies": { + "@ag-ui/core": ">=0.0.40", + "@ag-ui/client": ">=0.0.40" + }, + "devDependencies": { + "@ag-ui/core": "workspace:*", + "@ag-ui/client": "workspace:*", + "@types/jest": "^29.5.14", + "@types/node": "^20.11.19", + "jest": "^29.7.0", + "ts-jest": "^29.1.2", + "tsup": "^8.0.2", + "typescript": "^5.3.3" + } +} diff --git a/integrations/a2a/typescript/src/__tests__/agent.test.ts b/integrations/a2a/typescript/src/__tests__/agent.test.ts new file mode 100644 index 000000000..9d9ab88b9 --- /dev/null +++ b/integrations/a2a/typescript/src/__tests__/agent.test.ts @@ -0,0 +1,143 @@ +import type { Message } from "@ag-ui/client"; +import { A2AAgent } from "../agent"; +import type { MessageSendParams } from "@a2a-js/sdk"; + +const createMessage = (message: Partial): Message => message as Message; + +type SendMessageResponseSuccess = { + id: string | number | null; + jsonrpc: "2.0"; + result: any; +}; + +type SendMessageResponseError = { + id: string | number | null; + jsonrpc: "2.0"; + error: { code: number; message: string }; +}; + +class FakeA2AClient { + constructor( + readonly behaviour: { + stream?: () => AsyncGenerator; + send?: () => Promise; + card?: () => Promise; + } = {}, + ) {} + + sendMessageStream(params: MessageSendParams) { + if (!this.behaviour.stream) { + throw new Error("Streaming not configured"); + } + return this.behaviour.stream(); + } + + async sendMessage(params: MessageSendParams) { + if (!this.behaviour.send) { + throw new Error("sendMessage not configured"); + } + return this.behaviour.send(); + } + + isErrorResponse(response: SendMessageResponseSuccess | SendMessageResponseError): response is SendMessageResponseError { + return "error" in response && Boolean(response.error); + } + + async getAgentCard() { + if (this.behaviour.card) { + return this.behaviour.card(); + } + return { + name: "Test Agent", + description: "", + capabilities: {}, + }; + } +} + +describe("A2AAgent", () => { + it("streams responses and records run summary", async () => { + const fakeClient = new FakeA2AClient({ + stream: async function* () { + yield { + kind: "message", + messageId: "resp-1", + role: "agent", + parts: [{ kind: "text", text: "Hello from stream" }], + }; + }, + }); + + const agent = new A2AAgent({ + a2aClient: fakeClient as any, + initialMessages: [ + createMessage({ + id: "user-1", + role: "user", + content: "Hi there", + }), + ], + }); + + const result = await agent.runAgent(); + + expect(result.result).toBeUndefined(); + + expect(result.newMessages).toEqual( + expect.arrayContaining([ + expect.objectContaining({ role: "assistant" }), + ]), + ); + }); + + it("falls back to blocking when streaming fails", async () => { + const fakeClient = new FakeA2AClient({ + stream: async function* () { + throw new Error("Streaming unsupported"); + }, + send: async () => ({ + id: null, + jsonrpc: "2.0", + result: { + kind: "message", + messageId: "resp-2", + role: "agent", + parts: [{ kind: "text", text: "Blocking response" }], + }, + }), + }); + + const agent = new A2AAgent({ + a2aClient: fakeClient as any, + initialMessages: [ + createMessage({ id: "user-1", role: "user", content: "Ping" }), + ], + }); + + const result = await agent.runAgent(); + + expect(result.result).toBeUndefined(); + }); + + it("throws when the A2A service reports an error", async () => { + const fakeClient = new FakeA2AClient({ + stream: async function* () { + throw new Error("Streaming unsupported"); + }, + send: async () => ({ + id: null, + jsonrpc: "2.0", + error: { code: -32000, message: "Agent failure" }, + }), + }); + + const agent = new A2AAgent({ + a2aClient: fakeClient as any, + initialMessages: [ + createMessage({ id: "user-1", role: "user", content: "Trouble" }), + ], + }); + + await expect(agent.runAgent()).rejects.toThrow("Agent failure"); + }); +}); diff --git a/integrations/a2a/typescript/src/__tests__/utils.test.ts b/integrations/a2a/typescript/src/__tests__/utils.test.ts new file mode 100644 index 000000000..2621e62df --- /dev/null +++ b/integrations/a2a/typescript/src/__tests__/utils.test.ts @@ -0,0 +1,191 @@ +import { EventType } from "@ag-ui/client"; +import type { Message } from "@ag-ui/client"; +import { + convertAGUIMessagesToA2A, + convertA2AEventToAGUIEvents, + sendMessageToA2AAgentTool, +} from "../utils"; + +const createMessage = (message: Partial): Message => message as Message; + +describe("convertAGUIMessagesToA2A", () => { + it("converts AG-UI messages into A2A format while skipping system messages", () => { + const systemMessage = createMessage({ + id: "sys-1", + role: "system", + content: "Follow project guidelines", + }); + + const userMessage = createMessage({ + id: "user-1", + role: "user", + content: [ + { + type: "text", + text: "Draft a project plan", + }, + ], + }); + + const assistantMessage = createMessage({ + id: "assistant-1", + role: "assistant", + content: "Sure, preparing a plan", + toolCalls: [ + { + id: "tool-call-1", + type: "function", + function: { + name: "lookupRequirements", + arguments: JSON.stringify({ id: 123 }), + }, + }, + ], + }); + + const toolMessage = createMessage({ + id: "tool-1", + role: "tool", + toolCallId: "tool-call-1", + content: JSON.stringify({ status: "ok" }), + }); + + const converted = convertAGUIMessagesToA2A([ + systemMessage, + userMessage, + assistantMessage, + toolMessage, + ]); + + expect(converted.contextId).toBeUndefined(); + expect(converted.history).toHaveLength(3); + + const assistantEntry = converted.history.find((entry) => entry.role === "agent"); + expect(assistantEntry?.parts).toEqual( + expect.arrayContaining([ + expect.objectContaining({ kind: "text", text: "Sure, preparing a plan" }), + expect.objectContaining({ kind: "data" }), + ]), + ); + + const toolEntry = converted.history.find((entry) => + entry.parts.some((part) => part.kind === "data" && (part as any).data?.type === "tool-result"), + ); + expect(toolEntry?.parts).toEqual( + expect.arrayContaining([ + expect.objectContaining({ kind: "data", data: expect.objectContaining({ type: "tool-result" }) }), + ]), + ); + + expect(converted.latestUserMessage?.role).toBe("user"); + expect( + converted.history.some((msg) => + (msg.parts ?? []).some((part) => + part.kind === "text" && (part as any).text?.includes("Follow project guidelines"), + ), + ), + ).toBe(false); + }); +}); + +describe("convertA2AEventToAGUIEvents", () => { + it("produces AG-UI text chunks from A2A messages", () => { + const a2aEvent = { + kind: "message" as const, + messageId: "remote-1", + role: "agent" as const, + parts: [ + { kind: "text" as const, text: "Hello from A2A" }, + ], + }; + + const map = new Map(); + const events = convertA2AEventToAGUIEvents(a2aEvent, { + messageIdMap: map, + }); + + expect(events).toHaveLength(1); + expect(events[0]).toEqual( + expect.objectContaining({ + type: EventType.TEXT_MESSAGE_CHUNK, + delta: "Hello from A2A", + }), + ); + + expect(map.size).toBe(1); + }); + + it("maps tool-call payloads to tool events", () => { + const a2aEvent = { + kind: "message" as const, + messageId: "remote-call", + role: "agent" as const, + parts: [ + { + kind: "data" as const, + data: { type: "tool-call", id: "tool-123", name: "lookup", arguments: { query: "hi" } }, + }, + { + kind: "data" as const, + data: { type: "tool-result", toolCallId: "tool-123", payload: { ok: true } }, + }, + ], + }; + + const events = convertA2AEventToAGUIEvents(a2aEvent, { messageIdMap: new Map() }); + + expect(events).toEqual([ + expect.objectContaining({ type: EventType.TOOL_CALL_START, toolCallId: "tool-123" }), + expect.objectContaining({ type: EventType.TOOL_CALL_ARGS, toolCallId: "tool-123" }), + expect.objectContaining({ type: EventType.TOOL_CALL_RESULT, toolCallId: "tool-123" }), + expect.objectContaining({ type: EventType.TOOL_CALL_END, toolCallId: "tool-123" }), + ]); + }); + + it("maps tool-result payloads to ToolCallResult events", () => { + const a2aEvent = { + kind: "message" as const, + messageId: "remote-2", + role: "agent" as const, + parts: [ + { + kind: "data" as const, + data: { type: "tool-result", toolCallId: "call-1", payload: { ok: true } }, + }, + ], + }; + + const events = convertA2AEventToAGUIEvents(a2aEvent, { messageIdMap: new Map() }); + + expect(events).toHaveLength(1); + expect(events[0]).toEqual( + expect.objectContaining({ + type: EventType.TOOL_CALL_RESULT, + toolCallId: "call-1", + }), + ); + }); + + it("maps task status updates to raw events", () => { + const statusEvent = { + kind: "status-update" as const, + contextId: "ctx", + final: false, + status: { state: "working", message: undefined }, + taskId: "task-1", + }; + + const events = convertA2AEventToAGUIEvents(statusEvent as any, { + messageIdMap: new Map(), + }); + + expect(events).toHaveLength(0); + }); +}); + +describe("sendMessageToA2AAgentTool", () => { + it("matches the expected schema", () => { + expect(sendMessageToA2AAgentTool.name).toBe("send_message_to_a2a_agent"); + expect(sendMessageToA2AAgentTool.parameters.required).toContain("task"); + }); +}); diff --git a/integrations/a2a/typescript/src/agent.ts b/integrations/a2a/typescript/src/agent.ts new file mode 100644 index 000000000..e141763fc --- /dev/null +++ b/integrations/a2a/typescript/src/agent.ts @@ -0,0 +1,360 @@ +import { + AbstractAgent, + AgentConfig, + BaseEvent, + EventType, + RunAgentInput, + RunErrorEvent, + RunFinishedEvent, + RunStartedEvent, +} from "@ag-ui/client"; +import { Observable } from "rxjs"; +import { A2AClient } from "@a2a-js/sdk/client"; +import type { + MessageSendConfiguration, + MessageSendParams, + Message as A2AMessage, +} from "@a2a-js/sdk"; +import { convertAGUIMessagesToA2A, convertA2AEventToAGUIEvents } from "./utils"; +import type { + A2AAgentRunResultSummary, + ConvertedA2AMessages, + A2AStreamEvent, + SurfaceTracker, +} from "./types"; +import { randomUUID } from "@ag-ui/client"; + +export interface A2AAgentConfig extends AgentConfig { + a2aClient: A2AClient; +} + +const EXTENSION_URI = "https://a2ui.org/ext/a2a-ui/v0.1"; + +export class A2AAgent extends AbstractAgent { + private readonly a2aClient: A2AClient; + private readonly messageIdMap = new Map(); + + constructor(config: A2AAgentConfig) { + const { a2aClient, ...rest } = config; + if (!a2aClient) { + throw new Error("A2AAgent requires a configured A2AClient instance."); + } + + super(rest); + + this.a2aClient = a2aClient; + this.initializeExtension(this.a2aClient); + } + + clone() { + return new A2AAgent({ a2aClient: this.a2aClient, debug: this.debug }); + } + + protected run(input: RunAgentInput): Observable { + return new Observable((subscriber) => { + const run = async () => { + const runStarted: RunStartedEvent = { + type: EventType.RUN_STARTED, + threadId: input.threadId, + runId: input.runId, + }; + subscriber.next(runStarted); + + if (!input.messages?.length) { + const runFinished: RunFinishedEvent = { + type: EventType.RUN_FINISHED, + threadId: input.threadId, + runId: input.runId, + }; + subscriber.next(runFinished); + subscriber.complete(); + return; + } + + try { + const converted = this.prepareConversation(input); + + if (!converted.latestUserMessage) { + const runFinished: RunFinishedEvent = { + type: EventType.RUN_FINISHED, + threadId: input.threadId, + runId: input.runId, + } as unknown as RunFinishedEvent; + subscriber.next(runFinished); + subscriber.complete(); + return; + } + + const sendParams = await this.createSendParams(converted, input); + + const surfaceTracker = this.createSurfaceTracker(); + + try { + await this.streamMessage(sendParams, subscriber, surfaceTracker); + } catch (error) { + await this.fallbackToBlocking( + sendParams, + subscriber, + error as Error, + surfaceTracker, + ); + } + + const runFinished: RunFinishedEvent = { + type: EventType.RUN_FINISHED, + threadId: input.threadId, + runId: input.runId, + }; + subscriber.next(runFinished); + subscriber.complete(); + } catch (error) { + const runError: RunErrorEvent = { + type: EventType.RUN_ERROR, + message: (error as Error).message ?? "Unknown A2A error", + }; + subscriber.next(runError); + subscriber.error(error); + } + }; + + run(); + + return () => {}; + }); + } + + private prepareConversation(input: RunAgentInput): ConvertedA2AMessages { + return convertAGUIMessagesToA2A(input.messages ?? [], { + contextId: input.threadId, + }); + } + + private async createSendParams( + converted: ConvertedA2AMessages, + input: RunAgentInput, + ): Promise { + const latest = converted.latestUserMessage as A2AMessage; + + const message: A2AMessage = { + ...latest, + messageId: latest.messageId ?? randomUUID(), + contextId: converted.contextId ?? input.threadId, + }; + + const configuration: MessageSendConfiguration = { + acceptedOutputModes: ["text"], + } as MessageSendConfiguration; + + return { + message, + configuration, + } as MessageSendParams; + } + + private async streamMessage( + params: MessageSendParams, + subscriber: { next: (event: BaseEvent) => void }, + surfaceTracker?: SurfaceTracker, + ): Promise { + const aggregatedText = new Map(); + const rawEvents: A2AStreamEvent[] = []; + const tracker = surfaceTracker ?? this.createSurfaceTracker(); + + const stream = this.a2aClient.sendMessageStream(params); + for await (const chunk of stream) { + rawEvents.push(chunk as A2AStreamEvent); + const events = convertA2AEventToAGUIEvents(chunk as A2AStreamEvent, { + role: "assistant", + messageIdMap: this.messageIdMap, + onTextDelta: ({ messageId, delta }) => { + aggregatedText.set( + messageId, + (aggregatedText.get(messageId) ?? "") + delta, + ); + }, + getCurrentText: (messageId) => aggregatedText.get(messageId), + source: "a2a", + surfaceTracker: tracker, + }); + for (const event of events) { + subscriber.next(event); + } + } + + return { + messages: [], + rawEvents, + }; + } + + private async fallbackToBlocking( + params: MessageSendParams, + subscriber: { next: (event: BaseEvent) => void }, + error: Error, + surfaceTracker?: SurfaceTracker, + ): Promise { + const configuration: MessageSendConfiguration = { + ...params.configuration, + acceptedOutputModes: params.configuration?.acceptedOutputModes ?? [ + "text", + ], + blocking: true, + }; + + return this.blockingMessage( + { + ...params, + configuration, + }, + subscriber, + surfaceTracker, + ); + } + + private async blockingMessage( + params: MessageSendParams, + subscriber: { next: (event: BaseEvent) => void }, + surfaceTracker?: SurfaceTracker, + ): Promise { + const response = await this.a2aClient.sendMessage(params); + + if (this.a2aClient.isErrorResponse(response)) { + const errorMessage = + response.error?.message ?? "Unknown error from A2A agent"; + console.error("A2A sendMessage error", response.error); + throw new Error(errorMessage); + } + + const aggregatedText = new Map(); + const rawEvents: A2AStreamEvent[] = []; + const tracker = surfaceTracker ?? this.createSurfaceTracker(); + + const result = response.result as A2AStreamEvent; + rawEvents.push(result); + + const events = convertA2AEventToAGUIEvents(result, { + role: "assistant", + messageIdMap: this.messageIdMap, + onTextDelta: ({ messageId, delta }) => { + aggregatedText.set( + messageId, + (aggregatedText.get(messageId) ?? "") + delta, + ); + }, + getCurrentText: (messageId) => aggregatedText.get(messageId), + source: "a2a", + surfaceTracker: tracker, + }); + + for (const event of events) { + subscriber.next(event); + } + + return { + messages: [], + rawEvents, + }; + } + + private initializeExtension(client: A2AClient) { + const addExtensionHeader = (headers: Headers) => { + const existingValue = headers.get("X-A2A-Extensions") ?? ""; + const values = existingValue + .split(",") + .map((value) => value.trim()) + .filter(Boolean); + + if (!values.includes(EXTENSION_URI)) { + values.push(EXTENSION_URI); + headers.set("X-A2A-Extensions", values.join(", ")); + } + }; + + const patchFetch = () => { + const originalFetch = globalThis.fetch; + if (!originalFetch) { + return () => {}; + } + + const extensionFetch: typeof fetch = async (input, init) => { + const headers = new Headers(init?.headers); + addExtensionHeader(headers); + const nextInit: RequestInit = { + ...init, + headers, + }; + return originalFetch(input, nextInit); + }; + + globalThis.fetch = extensionFetch; + + return () => { + globalThis.fetch = originalFetch; + }; + }; + + const wrapPromise = async (operation: () => Promise): Promise => { + const restore = patchFetch(); + try { + return await operation(); + } finally { + restore(); + } + }; + + const wrapStream = ( + original: + | ((...args: any[]) => AsyncGenerator) + | undefined, + ) => { + if (!original) { + return undefined; + } + + return function wrapped(this: unknown, ...args: unknown[]) { + const restore = patchFetch(); + const iterator = original.apply(this, args); + + const wrappedIterator = (async function* () { + try { + for await (const value of iterator) { + yield value; + } + } finally { + restore(); + } + })(); + + return wrappedIterator; + }; + }; + + const originalSendMessage = client.sendMessage.bind(client); + client.sendMessage = (params) => + wrapPromise(() => originalSendMessage(params)); + + const originalSendMessageStream = client.sendMessageStream?.bind(client); + const wrappedSendMessageStream = wrapStream(originalSendMessageStream); + if (wrappedSendMessageStream) { + client.sendMessageStream = + wrappedSendMessageStream as typeof client.sendMessageStream; + } + + const originalResubscribeTask = client.resubscribeTask?.bind(client); + const wrappedResubscribeTask = wrapStream(originalResubscribeTask); + if (wrappedResubscribeTask) { + client.resubscribeTask = + wrappedResubscribeTask as typeof client.resubscribeTask; + } + } + + private createSurfaceTracker(): SurfaceTracker { + const seenSurfaceIds = new Set(); + return { + has: (surfaceId: string) => seenSurfaceIds.has(surfaceId), + add: (surfaceId: string) => { + seenSurfaceIds.add(surfaceId); + }, + }; + } +} diff --git a/integrations/a2a/typescript/src/index.ts b/integrations/a2a/typescript/src/index.ts new file mode 100644 index 000000000..c06315272 --- /dev/null +++ b/integrations/a2a/typescript/src/index.ts @@ -0,0 +1,3 @@ +export * from "./agent"; +export * from "./utils"; +export * from "./types"; diff --git a/integrations/a2a/typescript/src/types.ts b/integrations/a2a/typescript/src/types.ts new file mode 100644 index 000000000..b3e51181c --- /dev/null +++ b/integrations/a2a/typescript/src/types.ts @@ -0,0 +1,60 @@ +import type { + MessageSendConfiguration, + MessageSendParams, + Message as A2AMessage, + Part as A2APart, + TextPart as A2ATextPart, + DataPart as A2ADataPart, + FilePart as A2AFilePart, + Task as A2ATask, + TaskStatusUpdateEvent as A2ATaskStatusUpdateEvent, + TaskArtifactUpdateEvent as A2ATaskArtifactUpdateEvent, +} from "@a2a-js/sdk"; +import type { Message as AGUIMessage } from "@ag-ui/client"; + +export type { + A2AMessage, + A2APart, + A2ATextPart, + A2ADataPart, + A2AFilePart, + MessageSendParams, + MessageSendConfiguration, + AGUIMessage as AGUIConversationMessage, +}; + +export interface SurfaceTracker { + has(surfaceId: string): boolean; + add(surfaceId: string): void; +} + +export type A2AStreamEvent = + | A2AMessage + | A2ATask + | A2ATaskStatusUpdateEvent + | A2ATaskArtifactUpdateEvent; + +export interface ConvertAGUIMessagesOptions { + contextId?: string; + includeToolMessages?: boolean; +} + +export interface ConvertedA2AMessages { + contextId?: string; + history: A2AMessage[]; + latestUserMessage?: A2AMessage; +} + +export interface ConvertA2AEventOptions { + role?: "assistant" | "user"; + messageIdMap: Map; + onTextDelta?: (payload: { messageId: string; delta: string }) => void; + source?: string; + getCurrentText?: (messageId: string) => string | undefined; + surfaceTracker?: SurfaceTracker; +} + +export interface A2AAgentRunResultSummary { + messages: Array<{ messageId: string; text: string }>; + rawEvents: A2AStreamEvent[]; +} diff --git a/integrations/a2a/typescript/src/utils.ts b/integrations/a2a/typescript/src/utils.ts new file mode 100644 index 000000000..8aba08fcd --- /dev/null +++ b/integrations/a2a/typescript/src/utils.ts @@ -0,0 +1,488 @@ +import type { + BaseEvent, + InputContent, + Message, + TextMessageChunkEvent, + RawEvent, + ToolCallArgsEvent, + ToolCallEndEvent, + ToolCallStartEvent, + ToolCallResultEvent, +} from "@ag-ui/client"; +import { EventType, randomUUID } from "@ag-ui/client"; +import type { + A2AMessage, + A2APart, + A2ATextPart, + A2ADataPart, + A2AFilePart, + A2AStreamEvent, + ConvertAGUIMessagesOptions, + ConvertedA2AMessages, + ConvertA2AEventOptions, +} from "./types"; + +const ROLE_MAP: Record = { + user: "user", + assistant: "agent", + tool: "agent", + system: "user", + developer: "user", +}; + +const TOOL_RESULT_PART_TYPE = "tool-result"; +const TOOL_CALL_PART_TYPE = "tool-call"; +const SURFACE_OPERATION_KEYS = [ + "beginRendering", + "surfaceUpdate", + "dataModelUpdate", +] as const; + +type SurfaceOperationKey = (typeof SURFACE_OPERATION_KEYS)[number]; + +const isBinaryContent = ( + content: InputContent, +): content is Extract => content.type === "binary"; + +const isTextContent = (content: InputContent): content is Extract => + content.type === "text"; + +const createTextPart = (text: string): A2ATextPart => ({ + kind: "text", + text, +}); + +const createFilePart = (content: Extract): A2AFilePart | null => { + if (content.url) { + return { + kind: "file", + file: { + uri: content.url, + mimeType: content.mimeType, + name: content.filename, + }, + }; + } + + if (content.data) { + return { + kind: "file", + file: { + bytes: content.data, + mimeType: content.mimeType, + name: content.filename, + }, + }; + } + + return null; +}; + +const extractSurfaceOperation = ( + payload: unknown, +): { surfaceId: string; operation: Record } | null => { + if (!payload || typeof payload !== "object") { + return null; + } + + const record = payload as Record; + + for (const key of SURFACE_OPERATION_KEYS) { + const value = record[key as SurfaceOperationKey]; + if (value && typeof value === "object" && (value as { surfaceId?: unknown }).surfaceId) { + const surfaceId = (value as { surfaceId?: unknown }).surfaceId; + if (typeof surfaceId === "string" && surfaceId.length > 0) { + return { surfaceId, operation: record }; + } + } + } + + return null; +}; + +const safeJsonParse = (value: string): unknown => { + try { + return JSON.parse(value); + } catch (error) { + return value; + } +}; + +const messageContentToParts = (message: Message): A2APart[] => { + const parts: A2APart[] = []; + const { content } = message as { content?: Message["content"] }; + + if (typeof content === "string") { + const trimmed = content.trim(); + if (trimmed.length > 0) { + parts.push(createTextPart(trimmed)); + } + } else if (Array.isArray(content)) { + for (const chunk of content) { + if (isTextContent(chunk)) { + const value = chunk.text.trim(); + if (value.length > 0) { + parts.push(createTextPart(value)); + } + } else if (isBinaryContent(chunk)) { + const filePart = createFilePart(chunk); + if (filePart) { + parts.push(filePart); + } + } else { + parts.push({ kind: "data", data: chunk } as A2ADataPart); + } + } + } else if (content && typeof content === "object") { + parts.push({ + kind: "data", + data: content as Record, + }); + } + + if (message.role === "assistant" && "toolCalls" in message && message.toolCalls?.length) { + for (const toolCall of message.toolCalls) { + parts.push({ + kind: "data", + data: { + type: TOOL_CALL_PART_TYPE, + id: toolCall.id, + name: toolCall.function.name, + arguments: safeJsonParse(toolCall.function.arguments), + rawArguments: toolCall.function.arguments, + }, + }); + } + } + + if (message.role === "tool") { + const payload = typeof message.content === "string" ? safeJsonParse(message.content) : message.content; + parts.push({ + kind: "data", + data: { + type: TOOL_RESULT_PART_TYPE, + toolCallId: message.toolCallId, + payload, + }, + }); + } + + return parts; +}; + +const messageContentToText = (message: Message): string => { + const { content } = message as { content?: Message["content"] }; + if (typeof content === "string") { + return content; + } + if (Array.isArray(content)) { + return content + .filter((part): part is Extract => isTextContent(part)) + .map((part) => part.text) + .join("\n"); + } + if (content && typeof content === "object") { + return JSON.stringify(content); + } + return ""; +}; + +export function convertAGUIMessagesToA2A( + messages: Message[], + options: ConvertAGUIMessagesOptions = {}, +): ConvertedA2AMessages { + const history: A2AMessage[] = []; + const includeToolMessages = options.includeToolMessages ?? true; + const contextId = options.contextId; + + for (const message of messages) { + if (message.role === "activity") { + continue; + } + + if (message.role === "tool" && !includeToolMessages) { + continue; + } + + if (message.role === "system" || message.role === "developer") { + continue; + } + + const mappedRole = ROLE_MAP[message.role] ?? (message.role === "tool" ? "agent" : undefined); + + if (!mappedRole) { + continue; + } + + const parts = messageContentToParts(message); + + if (parts.length === 0 && mappedRole !== "agent") { + continue; + } + + const messageId = message.id ?? randomUUID(); + + history.push({ + kind: "message", + messageId, + role: mappedRole, + parts, + contextId, + }); + } + + const latestUserMessage = [...history].reverse().find((msg) => msg.role === "user"); + + return { + contextId, + history, + latestUserMessage, + }; +} + +const isA2AMessage = (event: A2AStreamEvent): event is A2AMessage => event.kind === "message"; + +const isA2ATask = (event: A2AStreamEvent): event is import("@a2a-js/sdk").Task => event.kind === "task"; + +const isA2AStatusUpdate = ( + event: A2AStreamEvent, +): event is import("@a2a-js/sdk").TaskStatusUpdateEvent => event.kind === "status-update"; + +function resolveMappedMessageId( + originalId: string, + options: ConvertA2AEventOptions, + aliasKey?: string, +): string { + if (aliasKey) { + const existingAliasId = options.messageIdMap.get(aliasKey); + if (existingAliasId) { + options.messageIdMap.set(originalId, existingAliasId); + return existingAliasId; + } + } + + const existingId = options.messageIdMap.get(originalId); + if (existingId) { + if (aliasKey) { + options.messageIdMap.set(aliasKey, existingId); + } + return existingId; + } + + const newId = randomUUID(); + options.messageIdMap.set(originalId, newId); + if (aliasKey) { + options.messageIdMap.set(aliasKey, newId); + } + return newId; +} + +function convertMessageToEvents( + message: A2AMessage, + options: ConvertA2AEventOptions, + aliasKey?: string, +): BaseEvent[] { + const role = options.role ?? "assistant"; + const events: BaseEvent[] = []; + + const originalId = message.messageId ?? randomUUID(); + const mappedId = resolveMappedMessageId(originalId, options, aliasKey); + + const openToolCalls = new Set(); + + for (const part of message.parts ?? []) { + if (part.kind === "text") { + const textPart = part as A2ATextPart; + const partText = textPart.text ?? ""; + if (partText) { + const previousText = options.getCurrentText?.(mappedId) ?? ""; + + if (partText !== previousText) { + const deltaText = partText.startsWith(previousText) + ? partText.slice(previousText.length) + : partText; + + if (deltaText.length > 0) { + const chunkEvent: TextMessageChunkEvent = { + type: EventType.TEXT_MESSAGE_CHUNK, + messageId: mappedId, + role, + delta: deltaText, + }; + options.onTextDelta?.({ messageId: mappedId, delta: deltaText }); + events.push(chunkEvent); + } + } + } + continue; + } + + if (part.kind === "data") { + const dataPart = part as A2ADataPart; + const payload = dataPart.data; + + if (payload && typeof payload === "object" && (payload as any).type === TOOL_CALL_PART_TYPE) { + const toolCallId = (payload as any).id ?? randomUUID(); + const toolCallName = (payload as any).name ?? "unknown_tool"; + const args = (payload as any).arguments; + + const startEvent: ToolCallStartEvent = { + type: EventType.TOOL_CALL_START, + toolCallId, + toolCallName, + parentMessageId: mappedId, + }; + events.push(startEvent); + + if (args !== undefined) { + const argsEvent: ToolCallArgsEvent = { + type: EventType.TOOL_CALL_ARGS, + toolCallId, + delta: JSON.stringify(args), + }; + events.push(argsEvent); + } + + openToolCalls.add(toolCallId); + continue; + } + + if ( + payload && + typeof payload === "object" && + (payload as any).type === TOOL_RESULT_PART_TYPE && + (payload as any).toolCallId + ) { + const toolCallId = (payload as any).toolCallId; + const toolResultEvent: ToolCallResultEvent = { + type: EventType.TOOL_CALL_RESULT, + toolCallId, + content: JSON.stringify((payload as any).payload ?? payload), + messageId: randomUUID(), + role: "tool", + }; + events.push(toolResultEvent); + + if (openToolCalls.has(toolCallId)) { + const endEvent: ToolCallEndEvent = { + type: EventType.TOOL_CALL_END, + toolCallId, + }; + events.push(endEvent); + openToolCalls.delete(toolCallId); + } + + continue; + } + + const surfaceOperation = extractSurfaceOperation(payload); + if (surfaceOperation && options.surfaceTracker) { + const tracker = options.surfaceTracker; + const { surfaceId, operation } = surfaceOperation; + const hasSeenSurface = tracker.has(surfaceId); + + if (!hasSeenSurface) { + tracker.add(surfaceId); + events.push({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: surfaceId, + activityType: "a2ui-surface", + content: { operations: [] }, + replace: false, + } as BaseEvent); + } + + events.push({ + type: EventType.ACTIVITY_DELTA, + messageId: surfaceId, + activityType: "a2ui-surface", + patch: [ + { + op: "add", + path: "/operations/-", + value: operation, + }, + ], + } as BaseEvent); + + continue; + } + + continue; + } + + // Ignore other part kinds for now. + } + + for (const toolCallId of openToolCalls) { + const endEvent: ToolCallEndEvent = { + type: EventType.TOOL_CALL_END, + toolCallId, + }; + events.push(endEvent); + } + + return events; +} + +export function convertA2AEventToAGUIEvents( + event: A2AStreamEvent, + options: ConvertA2AEventOptions, +): BaseEvent[] { + const events: BaseEvent[] = []; + const source = options.source ?? "a2a"; + + if (isA2AMessage(event)) { + return convertMessageToEvents(event, options); + } + + if (isA2AStatusUpdate(event)) { + const statusMessage = event.status?.message; + const statusState = event.status?.state; + const aliasKey = statusState && statusState !== "input-required" ? `${event.taskId}:status` : undefined; + + if (statusMessage && statusMessage.kind === "message") { + return convertMessageToEvents(statusMessage as A2AMessage, options, aliasKey); + } + return events; + } + + if (isA2ATask(event)) { + const rawEvent: RawEvent = { + type: EventType.RAW, + event, + source, + }; + events.push(rawEvent); + return events; + } + + const fallbackEvent: RawEvent = { + type: EventType.RAW, + event, + source, + }; + events.push(fallbackEvent); + return events; +} + +export const sendMessageToA2AAgentTool = { + name: "send_message_to_a2a_agent", + description: + "Sends a task to the agent named `agentName`, including the full conversation context and goal", + parameters: { + type: "object", + properties: { + agentName: { + type: "string", + description: "The name of the A2A agent to send the message to.", + }, + task: { + type: "string", + description: + "The comprehensive conversation-context summary and goal to be achieved regarding the user inquiry.", + }, + }, + required: ["task"], + }, +} as const; diff --git a/integrations/a2a/typescript/tsconfig.json b/integrations/a2a/typescript/tsconfig.json new file mode 100644 index 000000000..ceecfd457 --- /dev/null +++ b/integrations/a2a/typescript/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es2017", + "module": "NodeNext", + "lib": ["dom", "dom.iterable", "esnext"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "moduleResolution": "NodeNext", + "skipLibCheck": true, + "strict": true, + "jsx": "react-jsx", + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "stripInternal": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/integrations/a2a/typescript/tsup.config.ts b/integrations/a2a/typescript/tsup.config.ts new file mode 100644 index 000000000..a0ee0a256 --- /dev/null +++ b/integrations/a2a/typescript/tsup.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: { + index: "src/index.ts", + }, + format: ["cjs", "esm"], + dts: true, + splitting: false, + sourcemap: true, + clean: true, + minify: true, +}); diff --git a/integrations/adk-middleware/python/src/ag_ui_adk/utils/converters.py b/integrations/adk-middleware/python/src/ag_ui_adk/utils/converters.py index dd33b7b46..2cef5241d 100644 --- a/integrations/adk-middleware/python/src/ag_ui_adk/utils/converters.py +++ b/integrations/adk-middleware/python/src/ag_ui_adk/utils/converters.py @@ -8,7 +8,7 @@ from ag_ui.core import ( Message, UserMessage, AssistantMessage, SystemMessage, ToolMessage, - ToolCall, FunctionCall + ToolCall, FunctionCall, TextInputContent, BinaryInputContent ) from google.adk.events import Event as ADKEvent from google.genai import types @@ -38,18 +38,19 @@ def convert_ag_ui_messages_to_adk(messages: List[Message]) -> List[ADKEvent]: # Convert content based on message type if isinstance(message, (UserMessage, SystemMessage)): - if message.content: + flattened_content = flatten_message_content(message.content) + if flattened_content: event.content = types.Content( role=message.role, - parts=[types.Part(text=message.content)] + parts=[types.Part(text=flattened_content)] ) - + elif isinstance(message, AssistantMessage): parts = [] - + # Add text content if present if message.content: - parts.append(types.Part(text=message.content)) + parts.append(types.Part(text=flatten_message_content(message.content))) # Add tool calls if present if message.tool_calls: @@ -205,25 +206,32 @@ def convert_json_patch_to_state(patches: List[Dict[str, Any]]) -> Dict[str, Any] def extract_text_from_content(content: types.Content) -> str: - """Extract all text from ADK Content object. - - Args: - content: ADK Content object - - Returns: - Combined text from all text parts - """ + """Extract all text from ADK Content object.""" if not content or not content.parts: return "" - + text_parts = [] for part in content.parts: if part.text: text_parts.append(part.text) - + return "\n".join(text_parts) +def flatten_message_content(content: Any) -> str: + if content is None: + return "" + + if isinstance(content, str): + return content + + if isinstance(content, list): + text_parts = [part.text for part in content if isinstance(part, TextInputContent) and part.text] + return "\n".join(text_parts) + + return str(content) + + def create_error_message(error: Exception, context: str = "") -> str: """Create a user-friendly error message. @@ -240,4 +248,4 @@ def create_error_message(error: Exception, context: str = "") -> str: if context: return f"{context}: {error_type} - {error_msg}" else: - return f"{error_type}: {error_msg}" \ No newline at end of file + return f"{error_type}: {error_msg}" diff --git a/integrations/langgraph/python/ag_ui_langgraph/utils.py b/integrations/langgraph/python/ag_ui_langgraph/utils.py index ed5060887..4e0067512 100644 --- a/integrations/langgraph/python/ag_ui_langgraph/utils.py +++ b/integrations/langgraph/python/ag_ui_langgraph/utils.py @@ -13,6 +13,8 @@ ToolMessage as AGUIToolMessage, ToolCall as AGUIToolCall, FunctionCall as AGUIFunctionCall, + TextInputContent, + BinaryInputContent, ) from .types import State, SchemaKeys, LangGraphReasoning @@ -41,14 +43,56 @@ def stringify_if_needed(item: Any) -> str: return item return json.dumps(item) +def convert_langchain_multimodal_to_agui(content: List[Dict[str, Any]]) -> List[Union[TextInputContent, BinaryInputContent]]: + """Convert LangChain's multimodal content to AG-UI format.""" + agui_content = [] + for item in content: + if isinstance(item, dict): + if item.get("type") == "text": + agui_content.append(TextInputContent( + type="text", + text=item.get("text", "") + )) + elif item.get("type") == "image_url": + image_url_data = item.get("image_url", {}) + url = image_url_data.get("url", "") if isinstance(image_url_data, dict) else image_url_data + + # Parse data URLs to extract base64 data + if url.startswith("data:"): + # Format: data:mime_type;base64,data + parts = url.split(",", 1) + header = parts[0] + data = parts[1] if len(parts) > 1 else "" + mime_type = header.split(":")[1].split(";")[0] if ":" in header else "image/png" + + agui_content.append(BinaryInputContent( + type="binary", + mime_type=mime_type, + data=data + )) + else: + # Regular URL or ID + agui_content.append(BinaryInputContent( + type="binary", + mime_type="image/png", # Default MIME type + url=url + )) + return agui_content + def langchain_messages_to_agui(messages: List[BaseMessage]) -> List[AGUIMessage]: agui_messages: List[AGUIMessage] = [] for message in messages: if isinstance(message, HumanMessage): + # Handle multimodal content + if isinstance(message.content, list): + content = convert_langchain_multimodal_to_agui(message.content) + else: + content = stringify_if_needed(resolve_message_content(message.content)) + agui_messages.append(AGUIUserMessage( id=str(message.id), role="user", - content=stringify_if_needed(resolve_message_content(message.content)), + content=content, name=message.name, )) elif isinstance(message, AIMessage): @@ -91,14 +135,49 @@ def langchain_messages_to_agui(messages: List[BaseMessage]) -> List[AGUIMessage] raise TypeError(f"Unsupported message type: {type(message)}") return agui_messages +def convert_agui_multimodal_to_langchain(content: List[Union[TextInputContent, BinaryInputContent]]) -> List[Dict[str, Any]]: + """Convert AG-UI multimodal content to LangChain's multimodal format.""" + langchain_content = [] + for item in content: + if isinstance(item, TextInputContent): + langchain_content.append({ + "type": "text", + "text": item.text + }) + elif isinstance(item, BinaryInputContent): + # LangChain uses image_url format (OpenAI-style) + content_dict = {"type": "image_url"} + + # Prioritize url, then data, then id + if item.url: + content_dict["image_url"] = {"url": item.url} + elif item.data: + # Construct data URL from base64 data + content_dict["image_url"] = {"url": f"data:{item.mime_type};base64,{item.data}"} + elif item.id: + # Use id as a reference (some providers may support this) + content_dict["image_url"] = {"url": item.id} + + langchain_content.append(content_dict) + + return langchain_content + def agui_messages_to_langchain(messages: List[AGUIMessage]) -> List[BaseMessage]: langchain_messages = [] for message in messages: role = message.role if role == "user": + # Handle multimodal content + if isinstance(message.content, str): + content = message.content + elif isinstance(message.content, list): + content = convert_agui_multimodal_to_langchain(message.content) + else: + content = str(message.content) + langchain_messages.append(HumanMessage( id=message.id, - content=message.content, + content=content, name=message.name, )) elif role == "assistant": @@ -177,6 +256,36 @@ def resolve_message_content(content: Any) -> str | None: return None + +def flatten_user_content(content: Any) -> str: + """ + Flatten multimodal content into plain text. + Used for backwards compatibility or when multimodal is not supported. + """ + if content is None: + return "" + + if isinstance(content, str): + return content + + if isinstance(content, list): + parts = [] + for item in content: + if isinstance(item, TextInputContent): + if item.text: + parts.append(item.text) + elif isinstance(item, BinaryInputContent): + # Add descriptive placeholder for binary content + if item.filename: + parts.append(f"[Binary content: {item.filename}]") + elif item.url: + parts.append(f"[Binary content: {item.url}]") + else: + parts.append(f"[Binary content: {item.mime_type}]") + return "\n".join(parts) + + return str(content) + def camel_to_snake(name): return re.sub(r'(? Any: **make_json_safe(value.__dict__), } - return repr(value) \ No newline at end of file + return repr(value) diff --git a/integrations/langgraph/python/examples/agents/multimodal_messages/__init__.py b/integrations/langgraph/python/examples/agents/multimodal_messages/__init__.py new file mode 100644 index 000000000..989a9b717 --- /dev/null +++ b/integrations/langgraph/python/examples/agents/multimodal_messages/__init__.py @@ -0,0 +1,51 @@ +""" +Multimodal Messages Example + +This example demonstrates how to use AG-UI's multimodal message support +to send and receive messages containing both text and images. + +Key features: +- User messages can contain text and binary content (images, audio, files) +- Automatic conversion between AG-UI and LangChain multimodal formats +- Support for vision models like GPT-4o and Claude 3 + +Example usage: + +```python +from ag_ui.core import UserMessage, TextInputContent, BinaryInputContent + +# Create a multimodal user message +message = UserMessage( + id="user-123", + content=[ + TextInputContent(text="What's in this image?"), + BinaryInputContent( + mime_type="image/jpeg", + url="https://example.com/photo.jpg" + ), + ], +) + +# Or with base64 encoded data +message_with_data = UserMessage( + id="user-124", + content=[ + TextInputContent(text="Describe this picture"), + BinaryInputContent( + mime_type="image/png", + data="iVBORw0KGgoAAAANSUhEUgAAAAUA...", # base64 encoded + filename="screenshot.png" + ), + ], +) +``` + +The LangGraph integration automatically handles: +1. Converting AG-UI multimodal format to LangChain's format +2. Passing multimodal messages to vision models +3. Converting responses back to AG-UI format +""" + +from .agent import graph + +__all__ = ["graph"] diff --git a/integrations/langgraph/python/examples/agents/multimodal_messages/agent.py b/integrations/langgraph/python/examples/agents/multimodal_messages/agent.py new file mode 100644 index 000000000..067006f9b --- /dev/null +++ b/integrations/langgraph/python/examples/agents/multimodal_messages/agent.py @@ -0,0 +1,90 @@ +""" +An example demonstrating multimodal message support with images. + +This agent demonstrates how to: +1. Receive user messages with images +2. Process multimodal content (text + images) +3. Use vision models to analyze images +""" + +from typing import List, Any, Optional +import os + +from langchain_core.runnables import RunnableConfig +from langchain_core.messages import SystemMessage +from langchain_openai import ChatOpenAI +from langgraph.graph import StateGraph, END, START +from langgraph.graph import MessagesState +from langgraph.types import Command + +class AgentState(MessagesState): + """ + State of our graph. + """ + tools: List[Any] + +async def vision_chat_node(state: AgentState, config: Optional[RunnableConfig] = None): + """ + Chat node that supports multimodal input including images. + + The messages in state can contain multimodal content with text and images. + LangGraph will automatically handle the conversion from AG-UI format to + the format expected by the vision model. + """ + + # 1. Use a vision-capable model + # GPT-4o supports vision, as do other models like Claude 3 + model = ChatOpenAI(model="gpt-4o") + + # Define config for the model + if config is None: + config = RunnableConfig(recursion_limit=25) + + # 2. Bind tools if needed + model_with_tools = model.bind_tools( + state.get("tools", []), + parallel_tool_calls=False, + ) + + # 3. Define the system message + system_message = SystemMessage( + content=( + "You are a helpful vision assistant. You can analyze images and " + "answer questions about them. Describe what you see in detail." + ) + ) + + # 4. Run the model with multimodal messages + # The messages may contain both text and images + response = await model_with_tools.ainvoke([ + system_message, + *state["messages"], + ], config) + + # 5. Return the response + return Command( + goto=END, + update={ + "messages": response + } + ) + +# Define a new graph +workflow = StateGraph(AgentState) +workflow.add_node("vision_chat_node", vision_chat_node) +workflow.set_entry_point("vision_chat_node") + +# Add edges +workflow.add_edge(START, "vision_chat_node") +workflow.add_edge("vision_chat_node", END) + +# Conditionally use a checkpointer based on the environment +is_fast_api = os.environ.get("LANGGRAPH_FAST_API", "false").lower() == "true" + +# Compile the graph +if is_fast_api: + from langgraph.checkpoint.memory import MemorySaver + memory = MemorySaver() + graph = workflow.compile(checkpointer=memory) +else: + graph = workflow.compile() diff --git a/integrations/langgraph/python/poetry.lock b/integrations/langgraph/python/poetry.lock index d5ffe403a..aa5300483 100644 --- a/integrations/langgraph/python/poetry.lock +++ b/integrations/langgraph/python/poetry.lock @@ -1,19 +1,21 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "ag-ui-protocol" -version = "0.1.7" +version = "0.1.9" description = "" optional = false -python-versions = "<4.0,>=3.9" +python-versions = "^3.9" groups = ["main"] -files = [ - {file = "ag_ui_protocol-0.1.7-py3-none-any.whl", hash = "sha256:8c821662ca6e9852569022f449b9f7aeb3f16aa75390fa8c28ceae2cce642baa"}, - {file = "ag_ui_protocol-0.1.7.tar.gz", hash = "sha256:0e93fd9f7c74d52afbd824d6e9738bd3422e859503905ba7582481cbc3c67ab2"}, -] +files = [] +develop = true [package.dependencies] -pydantic = ">=2.11.2,<3.0.0" +pydantic = "^2.11.2" + +[package.source] +type = "directory" +url = "../../../sdks/python" [[package]] name = "annotated-types" @@ -1473,4 +1475,4 @@ fastapi = ["fastapi"] [metadata] lock-version = "2.1" python-versions = "<3.14,>=3.10" -content-hash = "b4f98fd8fba22b450b106c36ab12b2dc3bdc656b060ac257b5d21b40b51b4f17" +content-hash = "7b62eac41b70b284f5d430698b1e0c8dd23dca37f9178ee00f10edc774f006f5" diff --git a/integrations/langgraph/python/pyproject.toml b/integrations/langgraph/python/pyproject.toml index e64a0051e..26d515725 100644 --- a/integrations/langgraph/python/pyproject.toml +++ b/integrations/langgraph/python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ag-ui-langgraph" -version = "0.0.17" +version = "0.0.18a0" description = "Implementation of the AG-UI protocol for LangGraph." authors = ["Ran Shem Tov "] readme = "README.md" @@ -10,7 +10,7 @@ exclude = [ [tool.poetry.dependencies] python = "<3.14,>=3.10" -ag-ui-protocol = "==0.1.9" +ag-ui-protocol = "==0.2.0a0" fastapi = { version = "^0.115.12", optional = true } langchain = ">=0.3.0" langchain-core = ">=0.3.0" diff --git a/integrations/langgraph/python/tests/test_multimodal.py b/integrations/langgraph/python/tests/test_multimodal.py new file mode 100644 index 000000000..afc68fffc --- /dev/null +++ b/integrations/langgraph/python/tests/test_multimodal.py @@ -0,0 +1,227 @@ +""" +Tests for multimodal message conversion between AG-UI and LangChain formats. +""" + +import unittest +from ag_ui.core import ( + UserMessage, + TextInputContent, + BinaryInputContent, +) +from langchain_core.messages import HumanMessage + +from ag_ui_langgraph.utils import ( + agui_messages_to_langchain, + langchain_messages_to_agui, + convert_agui_multimodal_to_langchain, + convert_langchain_multimodal_to_agui, + flatten_user_content, +) + + +class TestMultimodalConversion(unittest.TestCase): + """Test multimodal message conversion between AG-UI and LangChain.""" + + def test_agui_text_only_to_langchain(self): + """Test converting a text-only AG-UI message to LangChain.""" + agui_message = UserMessage( + id="test-1", + role="user", + content="Hello, world!" + ) + + lc_messages = agui_messages_to_langchain([agui_message]) + + self.assertEqual(len(lc_messages), 1) + self.assertIsInstance(lc_messages[0], HumanMessage) + self.assertEqual(lc_messages[0].content, "Hello, world!") + self.assertEqual(lc_messages[0].id, "test-1") + + def test_agui_multimodal_to_langchain(self): + """Test converting a multimodal AG-UI message to LangChain.""" + agui_message = UserMessage( + id="test-2", + role="user", + content=[ + TextInputContent(type="text", text="What's in this image?"), + BinaryInputContent( + type="binary", + mime_type="image/jpeg", + url="https://example.com/photo.jpg" + ), + ] + ) + + lc_messages = agui_messages_to_langchain([agui_message]) + + self.assertEqual(len(lc_messages), 1) + self.assertIsInstance(lc_messages[0], HumanMessage) + self.assertIsInstance(lc_messages[0].content, list) + self.assertEqual(len(lc_messages[0].content), 2) + + # Check text content + self.assertEqual(lc_messages[0].content[0]["type"], "text") + self.assertEqual(lc_messages[0].content[0]["text"], "What's in this image?") + + # Check image content + self.assertEqual(lc_messages[0].content[1]["type"], "image_url") + self.assertEqual( + lc_messages[0].content[1]["image_url"]["url"], + "https://example.com/photo.jpg" + ) + + def test_agui_multimodal_with_data_to_langchain(self): + """Test converting AG-UI message with base64 data to LangChain.""" + agui_message = UserMessage( + id="test-3", + role="user", + content=[ + TextInputContent(type="text", text="Analyze this"), + BinaryInputContent( + type="binary", + mime_type="image/png", + data="iVBORw0KGgoAAAANSUhEUgAAAAUA", + filename="test.png" + ), + ] + ) + + lc_messages = agui_messages_to_langchain([agui_message]) + + self.assertEqual(len(lc_messages), 1) + self.assertIsInstance(lc_messages[0].content, list) + self.assertEqual(len(lc_messages[0].content), 2) + + # Check that data URL is properly formatted + image_content = lc_messages[0].content[1] + self.assertEqual(image_content["type"], "image_url") + self.assertTrue( + image_content["image_url"]["url"].startswith("data:image/png;base64,") + ) + + def test_langchain_multimodal_to_agui(self): + """Test converting LangChain multimodal message to AG-UI.""" + lc_message = HumanMessage( + id="test-4", + content=[ + {"type": "text", "text": "What do you see?"}, + { + "type": "image_url", + "image_url": {"url": "https://example.com/image.jpg"} + }, + ] + ) + + agui_messages = langchain_messages_to_agui([lc_message]) + + self.assertEqual(len(agui_messages), 1) + self.assertEqual(agui_messages[0].role, "user") + self.assertIsInstance(agui_messages[0].content, list) + self.assertEqual(len(agui_messages[0].content), 2) + + # Check text content + self.assertIsInstance(agui_messages[0].content[0], TextInputContent) + self.assertEqual(agui_messages[0].content[0].text, "What do you see?") + + # Check binary content + self.assertIsInstance(agui_messages[0].content[1], BinaryInputContent) + self.assertEqual(agui_messages[0].content[1].mime_type, "image/png") + self.assertEqual(agui_messages[0].content[1].url, "https://example.com/image.jpg") + + def test_langchain_data_url_to_agui(self): + """Test converting LangChain data URL to AG-UI.""" + lc_message = HumanMessage( + id="test-5", + content=[ + {"type": "text", "text": "Check this out"}, + { + "type": "image_url", + "image_url": {"url": "data:image/png;base64,iVBORw0KGgo"} + }, + ] + ) + + agui_messages = langchain_messages_to_agui([lc_message]) + + self.assertEqual(len(agui_messages), 1) + self.assertIsInstance(agui_messages[0].content, list) + self.assertEqual(len(agui_messages[0].content), 2) + + # Check that data URL was parsed correctly + binary_content = agui_messages[0].content[1] + self.assertIsInstance(binary_content, BinaryInputContent) + self.assertEqual(binary_content.mime_type, "image/png") + self.assertEqual(binary_content.data, "iVBORw0KGgo") + + def test_flatten_multimodal_content(self): + """Test flattening multimodal content to plain text.""" + content = [ + TextInputContent(type="text", text="Hello"), + BinaryInputContent( + type="binary", + mime_type="image/jpeg", + url="https://example.com/image.jpg" + ), + TextInputContent(type="text", text="World"), + ] + + flattened = flatten_user_content(content) + + self.assertIn("Hello", flattened) + self.assertIn("World", flattened) + self.assertIn("[Binary content: https://example.com/image.jpg]", flattened) + + def test_flatten_with_filename(self): + """Test flattening binary content with filename.""" + content = [ + TextInputContent(type="text", text="Check this file"), + BinaryInputContent( + type="binary", + mime_type="application/pdf", + url="https://example.com/doc.pdf", + filename="report.pdf" + ), + ] + + flattened = flatten_user_content(content) + + self.assertIn("Check this file", flattened) + self.assertIn("[Binary content: report.pdf]", flattened) + + def test_convert_agui_multimodal_to_langchain_helper(self): + """Test the convert_agui_multimodal_to_langchain helper function.""" + agui_content = [ + TextInputContent(type="text", text="Test text"), + BinaryInputContent( + type="binary", + mime_type="image/png", + url="https://example.com/test.png" + ), + ] + + lc_content = convert_agui_multimodal_to_langchain(agui_content) + + self.assertEqual(len(lc_content), 2) + self.assertEqual(lc_content[0]["type"], "text") + self.assertEqual(lc_content[0]["text"], "Test text") + self.assertEqual(lc_content[1]["type"], "image_url") + self.assertEqual(lc_content[1]["image_url"]["url"], "https://example.com/test.png") + + def test_convert_langchain_multimodal_to_agui_helper(self): + """Test the convert_langchain_multimodal_to_agui helper function.""" + lc_content = [ + {"type": "text", "text": "Test text"}, + {"type": "image_url", "image_url": {"url": "https://example.com/test.png"}}, + ] + + agui_content = convert_langchain_multimodal_to_agui(lc_content) + + self.assertEqual(len(agui_content), 2) + self.assertIsInstance(agui_content[0], TextInputContent) + self.assertEqual(agui_content[0].text, "Test text") + self.assertIsInstance(agui_content[1], BinaryInputContent) + self.assertEqual(agui_content[1].url, "https://example.com/test.png") + + +if __name__ == "__main__": + unittest.main() diff --git a/integrations/langgraph/typescript/examples/src/agents/multimodal_messages/agent.ts b/integrations/langgraph/typescript/examples/src/agents/multimodal_messages/agent.ts new file mode 100644 index 000000000..f6c0ac7da --- /dev/null +++ b/integrations/langgraph/typescript/examples/src/agents/multimodal_messages/agent.ts @@ -0,0 +1,120 @@ +/** + * An example demonstrating multimodal message support with images. + * + * This agent demonstrates how to: + * 1. Receive user messages with images + * 2. Process multimodal content (text + images) + * 3. Use vision models to analyze images + * + * Example usage: + * + * ```typescript + * import { UserMessage, TextInputContent, BinaryInputContent } from "@ag-ui/core"; + * + * // Create a multimodal user message + * const message: UserMessage = { + * id: "user-123", + * role: "user", + * content: [ + * { type: "text", text: "What's in this image?" }, + * { + * type: "binary", + * mimeType: "image/jpeg", + * url: "https://example.com/photo.jpg" + * }, + * ], + * }; + * + * // Or with base64 encoded data + * const messageWithData: UserMessage = { + * id: "user-124", + * role: "user", + * content: [ + * { type: "text", text: "Describe this picture" }, + * { + * type: "binary", + * mimeType: "image/png", + * data: "iVBORw0KGgoAAAANSUhEUgAAAAUA...", // base64 encoded + * filename: "screenshot.png" + * }, + * ], + * }; + * ``` + * + * The LangGraph integration automatically handles: + * 1. Converting AG-UI multimodal format to LangChain's format + * 2. Passing multimodal messages to vision models + * 3. Converting responses back to AG-UI format + */ + +import { ChatOpenAI } from "@langchain/openai"; +import { SystemMessage } from "@langchain/core/messages"; +import { RunnableConfig } from "@langchain/core/runnables"; +import { Annotation, MessagesAnnotation, StateGraph, Command, START, END } from "@langchain/langgraph"; + +const AgentStateAnnotation = Annotation.Root({ + tools: Annotation({ + reducer: (x, y) => y ?? x, + default: () => [] + }), + ...MessagesAnnotation.spec, +}); + +type AgentState = typeof AgentStateAnnotation.State; + +async function visionChatNode(state: AgentState, config?: RunnableConfig) { + /** + * Chat node that supports multimodal input including images. + * + * The messages in state can contain multimodal content with text and images. + * LangGraph will automatically handle the conversion from AG-UI format to + * the format expected by the vision model. + */ + + // 1. Use a vision-capable model + // GPT-4o supports vision, as do other models like Claude 3 + const model = new ChatOpenAI({ model: "gpt-4o" }); + + // Define config for the model + if (!config) { + config = { recursionLimit: 25 }; + } + + // 2. Bind tools if needed + const modelWithTools = model.bindTools( + state.tools ?? [], + { + parallel_tool_calls: false, + } + ); + + // 3. Define the system message + const systemMessage = new SystemMessage({ + content: "You are a helpful vision assistant. You can analyze images and " + + "answer questions about them. Describe what you see in detail." + }); + + // 4. Run the model with multimodal messages + // The messages may contain both text and images + const response = await modelWithTools.invoke([ + systemMessage, + ...state.messages, + ], config); + + // 5. Return the response + return new Command({ + goto: END, + update: { + messages: [response] + } + }); +} + +// Define a new graph +const workflow = new StateGraph(AgentStateAnnotation) + .addNode("visionChatNode", visionChatNode) + .addEdge(START, "visionChatNode") + .addEdge("visionChatNode", END); + +// Compile the graph +export const graph = workflow.compile(); diff --git a/integrations/langgraph/typescript/package.json b/integrations/langgraph/typescript/package.json index e465fb9d5..e6e4cd5a1 100644 --- a/integrations/langgraph/typescript/package.json +++ b/integrations/langgraph/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@ag-ui/langgraph", - "version": "0.0.18", + "version": "0.0.19-alpha.1", "main": "./dist/index.js", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", @@ -29,8 +29,8 @@ "rxjs": "7.8.1" }, "peerDependencies": { - "@ag-ui/core": ">=0.0.38", - "@ag-ui/client": ">=0.0.38" + "@ag-ui/core": "0.0.40-alpha.7", + "@ag-ui/client": "0.0.40-alpha.7" }, "devDependencies": { "@ag-ui/core": "workspace:*", diff --git a/integrations/langgraph/typescript/src/utils.test.ts b/integrations/langgraph/typescript/src/utils.test.ts new file mode 100644 index 000000000..5042c6e20 --- /dev/null +++ b/integrations/langgraph/typescript/src/utils.test.ts @@ -0,0 +1,224 @@ +/** + * Tests for multimodal message conversion between AG-UI and LangChain formats. + */ + +import { Message as LangGraphMessage } from "@langchain/langgraph-sdk"; +import { Message, UserMessage, TextInputContent, BinaryInputContent } from "@ag-ui/client"; +import { aguiMessagesToLangChain, langchainMessagesToAgui } from "./utils"; + +describe("Multimodal Message Conversion", () => { + describe("aguiMessagesToLangChain", () => { + it("should convert text-only AG-UI message to LangChain", () => { + const aguiMessage: UserMessage = { + id: "test-1", + role: "user", + content: "Hello, world!", + }; + + const lcMessages = aguiMessagesToLangChain([aguiMessage]); + + expect(lcMessages).toHaveLength(1); + expect(lcMessages[0].type).toBe("human"); + expect(lcMessages[0].content).toBe("Hello, world!"); + expect(lcMessages[0].id).toBe("test-1"); + }); + + it("should convert multimodal AG-UI message to LangChain", () => { + const aguiMessage: UserMessage = { + id: "test-2", + role: "user", + content: [ + { type: "text", text: "What's in this image?" }, + { + type: "binary", + mimeType: "image/jpeg", + url: "https://example.com/photo.jpg", + }, + ], + }; + + const lcMessages = aguiMessagesToLangChain([aguiMessage]); + + expect(lcMessages).toHaveLength(1); + expect(lcMessages[0].type).toBe("human"); + expect(Array.isArray(lcMessages[0].content)).toBe(true); + + const content = lcMessages[0].content as Array; + expect(content).toHaveLength(2); + + // Check text content + expect(content[0].type).toBe("text"); + expect(content[0].text).toBe("What's in this image?"); + + // Check image content + expect(content[1].type).toBe("image_url"); + expect(content[1].image_url.url).toBe("https://example.com/photo.jpg"); + }); + + it("should convert AG-UI message with base64 data to LangChain", () => { + const aguiMessage: UserMessage = { + id: "test-3", + role: "user", + content: [ + { type: "text", text: "Analyze this" }, + { + type: "binary", + mimeType: "image/png", + data: "iVBORw0KGgoAAAANSUhEUgAAAAUA", + filename: "test.png", + }, + ], + }; + + const lcMessages = aguiMessagesToLangChain([aguiMessage]); + + expect(lcMessages).toHaveLength(1); + expect(Array.isArray(lcMessages[0].content)).toBe(true); + + const content = lcMessages[0].content as Array; + expect(content).toHaveLength(2); + + // Check that data URL is properly formatted + const imageContent = content[1]; + expect(imageContent.type).toBe("image_url"); + expect(imageContent.image_url.url).toContain("data:image/png;base64,"); + }); + }); + + describe("langchainMessagesToAgui", () => { + it("should convert text-only LangChain message to AG-UI", () => { + const lcMessage: LangGraphMessage = { + id: "test-4", + type: "human", + content: "Hello from LangChain", + }; + + const aguiMessages = langchainMessagesToAgui([lcMessage]); + + expect(aguiMessages).toHaveLength(1); + expect(aguiMessages[0].role).toBe("user"); + expect(aguiMessages[0].content).toBe("Hello from LangChain"); + }); + + it("should convert LangChain multimodal message to AG-UI", () => { + const lcMessage: LangGraphMessage = { + id: "test-5", + type: "human", + content: [ + { type: "text", text: "What do you see?" }, + { + type: "image_url", + image_url: { url: "https://example.com/image.jpg" }, + }, + ] as any, + }; + + const aguiMessages = langchainMessagesToAgui([lcMessage]); + + expect(aguiMessages).toHaveLength(1); + expect(aguiMessages[0].role).toBe("user"); + expect(Array.isArray(aguiMessages[0].content)).toBe(true); + + const content = aguiMessages[0].content as Array; + expect(content).toHaveLength(2); + + // Check text content + expect(content[0].type).toBe("text"); + expect((content[0] as TextInputContent).text).toBe("What do you see?"); + + // Check binary content + expect(content[1].type).toBe("binary"); + expect((content[1] as BinaryInputContent).mimeType).toBe("image/png"); + expect((content[1] as BinaryInputContent).url).toBe("https://example.com/image.jpg"); + }); + + it("should convert LangChain data URL to AG-UI", () => { + const lcMessage: LangGraphMessage = { + id: "test-6", + type: "human", + content: [ + { type: "text", text: "Check this out" }, + { + type: "image_url", + image_url: { url: "data:image/png;base64,iVBORw0KGgo" }, + }, + ] as any, + }; + + const aguiMessages = langchainMessagesToAgui([lcMessage]); + + expect(aguiMessages).toHaveLength(1); + expect(Array.isArray(aguiMessages[0].content)).toBe(true); + + const content = aguiMessages[0].content as Array; + expect(content).toHaveLength(2); + + // Check that data URL was parsed correctly + const binaryContent = content[1] as BinaryInputContent; + expect(binaryContent.type).toBe("binary"); + expect(binaryContent.mimeType).toBe("image/png"); + expect(binaryContent.data).toBe("iVBORw0KGgo"); + }); + }); + + describe("Edge cases", () => { + it("should handle empty content arrays", () => { + const aguiMessage: UserMessage = { + id: "test-7", + role: "user", + content: [], + }; + + const lcMessages = aguiMessagesToLangChain([aguiMessage]); + + expect(lcMessages).toHaveLength(1); + expect(Array.isArray(lcMessages[0].content)).toBe(true); + expect((lcMessages[0].content as Array)).toHaveLength(0); + }); + + it("should handle binary content with only id", () => { + const aguiMessage: UserMessage = { + id: "test-8", + role: "user", + content: [ + { + type: "binary", + mimeType: "image/jpeg", + id: "img-123", + }, + ], + }; + + const lcMessages = aguiMessagesToLangChain([aguiMessage]); + + expect(lcMessages).toHaveLength(1); + const content = lcMessages[0].content as Array; + expect(content).toHaveLength(1); + expect(content[0].type).toBe("image_url"); + expect(content[0].image_url.url).toBe("img-123"); + }); + + it("should skip binary content without any source", () => { + const aguiMessage: UserMessage = { + id: "test-9", + role: "user", + content: [ + { type: "text", text: "Hello" }, + { + type: "binary", + mimeType: "image/jpeg", + // No url, data, or id + } as BinaryInputContent, + ], + }; + + const lcMessages = aguiMessagesToLangChain([aguiMessage]); + + expect(lcMessages).toHaveLength(1); + const content = lcMessages[0].content as Array; + // Binary content should be skipped, only text remains + expect(content).toHaveLength(1); + expect(content[0].type).toBe("text"); + }); + }); +}); diff --git a/integrations/langgraph/typescript/src/utils.ts b/integrations/langgraph/typescript/src/utils.ts index d69ac6778..8866a9d7f 100644 --- a/integrations/langgraph/typescript/src/utils.ts +++ b/integrations/langgraph/typescript/src/utils.ts @@ -1,6 +1,6 @@ import { Message as LangGraphMessage } from "@langchain/langgraph-sdk"; import { State, SchemaKeys, LangGraphReasoning } from "./types"; -import { Message, ToolCall } from "@ag-ui/client"; +import { Message, ToolCall, TextInputContent, BinaryInputContent, InputContent , UserMessage} from "@ag-ui/client"; export const DEFAULT_SCHEMA_KEYS = ["messages", "tools"]; @@ -26,21 +26,118 @@ export function getStreamPayloadInput({ return input; } +/** + * Convert LangChain's multimodal content to AG-UI format + */ +function convertLangchainMultimodalToAgui( + content: Array<{ type: string; text?: string; image_url?: any }> +): InputContent[] { + const aguiContent: InputContent[] = []; + + for (const item of content) { + if (item.type === "text" && item.text) { + aguiContent.push({ + type: "text", + text: item.text, + }); + } else if (item.type === "image_url") { + const imageUrl = typeof item.image_url === "string" + ? item.image_url + : item.image_url?.url; + + if (!imageUrl) continue; + + // Parse data URLs to extract base64 data + if (imageUrl.startsWith("data:")) { + // Format: data:mime_type;base64,data + const [header, data] = imageUrl.split(",", 2); + const mimeType = header.includes(":") + ? header.split(":")[1].split(";")[0] + : "image/png"; + + aguiContent.push({ + type: "binary", + mimeType, + data: data || "", + }); + } else { + // Regular URL or ID + aguiContent.push({ + type: "binary", + mimeType: "image/png", // Default MIME type + url: imageUrl, + }); + } + } + } + + return aguiContent; +} + +/** + * Convert AG-UI multimodal content to LangChain's format + */ +function convertAguiMultimodalToLangchain( + content: InputContent[] +): Array<{ type: string; text?: string; image_url?: { url: string } }> { + const langchainContent: Array<{ type: string; text?: string; image_url?: { url: string } }> = []; + + for (const item of content) { + if (item.type === "text") { + langchainContent.push({ + type: "text", + text: item.text, + }); + } else if (item.type === "binary") { + // LangChain uses image_url format (OpenAI-style) + let url: string; + + // Prioritize url, then data, then id + if (item.url) { + url = item.url; + } else if (item.data) { + // Construct data URL from base64 data + url = `data:${item.mimeType};base64,${item.data}`; + } else if (item.id) { + // Use id as a reference + url = item.id; + } else { + continue; // Skip if no source is provided + } + + langchainContent.push({ + type: "image_url", + image_url: { url }, + }); + } + } + + return langchainContent; +} + export function langchainMessagesToAgui(messages: LangGraphMessage[]): Message[] { return messages.map((message) => { switch (message.type) { case "human": + // Handle multimodal content + let userContent: string | InputContent[]; + if (Array.isArray(message.content)) { + userContent = convertLangchainMultimodalToAgui(message.content as any); + } else { + userContent = stringifyIfNeeded(resolveMessageContent(message.content)); + } + return { id: message.id!, role: "user", - content: stringifyIfNeeded(resolveMessageContent(message.content)), + content: userContent, }; case "ai": - const content = resolveMessageContent(message.content) + const aiContent = resolveMessageContent(message.content) return { id: message.id!, role: "assistant", - content: content ? stringifyIfNeeded(content) : '', + content: aiContent ? stringifyIfNeeded(aiContent) : '', toolCalls: message.tool_calls?.map((tc) => ({ id: tc.id!, type: "function", @@ -73,12 +170,22 @@ export function aguiMessagesToLangChain(messages: Message[]): LangGraphMessage[] return messages.map((message, index) => { switch (message.role) { case "user": + // Handle multimodal content + let content: UserMessage['content']; + if (typeof message.content === "string") { + content = message.content; + } else if (Array.isArray(message.content)) { + content = convertAguiMultimodalToLangchain(message.content) as any; + } else { + content = String(message.content); + } + return { id: message.id, role: message.role, - content: message.content, + content, type: "human", - }; + } as LangGraphMessage; case "assistant": return { id: message.id, @@ -119,6 +226,42 @@ function stringifyIfNeeded(item: any) { return JSON.stringify(item); } +/** + * Flatten multimodal content into plain text. + * Used for backwards compatibility or when multimodal is not supported. + */ +function flattenUserContent(content: Message["content"]): string { + if (typeof content === "string") { + return content; + } + + if (!Array.isArray(content)) { + return ""; + } + + const parts: string[] = []; + + for (const item of content) { + if (item.type === "text" && "text" in item) { + if (item.text) { + parts.push(item.text); + } + } else if (item.type === "binary" && "mimeType" in item) { + // Add descriptive placeholder for binary content + const binaryItem = item as BinaryInputContent; + if (binaryItem.filename) { + parts.push(`[Binary content: ${binaryItem.filename}]`); + } else if (binaryItem.url) { + parts.push(`[Binary content: ${binaryItem.url}]`); + } else { + parts.push(`[Binary content: ${binaryItem.mimeType}]`); + } + } + } + + return parts.join("\n"); +} + export function resolveReasoningContent(eventData: any): LangGraphReasoning | null { const content = eventData.chunk?.content diff --git a/integrations/mastra/typescript/src/mastra.ts b/integrations/mastra/typescript/src/mastra.ts index 63aab3f86..e6b9f95a8 100644 --- a/integrations/mastra/typescript/src/mastra.ts +++ b/integrations/mastra/typescript/src/mastra.ts @@ -51,14 +51,19 @@ export class MastraAgent extends AbstractAgent { resourceId?: string; runtimeContext?: RuntimeContext; - constructor({ agent, resourceId, runtimeContext, ...rest }: MastraAgentConfig) { + constructor(private config: MastraAgentConfig) { + const { agent, resourceId, runtimeContext, ...rest } = config; super(rest); this.agent = agent; this.resourceId = resourceId; this.runtimeContext = runtimeContext ?? new RuntimeContext(); } - run(input: RunAgentInput): Observable { + public clone() { + return new MastraAgent(this.config); + } + + protected run(input: RunAgentInput): Observable { let messageId = randomUUID(); return new Observable((subscriber) => { diff --git a/integrations/mastra/typescript/src/utils.ts b/integrations/mastra/typescript/src/utils.ts index 665e846fa..e775d353f 100644 --- a/integrations/mastra/typescript/src/utils.ts +++ b/integrations/mastra/typescript/src/utils.ts @@ -1,4 +1,4 @@ -import type { Message } from "@ag-ui/client"; +import type { InputContent, Message } from "@ag-ui/client"; import { AbstractAgent } from "@ag-ui/client"; import { MastraClient } from "@mastra/client-js"; import type { CoreMessage, Mastra } from "@mastra/core"; @@ -6,12 +6,39 @@ import { Agent as LocalMastraAgent } from "@mastra/core/agent"; import { RuntimeContext } from "@mastra/core/runtime-context"; import { MastraAgent } from "./mastra"; +const toMastraTextContent = (content: Message["content"]): string => { + if (!content) { + return ""; + } + + if (typeof content === "string") { + return content; + } + + if (!Array.isArray(content)) { + return ""; + } + + type TextInput = Extract; + + const textParts = content + .filter((part): part is TextInput => part.type === "text") + .map((part: TextInput) => part.text.trim()) + .filter(Boolean); + + return textParts.join("\n"); +}; + export function convertAGUIMessagesToMastra(messages: Message[]): CoreMessage[] { const result: CoreMessage[] = []; for (const message of messages) { if (message.role === "assistant") { - const parts: any[] = message.content ? [{ type: "text", text: message.content }] : []; + const assistantContent = toMastraTextContent(message.content); + const parts: any[] = []; + if (assistantContent) { + parts.push({ type: "text", text: assistantContent }); + } for (const toolCall of message.toolCalls ?? []) { parts.push({ type: "tool-call", @@ -25,9 +52,10 @@ export function convertAGUIMessagesToMastra(messages: Message[]): CoreMessage[] content: parts, }); } else if (message.role === "user") { + const userContent = toMastraTextContent(message.content); result.push({ role: "user", - content: message.content || "", + content: userContent, }); } else if (message.role === "tool") { let toolName = "unknown"; diff --git a/integrations/vercel-ai-sdk/typescript/src/index.ts b/integrations/vercel-ai-sdk/typescript/src/index.ts index e2d3436c4..80498d7c7 100644 --- a/integrations/vercel-ai-sdk/typescript/src/index.ts +++ b/integrations/vercel-ai-sdk/typescript/src/index.ts @@ -25,10 +25,54 @@ import { tool as createVercelAISDKTool, ToolChoice, ToolSet, + FilePart, + ImagePart, + TextPart, } from "ai"; import { randomUUID } from "@ag-ui/client"; import { z } from "zod"; +type VercelUserContent = Extract["content"]; +type VercelUserArrayContent = Extract; +type VercelUserPart = VercelUserArrayContent extends Array ? Part : never; + +const toVercelUserParts = (inputContent: Message["content"]): VercelUserPart[] => { + if (!Array.isArray(inputContent)) { + return []; + } + + const parts: VercelUserPart[] = []; + + for (const part of inputContent) { + if (part.type === "text") { + parts.push({ type: "text", text: part.text } as VercelUserPart); + } + } + + return parts; +}; + +const toVercelUserContent = (content: Message["content"]): VercelUserContent => { + if (!content) { + return ""; + } + + if (typeof content === "string") { + return content; + } + + const parts = toVercelUserParts(content); + if (parts.length === 0) { + return ""; + } + + if (parts.length === 1 && parts[0].type === "text") { + return parts[0].text; + } + + return parts; +}; + type ProcessedEvent = | MessagesSnapshotEvent | RunFinishedEvent @@ -48,14 +92,19 @@ export class VercelAISDKAgent extends AbstractAgent { model: LanguageModelV1; maxSteps: number; toolChoice: ToolChoice>; - constructor({ model, maxSteps, toolChoice, ...rest }: VercelAISDKAgentConfig) { + constructor(private config: VercelAISDKAgentConfig) { + const { model, maxSteps, toolChoice, ...rest } = config; super({ ...rest }); this.model = model; this.maxSteps = maxSteps ?? 1; this.toolChoice = toolChoice ?? "auto"; } - run(input: RunAgentInput): Observable { + public clone() { + return new VercelAISDKAgent(this.config); + } + + protected run(input: RunAgentInput): Observable { const finalMessages: Message[] = input.messages; return new Observable((subscriber) => { @@ -188,7 +237,7 @@ export function convertMessagesToVercelAISDKMessages(messages: Message[]): CoreM } else if (message.role === "user") { result.push({ role: "user", - content: message.content || "", + content: toVercelUserContent(message.content), }); } else if (message.role === "tool") { let toolName = "unknown"; diff --git a/middlewares/a2a-middleware/src/index.ts b/middlewares/a2a-middleware/src/index.ts index 7e5b90e16..3a18dab43 100644 --- a/middlewares/a2a-middleware/src/index.ts +++ b/middlewares/a2a-middleware/src/index.ts @@ -304,6 +304,7 @@ export class A2AMiddlewareAgent extends AbstractAgent { pendingA2ACalls: Set, pendingTextMessages: Set, ): void { + // @ts-expect-error orchestrationAgent.run remains protected; middleware intentionally bypasses until public API exists. const newRunStream = this.orchestrationAgent.run(input); this.wrapStream(newRunStream, pendingA2ACalls, pendingTextMessages, observer, input); } diff --git a/package.json b/package.json index 8e7e9a40c..ec3d2bda9 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,11 @@ "check-types": "turbo run check-types", "test": "turbo run test", "create-integration": "pnpm dlx tsx create-integration.ts", - "bump": "pnpm --filter './packages/*' exec -- pnpm version", - "bump:alpha": "pnpm --filter './packages/*' exec -- pnpm version --preid alpha", - "publish": "pnpm -r clean && pnpm install && turbo run build && pnpm publish -r --filter='./packages/*'", + "bump": "pnpm --filter './sdks/typescript/packages/*' exec -- pnpm version", + "bump:alpha": "pnpm --filter './sdks/typescript/packages/*' exec -- pnpm version --preid alpha", + "publish": "pnpm -r clean && pnpm install && turbo run build && pnpm publish -r --filter='./sdks/typescript/packages/*'", "publish:integrations": "pnpm -r clean && pnpm install && turbo run build && pnpm publish -r --filter='./integrations/*'", - "publish:alpha": "pnpm -r clean && pnpm install && turbo run build && pnpm publish -r --no-git-checks --filter='./packages/*' --tag alpha" + "publish:alpha": "pnpm -r clean && pnpm install && turbo run build && pnpm publish -r --no-git-checks --filter='./sdks/typescript/packages/*' --tag alpha" }, "devDependencies": { "prettier": "^3.5.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70be6bfa3..f9ec0554c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,12 @@ importers: apps/dojo: dependencies: + '@a2a-js/sdk': + specifier: 0.2.5 + version: 0.2.5 + '@ag-ui/a2a': + specifier: workspace:* + version: link:../../integrations/a2a/typescript '@ag-ui/a2a-middleware': specifier: workspace:* version: link:../../middlewares/a2a-middleware @@ -77,16 +83,16 @@ importers: version: link:../../integrations/agno/typescript '@ag-ui/client': specifier: workspace:* - version: link:../../sdks/typescript/packages/client + version: 0.0.40-alpha.10 '@ag-ui/core': specifier: workspace:* - version: link:../../sdks/typescript/packages/core + version: 0.0.40-alpha.10 '@ag-ui/crewai': specifier: workspace:* version: link:../../integrations/crew-ai/typescript '@ag-ui/encoder': specifier: workspace:* - version: link:../../sdks/typescript/packages/encoder + version: 0.0.40-alpha.10 '@ag-ui/langgraph': specifier: workspace:* version: link:../../integrations/langgraph/typescript @@ -101,7 +107,7 @@ importers: version: link:../../middlewares/middleware-starter '@ag-ui/proto': specifier: workspace:* - version: link:../../sdks/typescript/packages/proto + version: 0.0.40-alpha.10 '@ag-ui/pydantic-ai': specifier: workspace:* version: link:../../integrations/pydantic-ai/typescript @@ -128,13 +134,22 @@ importers: version: 1.10.6(@types/react@19.2.2)(graphql@16.11.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@copilotkit/runtime': specifier: 1.10.6 - version: 1.10.6(43a54c62826e391639c20a8a0387b983) + version: 1.10.6(mowpvgryoykmzvzmtdo322deu4) '@copilotkit/runtime-client-gql': specifier: 1.10.6 version: 1.10.6(graphql@16.11.0)(react@19.2.0) '@copilotkit/shared': specifier: 1.10.6 version: 1.10.6 + '@copilotkitnext/agent': + specifier: 0.0.19-alpha.0 + version: 0.0.19-alpha.0 + '@copilotkitnext/react': + specifier: 0.0.19-alpha.0 + version: 0.0.19-alpha.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@copilotkitnext/runtime': + specifier: 0.0.19-alpha.0 + version: 0.0.19-alpha.0(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) '@mastra/client-js': specifier: ^0.15.2 version: 0.15.2(openapi-types@12.1.3)(react@19.2.0)(zod@3.25.76) @@ -216,6 +231,9 @@ importers: fast-json-patch: specifier: ^3.1.1 version: 3.1.1 + hono: + specifier: ^4.10.3 + version: 4.10.3 lucide-react: specifier: ^0.477.0 version: 0.477.0(react@19.2.0) @@ -314,6 +332,40 @@ importers: specifier: ^1.1.0 version: 1.1.0 + integrations/a2a/typescript: + dependencies: + '@a2a-js/sdk': + specifier: ^0.2.2 + version: 0.2.5 + rxjs: + specifier: 7.8.1 + version: 7.8.1 + devDependencies: + '@ag-ui/client': + specifier: workspace:* + version: link:../../../sdks/typescript/packages/client + '@ag-ui/core': + specifier: workspace:* + version: link:../../../sdks/typescript/packages/core + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 + '@types/node': + specifier: ^20.11.19 + version: 20.19.21 + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@20.19.21) + ts-jest: + specifier: ^29.1.2 + version: 29.4.5(@babel/core@7.28.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.4))(jest-util@29.7.0)(jest@29.7.0)(typescript@5.9.3) + tsup: + specifier: ^8.0.2 + version: 8.5.0(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) + typescript: + specifier: ^5.3.3 + version: 5.9.3 + integrations/adk-middleware/typescript: dependencies: rxjs: @@ -507,7 +559,7 @@ importers: version: 1.2.11(zod@3.25.76) '@copilotkit/runtime': specifier: ^1.10.5 - version: 1.10.6(2963fdc46a5185bf1f60e289781c45cd) + version: 1.10.6(oefq6yilp5bsxwbn3p2ta6g6we) '@mastra/client-js': specifier: ^0.15.2 version: 0.15.2(openapi-types@12.1.3)(react@19.2.0)(zod@3.25.76) @@ -953,32 +1005,35 @@ packages: '@ag-ui/client@0.0.35': resolution: {integrity: sha512-rHtMQSU232dZeVx9qAGt1+j4ar4RWqwFanXcyNxAwbAh0XrY7VZeXFBDUeazy1LtBoViS7xehX8V1Ssf1a+bUw==} + '@ag-ui/client@0.0.40-alpha.10': + resolution: {integrity: sha512-b1hcS1+nMzsNSSt4E3tryFD4Bd9DQqcqYg0R1CeD9dx4bbDg2cF0sjtUBDpbAQ6N3l4438VranGE9Sj+UozUBg==} + '@ag-ui/core@0.0.35': resolution: {integrity: sha512-YAqrln3S3fdo+Hs5FFQPODXiBttyilv/E3xSSHCuxqC0Y/Fp3+VqyDx97BorO3NVp2VKZ9cG2nsO3cbmcTwkQw==} '@ag-ui/core@0.0.37': resolution: {integrity: sha512-7bmjPn1Ol0Zo00F+MrPr0eOwH4AFZbhmq/ZMhCsrMILtVYBiBLcLU9QFBpBL3Zm9MCHha8b79N7JE2FzwcMaVA==} - '@ag-ui/core@0.0.39': - resolution: {integrity: sha512-T5Hp4oFkQ+H5MynWAvSwrX/rNYJOD+PJ4qPQ0o771oSZQAxoIvDDft47Cx5wRyBNNLXAe1RWqJjfWUUwJFNKqA==} + '@ag-ui/core@0.0.40-alpha.10': + resolution: {integrity: sha512-VczUym5UTwVdvJPD95z4cqSEnrygjINqrqZX4ru1gnNlf8PPmhElNPdE/ZiFEgmZAXaxzK6rI+LB6iDx5NltQA==} '@ag-ui/encoder@0.0.35': resolution: {integrity: sha512-Ym0h0ZKIiD1Ld3+e3v/WQSogY62xs72ysoEBW1kt+dDs79QazBsW5ZlcBBj2DelEs9NrczQLxTVEvrkcvhrHqA==} - '@ag-ui/encoder@0.0.39': - resolution: {integrity: sha512-6fsoFwPWkStK7Uyj3pwBn7+aQjUWf7pbDTSI43cD53sBLvTr5oEFNnoKOzRfC5UqvHc4JjUIuLKPQyjHRwWg4g==} + '@ag-ui/encoder@0.0.40-alpha.10': + resolution: {integrity: sha512-aoBhFIcX+SGWzvw/FAK4+mHY6NIz5YA7DchjRCBWAyAGWrdSEObKRgPRifahOrl3hhKgSZo0MYwOin9Q33B+rg==} - '@ag-ui/langgraph@0.0.18': - resolution: {integrity: sha512-soWSV8+xR91jMArZUJoRv85UCgTi3Zt3u3gTMZhvs1t6fGFpAi6+hEQ4AqP13Rgvg90IlmIU8MTWo2k0OZDnoA==} + '@ag-ui/langgraph@0.0.19-alpha.1': + resolution: {integrity: sha512-rX8Y4LSxTXWUMFzCspO0c42b6YWGTuciP69Okrh7Lw3kpGsmFq/zmXoBLFz654Yuii2zLHl5mZvkBJ5a3nI6lA==} peerDependencies: - '@ag-ui/client': '>=0.0.38' - '@ag-ui/core': '>=0.0.38' + '@ag-ui/client': 0.0.40-alpha.7 + '@ag-ui/core': 0.0.40-alpha.7 '@ag-ui/proto@0.0.35': resolution: {integrity: sha512-+rz3LAYHcR3D2xVgRKa7QE5mp+cwmZs6j+1XxG5dT7HNdg51uKea12L57EVY2bxE3JzpAvCIgOjFEmQCNH82pw==} - '@ag-ui/proto@0.0.39': - resolution: {integrity: sha512-xlj/PzZHkJ3CgoQC5QP9g7DEl/78wUK1+A2rdkoLKoNAMOkM2g6jKw0N88iFIh5GZhtiCNN2wb8XwRWPYx9XQQ==} + '@ag-ui/proto@0.0.40-alpha.10': + resolution: {integrity: sha512-d7FzAIjWyQzaMEZyMkTMgIyW+qK7LUg2T/MpjAGqWjjcrWGk2Zh6DU/rNMwMbYnK/YlXS3Ljo5a5gI95SrLS+Q==} '@ai-sdk/anthropic@2.0.23': resolution: {integrity: sha512-ZEBiiv1UhjGjBwUU63pFhLK5LCSlNDb1idY9K1oZHm5/Fda1cuTojf32tOp0opH0RPbPAN/F8fyyNjbU33n9Kw==} @@ -1074,6 +1129,12 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + + '@antfu/utils@9.3.0': + resolution: {integrity: sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==} + '@anthropic-ai/sdk@0.27.3': resolution: {integrity: sha512-IjLt0gd3L4jlOfilxVXTifn42FnVffMgDC04RJK1KDZpmkBWLv0XC92MVVmkxrFZNS/7l3xWgP/I3nqtX1sQHw==} @@ -1471,6 +1532,9 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@braintree/sanitize-url@7.1.1': + resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} + '@browserbasehq/sdk@2.6.0': resolution: {integrity: sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA==} @@ -1489,6 +1553,21 @@ packages: '@cfworker/json-schema@4.1.1': resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} + '@chevrotain/cst-dts-gen@11.0.3': + resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + + '@chevrotain/gast@11.0.3': + resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} + + '@chevrotain/regexp-to-ast@11.0.3': + resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} + + '@chevrotain/types@11.0.3': + resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} + + '@chevrotain/utils@11.0.3': + resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + '@clack/core@0.5.0': resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} @@ -1523,6 +1602,35 @@ packages: '@copilotkit/shared@1.10.6': resolution: {integrity: sha512-56Rltf4fDBqCpl1ZXARypt5NdE4LTg3tGPPLurZpgPmm31Lv5EAHpfjC7I55vt9A0mXWlTCHtCrpiaAlTyzGJw==} + '@copilotkitnext/agent@0.0.19-alpha.0': + resolution: {integrity: sha512-8S9Ds+9gHeNYyaGLA0luExqrqyEpJG4msQn5d5RM+Vw/mcQ4870b4JR5WG6QUi/pzvrsYhTKI//rWl8LrDAzLA==} + engines: {node: '>=18'} + + '@copilotkitnext/core@0.0.19-alpha.0': + resolution: {integrity: sha512-pIv1mrAW2uNNpaWWgUtkZNhscQwu6dGAfL1BA9WYW0ipZPgrlSbR3OleKV0D5z+uV51XCknWTSNM49ioTcgZZQ==} + engines: {node: '>=18'} + + '@copilotkitnext/react@0.0.19-alpha.0': + resolution: {integrity: sha512-ApVlwXDalzwYaVNki6srA4Ab9LDw5VcqECzoZK8wf+6aSYvdLIXLe1ZLxIFemzU/Cuks8rmhJ0Bwk0gbRSk9eA==} + engines: {node: '>=18'} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@copilotkitnext/runtime@0.0.19-alpha.0': + resolution: {integrity: sha512-QVlHOg/hJyg3i5J2Sm+OEHACeYJYYtBBmwzQpqz2o0j4B20Z4PPgeffMEBta0LjbPen+mdsDykTVRvupVTd+5g==} + engines: {node: '>=18'} + peerDependencies: + openai: ^5.9.0 + + '@copilotkitnext/shared@0.0.19-alpha.0': + resolution: {integrity: sha512-dfQCod+NmD0ttfpLOZxeEj3IQprlgsj8Z5+XCRAkXtKRWf+BaW53BzPZMPPKN1oUSJSK8g7jb3aoD+3Ek5w1AA==} + engines: {node: '>=18'} + + '@copilotkitnext/web-inspector@0.0.19-alpha.0': + resolution: {integrity: sha512-em2OACHCarRXuZgpqpULshXGQpZhAeZVbwzkD5u60zy4ALmGDjFZ4Rb7FUhR+vwIpFQ/yZfOjvxoduVzu+wrBA==} + engines: {node: '>=18'} + '@emnapi/core@1.5.0': resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} @@ -1852,6 +1960,12 @@ packages: resolution: {integrity: sha512-TmLaoFXmLc7yVFJIQS25mzZcuWfju4JmRXcO62KthDKNENyPpXXJukrHN6gXfv1BotzFt0M2kyRnO1Vt8ZLlxQ==} engines: {node: '>=18.0.0'} + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.0.2': + resolution: {integrity: sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==} + '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -2711,6 +2825,20 @@ packages: cpu: [x64] os: [win32] + '@lit-labs/react@2.1.3': + resolution: {integrity: sha512-OD9h2JynerBQUMNzb563jiVpxfvPF0HjQkKY2mx0lpVYvD7F+rtJpOGz6ek+6ufMidV3i+MPT9SX62OKWHFrQg==} + + '@lit-labs/ssr-dom-shim@1.4.0': + resolution: {integrity: sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==} + + '@lit/react@1.0.8': + resolution: {integrity: sha512-p2+YcF+JE67SRX3mMlJ1TKCSTsgyOVdAwd/nxp3NuV1+Cb6MWALbN6nT7Ld4tpmYofcE5kcaSY1YBB9erY+6fw==} + peerDependencies: + '@types/react': 17 || 18 || 19 + + '@lit/reactive-element@2.1.1': + resolution: {integrity: sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==} + '@lukeed/csprng@1.1.0': resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} @@ -2824,6 +2952,9 @@ packages: '@types/react': '>=16' react: '>=16' + '@mermaid-js/parser@0.6.3': + resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} + '@modelcontextprotocol/sdk@1.20.0': resolution: {integrity: sha512-kOQ4+fHuT4KbR2iq2IjeV32HiihueuOf1vJkq18z08CLZ1UQrTc8BXJpVfxZkq45+inLLD+D4xx4nBjUelJa4Q==} engines: {node: '>=18'} @@ -3696,6 +3827,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-use-callback-ref@1.1.1': resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} peerDependencies: @@ -3759,6 +3903,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} @@ -4137,6 +4294,27 @@ packages: resolution: {integrity: sha512-0dtu/5ApsOZ24qgaZwtif8jVwqol7a4m1x5AxPuM1k5wxhqU7t/qEfBGtaSki1R8VlbTQfCj5PAlO45NKCa7Gg==} hasBin: true + '@shikijs/core@3.14.0': + resolution: {integrity: sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw==} + + '@shikijs/engine-javascript@3.14.0': + resolution: {integrity: sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ==} + + '@shikijs/engine-oniguruma@3.14.0': + resolution: {integrity: sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug==} + + '@shikijs/langs@3.14.0': + resolution: {integrity: sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg==} + + '@shikijs/themes@3.14.0': + resolution: {integrity: sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA==} + + '@shikijs/types@3.14.0': + resolution: {integrity: sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -4644,6 +4822,99 @@ packages: '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.7': + resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -4665,6 +4936,9 @@ packages: '@types/express@4.17.23': resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -4802,6 +5076,9 @@ packages: '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -5390,6 +5667,14 @@ packages: chardet@2.1.0: resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} + chevrotain-allstar@0.3.1: + resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} + peerDependencies: + chevrotain: ^11.0.0 + + chevrotain@11.0.3: + resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -5501,6 +5786,10 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -5567,6 +5856,12 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + create-jest@29.7.0: resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5594,6 +5889,162 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.1: + resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.13: + resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -5622,6 +6073,9 @@ packages: dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + dayjs@1.11.18: + resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -5704,6 +6158,9 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -5765,6 +6222,9 @@ packages: dompurify@3.1.7: resolution: {integrity: sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==} + dompurify@3.3.0: + resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==} + dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} @@ -6397,6 +6857,10 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -6458,6 +6922,9 @@ packages: resolution: {integrity: sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==} engines: {node: '>=12.0.0'} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} @@ -6490,9 +6957,21 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-from-dom@5.0.1: + resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} + + hast-util-from-html-isomorphic@2.0.0: + resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + hast-util-from-parse5@8.0.3: resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + hast-util-parse-selector@2.2.5: resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==} @@ -6505,12 +6984,18 @@ packages: hast-util-to-estree@3.1.3: resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} hast-util-to-parse5@8.0.0: resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + hast-util-whitespace@2.0.1: resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} @@ -6577,6 +7062,10 @@ packages: zod-openapi: optional: true + hono@4.10.3: + resolution: {integrity: sha512-2LOYWUbnhdxdL8MNbNg9XZig6k+cZXm5IjHn2Aviv7honhBMOHb+jxrKIeJRZJRmn+htUCKhaicxwXuUDlchRA==} + engines: {node: '>=16.9.0'} + hono@4.9.12: resolution: {integrity: sha512-SrTC0YxqPwnN7yKa8gg/giLyQ2pILCKoideIHbYbFQlWZjYt68D2A4Ae1hehO/aDQ6RmTcpqOV/O2yBtMzx/VQ==} engines: {node: '>=16.9.0'} @@ -6686,6 +7175,13 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + ip-regex@4.3.0: resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} engines: {node: '>=8'} @@ -7176,6 +7672,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -7184,6 +7683,9 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + langchain@0.3.36: resolution: {integrity: sha512-PqC19KChFF0QlTtYDFgfEbIg+SCnCXox29G8tY62QWfj9bOW7ew2kgWmPw5qoHLOTKOdQPvXET20/1Pdq8vAtQ==} engines: {node: '>=18'} @@ -7242,6 +7744,10 @@ packages: typeorm: optional: true + langium@3.3.1: + resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} + engines: {node: '>=16.0.0'} + langsmith@0.3.74: resolution: {integrity: sha512-ZuW3Qawz8w88XcuCRH91yTp6lsdGuwzRqZ5J0Hf5q/AjMz7DwcSv0MkE6V5W+8hFMI850QZN2Wlxwm3R9lHlZg==} peerDependencies: @@ -7266,6 +7772,12 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -7279,7 +7791,6 @@ packages: libsql@0.5.22: resolution: {integrity: sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA==} - cpu: [x64, arm64, wasm32, arm] os: [darwin, linux, win32] lightningcss-darwin-arm64@1.30.1: @@ -7356,6 +7867,15 @@ packages: linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + lit-element@4.2.1: + resolution: {integrity: sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==} + + lit-html@3.3.1: + resolution: {integrity: sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==} + + lit@3.3.1: + resolution: {integrity: sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==} + load-tsconfig@0.2.5: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -7372,6 +7892,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -7441,6 +7964,19 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lucide-react@0.525.0: + resolution: {integrity: sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lucide-react@0.542.0: + resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lucide@0.525.0: + resolution: {integrity: sha512-sfehWlaE/7NVkcEQ4T9JD3eID8RNMIGJBBUq9wF3UFiJIrcMKRbU3g1KGfDk4svcW7yw8BtDLXaXo02scDtUYQ==} + magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} @@ -7473,6 +8009,11 @@ packages: engines: {node: '>= 18'} hasBin: true + marked@16.4.1: + resolution: {integrity: sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg==} + engines: {node: '>= 20'} + hasBin: true + mastra@0.15.1: resolution: {integrity: sha512-8C+2/ANWRrDN82gBvWpvcsi0tuntgZvXu2QdPGuqLAoap/vinzMVQXMXDgLtvlQ8shW1E7x6Vwsr0UzoNPKrAg==} hasBin: true @@ -7572,6 +8113,9 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + mermaid@11.12.1: + resolution: {integrity: sha512-UlIZrRariB11TY1RtTgUWp65tphtBv4CSq7vyS2ZZ2TgoMjs2nloq+wFqxiwcxlhHUvs7DPGgMjs2aeQxz5h9g==} + methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -8000,6 +8544,12 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@4.3.3: + resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} + open@10.2.0: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} @@ -8089,6 +8639,9 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@1.5.0: + resolution: {integrity: sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -8117,6 +8670,9 @@ packages: partial-json@0.1.7: resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -8244,6 +8800,12 @@ packages: engines: {node: '>=18'} hasBin: true + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -8585,10 +9147,25 @@ packages: refractor@3.6.0: resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.0.1: + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} + regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + rehype-harden@1.1.5: + resolution: {integrity: sha512-JrtBj5BVd/5vf3H3/blyJatXJbzQfRT9pJBmjafbTaPouQCAKxHwRyCc7dle9BXQKxv4z1OzZylz/tNamoiG3A==} + + rehype-katex@7.0.1: + resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} + rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} @@ -8676,6 +9253,9 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + rollup-plugin-esbuild@6.2.1: resolution: {integrity: sha512-jTNOMGoMRhs0JuueJrJqbW8tOwxumaWYq+V5i+PD+8ecSCVkuX27tGW7BXqDgoULQ55rO7IdNxPcnsWtshz3AA==} engines: {node: '>=14.18.0'} @@ -8702,6 +9282,9 @@ packages: rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -8717,6 +9300,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} @@ -8814,6 +9400,9 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} + shiki@3.14.0: + resolution: {integrity: sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g==} + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -8918,6 +9507,11 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + streamdown@1.4.0: + resolution: {integrity: sha512-ylhDSQ4HpK5/nAH9v7OgIIdGJxlJB2HoYrYkJNGrO8lMpnWuKUcrz/A8xAMwA6eILA27469vIavcOTjmxctrKg==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -9028,6 +9622,9 @@ packages: babel-plugin-macros: optional: true + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -9147,6 +9744,14 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + + ts-deepmerge@7.0.3: + resolution: {integrity: sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==} + engines: {node: '>=14.13.1'} + ts-error@1.0.6: resolution: {integrity: sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==} @@ -9254,6 +9859,9 @@ packages: resolution: {integrity: sha512-5c9Fdsr9qfpT3hA0EyYSFRZj1dVVsb6KIWubA9JBYZ/9ZEAijgUEae0BBR/Xl/wekt4w65/lYLTFaP3JmwSO8w==} hasBin: true + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -9354,6 +9962,9 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + unist-util-generated@2.0.1: resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} @@ -9456,6 +10067,11 @@ packages: '@types/react': optional: true + use-stick-to-bottom@1.1.1: + resolution: {integrity: sha512-JkDp0b0tSmv7HQOOpL1hT7t7QaoUBXkq045WWWOFDTlLGRzgIIyW7vyzOIJzY7L2XVIG7j1yUxeDj2LHm9Vwng==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -9512,6 +10128,26 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -9721,6 +10357,18 @@ snapshots: uuid: 11.1.0 zod: 3.25.76 + '@ag-ui/client@0.0.40-alpha.10': + dependencies: + '@ag-ui/core': 0.0.40-alpha.10 + '@ag-ui/encoder': 0.0.40-alpha.10 + '@ag-ui/proto': 0.0.40-alpha.10 + '@types/uuid': 10.0.0 + fast-json-patch: 3.1.1 + rxjs: 7.8.1 + untruncate-json: 0.0.1 + uuid: 11.1.0 + zod: 3.25.76 + '@ag-ui/core@0.0.35': dependencies: rxjs: 7.8.1 @@ -9731,7 +10379,7 @@ snapshots: rxjs: 7.8.1 zod: 3.25.76 - '@ag-ui/core@0.0.39': + '@ag-ui/core@0.0.40-alpha.10': dependencies: rxjs: 7.8.1 zod: 3.25.76 @@ -9741,12 +10389,12 @@ snapshots: '@ag-ui/core': 0.0.35 '@ag-ui/proto': 0.0.35 - '@ag-ui/encoder@0.0.39': + '@ag-ui/encoder@0.0.40-alpha.10': dependencies: - '@ag-ui/core': 0.0.39 - '@ag-ui/proto': 0.0.39 + '@ag-ui/core': 0.0.40-alpha.10 + '@ag-ui/proto': 0.0.40-alpha.10 - '@ag-ui/langgraph@0.0.18(@ag-ui/client@sdks+typescript+packages+client)(@ag-ui/core@sdks+typescript+packages+core)(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@ag-ui/langgraph@0.0.19-alpha.1(@ag-ui/client@sdks+typescript+packages+client)(@ag-ui/core@sdks+typescript+packages+core)(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@ag-ui/client': link:sdks/typescript/packages/client '@ag-ui/core': link:sdks/typescript/packages/core @@ -9767,9 +10415,9 @@ snapshots: '@ag-ui/core': 0.0.35 '@bufbuild/protobuf': 2.9.0 - '@ag-ui/proto@0.0.39': + '@ag-ui/proto@0.0.40-alpha.10': dependencies: - '@ag-ui/core': 0.0.39 + '@ag-ui/core': 0.0.40-alpha.10 '@bufbuild/protobuf': 2.9.0 '@protobuf-ts/protoc': 2.11.1 @@ -9871,6 +10519,13 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.5.0 + tinyexec: 1.0.1 + + '@antfu/utils@9.3.0': {} + '@anthropic-ai/sdk@0.27.3': dependencies: '@types/node': 18.19.130 @@ -10732,6 +11387,8 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@braintree/sanitize-url@7.1.1': {} + '@browserbasehq/sdk@2.6.0': dependencies: '@types/node': 18.19.130 @@ -10780,6 +11437,23 @@ snapshots: '@cfworker/json-schema@4.1.1': {} + '@chevrotain/cst-dts-gen@11.0.3': + dependencies: + '@chevrotain/gast': 11.0.3 + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/gast@11.0.3': + dependencies: + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/regexp-to-ast@11.0.3': {} + + '@chevrotain/types@11.0.3': {} + + '@chevrotain/utils@11.0.3': {} + '@clack/core@0.5.0': dependencies: picocolors: 1.1.1 @@ -10836,22 +11510,22 @@ snapshots: - encoding - graphql - '@copilotkit/runtime@1.10.6(2963fdc46a5185bf1f60e289781c45cd)': + '@copilotkit/runtime@1.10.6(mowpvgryoykmzvzmtdo322deu4)': dependencies: - '@ag-ui/client': link:sdks/typescript/packages/client - '@ag-ui/core': link:sdks/typescript/packages/core - '@ag-ui/encoder': 0.0.39 - '@ag-ui/langgraph': 0.0.18(@ag-ui/client@sdks+typescript+packages+client)(@ag-ui/core@sdks+typescript+packages+core)(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76))(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@ag-ui/proto': 0.0.39 + '@ag-ui/client': 0.0.40-alpha.10 + '@ag-ui/core': 0.0.40-alpha.10 + '@ag-ui/encoder': 0.0.40-alpha.10 + '@ag-ui/langgraph': link:integrations/langgraph/typescript + '@ag-ui/proto': 0.0.40-alpha.10 '@anthropic-ai/sdk': 0.57.0 '@copilotkit/shared': 1.10.6 '@graphql-yoga/plugin-defer-stream': 3.16.0(graphql-yoga@5.16.0(graphql@16.11.0))(graphql@16.11.0) - '@langchain/aws': 0.1.15(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76))) - '@langchain/community': 0.3.57(8d705aac09841dc81e24dfe2c773558d) + '@langchain/aws': 0.1.15(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76))) + '@langchain/community': 0.3.57(37emb7xvj5c4vxjobtfi323cve) '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) - '@langchain/google-gauth': 0.1.8(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(zod@3.25.76) - '@langchain/langgraph-sdk': 0.0.70(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(react@19.2.0) - '@langchain/openai': 0.4.9(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(ws@8.18.3) + '@langchain/google-gauth': 0.1.8(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(zod@3.25.76) + '@langchain/langgraph-sdk': 0.0.70(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(react@19.2.0) + '@langchain/openai': 0.4.9(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(ws@8.18.3) '@scarf/scarf': 1.4.0 class-transformer: 0.5.1 class-validator: 0.14.2 @@ -10860,7 +11534,7 @@ snapshots: graphql-scalars: 1.24.2(graphql@16.11.0) graphql-yoga: 5.16.0(graphql@16.11.0) groq-sdk: 0.5.0 - langchain: 0.3.36(@langchain/aws@0.1.15(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76))))(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(axios@1.12.2)(handlebars@4.7.8)(openai@4.104.0(ws@8.18.3)(zod@3.25.76))(ws@8.18.3) + langchain: 0.3.36(@langchain/aws@0.1.15(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76))))(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(axios@1.12.2)(handlebars@4.7.8)(openai@4.104.0(ws@8.18.3)(zod@3.25.76))(ws@8.18.3) openai: 4.104.0(ws@8.18.3)(zod@3.25.76) partial-json: 0.1.7 pino: 9.13.1 @@ -11018,18 +11692,18 @@ snapshots: - ws - youtubei.js - '@copilotkit/runtime@1.10.6(43a54c62826e391639c20a8a0387b983)': + '@copilotkit/runtime@1.10.6(oefq6yilp5bsxwbn3p2ta6g6we)': dependencies: '@ag-ui/client': link:sdks/typescript/packages/client '@ag-ui/core': link:sdks/typescript/packages/core - '@ag-ui/encoder': link:sdks/typescript/packages/encoder - '@ag-ui/langgraph': link:integrations/langgraph/typescript - '@ag-ui/proto': link:sdks/typescript/packages/proto + '@ag-ui/encoder': 0.0.40-alpha.10 + '@ag-ui/langgraph': 0.0.19-alpha.1(@ag-ui/client@sdks+typescript+packages+client)(@ag-ui/core@sdks+typescript+packages+core)(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76))(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@ag-ui/proto': 0.0.40-alpha.10 '@anthropic-ai/sdk': 0.57.0 '@copilotkit/shared': 1.10.6 '@graphql-yoga/plugin-defer-stream': 3.16.0(graphql-yoga@5.16.0(graphql@16.11.0))(graphql@16.11.0) '@langchain/aws': 0.1.15(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76))) - '@langchain/community': 0.3.57(a6f05470c76b31786172bd3244671918) + '@langchain/community': 0.3.57(75igdgciibrgswysse3hw62tgi) '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) '@langchain/google-gauth': 0.1.8(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(zod@3.25.76) '@langchain/langgraph-sdk': 0.0.70(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(react@19.2.0) @@ -11212,6 +11886,78 @@ snapshots: transitivePeerDependencies: - encoding + '@copilotkitnext/agent@0.0.19-alpha.0': + dependencies: + '@ag-ui/client': 0.0.40-alpha.10 + '@ai-sdk/anthropic': 2.0.23(zod@3.25.76) + '@ai-sdk/google': 2.0.17(zod@3.25.76) + '@ai-sdk/openai': 2.0.52(zod@3.25.76) + '@modelcontextprotocol/sdk': 1.20.0 + ai: 5.0.60(zod@3.25.76) + rxjs: 7.8.1 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@copilotkitnext/core@0.0.19-alpha.0': + dependencies: + '@ag-ui/client': 0.0.40-alpha.10 + '@copilotkitnext/shared': 0.0.19-alpha.0 + rxjs: 7.8.1 + zod: 3.25.76 + zod-to-json-schema: 3.24.6(zod@3.25.76) + + '@copilotkitnext/react@0.0.19-alpha.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@ag-ui/client': 0.0.40-alpha.10 + '@ag-ui/core': 0.0.40-alpha.10 + '@copilotkitnext/core': 0.0.19-alpha.0 + '@copilotkitnext/shared': 0.0.19-alpha.0 + '@copilotkitnext/web-inspector': 0.0.19-alpha.0 + '@lit-labs/react': 2.1.3(@types/react@19.2.2) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + class-variance-authority: 0.7.1 + clsx: 2.1.1 + katex: 0.16.25 + lucide-react: 0.525.0(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + streamdown: 1.4.0(@types/react@19.2.2)(react@19.2.0) + tailwind-merge: 3.3.1 + ts-deepmerge: 7.0.3 + tw-animate-css: 1.4.0 + use-stick-to-bottom: 1.1.1(react@19.2.0) + zod: 3.25.76 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - supports-color + + '@copilotkitnext/runtime@0.0.19-alpha.0(openai@4.104.0(ws@8.18.3)(zod@3.25.76))': + dependencies: + '@ag-ui/client': 0.0.40-alpha.10 + '@ag-ui/core': 0.0.40-alpha.10 + '@ag-ui/encoder': 0.0.40-alpha.10 + '@copilotkitnext/shared': 0.0.19-alpha.0 + hono: 4.10.3 + openai: 4.104.0(ws@8.18.3)(zod@3.25.76) + rxjs: 7.8.1 + + '@copilotkitnext/shared@0.0.19-alpha.0': + dependencies: + '@ag-ui/client': 0.0.40-alpha.10 + partial-json: 0.1.7 + uuid: 11.1.0 + + '@copilotkitnext/web-inspector@0.0.19-alpha.0': + dependencies: + '@ag-ui/client': 0.0.40-alpha.10 + '@copilotkitnext/core': 0.0.19-alpha.0 + lit: 3.3.1 + lucide: 0.525.0 + '@emnapi/core@1.5.0': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -11506,6 +12252,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.0.2': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@antfu/utils': 9.3.0 + '@iconify/types': 2.0.0 + debug: 4.4.3 + globals: 15.15.0 + kolorist: 1.8.0 + local-pkg: 1.1.2 + mlly: 1.8.0 + transitivePeerDependencies: + - supports-color + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 @@ -11926,29 +12687,19 @@ snapshots: transitivePeerDependencies: - aws-crt - '@langchain/aws@0.1.15(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))': - dependencies: - '@aws-sdk/client-bedrock-agent-runtime': 3.910.0 - '@aws-sdk/client-bedrock-runtime': 3.910.0 - '@aws-sdk/client-kendra': 3.910.0 - '@aws-sdk/credential-provider-node': 3.910.0 - '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)) - transitivePeerDependencies: - - aws-crt - - '@langchain/community@0.3.57(8d705aac09841dc81e24dfe2c773558d)': + '@langchain/community@0.3.57(37emb7xvj5c4vxjobtfi323cve)': dependencies: - '@browserbasehq/stagehand': 1.14.0(@playwright/test@1.56.0)(deepmerge@4.3.1)(dotenv@16.6.1)(openai@5.12.2(ws@8.18.3)(zod@3.25.76))(zod@3.25.76) + '@browserbasehq/stagehand': 1.14.0(@playwright/test@1.56.0)(deepmerge@4.3.1)(dotenv@16.6.1)(openai@4.104.0(ws@8.18.3)(zod@3.25.76))(zod@3.25.76) '@ibm-cloud/watsonx-ai': 1.7.0 - '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)) - '@langchain/openai': 0.6.16(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(ws@8.18.3) - '@langchain/weaviate': 0.2.3(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76))) + '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) + '@langchain/openai': 0.6.16(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(ws@8.18.3) + '@langchain/weaviate': 0.2.3(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76))) binary-extensions: 2.3.0 expr-eval: 2.0.2 flat: 5.0.2 ibm-cloud-sdk-core: 5.4.3 js-yaml: 4.1.0 - langchain: 0.3.36(@langchain/aws@0.1.15(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76))))(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(axios@1.12.2)(handlebars@4.7.8)(openai@4.104.0(ws@8.18.3)(zod@3.25.76))(ws@8.18.3) + langchain: 0.3.36(@langchain/aws@0.1.15(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76))))(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(axios@1.12.2)(handlebars@4.7.8)(openai@4.104.0(ws@8.18.3)(zod@3.25.76))(ws@8.18.3) langsmith: 0.3.74(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) openai: 4.104.0(ws@8.18.3)(zod@3.25.76) uuid: 10.0.0 @@ -11993,9 +12744,9 @@ snapshots: - handlebars - peggy - '@langchain/community@0.3.57(a6f05470c76b31786172bd3244671918)': + '@langchain/community@0.3.57(75igdgciibrgswysse3hw62tgi)': dependencies: - '@browserbasehq/stagehand': 1.14.0(@playwright/test@1.56.0)(deepmerge@4.3.1)(dotenv@16.6.1)(openai@4.104.0(ws@8.18.3)(zod@3.25.76))(zod@3.25.76) + '@browserbasehq/stagehand': 1.14.0(@playwright/test@1.56.0)(deepmerge@4.3.1)(dotenv@16.6.1)(openai@5.12.2(ws@8.18.3)(zod@3.25.76))(zod@3.25.76) '@ibm-cloud/watsonx-ai': 1.7.0 '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) '@langchain/openai': 0.6.16(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(ws@8.18.3) @@ -12098,14 +12849,6 @@ snapshots: transitivePeerDependencies: - zod - '@langchain/google-common@0.1.8(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(zod@3.25.76)': - dependencies: - '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)) - uuid: 10.0.0 - zod-to-json-schema: 3.24.6(zod@3.25.76) - transitivePeerDependencies: - - zod - '@langchain/google-gauth@0.1.8(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(zod@3.25.76)': dependencies: '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) @@ -12116,16 +12859,6 @@ snapshots: - supports-color - zod - '@langchain/google-gauth@0.1.8(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(zod@3.25.76)': - dependencies: - '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)) - '@langchain/google-common': 0.1.8(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(zod@3.25.76) - google-auth-library: 8.9.0 - transitivePeerDependencies: - - encoding - - supports-color - - zod - '@langchain/langgraph-sdk@0.0.70(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(react@19.2.0)': dependencies: '@types/json-schema': 7.0.15 @@ -12136,16 +12869,6 @@ snapshots: '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) react: 19.2.0 - '@langchain/langgraph-sdk@0.0.70(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(react@19.2.0)': - dependencies: - '@types/json-schema': 7.0.15 - p-queue: 6.6.2 - p-retry: 4.6.2 - uuid: 9.0.1 - optionalDependencies: - '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)) - react: 19.2.0 - '@langchain/langgraph-sdk@0.1.10(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@types/json-schema': 7.0.15 @@ -12168,17 +12891,6 @@ snapshots: - encoding - ws - '@langchain/openai@0.4.9(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(ws@8.18.3)': - dependencies: - '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)) - js-tiktoken: 1.0.21 - openai: 4.104.0(ws@8.18.3)(zod@3.25.76) - zod: 3.25.76 - zod-to-json-schema: 3.24.6(zod@3.25.76) - transitivePeerDependencies: - - encoding - - ws - '@langchain/openai@0.6.16(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(ws@8.18.3)': dependencies: '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) @@ -12188,25 +12900,11 @@ snapshots: transitivePeerDependencies: - ws - '@langchain/openai@0.6.16(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(ws@8.18.3)': - dependencies: - '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)) - js-tiktoken: 1.0.21 - openai: 5.12.2(ws@8.18.3)(zod@3.25.76) - zod: 3.25.76 - transitivePeerDependencies: - - ws - '@langchain/textsplitters@0.1.0(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))': dependencies: '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) js-tiktoken: 1.0.21 - '@langchain/textsplitters@0.1.0(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))': - dependencies: - '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)) - js-tiktoken: 1.0.21 - '@langchain/weaviate@0.2.3(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))': dependencies: '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) @@ -12215,14 +12913,6 @@ snapshots: transitivePeerDependencies: - encoding - '@langchain/weaviate@0.2.3(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))': - dependencies: - '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)) - uuid: 10.0.0 - weaviate-client: 3.9.0 - transitivePeerDependencies: - - encoding - '@libsql/client@0.15.15': dependencies: '@libsql/core': 0.15.15 @@ -12285,6 +12975,22 @@ snapshots: '@libsql/win32-x64-msvc@0.5.22': optional: true + '@lit-labs/react@2.1.3(@types/react@19.2.2)': + dependencies: + '@lit/react': 1.0.8(@types/react@19.2.2) + transitivePeerDependencies: + - '@types/react' + + '@lit-labs/ssr-dom-shim@1.4.0': {} + + '@lit/react@1.0.8(@types/react@19.2.2)': + dependencies: + '@types/react': 19.2.2 + + '@lit/reactive-element@2.1.1': + dependencies: + '@lit-labs/ssr-dom-shim': 1.4.0 + '@lukeed/csprng@1.1.0': {} '@lukeed/uuid@2.0.1': @@ -12429,8 +13135,8 @@ snapshots: ai-v5: ai@5.0.60(zod@3.25.76) date-fns: 3.6.0 dotenv: 16.6.1 - hono: 4.9.12 - hono-openapi: 0.4.8(hono@4.9.12)(openapi-types@12.1.3)(zod@3.25.76) + hono: 4.10.3 + hono-openapi: 0.4.8(hono@4.10.3)(openapi-types@12.1.3)(zod@3.25.76) js-tiktoken: 1.0.21 json-schema: 0.4.0 json-schema-to-zod: 2.6.1 @@ -12654,6 +13360,10 @@ snapshots: '@types/react': 19.2.2 react: 19.2.0 + '@mermaid-js/parser@0.6.3': + dependencies: + langium: 3.3.1 + '@modelcontextprotocol/sdk@1.20.0': dependencies: ajv: 6.12.6 @@ -13707,6 +14417,26 @@ snapshots: '@types/react': 19.2.2 '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: react: 19.2.0 @@ -13755,6 +14485,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.2 + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/rect@1.1.1': {} '@react-aria/focus@3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': @@ -14052,6 +14791,39 @@ snapshots: prompts: 2.4.2 zod: 3.25.76 + '@shikijs/core@3.14.0': + dependencies: + '@shikijs/types': 3.14.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.14.0': + dependencies: + '@shikijs/types': 3.14.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.3 + + '@shikijs/engine-oniguruma@3.14.0': + dependencies: + '@shikijs/types': 3.14.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.14.0': + dependencies: + '@shikijs/types': 3.14.0 + + '@shikijs/themes@3.14.0': + dependencies: + '@shikijs/types': 3.14.0 + + '@shikijs/types@3.14.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + '@sinclair/typebox@0.27.8': {} '@sindresorhus/merge-streams@4.0.0': {} @@ -14690,6 +15462,123 @@ snapshots: dependencies: '@types/node': 20.19.21 + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.7': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -14718,6 +15607,8 @@ snapshots: '@types/qs': 6.14.0 '@types/serve-static': 1.15.9 + '@types/geojson@7946.0.16': {} + '@types/graceful-fs@4.1.9': dependencies: '@types/node': 20.19.21 @@ -14871,6 +15762,8 @@ snapshots: '@types/tough-cookie@4.0.5': {} + '@types/trusted-types@2.0.7': {} + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -15515,6 +16408,20 @@ snapshots: chardet@2.1.0: {} + chevrotain-allstar@0.3.1(chevrotain@11.0.3): + dependencies: + chevrotain: 11.0.3 + lodash-es: 4.17.21 + + chevrotain@11.0.3: + dependencies: + '@chevrotain/cst-dts-gen': 11.0.3 + '@chevrotain/gast': 11.0.3 + '@chevrotain/regexp-to-ast': 11.0.3 + '@chevrotain/types': 11.0.3 + '@chevrotain/utils': 11.0.3 + lodash-es: 4.17.21 + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -15605,6 +16512,8 @@ snapshots: commander@4.1.1: {} + commander@7.2.0: {} + commander@8.3.0: {} commander@9.5.0: {} @@ -15657,6 +16566,14 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + create-jest@29.7.0(@types/node@20.19.21): dependencies: '@jest/types': 29.6.3 @@ -15694,6 +16611,190 @@ snapshots: csstype@3.1.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.1 + + cytoscape-fcose@2.2.0(cytoscape@3.33.1): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.33.1 + + cytoscape@3.33.1: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.0: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.13: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.21 + damerau-levenshtein@1.0.8: {} data-uri-to-buffer@4.0.1: {} @@ -15722,6 +16823,8 @@ snapshots: dateformat@4.6.3: {} + dayjs@1.11.18: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -15777,6 +16880,10 @@ snapshots: defu@6.1.4: {} + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + delayed-stream@1.0.0: {} depd@2.0.0: {} @@ -15813,6 +16920,10 @@ snapshots: dompurify@3.1.7: {} + dompurify@3.3.0: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dotenv@16.6.1: {} dprint-node@1.0.8: @@ -16085,7 +17196,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.37.0(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: @@ -16107,7 +17218,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.37.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.37.0(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -16737,6 +17848,8 @@ snapshots: globals@14.0.0: {} + globals@15.15.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -16828,6 +17941,8 @@ snapshots: - encoding - supports-color + hachure-fill@0.5.2: {} + handlebars@4.7.8: dependencies: minimist: 1.2.8 @@ -16859,6 +17974,28 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-from-dom@5.0.1: + dependencies: + '@types/hast': 3.0.4 + hastscript: 9.0.1 + web-namespaces: 2.0.1 + + hast-util-from-html-isomorphic@2.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-dom: 5.0.1 + hast-util-from-html: 2.0.3 + unist-util-remove-position: 5.0.0 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + hast-util-from-parse5@8.0.3: dependencies: '@types/hast': 3.0.4 @@ -16870,6 +18007,10 @@ snapshots: vfile-location: 5.0.3 web-namespaces: 2.0.1 + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-parse-selector@2.2.5: {} hast-util-parse-selector@4.0.0: @@ -16913,6 +18054,20 @@ snapshots: transitivePeerDependencies: - supports-color + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + hast-util-to-jsx-runtime@2.3.6: dependencies: '@types/estree': 1.0.8 @@ -16943,6 +18098,13 @@ snapshots: web-namespaces: 2.0.1 zwitch: 2.0.4 + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + hast-util-whitespace@2.0.1: {} hast-util-whitespace@3.0.0: @@ -16971,6 +18133,14 @@ snapshots: highlightjs-vue@1.0.0: {} + hono-openapi@0.4.8(hono@4.10.3)(openapi-types@12.1.3)(zod@3.25.76): + dependencies: + json-schema-walker: 2.0.0 + openapi-types: 12.1.3 + optionalDependencies: + hono: 4.10.3 + zod: 3.25.76 + hono-openapi@0.4.8(hono@4.9.12)(openapi-types@12.1.3)(zod@3.25.76): dependencies: json-schema-walker: 2.0.0 @@ -16979,6 +18149,8 @@ snapshots: hono: 4.9.12 zod: 3.25.76 + hono@4.10.3: {} + hono@4.9.12: {} html-escaper@2.0.2: {} @@ -17034,7 +18206,7 @@ snapshots: isstream: 0.1.2 jsonwebtoken: 9.0.2 mime-types: 2.1.35 - retry-axios: 2.6.0(axios@1.12.2) + retry-axios: 2.6.0(axios@1.12.2(debug@4.4.3)) tough-cookie: 4.1.4 transitivePeerDependencies: - supports-color @@ -17105,6 +18277,10 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + internmap@1.0.1: {} + + internmap@2.0.3: {} + ip-regex@4.3.0: {} ipaddr.js@1.9.1: {} @@ -17782,10 +18958,14 @@ snapshots: dependencies: json-buffer: 3.0.1 + khroma@2.1.0: {} + kleur@3.0.3: {} kleur@4.1.5: {} + kolorist@1.8.0: {} + langchain@0.3.36(@langchain/aws@0.1.15(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76))))(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(axios@1.12.2)(handlebars@4.7.8)(openai@4.104.0(ws@8.18.3)(zod@3.25.76))(ws@8.18.3): dependencies: '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) @@ -17811,30 +18991,13 @@ snapshots: - openai - ws - langchain@0.3.36(@langchain/aws@0.1.15(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76))))(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(axios@1.12.2)(handlebars@4.7.8)(openai@4.104.0(ws@8.18.3)(zod@3.25.76))(ws@8.18.3): + langium@3.3.1: dependencies: - '@langchain/core': 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)) - '@langchain/openai': 0.6.16(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76)))(ws@8.18.3) - '@langchain/textsplitters': 0.1.0(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76))) - js-tiktoken: 1.0.21 - js-yaml: 4.1.0 - jsonpointer: 5.0.1 - langsmith: 0.3.74(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) - openapi-types: 12.1.3 - p-retry: 4.6.2 - uuid: 10.0.0 - yaml: 2.8.1 - zod: 3.25.76 - optionalDependencies: - '@langchain/aws': 0.1.15(@langchain/core@0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.76))) - axios: 1.12.2(debug@4.4.3) - handlebars: 4.7.8 - transitivePeerDependencies: - - '@opentelemetry/api' - - '@opentelemetry/exporter-trace-otlp-proto' - - '@opentelemetry/sdk-trace-base' - - openai - - ws + chevrotain: 11.0.3 + chevrotain-allstar: 0.3.1(chevrotain@11.0.3) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 langsmith@0.3.74(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)): dependencies: @@ -17872,6 +19035,10 @@ snapshots: dependencies: language-subtag-registry: 0.3.23 + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + leven@3.1.0: {} levn@0.4.1: @@ -17949,6 +19116,22 @@ snapshots: dependencies: uc.micro: 2.1.0 + lit-element@4.2.1: + dependencies: + '@lit-labs/ssr-dom-shim': 1.4.0 + '@lit/reactive-element': 2.1.1 + lit-html: 3.3.1 + + lit-html@3.3.1: + dependencies: + '@types/trusted-types': 2.0.7 + + lit@3.3.1: + dependencies: + '@lit/reactive-element': 2.1.1 + lit-element: 4.2.1 + lit-html: 3.3.1 + load-tsconfig@0.2.5: {} local-pkg@1.1.2: @@ -17965,6 +19148,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.17.21: {} + lodash.camelcase@4.3.0: {} lodash.get@4.4.2: {} @@ -18021,6 +19206,16 @@ snapshots: dependencies: react: 19.2.0 + lucide-react@0.525.0(react@19.2.0): + dependencies: + react: 19.2.0 + + lucide-react@0.542.0(react@19.2.0): + dependencies: + react: 19.2.0 + + lucide@0.525.0: {} + magic-string@0.30.19: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -18052,6 +19247,8 @@ snapshots: marked@14.0.0: {} + marked@16.4.1: {} + mastra@0.15.1(@mastra/core@0.20.2(openapi-types@12.1.3)(react@19.2.0)(zod@3.25.76))(@opentelemetry/api@1.9.0)(@types/json-schema@7.0.15)(typescript@5.9.3)(zod@3.25.76): dependencies: '@clack/prompts': 0.11.0 @@ -18322,6 +19519,31 @@ snapshots: merge2@1.4.1: {} + mermaid@11.12.1: + dependencies: + '@braintree/sanitize-url': 7.1.1 + '@iconify/utils': 3.0.2 + '@mermaid-js/parser': 0.6.3 + '@types/d3': 7.4.3 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.13 + dayjs: 1.11.18 + dompurify: 3.3.0 + katex: 0.16.25 + khroma: 2.1.0 + lodash-es: 4.17.21 + marked: 16.4.1 + roughjs: 4.6.6 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 11.1.0 + transitivePeerDependencies: + - supports-color + methods@1.1.2: {} micromark-core-commonmark@1.1.0: @@ -18970,6 +20192,14 @@ snapshots: dependencies: mimic-fn: 4.0.0 + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.3: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.0.1 + regex-recursion: 6.0.2 + open@10.2.0: dependencies: default-browser: 5.2.1 @@ -19066,6 +20296,8 @@ snapshots: package-json-from-dist@1.0.1: {} + package-manager-detector@1.5.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -19106,6 +20338,8 @@ snapshots: partial-json@0.1.7: {} + path-data-parser@0.1.0: {} + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -19251,6 +20485,13 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + possible-typed-array-names@1.1.0: {} postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.6)(yaml@2.8.1): @@ -19680,6 +20921,16 @@ snapshots: parse-entities: 2.0.0 prismjs: 1.27.0 + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.0.1: + dependencies: + regex-utilities: 2.3.0 + regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -19689,6 +20940,18 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + rehype-harden@1.1.5: {} + + rehype-katex@7.0.1: + dependencies: + '@types/hast': 3.0.4 + '@types/katex': 0.16.7 + hast-util-from-html-isomorphic: 2.0.0 + hast-util-to-text: 4.0.2 + katex: 0.16.25 + unist-util-visit-parents: 6.0.1 + vfile: 6.0.3 + rehype-raw@7.0.0: dependencies: '@types/hast': 3.0.4 @@ -19809,7 +21072,7 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - retry-axios@2.6.0(axios@1.12.2): + retry-axios@2.6.0(axios@1.12.2(debug@4.4.3)): dependencies: axios: 1.12.2(debug@4.4.3) @@ -19817,6 +21080,8 @@ snapshots: reusify@1.1.0: {} + robust-predicates@3.0.2: {} + rollup-plugin-esbuild@6.2.1(esbuild@0.25.10)(rollup@4.50.2): dependencies: debug: 4.4.3 @@ -19889,6 +21154,13 @@ snapshots: rope-sequence@1.3.4: {} + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + router@2.2.0: dependencies: debug: 4.4.3 @@ -19907,6 +21179,8 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + rxjs@7.8.1: dependencies: tslib: 2.8.1 @@ -20065,6 +21339,17 @@ snapshots: shell-quote@1.8.3: {} + shiki@3.14.0: + dependencies: + '@shikijs/core': 3.14.0 + '@shikijs/engine-javascript': 3.14.0 + '@shikijs/engine-oniguruma': 3.14.0 + '@shikijs/langs': 3.14.0 + '@shikijs/themes': 3.14.0 + '@shikijs/types': 3.14.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -20160,6 +21445,26 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + streamdown@1.4.0(@types/react@19.2.2)(react@19.2.0): + dependencies: + clsx: 2.1.1 + katex: 0.16.25 + lucide-react: 0.542.0(react@19.2.0) + marked: 16.4.1 + mermaid: 11.12.1 + react: 19.2.0 + react-markdown: 10.1.0(@types/react@19.2.2)(react@19.2.0) + rehype-harden: 1.1.5 + rehype-katex: 7.0.1 + rehype-raw: 7.0.0 + remark-gfm: 4.0.1 + remark-math: 6.0.0 + shiki: 3.14.0 + tailwind-merge: 3.3.1 + transitivePeerDependencies: + - '@types/react' + - supports-color + streamsearch@1.1.0: {} string-length@4.0.2: @@ -20284,6 +21589,8 @@ snapshots: client-only: 0.0.1 react: 19.2.0 + stylis@4.3.6: {} + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -20406,6 +21713,10 @@ snapshots: dependencies: typescript: 5.9.3 + ts-dedent@2.2.0: {} + + ts-deepmerge@7.0.3: {} + ts-error@1.0.6: {} ts-interface-checker@0.1.13: {} @@ -20516,6 +21827,8 @@ snapshots: turbo-windows-64: 2.5.8 turbo-windows-arm64: 2.5.8 + tw-animate-css@1.4.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -20633,6 +21946,11 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-generated@2.0.1: {} unist-util-is@5.2.1: @@ -20765,6 +22083,10 @@ snapshots: optionalDependencies: '@types/react': 19.2.2 + use-stick-to-bottom@1.1.1(react@19.2.0): + dependencies: + react: 19.2.0 + use-sync-external-store@1.6.0(react@19.2.0): dependencies: react: 19.2.0 @@ -20823,6 +22145,23 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-uri@3.0.8: {} + w3c-keyname@2.2.8: {} wait-port@1.1.0: diff --git a/sdks/python/README.md b/sdks/python/README.md index 843d9d028..d9e48e26a 100644 --- a/sdks/python/README.md +++ b/sdks/python/README.md @@ -38,6 +38,23 @@ sse_data = encoder.encode(event) # Output: data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_123","delta":"Hello from Python!"}\n\n ``` +### Multimodal user message + +```python +from ag_ui.core import UserMessage, TextInputContent, BinaryInputContent + +message = UserMessage( + id="user-123", + content=[ + TextInputContent(text="Please describe this image"), + BinaryInputContent(mime_type="image/png", url="https://example.com/cat.png"), + ], +) + +payload = message.model_dump(by_alias=True) +# {"id": "user-123", "role": "user", "content": [...]} +``` + ## Packages - **`ag_ui.core`** – Types, events, and data models for AG-UI protocol diff --git a/sdks/python/ag_ui/core/__init__.py b/sdks/python/ag_ui/core/__init__.py index 7e909ad5b..248ff1005 100644 --- a/sdks/python/ag_ui/core/__init__.py +++ b/sdks/python/ag_ui/core/__init__.py @@ -22,6 +22,8 @@ StateSnapshotEvent, StateDeltaEvent, MessagesSnapshotEvent, + ActivitySnapshotEvent, + ActivityDeltaEvent, RawEvent, CustomEvent, RunStartedEvent, @@ -41,12 +43,16 @@ AssistantMessage, UserMessage, ToolMessage, + ActivityMessage, Message, Role, Context, Tool, RunAgentInput, - State + State, + TextInputContent, + BinaryInputContent, + InputContent, ) __all__ = [ @@ -70,6 +76,8 @@ "StateSnapshotEvent", "StateDeltaEvent", "MessagesSnapshotEvent", + "ActivitySnapshotEvent", + "ActivityDeltaEvent", "RawEvent", "CustomEvent", "RunStartedEvent", @@ -87,10 +95,14 @@ "AssistantMessage", "UserMessage", "ToolMessage", + "ActivityMessage", "Message", "Role", "Context", "Tool", "RunAgentInput", - "State" + "State", + "TextInputContent", + "BinaryInputContent", + "InputContent", ] diff --git a/sdks/python/ag_ui/core/events.py b/sdks/python/ag_ui/core/events.py index 2a54a9c8e..94fb63c75 100644 --- a/sdks/python/ag_ui/core/events.py +++ b/sdks/python/ag_ui/core/events.py @@ -7,7 +7,7 @@ from pydantic import Field -from .types import ConfiguredBaseModel, Message, State, Role +from .types import ConfiguredBaseModel, Message, State, Role, RunAgentInput # Text messages can have any role except "tool" TextMessageRole = Literal["developer", "system", "assistant", "user"] @@ -34,6 +34,8 @@ class EventType(str, Enum): STATE_SNAPSHOT = "STATE_SNAPSHOT" STATE_DELTA = "STATE_DELTA" MESSAGES_SNAPSHOT = "MESSAGES_SNAPSHOT" + ACTIVITY_SNAPSHOT = "ACTIVITY_SNAPSHOT" + ACTIVITY_DELTA = "ACTIVITY_DELTA" RAW = "RAW" CUSTOM = "CUSTOM" RUN_STARTED = "RUN_STARTED" @@ -188,6 +190,25 @@ class MessagesSnapshotEvent(BaseEvent): messages: List[Message] +class ActivitySnapshotEvent(BaseEvent): + """Event containing a snapshot of an activity message.""" + + type: Literal[EventType.ACTIVITY_SNAPSHOT] = EventType.ACTIVITY_SNAPSHOT # pyright: ignore[reportIncompatibleVariableOverride] + message_id: str + activity_type: str + content: Any + replace: bool = True + + +class ActivityDeltaEvent(BaseEvent): + """Event containing a JSON Patch delta for an activity message.""" + + type: Literal[EventType.ACTIVITY_DELTA] = EventType.ACTIVITY_DELTA # pyright: ignore[reportIncompatibleVariableOverride] + message_id: str + activity_type: str + patch: List[Any] + + class RawEvent(BaseEvent): """ Event containing a raw event. @@ -213,6 +234,8 @@ class RunStartedEvent(BaseEvent): type: Literal[EventType.RUN_STARTED] = EventType.RUN_STARTED # pyright: ignore[reportIncompatibleVariableOverride] thread_id: str run_id: str + parent_run_id: Optional[str] = None + input: Optional[RunAgentInput] = None class RunFinishedEvent(BaseEvent): @@ -264,6 +287,8 @@ class StepFinishedEvent(BaseEvent): StateSnapshotEvent, StateDeltaEvent, MessagesSnapshotEvent, + ActivitySnapshotEvent, + ActivityDeltaEvent, RawEvent, CustomEvent, RunStartedEvent, diff --git a/sdks/python/ag_ui/core/types.py b/sdks/python/ag_ui/core/types.py index 47b7ae182..e4e358caf 100644 --- a/sdks/python/ag_ui/core/types.py +++ b/sdks/python/ag_ui/core/types.py @@ -2,9 +2,9 @@ This module contains the types for the Agent User Interaction Protocol Python SDK. """ -from typing import Annotated, Any, List, Literal, Optional, Union +from typing import Annotated, Any, Dict, List, Literal, Optional, Union -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, model_validator from pydantic.alias_generators import to_camel @@ -13,7 +13,7 @@ class ConfiguredBaseModel(BaseModel): A configurable base model. """ model_config = ConfigDict( - extra="forbid", + extra="allow", alias_generator=to_camel, populate_by_name=True, ) @@ -70,12 +70,44 @@ class AssistantMessage(BaseMessage): tool_calls: Optional[List[ToolCall]] = None +class TextInputContent(ConfiguredBaseModel): + """A text fragment in a multimodal user message.""" + + type: Literal["text"] = "text" + text: str + + +class BinaryInputContent(ConfiguredBaseModel): + """A binary payload reference in a multimodal user message.""" + + type: Literal["binary"] = "binary" # pyright: ignore[reportIncompatibleVariableOverride] + mime_type: str + id: Optional[str] = None + url: Optional[str] = None + data: Optional[str] = None + filename: Optional[str] = None + + @model_validator(mode="after") + def validate_source(self) -> "BinaryInputContent": + """Ensure at least one binary payload source is provided.""" + if not any([self.id, self.url, self.data]): + raise ValueError("BinaryInputContent requires id, url, or data to be provided.") + return self + + +InputContent = Annotated[ + Union[TextInputContent, BinaryInputContent], + Field(discriminator="type"), +] + + class UserMessage(BaseMessage): """ - A user message. + A user message supporting text or multimodal content. """ - role: Literal["user"] = "user" # pyright: ignore[reportIncompatibleVariableOverride] - content: str + + role: Literal["user"] = "user" # pyright: ignore[reportIncompatibleVariableOverride] + content: Union[str, List[InputContent]] class ToolMessage(ConfiguredBaseModel): @@ -89,12 +121,30 @@ class ToolMessage(ConfiguredBaseModel): error: Optional[str] = None +class ActivityMessage(ConfiguredBaseModel): + """ + An activity progress message emitted between chat messages. + """ + + id: str + role: Literal["activity"] = "activity" # pyright: ignore[reportIncompatibleVariableOverride] + activity_type: str + content: Dict[str, Any] + + Message = Annotated[ - Union[DeveloperMessage, SystemMessage, AssistantMessage, UserMessage, ToolMessage], + Union[ + DeveloperMessage, + SystemMessage, + AssistantMessage, + UserMessage, + ToolMessage, + ActivityMessage, + ], Field(discriminator="role") ] -Role = Literal["developer", "system", "assistant", "user", "tool"] +Role = Literal["developer", "system", "assistant", "user", "tool", "activity"] class Context(ConfiguredBaseModel): @@ -120,6 +170,7 @@ class RunAgentInput(ConfiguredBaseModel): """ thread_id: str run_id: str + parent_run_id: Optional[str] = None state: Any messages: List[Message] tools: List[Tool] diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml index a8de00217..f38eb2dff 100644 --- a/sdks/python/pyproject.toml +++ b/sdks/python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ag-ui-protocol" -version = "0.1.9" +version = "0.2.0a0" description = "" authors = ["Markus Ecker "] readme = "README.md" diff --git a/sdks/python/tests/test_events.py b/sdks/python/tests/test_events.py index c73a2537c..7245d88aa 100644 --- a/sdks/python/tests/test_events.py +++ b/sdks/python/tests/test_events.py @@ -16,6 +16,8 @@ StateSnapshotEvent, StateDeltaEvent, MessagesSnapshotEvent, + ActivitySnapshotEvent, + ActivityDeltaEvent, RawEvent, CustomEvent, RunStartedEvent, @@ -202,6 +204,58 @@ def test_messages_snapshot(self): self.assertEqual(serialized["messages"][0]["role"], "user") self.assertEqual(serialized["messages"][1]["toolCalls"][0]["function"]["name"], "get_weather") + def test_activity_snapshot(self): + """Test creating and serializing an ActivitySnapshotEvent""" + content = {"tasks": ["search", "summarize"]} + event = ActivitySnapshotEvent( + message_id="msg_activity", + activity_type="PLAN", + content=content, + timestamp=1648214400000, + ) + + self.assertEqual(event.message_id, "msg_activity") + self.assertEqual(event.activity_type, "PLAN") + self.assertEqual(event.content, content) + self.assertTrue(event.replace) + + serialized = event.model_dump(by_alias=True) + self.assertEqual(serialized["type"], "ACTIVITY_SNAPSHOT") + self.assertEqual(serialized["messageId"], "msg_activity") + self.assertEqual(serialized["activityType"], "PLAN") + self.assertEqual(serialized["content"], content) + self.assertTrue(serialized["replace"]) + + event_replace_false = ActivitySnapshotEvent( + message_id="msg_activity", + activity_type="PLAN", + content=content, + replace=False, + ) + self.assertFalse(event_replace_false.replace) + serialized_false = event_replace_false.model_dump(by_alias=True) + self.assertFalse(serialized_false["replace"]) + + def test_activity_delta(self): + """Test creating and serializing an ActivityDeltaEvent""" + patch = [{"op": "replace", "path": "/tasks/0", "value": "✓ search"}] + event = ActivityDeltaEvent( + message_id="msg_activity", + activity_type="PLAN", + patch=patch, + timestamp=1648214400000, + ) + + self.assertEqual(event.message_id, "msg_activity") + self.assertEqual(event.activity_type, "PLAN") + self.assertEqual(event.patch, patch) + + serialized = event.model_dump(by_alias=True) + self.assertEqual(serialized["type"], "ACTIVITY_DELTA") + self.assertEqual(serialized["messageId"], "msg_activity") + self.assertEqual(serialized["activityType"], "PLAN") + self.assertEqual(serialized["patch"], patch) + def test_raw_event(self): """Test creating and serializing a RawEvent""" raw_data = {"origin": "server", "data": {"key": "value"}} @@ -338,6 +392,13 @@ def test_event_union_deserialization(self): "snapshot": {"status": "active"}, "timestamp": 1648214400000 }, + { + "type": "ACTIVITY_SNAPSHOT", + "messageId": "msg_activity", + "activityType": "PLAN", + "content": {"tasks": []}, + "timestamp": 1648214400000, + }, { "type": "RUN_ERROR", "message": "Error occurred", @@ -345,12 +406,13 @@ def test_event_union_deserialization(self): "timestamp": 1648214400000 } ] - + expected_types = [ TextMessageStartEvent, TextMessageContentEvent, ToolCallStartEvent, StateSnapshotEvent, + ActivitySnapshotEvent, RunErrorEvent ] @@ -392,6 +454,16 @@ def test_serialization_round_trip(self): UserMessage(id="user_1", content="Hello") ] ), + ActivitySnapshotEvent( + message_id="msg_activity", + activity_type="PLAN", + content={"tasks": []}, + ), + ActivityDeltaEvent( + message_id="msg_activity", + activity_type="PLAN", + patch=[{"op": "add", "path": "/tasks/-", "value": "search"}], + ), RunStartedEvent( thread_id="thread_123", run_id="run_456" diff --git a/sdks/python/tests/test_types.py b/sdks/python/tests/test_types.py index e534aa5ab..843a5a977 100644 --- a/sdks/python/tests/test_types.py +++ b/sdks/python/tests/test_types.py @@ -10,8 +10,11 @@ AssistantMessage, UserMessage, ToolMessage, + ActivityMessage, Message, - RunAgentInput + RunAgentInput, + TextInputContent, + BinaryInputContent, ) @@ -56,6 +59,24 @@ def test_tool_message_camel_case(self): self.assertIn("toolCallId", serialized) self.assertEqual(serialized["toolCallId"], "call_456") + def test_activity_message(self): + """Test creating and serializing an activity message""" + content = {"steps": ["search", "summarize"]} + msg = ActivityMessage( + id="activity_123", + activity_type="PLAN", + content=content, + ) + + self.assertEqual(msg.role, "activity") + self.assertEqual(msg.activity_type, "PLAN") + self.assertEqual(msg.content, content) + + serialized = msg.model_dump(by_alias=True) + self.assertEqual(serialized["role"], "activity") + self.assertEqual(serialized["activityType"], "PLAN") + self.assertEqual(serialized["content"], content) + def test_parse_camel_case_json_tool_message(self): """Test parsing JSON with camelCase field names""" # JSON data with camelCase field names @@ -143,6 +164,31 @@ def test_user_message(self): self.assertEqual(serialized["role"], "user") self.assertEqual(serialized["content"], "User query") + def test_user_message_multimodal_content(self): + """Test creating and serializing a multimodal user message""" + contents = [ + TextInputContent(text="Check this out"), + BinaryInputContent(mime_type="image/png", url="https://example.com/image.png"), + ] + msg = UserMessage( + id="user_multi", + content=contents, + ) + self.assertIsInstance(msg.content, list) + self.assertEqual(len(msg.content), 2) + serialized = msg.model_dump(by_alias=True) + self.assertIsInstance(serialized["content"], list) + self.assertEqual(serialized["content"][0]["type"], "text") + self.assertEqual(serialized["content"][0]["text"], "Check this out") + self.assertEqual(serialized["content"][1]["mimeType"], "image/png") + self.assertEqual(serialized["content"][1]["url"], "https://example.com/image.png") + + def test_binary_input_requires_payload_source(self): + """Binary content must specify at least one delivery channel""" + with self.assertRaises(ValidationError): + BinaryInputContent(mime_type="image/png") + + def test_message_union_deserialization(self): """Test that the Message union correctly deserializes to the appropriate type""" # Create type adapter for the union @@ -159,7 +205,13 @@ def test_message_union_deserialization(self): "role": "tool", "content": "Tool result", "toolCallId": "call_303" - } + }, + { + "id": "activity_404", + "role": "activity", + "activityType": "PLAN", + "content": {"steps": []}, + }, ] expected_types = [ @@ -167,7 +219,8 @@ def test_message_union_deserialization(self): SystemMessage, AssistantMessage, UserMessage, - ToolMessage + ToolMessage, + ActivityMessage, ] for data, expected_type in zip(message_data, expected_types): @@ -209,6 +262,7 @@ def test_run_agent_input_deserialization(self): run_agent_input_data = { "threadId": "thread_12345", "runId": "run_67890", + "parentRunId": "run_parent_123", "state": {"conversation_state": "active", "custom_data": {"key": "value"}}, "messages": [ # System message @@ -256,7 +310,14 @@ def test_run_agent_input_deserialization(self): { "id": "user_002", "role": "user", - "content": "Can you explain these results?" + "content": [ + {"type": "text", "text": "Can you explain these results?"}, + { + "type": "binary", + "mimeType": "image/png", + "url": "https://example.com/results-chart.png" + } + ] } ], "tools": [ @@ -307,6 +368,7 @@ def test_run_agent_input_deserialization(self): # Verify basic fields self.assertEqual(run_agent_input.thread_id, "thread_12345") self.assertEqual(run_agent_input.run_id, "run_67890") + self.assertEqual(run_agent_input.parent_run_id, "run_parent_123") self.assertEqual(run_agent_input.state["conversation_state"], "active") # Verify messages count and types @@ -321,6 +383,12 @@ def test_run_agent_input_deserialization(self): # Verify specific message content self.assertEqual(run_agent_input.messages[0].content, "You are a helpful assistant.") self.assertEqual(run_agent_input.messages[1].content, "Can you help me analyze this data?") + multimodal_content = run_agent_input.messages[5].content + self.assertIsInstance(multimodal_content, list) + self.assertEqual(multimodal_content[0].type, "text") + self.assertEqual(multimodal_content[0].text, "Can you explain these results?") + self.assertEqual(multimodal_content[1].mime_type, "image/png") + self.assertEqual(multimodal_content[1].url, "https://example.com/results-chart.png") # Verify assistant message with tool call assistant_msg = run_agent_input.messages[3] @@ -368,15 +436,17 @@ def test_validation_errors(self): with self.assertRaises(ValidationError): UserMessage.model_validate(missing_id_data) - # Test extra fields + # Test extra fields are now allowed for backwards compatibility extra_field_data = { "id": "msg_456", "role": "user", "content": "Hello", - "extra_field": "This shouldn't be here" # Extra field + "extra_field": "This is allowed for backwards compatibility" # Extra field } - with self.assertRaises(ValidationError): - UserMessage.model_validate(extra_field_data) + # Should not raise an error - extra fields are allowed + msg = UserMessage.model_validate(extra_field_data) + self.assertEqual(msg.id, "msg_456") + self.assertEqual(msg.content, "Hello") # Test invalid tool_call_id in ToolMessage invalid_tool_data = { diff --git a/sdks/typescript/README.md b/sdks/typescript/README.md index 64b9a5f4a..1de4bd2f4 100644 --- a/sdks/typescript/README.md +++ b/sdks/typescript/README.md @@ -3,3 +3,21 @@ The TypeScript SDK for the [Agent User Interaction Protocol](https://ag-ui.com). For more information visit the [official documentation](https://docs.ag-ui.com/). + +## Multimodal user messages + +```ts +import { UserMessageSchema } from "@ag-ui/core"; + +const message = UserMessageSchema.parse({ + id: "user-123", + role: "user" as const, + content: [ + { type: "text", text: "Please describe this image" }, + { type: "binary", mimeType: "image/png", url: "https://example.com/cat.png" }, + ], +}); + +console.log(message); +// { id: "user-123", role: "user", content: [...] } +``` diff --git a/sdks/typescript/packages/cli/package.json b/sdks/typescript/packages/cli/package.json index 08d7a16d6..b3a480309 100644 --- a/sdks/typescript/packages/cli/package.json +++ b/sdks/typescript/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "create-ag-ui-app", "author": "Markus Ecker ", - "version": "0.0.40", + "version": "0.0.40-alpha.11", "private": false, "publishConfig": { "access": "public" diff --git a/sdks/typescript/packages/client/jest.config.js b/sdks/typescript/packages/client/jest.config.js index 0521f8d91..919fd78b9 100644 --- a/sdks/typescript/packages/client/jest.config.js +++ b/sdks/typescript/packages/client/jest.config.js @@ -6,5 +6,11 @@ module.exports = { passWithNoTests: true, moduleNameMapper: { "^@/(.*)$": "/src/$1", + "^@ag-ui/core$": "/../core/src/index.ts", + "^@ag-ui/core/(.*)$": "/../core/src/$1", + "^@ag-ui/proto$": "/../proto/src/index.ts", + "^@ag-ui/proto/(.*)$": "/../proto/src/$1", + "^@ag-ui/encoder$": "/../encoder/src/index.ts", + "^@ag-ui/encoder/(.*)$": "/../encoder/src/$1", }, }; diff --git a/sdks/typescript/packages/client/package.json b/sdks/typescript/packages/client/package.json index c963ffe1a..ef6eefbba 100644 --- a/sdks/typescript/packages/client/package.json +++ b/sdks/typescript/packages/client/package.json @@ -1,7 +1,7 @@ { "name": "@ag-ui/client", "author": "Markus Ecker ", - "version": "0.0.40", + "version": "0.0.40-alpha.11", "private": false, "publishConfig": { "access": "public" diff --git a/sdks/typescript/packages/client/src/agent/__tests__/agent-clone.test.ts b/sdks/typescript/packages/client/src/agent/__tests__/agent-clone.test.ts new file mode 100644 index 000000000..3fb00e8a0 --- /dev/null +++ b/sdks/typescript/packages/client/src/agent/__tests__/agent-clone.test.ts @@ -0,0 +1,81 @@ +import { AbstractAgent } from "../agent"; +import { HttpAgent } from "../http"; +import { BaseEvent, Message, RunAgentInput } from "@ag-ui/core"; +import { EMPTY, Observable } from "rxjs"; + +class CloneableTestAgent extends AbstractAgent { + constructor() { + super({ + agentId: "test-agent", + description: "Cloneable test agent", + threadId: "thread-test", + initialMessages: [ + { + id: "msg-1", + role: "user", + content: "Hello world", + toolCalls: [], + } as Message, + ], + initialState: { stage: "initial" }, + }); + } + + protected run(_: RunAgentInput): Observable { + return EMPTY as Observable; + } +} + +describe("AbstractAgent cloning", () => { + it("clones subclass instances with independent state", () => { + const agent = new CloneableTestAgent(); + + const cloned = agent.clone() as CloneableTestAgent; + + expect(cloned).toBeInstanceOf(CloneableTestAgent); + expect(cloned).not.toBe(agent); + expect(cloned.agentId).toBe(agent.agentId); + expect(cloned.threadId).toBe(agent.threadId); + expect(cloned.messages).toEqual(agent.messages); + expect(cloned.messages).not.toBe(agent.messages); + expect(cloned.state).toEqual(agent.state); + expect(cloned.state).not.toBe(agent.state); + }); +}); + +describe("HttpAgent cloning", () => { + it("produces a new HttpAgent with cloned configuration and abort controller", () => { + const httpAgent = new HttpAgent({ + url: "https://example.com/agent", + headers: { Authorization: "Bearer token" }, + threadId: "thread-http", + initialMessages: [ + { + id: "msg-http", + role: "assistant", + content: "response", + toolCalls: [], + } as Message, + ], + initialState: { status: "ready" }, + }); + + httpAgent.abortController.abort("cancelled"); + + const cloned = httpAgent.clone() as HttpAgent; + + expect(cloned).toBeInstanceOf(HttpAgent); + expect(cloned).not.toBe(httpAgent); + expect(cloned.url).toBe(httpAgent.url); + expect(cloned.headers).toEqual(httpAgent.headers); + expect(cloned.headers).not.toBe(httpAgent.headers); + expect(cloned.messages).toEqual(httpAgent.messages); + expect(cloned.messages).not.toBe(httpAgent.messages); + expect(cloned.state).toEqual(httpAgent.state); + expect(cloned.state).not.toBe(httpAgent.state); + expect(cloned.abortController).not.toBe(httpAgent.abortController); + expect(cloned.abortController).toBeInstanceOf(AbortController); + expect(cloned.abortController.signal.aborted).toBe(true); + expect(cloned.abortController.signal.reason).toBe("cancelled"); + }); +}); diff --git a/sdks/typescript/packages/client/src/agent/__tests__/agent-multiple-runs.test.ts b/sdks/typescript/packages/client/src/agent/__tests__/agent-multiple-runs.test.ts index 4f3633d6a..009df2cb7 100644 --- a/sdks/typescript/packages/client/src/agent/__tests__/agent-multiple-runs.test.ts +++ b/sdks/typescript/packages/client/src/agent/__tests__/agent-multiple-runs.test.ts @@ -1,5 +1,5 @@ -import { AbstractAgent, RunAgentResult } from "../agent"; -import { BaseEvent, EventType, Message, RunAgentInput, TextMessageStartEvent, TextMessageContentEvent, TextMessageEndEvent, RunStartedEvent, RunFinishedEvent } from "@ag-ui/core"; +import { AbstractAgent } from "../agent"; +import { BaseEvent, EventType, Message, RunAgentInput, TextMessageStartEvent, TextMessageContentEvent, TextMessageEndEvent, RunStartedEvent, RunFinishedEvent, ActivitySnapshotEvent } from "@ag-ui/core"; import { Observable, of } from "rxjs"; describe("AbstractAgent multiple runs", () => { @@ -273,4 +273,65 @@ describe("AbstractAgent multiple runs", () => { expect(agent.messages[0].content).toBe("Initial message"); expect(agent.messages[1].content).toBe("Response message"); }); -}); \ No newline at end of file + + it("should retain activity messages across runs", async () => { + const agent = new TestAgent({ + threadId: "test-thread", + initialMessages: [], + }); + + const firstRunEvents: BaseEvent[] = [ + { + type: EventType.RUN_STARTED, + threadId: "test-thread", + runId: "run-1", + } as RunStartedEvent, + { + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "activity-1", + activityType: "PLAN", + content: { tasks: ["task 1"] }, + } as ActivitySnapshotEvent, + { + type: EventType.RUN_FINISHED, + } as RunFinishedEvent, + ]; + + agent.setEvents(firstRunEvents); + await agent.runAgent({ runId: "run-1" }); + + expect(agent.messages.length).toBe(1); + expect(agent.messages[0].role).toBe("activity"); + + const secondRunEvents: BaseEvent[] = [ + { + type: EventType.RUN_STARTED, + threadId: "test-thread", + runId: "run-2", + } as RunStartedEvent, + { + type: EventType.TEXT_MESSAGE_START, + messageId: "msg-2", + role: "assistant", + } as TextMessageStartEvent, + { + type: EventType.TEXT_MESSAGE_CONTENT, + messageId: "msg-2", + delta: "Hello from run 2", + } as TextMessageContentEvent, + { + type: EventType.TEXT_MESSAGE_END, + messageId: "msg-2", + } as TextMessageEndEvent, + { + type: EventType.RUN_FINISHED, + } as RunFinishedEvent, + ]; + + agent.setEvents(secondRunEvents); + await agent.runAgent({ runId: "run-2" }); + + expect(agent.messages.length).toBe(2); + expect(agent.messages.some((message) => message.role === "activity" && message.id === "activity-1")).toBe(true); + }); +}); diff --git a/sdks/typescript/packages/client/src/agent/__tests__/agent-result.test.ts b/sdks/typescript/packages/client/src/agent/__tests__/agent-result.test.ts index 5df3a5cf1..bcc39d219 100644 --- a/sdks/typescript/packages/client/src/agent/__tests__/agent-result.test.ts +++ b/sdks/typescript/packages/client/src/agent/__tests__/agent-result.test.ts @@ -1,11 +1,12 @@ import { AbstractAgent } from "../agent"; import { AgentSubscriber } from "../subscriber"; import { + ActivityDeltaEvent, + ActivitySnapshotEvent, BaseEvent, EventType, Message, RunAgentInput, - State, MessagesSnapshotEvent, RunFinishedEvent, RunStartedEvent, @@ -307,6 +308,60 @@ describe("Agent Result", () => { expect(result.newMessages[1].id).toBe("new-2"); expect(result.newMessages[2].id).toBe("new-3"); }); + + it("should retain appended activity operations in agent messages", async () => { + const firstOperation = { id: "op-1", status: "PENDING" }; + const secondOperation = { id: "op-2", status: "COMPLETE" }; + + agent.setEventsToEmit([ + { + type: EventType.RUN_STARTED, + threadId: "test-thread", + runId: "run-ops", + } as RunStartedEvent, + { + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "activity-ops", + activityType: "PLAN", + content: { operations: [] }, + replace: false, + } as ActivitySnapshotEvent, + { + type: EventType.ACTIVITY_DELTA, + messageId: "activity-ops", + activityType: "PLAN", + patch: [{ op: "add", path: "/operations/-", value: firstOperation }], + } as ActivityDeltaEvent, + { + type: EventType.ACTIVITY_DELTA, + messageId: "activity-ops", + activityType: "PLAN", + patch: [{ op: "add", path: "/operations/-", value: secondOperation }], + } as ActivityDeltaEvent, + { + type: EventType.RUN_FINISHED, + threadId: "test-thread", + runId: "run-ops", + } as RunFinishedEvent, + ]); + + const result = await agent.runAgent({ runId: "run-ops" }); + + const activityMessage = agent.messages.find((message) => message.id === "activity-ops"); + + expect(activityMessage).toBeTruthy(); + expect(activityMessage?.role).toBe("activity"); + expect(activityMessage?.activityType).toBe("PLAN"); + expect(activityMessage?.content).toEqual({ + operations: [firstOperation, secondOperation], + }); + + expect(result.newMessages).toHaveLength(1); + expect(result.newMessages[0].id).toBe("activity-ops"); + expect(result.newMessages[0].content).toEqual({ + operations: [firstOperation, secondOperation], + }); + }); }); describe("combined result and newMessages", () => { diff --git a/sdks/typescript/packages/client/src/agent/agent.ts b/sdks/typescript/packages/client/src/agent/agent.ts index 8e2f95610..a85af6765 100644 --- a/sdks/typescript/packages/client/src/agent/agent.ts +++ b/sdks/typescript/packages/client/src/agent/agent.ts @@ -6,13 +6,14 @@ import { v4 as uuidv4 } from "uuid"; import { structuredClone_ } from "@/utils"; import { catchError, map, tap } from "rxjs/operators"; import { finalize } from "rxjs/operators"; -import { pipe, Observable, from, of } from "rxjs"; +import { pipe, Observable, from, of, EMPTY } from "rxjs"; import { verifyEvents } from "@/verify"; import { convertToLegacyEvents } from "@/legacy/convert"; import { LegacyRuntimeProtocolEvent } from "@/legacy/types"; import { lastValueFrom } from "rxjs"; import { transformChunks } from "@/chunks"; import { AgentStateMutation, AgentSubscriber, runSubscribersWithMutation } from "./subscriber"; +import { AGUIConnectNotImplementedError } from "@ag-ui/core"; export interface RunAgentResult { result: any; @@ -27,6 +28,7 @@ export abstract class AbstractAgent { public state: State; public debug: boolean = false; public subscribers: AgentSubscriber[] = []; + public isRunning: boolean = false; constructor({ agentId, @@ -53,49 +55,110 @@ export abstract class AbstractAgent { }; } - abstract run(input: RunAgentInput): Observable; + protected abstract run(input: RunAgentInput): Observable; public async runAgent( parameters?: RunAgentParameters, subscriber?: AgentSubscriber, ): Promise { - this.agentId = this.agentId ?? uuidv4(); - const input = this.prepareRunAgentInput(parameters); - let result: any = undefined; - const currentMessageIds = new Set(this.messages.map((message) => message.id)); - - const subscribers: AgentSubscriber[] = [ - { - onRunFinishedEvent: (params) => { - result = params.result; + try { + this.isRunning = true; + this.agentId = this.agentId ?? uuidv4(); + const input = this.prepareRunAgentInput(parameters); + let result: any = undefined; + const currentMessageIds = new Set(this.messages.map((message) => message.id)); + + const subscribers: AgentSubscriber[] = [ + { + onRunFinishedEvent: (params) => { + result = params.result; + }, }, - }, - ...this.subscribers, - subscriber ?? {}, - ]; + ...this.subscribers, + subscriber ?? {}, + ]; + + await this.onInitialize(input, subscribers); + + const pipeline = pipe( + () => this.run(input), + transformChunks(this.debug), + verifyEvents(this.debug), + (source$) => this.apply(input, source$, subscribers), + (source$) => this.processApplyEvents(input, source$, subscribers), + catchError((error) => { + this.isRunning = false; + return this.onError(input, error, subscribers); + }), + finalize(() => { + this.isRunning = false; + void this.onFinalize(input, subscribers); + }), + ); - await this.onInitialize(input, subscribers); + await lastValueFrom(pipeline(of(null))); + const newMessages = structuredClone_(this.messages).filter( + (message: Message) => !currentMessageIds.has(message.id), + ); + return { result, newMessages }; + } finally { + this.isRunning = false; + } + } - const pipeline = pipe( - () => this.run(input), - transformChunks(this.debug), - verifyEvents(this.debug), - (source$) => this.apply(input, source$, subscribers), - (source$) => this.processApplyEvents(input, source$, subscribers), - catchError((error) => { - return this.onError(input, error, subscribers); - }), - finalize(() => { - void this.onFinalize(input, subscribers); - }), - ); + protected connect(input: RunAgentInput): Observable { + throw new AGUIConnectNotImplementedError(); + } + public async connectAgent( + parameters?: RunAgentParameters, + subscriber?: AgentSubscriber, + ): Promise { + try { + this.isRunning = true; + this.agentId = this.agentId ?? uuidv4(); + const input = this.prepareRunAgentInput(parameters); + let result: any = undefined; + const currentMessageIds = new Set(this.messages.map((message) => message.id)); + + const subscribers: AgentSubscriber[] = [ + { + onRunFinishedEvent: (params) => { + result = params.result; + }, + }, + ...this.subscribers, + subscriber ?? {}, + ]; + + await this.onInitialize(input, subscribers); + + const pipeline = pipe( + () => this.connect(input), + transformChunks(this.debug), + verifyEvents(this.debug), + (source$) => this.apply(input, source$, subscribers), + (source$) => this.processApplyEvents(input, source$, subscribers), + catchError((error) => { + this.isRunning = false; + if (!(error instanceof AGUIConnectNotImplementedError)) { + return this.onError(input, error, subscribers); + } + return EMPTY; + }), + finalize(() => { + this.isRunning = false; + void this.onFinalize(input, subscribers); + }), + ); - return lastValueFrom(pipeline(of(null))).then(() => { + await lastValueFrom(pipeline(of(null))); // wait for stream completion before toggling isRunning const newMessages = structuredClone_(this.messages).filter( (message: Message) => !currentMessageIds.has(message.id), ); return { result, newMessages }; - }); + } finally { + this.isRunning = false; + } } public abortRun() {} @@ -142,6 +205,11 @@ export abstract class AbstractAgent { } protected prepareRunAgentInput(parameters?: RunAgentParameters): RunAgentInput { + const clonedMessages = structuredClone_(this.messages) as Message[]; + const messagesWithoutActivity = clonedMessages.filter( + (message) => message.role !== "activity", + ); + return { threadId: this.threadId, runId: parameters?.runId || uuidv4(), @@ -149,7 +217,7 @@ export abstract class AbstractAgent { context: structuredClone_(parameters?.context ?? []), forwardedProps: structuredClone_(parameters?.forwardedProps ?? {}), state: structuredClone_(this.state), - messages: structuredClone_(this.messages), + messages: messagesWithoutActivity, }; } @@ -281,12 +349,14 @@ export abstract class AbstractAgent { public clone() { const cloned = Object.create(Object.getPrototypeOf(this)); - for (const key of Object.getOwnPropertyNames(this)) { - const value = (this as any)[key]; - if (typeof value !== "function") { - cloned[key] = structuredClone_(value); - } - } + cloned.agentId = this.agentId; + cloned.description = this.description; + cloned.threadId = this.threadId; + cloned.messages = structuredClone_(this.messages); + cloned.state = structuredClone_(this.state); + cloned.debug = this.debug; + cloned.isRunning = this.isRunning; + cloned.subscribers = [...this.subscribers]; return cloned; } diff --git a/sdks/typescript/packages/client/src/agent/http.ts b/sdks/typescript/packages/client/src/agent/http.ts index 49fae2173..f9d9c3002 100644 --- a/sdks/typescript/packages/client/src/agent/http.ts +++ b/sdks/typescript/packages/client/src/agent/http.ts @@ -58,4 +58,19 @@ export class HttpAgent extends AbstractAgent { const httpEvents = runHttpRequest(this.url, this.requestInit(input)); return transformHttpEventStream(httpEvents); } + + public clone(): HttpAgent { + const cloned = super.clone() as HttpAgent; + cloned.url = this.url; + cloned.headers = structuredClone_(this.headers ?? {}); + + const newController = new AbortController(); + const originalSignal = this.abortController.signal as AbortSignal & { reason?: unknown }; + if (originalSignal.aborted) { + newController.abort(originalSignal.reason); + } + cloned.abortController = newController; + + return cloned; + } } diff --git a/sdks/typescript/packages/client/src/agent/index.ts b/sdks/typescript/packages/client/src/agent/index.ts index e1a25b101..046bfa90b 100644 --- a/sdks/typescript/packages/client/src/agent/index.ts +++ b/sdks/typescript/packages/client/src/agent/index.ts @@ -2,4 +2,4 @@ export { AbstractAgent } from "./agent"; export type { RunAgentResult } from "./agent"; export { HttpAgent } from "./http"; export type { AgentConfig, HttpAgentConfig, RunAgentParameters } from "./types"; -export type { AgentSubscriber, AgentStateMutation, AgentSubscriberParams} from "./subscriber"; \ No newline at end of file +export type { AgentSubscriber, AgentStateMutation, AgentSubscriberParams } from "./subscriber"; diff --git a/sdks/typescript/packages/client/src/agent/subscriber.ts b/sdks/typescript/packages/client/src/agent/subscriber.ts index ab7d09e9f..204038e62 100644 --- a/sdks/typescript/packages/client/src/agent/subscriber.ts +++ b/sdks/typescript/packages/client/src/agent/subscriber.ts @@ -21,6 +21,9 @@ import { RawEvent, CustomEvent, ToolCall, + ActivitySnapshotEvent, + ActivityDeltaEvent, + ActivityMessage, } from "@ag-ui/core"; import { AbstractAgent } from "./agent"; import { structuredClone_ } from "@/utils"; @@ -123,6 +126,21 @@ export interface AgentSubscriber { params: { event: MessagesSnapshotEvent } & AgentSubscriberParams, ): MaybePromise; + onActivitySnapshotEvent?( + params: { + event: ActivitySnapshotEvent; + activityMessage?: ActivityMessage; + existingMessage?: Message; + } & AgentSubscriberParams, + ): MaybePromise; + + onActivityDeltaEvent?( + params: { + event: ActivityDeltaEvent; + activityMessage?: ActivityMessage; + } & AgentSubscriberParams, + ): MaybePromise; + onRawEvent?( params: { event: RawEvent } & AgentSubscriberParams, ): MaybePromise; diff --git a/sdks/typescript/packages/client/src/apply/__tests__/default.activity.test.ts b/sdks/typescript/packages/client/src/apply/__tests__/default.activity.test.ts new file mode 100644 index 000000000..e6d0c8263 --- /dev/null +++ b/sdks/typescript/packages/client/src/apply/__tests__/default.activity.test.ts @@ -0,0 +1,381 @@ +import { Subject } from "rxjs"; +import { toArray } from "rxjs/operators"; +import { firstValueFrom } from "rxjs"; +import { + ActivityDeltaEvent, + ActivitySnapshotEvent, + BaseEvent, + EventType, + Message, + RunAgentInput, +} from "@ag-ui/core"; +import { defaultApplyEvents } from "../default"; +import { AbstractAgent } from "@/agent"; + +const createAgent = (messages: Message[] = []) => + ({ + messages: messages.map((message) => ({ ...message })), + state: {}, + } as unknown as AbstractAgent); + +describe("defaultApplyEvents with activity events", () => { + it("creates and updates activity messages via snapshot and delta", async () => { + const events$ = new Subject(); + const initialState: RunAgentInput = { + messages: [], + state: {}, + threadId: "thread-activity", + runId: "run-activity", + tools: [], + context: [], + }; + + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); + const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); + + events$.next({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "activity-1", + activityType: "PLAN", + content: { tasks: ["search"] }, + } as ActivitySnapshotEvent); + + events$.next({ + type: EventType.ACTIVITY_DELTA, + messageId: "activity-1", + activityType: "PLAN", + patch: [{ op: "replace", path: "/tasks/0", value: "✓ search" }], + } as ActivityDeltaEvent); + + events$.complete(); + + const stateUpdates = await stateUpdatesPromise; + + expect(stateUpdates.length).toBe(2); + + const snapshotUpdate = stateUpdates[0]; + expect(snapshotUpdate?.messages?.[0]?.role).toBe("activity"); + expect(snapshotUpdate?.messages?.[0]?.activityType).toBe("PLAN"); + expect(snapshotUpdate?.messages?.[0]?.content).toEqual({ tasks: ["search"] }); + + const deltaUpdate = stateUpdates[1]; + expect(deltaUpdate?.messages?.[0]?.content).toEqual({ tasks: ["✓ search"] }); + }); + + it("appends operations via delta when snapshot starts with an empty array", async () => { + const events$ = new Subject(); + const initialState: RunAgentInput = { + messages: [], + state: {}, + threadId: "thread-activity", + runId: "run-activity", + tools: [], + context: [], + }; + + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); + const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); + + const firstOperation = { id: "op-1", status: "PENDING" }; + const secondOperation = { id: "op-2", status: "COMPLETED" }; + + events$.next({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "activity-ops", + activityType: "PLAN", + content: { operations: [] }, + } as ActivitySnapshotEvent); + + events$.next({ + type: EventType.ACTIVITY_DELTA, + messageId: "activity-ops", + activityType: "PLAN", + patch: [ + { op: "add", path: "/operations/-", value: firstOperation }, + ], + } as ActivityDeltaEvent); + + events$.next({ + type: EventType.ACTIVITY_DELTA, + messageId: "activity-ops", + activityType: "PLAN", + patch: [ + { op: "add", path: "/operations/-", value: secondOperation }, + ], + } as ActivityDeltaEvent); + + events$.complete(); + + const stateUpdates = await stateUpdatesPromise; + + expect(stateUpdates.length).toBe(3); + + const snapshotUpdate = stateUpdates[0]; + expect(snapshotUpdate?.messages?.[0]?.content).toEqual({ operations: [] }); + + const firstDeltaUpdate = stateUpdates[1]; + expect(firstDeltaUpdate?.messages?.[0]?.content?.operations).toEqual([ + firstOperation, + ]); + + const secondDeltaUpdate = stateUpdates[2]; + expect(secondDeltaUpdate?.messages?.[0]?.content?.operations).toEqual([ + firstOperation, + secondOperation, + ]); + }); + + it("does not replace existing activity message when replace is false", async () => { + const events$ = new Subject(); + const initialState: RunAgentInput = { + messages: [ + { + id: "activity-1", + role: "activity", + activityType: "PLAN", + content: { tasks: ["initial"] }, + }, + ], + state: {}, + threadId: "thread-activity", + runId: "run-activity", + tools: [], + context: [], + }; + + const agent = createAgent(initialState.messages as Message[]); + const result$ = defaultApplyEvents(initialState, events$, agent, []); + const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); + + events$.next({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "activity-1", + activityType: "PLAN", + content: { tasks: ["updated"] }, + replace: false, + } as ActivitySnapshotEvent); + + events$.complete(); + + const stateUpdates = await stateUpdatesPromise; + expect(stateUpdates.length).toBe(1); + const update = stateUpdates[0]; + expect(update?.messages?.[0]?.content).toEqual({ tasks: ["initial"] }); + }); + + it("adds activity message when replace is false and none exists", async () => { + const events$ = new Subject(); + const initialState: RunAgentInput = { + messages: [], + state: {}, + threadId: "thread-activity", + runId: "run-activity", + tools: [], + context: [], + }; + + const agent = createAgent(initialState.messages as Message[]); + const result$ = defaultApplyEvents(initialState, events$, agent, []); + const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); + + events$.next({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "activity-1", + activityType: "PLAN", + content: { tasks: ["first"] }, + replace: false, + } as ActivitySnapshotEvent); + + events$.complete(); + + const stateUpdates = await stateUpdatesPromise; + expect(stateUpdates.length).toBe(1); + const update = stateUpdates[0]; + expect(update?.messages?.[0]?.content).toEqual({ tasks: ["first"] }); + expect(update?.messages?.[0]?.role).toBe("activity"); + }); + + it("replaces existing activity message when replace is true", async () => { + const events$ = new Subject(); + const initialState: RunAgentInput = { + messages: [ + { + id: "activity-1", + role: "activity" as const, + activityType: "PLAN", + content: { tasks: ["initial"] }, + }, + ], + state: {}, + threadId: "thread-activity", + runId: "run-activity", + tools: [], + context: [], + }; + + const agent = createAgent(initialState.messages as Message[]); + const result$ = defaultApplyEvents(initialState, events$, agent, []); + const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); + + events$.next({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "activity-1", + activityType: "PLAN", + content: { tasks: ["updated"] }, + replace: true, + } as ActivitySnapshotEvent); + + events$.complete(); + + const stateUpdates = await stateUpdatesPromise; + expect(stateUpdates.length).toBe(1); + const update = stateUpdates[0]; + expect(update?.messages?.[0]?.content).toEqual({ tasks: ["updated"] }); + }); + + it("replaces non-activity message when replace is true", async () => { + const events$ = new Subject(); + const initialState: RunAgentInput = { + messages: [ + { + id: "activity-1", + role: "user" as const, + content: "placeholder", + }, + ], + state: {}, + threadId: "thread-activity", + runId: "run-activity", + tools: [], + context: [], + }; + + const agent = createAgent(initialState.messages as Message[]); + const result$ = defaultApplyEvents(initialState, events$, agent, []); + const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); + + events$.next({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "activity-1", + activityType: "PLAN", + content: { tasks: ["first"] }, + replace: true, + } as ActivitySnapshotEvent); + + events$.complete(); + + const stateUpdates = await stateUpdatesPromise; + expect(stateUpdates.length).toBe(1); + const update = stateUpdates[0]; + expect(update?.messages?.[0]?.role).toBe("activity"); + expect(update?.messages?.[0]?.content).toEqual({ tasks: ["first"] }); + }); + + it("does not alter non-activity message when replace is false", async () => { + const events$ = new Subject(); + const initialState: RunAgentInput = { + messages: [ + { + id: "activity-1", + role: "user" as const, + content: "placeholder", + }, + ], + state: {}, + threadId: "thread-activity", + runId: "run-activity", + tools: [], + context: [], + }; + + const agent = createAgent(initialState.messages as Message[]); + const result$ = defaultApplyEvents(initialState, events$, agent, []); + const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); + + events$.next({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "activity-1", + activityType: "PLAN", + content: { tasks: ["first"] }, + replace: false, + } as ActivitySnapshotEvent); + + events$.complete(); + + const stateUpdates = await stateUpdatesPromise; + expect(stateUpdates.length).toBe(1); + const update = stateUpdates[0]; + expect(update?.messages?.[0]?.role).toBe("user"); + expect(update?.messages?.[0]?.content).toBe("placeholder"); + }); + + it("maintains replace semantics across runs", async () => { + const firstRunEvents$ = new Subject(); + const baseInput: RunAgentInput = { + messages: [], + state: {}, + threadId: "thread-activity", + runId: "run-activity", + tools: [], + context: [], + }; + + const baseAgent = createAgent(baseInput.messages); + const firstResult$ = defaultApplyEvents(baseInput, firstRunEvents$, baseAgent, []); + const firstUpdatesPromise = firstValueFrom(firstResult$.pipe(toArray())); + + firstRunEvents$.next({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "activity-1", + activityType: "PLAN", + content: { tasks: ["initial"] }, + replace: true, + } as ActivitySnapshotEvent); + firstRunEvents$.complete(); + + const firstUpdates = await firstUpdatesPromise; + const nextMessages = firstUpdates[0]?.messages ?? []; + + const secondRunEvents$ = new Subject(); + const secondInput: RunAgentInput = { + ...baseInput, + messages: nextMessages, + }; + + const secondAgent = createAgent(secondInput.messages); + const secondResult$ = defaultApplyEvents( + secondInput, + secondRunEvents$, + secondAgent, + [], + ); + const secondUpdatesPromise = firstValueFrom(secondResult$.pipe(toArray())); + + secondRunEvents$.next({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "activity-1", + activityType: "PLAN", + content: { tasks: ["updated"] }, + replace: false, + } as ActivitySnapshotEvent); + + secondRunEvents$.next({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "activity-1", + activityType: "PLAN", + content: { tasks: ["final"] }, + replace: true, + } as ActivitySnapshotEvent); + + secondRunEvents$.complete(); + + const secondUpdates = await secondUpdatesPromise; + expect(secondUpdates.length).toBe(2); + const afterReplaceFalse = secondUpdates[0]; + expect(afterReplaceFalse?.messages?.[0]?.content).toEqual({ tasks: ["initial"] }); + const afterReplaceTrue = secondUpdates[1]; + expect(afterReplaceTrue?.messages?.[0]?.content).toEqual({ tasks: ["final"] }); + }); +}); diff --git a/sdks/typescript/packages/client/src/apply/__tests__/default.concurrent.test.ts b/sdks/typescript/packages/client/src/apply/__tests__/default.concurrent.test.ts index 49cad4552..322ada5e8 100644 --- a/sdks/typescript/packages/client/src/apply/__tests__/default.concurrent.test.ts +++ b/sdks/typescript/packages/client/src/apply/__tests__/default.concurrent.test.ts @@ -19,12 +19,12 @@ import { } from "@ag-ui/core"; import { AbstractAgent } from "../../agent"; -// Mock agent for testing -const FAKE_AGENT = { - messages: [], - state: {}, - agentId: "test-agent", -} as unknown as AbstractAgent; +const createAgent = (messages: Message[] = []) => + ({ + messages: messages.map((message) => ({ ...message })), + state: {}, + agentId: "test-agent", + } as unknown as AbstractAgent); describe("defaultApplyEvents concurrent operations", () => { // Test: Concurrent text messages should create separate messages @@ -41,7 +41,8 @@ describe("defaultApplyEvents concurrent operations", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); @@ -125,7 +126,8 @@ describe("defaultApplyEvents concurrent operations", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); @@ -218,7 +220,8 @@ describe("defaultApplyEvents concurrent operations", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); @@ -333,7 +336,8 @@ describe("defaultApplyEvents concurrent operations", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); @@ -444,7 +448,8 @@ describe("defaultApplyEvents concurrent operations", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); @@ -552,7 +557,8 @@ describe("defaultApplyEvents concurrent operations", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); diff --git a/sdks/typescript/packages/client/src/apply/__tests__/default.state.test.ts b/sdks/typescript/packages/client/src/apply/__tests__/default.state.test.ts index 71e64efc1..41627ee2e 100644 --- a/sdks/typescript/packages/client/src/apply/__tests__/default.state.test.ts +++ b/sdks/typescript/packages/client/src/apply/__tests__/default.state.test.ts @@ -1,10 +1,14 @@ import { AbstractAgent } from "@/agent"; import { defaultApplyEvents } from "../default"; -import { EventType, StateDeltaEvent } from "@ag-ui/core"; +import { EventType, Message, StateDeltaEvent } from "@ag-ui/core"; import { of } from "rxjs"; import { AgentStateMutation } from "@/agent/subscriber"; -const FAKE_AGENT = null as unknown as AbstractAgent; +const createAgent = (messages: Message[] = []) => + ({ + messages: messages.map((message) => ({ ...message })), + state: {}, + } as unknown as AbstractAgent); describe("defaultApplyEvents - State Patching", () => { it("should apply state delta patch correctly", (done) => { @@ -30,7 +34,8 @@ describe("defaultApplyEvents - State Patching", () => { const events$ = of(stateDelta); - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages as Message[]); + const result$ = defaultApplyEvents(initialState, events$, agent, []); result$.subscribe((update: AgentStateMutation) => { expect(update.state).toEqual({ @@ -65,7 +70,8 @@ describe("defaultApplyEvents - State Patching", () => { const events$ = of(stateDelta); // Cast to any to bypass strict type checking - const result$ = defaultApplyEvents(initialState as any, events$, FAKE_AGENT, []); + const agent = createAgent((initialState as any).messages as Message[]); + const result$ = defaultApplyEvents(initialState as any, events$, agent, []); result$.subscribe((update: AgentStateMutation) => { expect(update.state).toEqual({ @@ -102,7 +108,8 @@ describe("defaultApplyEvents - State Patching", () => { const events$ = of(stateDelta); // Cast to any to bypass strict type checking - const result$ = defaultApplyEvents(initialState as any, events$, FAKE_AGENT, []); + const agent = createAgent((initialState as any).messages as Message[]); + const result$ = defaultApplyEvents(initialState as any, events$, agent, []); result$.subscribe((update: AgentStateMutation) => { expect(update.state).toEqual({ @@ -137,7 +144,8 @@ describe("defaultApplyEvents - State Patching", () => { const events$ = of(...stateDeltas); // Cast to any to bypass strict type checking - const result$ = defaultApplyEvents(initialState as any, events$, FAKE_AGENT, []); + const agent = createAgent((initialState as any).messages as Message[]); + const result$ = defaultApplyEvents(initialState as any, events$, agent, []); let updateCount = 0; result$.subscribe((update: AgentStateMutation) => { @@ -176,7 +184,8 @@ describe("defaultApplyEvents - State Patching", () => { const events$ = of(stateDelta); // Cast to any to bypass strict type checking - const result$ = defaultApplyEvents(initialState as any, events$, FAKE_AGENT, []); + const agent = createAgent((initialState as any).messages as Message[]); + const result$ = defaultApplyEvents(initialState as any, events$, agent, []); let updateCount = 0; result$.subscribe({ diff --git a/sdks/typescript/packages/client/src/apply/__tests__/default.text-message.test.ts b/sdks/typescript/packages/client/src/apply/__tests__/default.text-message.test.ts index 581f418df..031713f29 100644 --- a/sdks/typescript/packages/client/src/apply/__tests__/default.text-message.test.ts +++ b/sdks/typescript/packages/client/src/apply/__tests__/default.text-message.test.ts @@ -4,6 +4,7 @@ import { firstValueFrom } from "rxjs"; import { BaseEvent, EventType, + Message, RunStartedEvent, TextMessageStartEvent, TextMessageContentEvent, @@ -13,7 +14,11 @@ import { import { defaultApplyEvents } from "../default"; import { AbstractAgent } from "@/agent"; -const FAKE_AGENT = null as unknown as AbstractAgent; +const createAgent = (messages: Message[] = []) => + ({ + messages: messages.map((message) => ({ ...message })), + state: {}, + } as unknown as AbstractAgent); describe("defaultApplyEvents with text messages", () => { it("should handle text message events correctly", async () => { @@ -29,7 +34,8 @@ describe("defaultApplyEvents with text messages", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); @@ -102,7 +108,8 @@ describe("defaultApplyEvents with text messages", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); diff --git a/sdks/typescript/packages/client/src/apply/__tests__/default.tool-calls.test.ts b/sdks/typescript/packages/client/src/apply/__tests__/default.tool-calls.test.ts index bcce0ab40..261057677 100644 --- a/sdks/typescript/packages/client/src/apply/__tests__/default.tool-calls.test.ts +++ b/sdks/typescript/packages/client/src/apply/__tests__/default.tool-calls.test.ts @@ -2,19 +2,24 @@ import { Subject } from "rxjs"; import { toArray } from "rxjs/operators"; import { firstValueFrom } from "rxjs"; import { + AssistantMessage, BaseEvent, EventType, + Message, + RunAgentInput, RunStartedEvent, - ToolCallStartEvent, ToolCallArgsEvent, ToolCallEndEvent, - RunAgentInput, - AssistantMessage, + ToolCallStartEvent, } from "@ag-ui/core"; import { defaultApplyEvents } from "../default"; import { AbstractAgent } from "@/agent"; -const FAKE_AGENT = null as unknown as AbstractAgent; +const createAgent = (messages: Message[] = []) => + ({ + messages: messages.map((message) => ({ ...message })), + state: {}, + } as unknown as AbstractAgent); describe("defaultApplyEvents with tool calls", () => { it("should handle a single tool call correctly", async () => { @@ -33,7 +38,8 @@ describe("defaultApplyEvents with tool calls", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); @@ -120,7 +126,8 @@ describe("defaultApplyEvents with tool calls", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); @@ -224,7 +231,8 @@ describe("defaultApplyEvents with tool calls", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages as Message[]); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); @@ -287,7 +295,8 @@ describe("defaultApplyEvents with tool calls", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); @@ -346,7 +355,8 @@ describe("defaultApplyEvents with tool calls", () => { }; // Create the observable stream - const result$ = defaultApplyEvents(initialState, events$, FAKE_AGENT, []); + const agent = createAgent(initialState.messages); + const result$ = defaultApplyEvents(initialState, events$, agent, []); // Collect all emitted state updates in an array const stateUpdatesPromise = firstValueFrom(result$.pipe(toArray())); diff --git a/sdks/typescript/packages/client/src/apply/__tests__/run-started-input.test.ts b/sdks/typescript/packages/client/src/apply/__tests__/run-started-input.test.ts new file mode 100644 index 000000000..9be1a0133 --- /dev/null +++ b/sdks/typescript/packages/client/src/apply/__tests__/run-started-input.test.ts @@ -0,0 +1,416 @@ +import { AbstractAgent } from "../../agent/agent"; +import { + BaseEvent, + EventType, + Message, + RunAgentInput, + RunStartedEvent, + RunFinishedEvent, + TextMessageStartEvent, + TextMessageContentEvent, + TextMessageEndEvent, +} from "@ag-ui/core"; +import { Observable, of } from "rxjs"; +import { AgentSubscriber } from "../../agent/subscriber"; + +describe("RunStartedEvent with input.messages", () => { + class TestAgent extends AbstractAgent { + private events: BaseEvent[] = []; + + setEvents(events: BaseEvent[]) { + this.events = events; + } + + protected run(input: RunAgentInput): Observable { + return of(...this.events); + } + } + + it("should add messages from RunStartedEvent.input that are not already present", async () => { + const agent = new TestAgent({ + threadId: "test-thread", + initialMessages: [], + }); + + const events: BaseEvent[] = [ + { + type: EventType.RUN_STARTED, + threadId: "test-thread", + runId: "run-1", + input: { + threadId: "test-thread", + runId: "run-1", + messages: [ + { + id: "msg-1", + role: "user", + content: "Hello", + }, + { + id: "msg-2", + role: "user", + content: "How are you?", + }, + ], + tools: [], + context: [], + state: {}, + forwardedProps: {}, + }, + } as RunStartedEvent, + { + type: EventType.RUN_FINISHED, + threadId: "test-thread", + runId: "run-1", + } as RunFinishedEvent, + ]; + + agent.setEvents(events); + const result = await agent.runAgent({ runId: "run-1" }); + + // Verify both messages were added + expect(agent.messages.length).toBe(2); + expect(agent.messages[0].id).toBe("msg-1"); + expect(agent.messages[0].content).toBe("Hello"); + expect(agent.messages[1].id).toBe("msg-2"); + expect(agent.messages[1].content).toBe("How are you?"); + + // Verify they appear in newMessages + expect(result.newMessages.length).toBe(2); + }); + + it("should not duplicate messages that already exist (by ID)", async () => { + const initialMessages: Message[] = [ + { + id: "msg-1", + role: "user", + content: "Existing message", + }, + ]; + + const agent = new TestAgent({ + threadId: "test-thread", + initialMessages, + }); + + const events: BaseEvent[] = [ + { + type: EventType.RUN_STARTED, + threadId: "test-thread", + runId: "run-1", + input: { + threadId: "test-thread", + runId: "run-1", + messages: [ + { + id: "msg-1", + role: "user", + content: "Duplicate message (should be ignored)", + }, + { + id: "msg-2", + role: "user", + content: "New message", + }, + ], + tools: [], + context: [], + state: {}, + forwardedProps: {}, + }, + } as RunStartedEvent, + { + type: EventType.RUN_FINISHED, + threadId: "test-thread", + runId: "run-1", + } as RunFinishedEvent, + ]; + + agent.setEvents(events); + const result = await agent.runAgent({ runId: "run-1" }); + + // Verify only the new message was added + expect(agent.messages.length).toBe(2); + expect(agent.messages[0].id).toBe("msg-1"); + expect(agent.messages[0].content).toBe("Existing message"); // Original content preserved + expect(agent.messages[1].id).toBe("msg-2"); + expect(agent.messages[1].content).toBe("New message"); + + // Verify only the new message appears in newMessages + expect(result.newMessages.length).toBe(1); + expect(result.newMessages[0].id).toBe("msg-2"); + }); + + it("should handle RunStartedEvent without input field", async () => { + const agent = new TestAgent({ + threadId: "test-thread", + initialMessages: [], + }); + + const events: BaseEvent[] = [ + { + type: EventType.RUN_STARTED, + threadId: "test-thread", + runId: "run-1", + // No input field + } as RunStartedEvent, + { + type: EventType.RUN_FINISHED, + threadId: "test-thread", + runId: "run-1", + } as RunFinishedEvent, + ]; + + agent.setEvents(events); + const result = await agent.runAgent({ runId: "run-1" }); + + // Verify no errors and messages remain empty + expect(agent.messages.length).toBe(0); + expect(result.newMessages.length).toBe(0); + }); + + it("should handle RunStartedEvent with input but no messages", async () => { + const agent = new TestAgent({ + threadId: "test-thread", + initialMessages: [], + }); + + const events: BaseEvent[] = [ + { + type: EventType.RUN_STARTED, + threadId: "test-thread", + runId: "run-1", + input: { + threadId: "test-thread", + runId: "run-1", + messages: [], // Empty messages array + tools: [], + context: [], + state: {}, + forwardedProps: {}, + }, + } as RunStartedEvent, + { + type: EventType.RUN_FINISHED, + threadId: "test-thread", + runId: "run-1", + } as RunFinishedEvent, + ]; + + agent.setEvents(events); + const result = await agent.runAgent({ runId: "run-1" }); + + // Verify no errors and messages remain empty + expect(agent.messages.length).toBe(0); + expect(result.newMessages.length).toBe(0); + }); + + it("should respect stopPropagation from subscribers", async () => { + const agent = new TestAgent({ + threadId: "test-thread", + initialMessages: [], + }); + + // Create a subscriber that stops propagation + const stopPropagationSubscriber: AgentSubscriber = { + onRunStartedEvent: () => { + return { stopPropagation: true }; + }, + }; + + const events: BaseEvent[] = [ + { + type: EventType.RUN_STARTED, + threadId: "test-thread", + runId: "run-1", + input: { + threadId: "test-thread", + runId: "run-1", + messages: [ + { + id: "msg-1", + role: "user", + content: "Should not be added", + }, + ], + tools: [], + context: [], + state: {}, + forwardedProps: {}, + }, + } as RunStartedEvent, + { + type: EventType.RUN_FINISHED, + threadId: "test-thread", + runId: "run-1", + } as RunFinishedEvent, + ]; + + agent.setEvents(events); + const result = await agent.runAgent({ runId: "run-1" }, stopPropagationSubscriber); + + // Verify messages were NOT added due to stopPropagation + expect(agent.messages.length).toBe(0); + expect(result.newMessages.length).toBe(0); + }); + + it("should add messages before other events in the same run", async () => { + const agent = new TestAgent({ + threadId: "test-thread", + initialMessages: [], + }); + + const events: BaseEvent[] = [ + { + type: EventType.RUN_STARTED, + threadId: "test-thread", + runId: "run-1", + input: { + threadId: "test-thread", + runId: "run-1", + messages: [ + { + id: "msg-from-input", + role: "user", + content: "From input", + }, + ], + tools: [], + context: [], + state: {}, + forwardedProps: {}, + }, + } as RunStartedEvent, + { + type: EventType.TEXT_MESSAGE_START, + messageId: "msg-streamed", + role: "assistant", + } as TextMessageStartEvent, + { + type: EventType.TEXT_MESSAGE_CONTENT, + messageId: "msg-streamed", + delta: "Streamed response", + } as TextMessageContentEvent, + { + type: EventType.TEXT_MESSAGE_END, + messageId: "msg-streamed", + } as TextMessageEndEvent, + { + type: EventType.RUN_FINISHED, + threadId: "test-thread", + runId: "run-1", + } as RunFinishedEvent, + ]; + + agent.setEvents(events); + const result = await agent.runAgent({ runId: "run-1" }); + + // Verify message order: input message first, then streamed message + expect(agent.messages.length).toBe(2); + expect(agent.messages[0].id).toBe("msg-from-input"); + expect(agent.messages[0].content).toBe("From input"); + expect(agent.messages[1].id).toBe("msg-streamed"); + expect(agent.messages[1].content).toBe("Streamed response"); + + expect(result.newMessages.length).toBe(2); + }); + + it("should handle multiple runs with input.messages", async () => { + const agent = new TestAgent({ + threadId: "test-thread", + initialMessages: [], + }); + + // First run with one message + const firstRunEvents: BaseEvent[] = [ + { + type: EventType.RUN_STARTED, + threadId: "test-thread", + runId: "run-1", + input: { + threadId: "test-thread", + runId: "run-1", + messages: [ + { + id: "msg-1", + role: "user", + content: "First message", + }, + ], + tools: [], + context: [], + state: {}, + forwardedProps: {}, + }, + } as RunStartedEvent, + { + type: EventType.RUN_FINISHED, + threadId: "test-thread", + runId: "run-1", + } as RunFinishedEvent, + ]; + + agent.setEvents(firstRunEvents); + const result1 = await agent.runAgent({ runId: "run-1" }); + + expect(agent.messages.length).toBe(1); + expect(agent.messages[0].id).toBe("msg-1"); + expect(result1.newMessages.length).toBe(1); + + // Second run with three messages (one duplicate, two new) + const secondRunEvents: BaseEvent[] = [ + { + type: EventType.RUN_STARTED, + threadId: "test-thread", + runId: "run-2", + input: { + threadId: "test-thread", + runId: "run-2", + messages: [ + { + id: "msg-1", + role: "user", + content: "First message (duplicate)", + }, + { + id: "msg-2", + role: "user", + content: "Second message", + }, + { + id: "msg-3", + role: "user", + content: "Third message", + }, + ], + tools: [], + context: [], + state: {}, + forwardedProps: {}, + }, + } as RunStartedEvent, + { + type: EventType.RUN_FINISHED, + threadId: "test-thread", + runId: "run-2", + } as RunFinishedEvent, + ]; + + agent.setEvents(secondRunEvents); + const result2 = await agent.runAgent({ runId: "run-2" }); + + // Verify only new messages were added + expect(agent.messages.length).toBe(3); + expect(agent.messages[0].id).toBe("msg-1"); + expect(agent.messages[0].content).toBe("First message"); // Original content preserved + expect(agent.messages[1].id).toBe("msg-2"); + expect(agent.messages[1].content).toBe("Second message"); + expect(agent.messages[2].id).toBe("msg-3"); + expect(agent.messages[2].content).toBe("Third message"); + + // Verify only the two new messages appear in newMessages for the second run + expect(result2.newMessages.length).toBe(2); + expect(result2.newMessages[0].id).toBe("msg-2"); + expect(result2.newMessages[1].id).toBe("msg-3"); + }); +}); diff --git a/sdks/typescript/packages/client/src/apply/default.ts b/sdks/typescript/packages/client/src/apply/default.ts index 8f720c7a0..6cd3dcd8b 100644 --- a/sdks/typescript/packages/client/src/apply/default.ts +++ b/sdks/typescript/packages/client/src/apply/default.ts @@ -25,6 +25,9 @@ import { RunErrorEvent, StepStartedEvent, StepFinishedEvent, + ActivitySnapshotEvent, + ActivityDeltaEvent, + ActivityMessage, } from "@ag-ui/core"; import { mergeMap, mergeAll, defaultIfEmpty, concatMap } from "rxjs/operators"; import { of, EMPTY } from "rxjs"; @@ -45,7 +48,7 @@ export const defaultApplyEvents = ( agent: AbstractAgent, subscribers: AgentSubscriber[], ): Observable => { - let messages = structuredClone_(input.messages); + let messages = structuredClone_(agent.messages); let state = structuredClone_(input.state); let currentMutation: AgentStateMutation = {}; @@ -140,14 +143,17 @@ export const defaultApplyEvents = ( state, agent, input, - textMessageBuffer: targetMessage.content ?? "", + textMessageBuffer: + typeof targetMessage.content === "string" ? targetMessage.content : "", }), ); applyMutation(mutation); if (mutation.stopPropagation !== true) { // Append content to the correct message by ID - targetMessage.content = (targetMessage.content || "") + delta; + const existingContent = + typeof targetMessage.content === "string" ? targetMessage.content : ""; + targetMessage.content = `${existingContent}${delta}`; applyMutation({ messages }); } @@ -175,7 +181,8 @@ export const defaultApplyEvents = ( state, agent, input, - textMessageBuffer: targetMessage.content ?? "", + textMessageBuffer: + typeof targetMessage.content === "string" ? targetMessage.content : "", }), ); applyMutation(mutation); @@ -513,6 +520,143 @@ export const defaultApplyEvents = ( return emitUpdates(); } + case EventType.ACTIVITY_SNAPSHOT: { + const activityEvent = event as ActivitySnapshotEvent; + const existingIndex = messages.findIndex((m) => m.id === activityEvent.messageId); + const existingMessage = existingIndex >= 0 ? messages[existingIndex] : undefined; + const existingActivityMessage = + existingMessage?.role === "activity" ? (existingMessage as ActivityMessage) : undefined; + const replace = activityEvent.replace ?? true; + + const mutation = await runSubscribersWithMutation( + subscribers, + messages, + state, + (subscriber, messages, state) => + subscriber.onActivitySnapshotEvent?.({ + event: activityEvent, + messages, + state, + agent, + input, + activityMessage: existingActivityMessage, + existingMessage, + }), + ); + applyMutation(mutation); + + if (mutation.stopPropagation !== true) { + const activityMessage: ActivityMessage = { + id: activityEvent.messageId, + role: "activity", + activityType: activityEvent.activityType, + content: structuredClone_(activityEvent.content), + }; + + let createdMessage: ActivityMessage | undefined; + + if (existingIndex === -1) { + messages.push(activityMessage); + createdMessage = activityMessage; + } else if (existingActivityMessage) { + if (replace) { + messages[existingIndex] = { + ...existingActivityMessage, + activityType: activityEvent.activityType, + content: structuredClone_(activityEvent.content), + }; + } + } else if (replace) { + messages[existingIndex] = activityMessage; + createdMessage = activityMessage; + } + + applyMutation({ messages }); + + if (createdMessage) { + await Promise.all( + subscribers.map((subscriber) => + subscriber.onNewMessage?.({ + message: createdMessage, + messages, + state, + agent, + input, + }), + ), + ); + } + } + + return emitUpdates(); + } + + case EventType.ACTIVITY_DELTA: { + const activityEvent = event as ActivityDeltaEvent; + const existingIndex = messages.findIndex((m) => m.id === activityEvent.messageId); + if (existingIndex === -1) { + console.warn( + `ACTIVITY_DELTA: No message found with ID '${activityEvent.messageId}' to apply patch`, + ); + return emitUpdates(); + } + + const existingMessage = messages[existingIndex]; + if (existingMessage.role !== "activity") { + console.warn( + `ACTIVITY_DELTA: Message '${activityEvent.messageId}' is not an activity message`, + ); + return emitUpdates(); + } + + const existingActivityMessage = existingMessage as ActivityMessage; + + const mutation = await runSubscribersWithMutation( + subscribers, + messages, + state, + (subscriber, messages, state) => + subscriber.onActivityDeltaEvent?.({ + event: activityEvent, + messages, + state, + agent, + input, + activityMessage: existingActivityMessage, + }), + ); + applyMutation(mutation); + + if (mutation.stopPropagation !== true) { + try { + const baseContent = structuredClone_(existingActivityMessage.content ?? {}); + + const result = applyPatch( + baseContent, + activityEvent.patch ?? [], + true, + false, + ); + const updatedContent = result.newDocument as ActivityMessage["content"]; + + messages[existingIndex] = { + ...existingActivityMessage, + content: structuredClone_(updatedContent), + activityType: activityEvent.activityType, + }; + + applyMutation({ messages }); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.warn( + `Failed to apply activity patch for '${activityEvent.messageId}': ${errorMessage}`, + ); + } + } + + return emitUpdates(); + } + case EventType.RAW: { const mutation = await runSubscribersWithMutation( subscribers, @@ -567,6 +711,25 @@ export const defaultApplyEvents = ( ); applyMutation(mutation); + // Handle input.messages if present and stopPropagation is not set + if (mutation.stopPropagation !== true) { + const runStartedEvent = event as RunStartedEvent; + + // Check if the event contains input with messages + if (runStartedEvent.input?.messages) { + // Add messages that aren't already present (checked by ID) + for (const message of runStartedEvent.input.messages) { + const existingMessage = messages.find((m) => m.id === message.id); + if (!existingMessage) { + messages.push(message); + } + } + + // Apply mutation to emit the updated messages + applyMutation({ messages }); + } + } + return emitUpdates(); } diff --git a/sdks/typescript/packages/client/src/chunks/transform.ts b/sdks/typescript/packages/client/src/chunks/transform.ts index bb0ec6ba4..0f2f4e15c 100644 --- a/sdks/typescript/packages/client/src/chunks/transform.ts +++ b/sdks/typescript/packages/client/src/chunks/transform.ts @@ -101,6 +101,8 @@ export const transformChunks = case EventType.THINKING_TEXT_MESSAGE_END: return [...closePendingEvent(), event]; case EventType.RAW: + case EventType.ACTIVITY_SNAPSHOT: + case EventType.ACTIVITY_DELTA: return [event]; case EventType.TEXT_MESSAGE_CHUNK: const messageChunkEvent = event as TextMessageChunkEvent; @@ -220,10 +222,11 @@ export const transformChunks = return toolMessageResult; } const _exhaustiveCheck: never = event.type; + return []; }), finalize(() => { // This ensures that we close any pending events when the source observable completes - return closePendingEvent(); + closePendingEvent(); }), ); }; diff --git a/sdks/typescript/packages/client/src/compact/__tests__/compact.test.ts b/sdks/typescript/packages/client/src/compact/__tests__/compact.test.ts new file mode 100644 index 000000000..17ccb017c --- /dev/null +++ b/sdks/typescript/packages/client/src/compact/__tests__/compact.test.ts @@ -0,0 +1,294 @@ +import { compactEvents } from "../compact"; +import { + EventType, + TextMessageStartEvent, + TextMessageContentEvent, + ToolCallStartEvent, + ToolCallArgsEvent, + CustomEvent, +} from "@ag-ui/core"; + +describe("Event Compaction", () => { + describe("Text Message Compaction", () => { + it("should compact multiple text message content events into one", () => { + const events = [ + { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "user" }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Hello" }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: " " }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "world" }, + { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, + ]; + + const compacted = compactEvents(events); + + expect(compacted).toHaveLength(3); + expect(compacted[0].type).toBe(EventType.TEXT_MESSAGE_START); + expect(compacted[1].type).toBe(EventType.TEXT_MESSAGE_CONTENT); + expect((compacted[1] as TextMessageContentEvent).delta).toBe("Hello world"); + expect(compacted[2].type).toBe(EventType.TEXT_MESSAGE_END); + }); + + it("should move interleaved events to after text message events", () => { + const events = [ + { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "assistant" }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Processing" }, + { type: EventType.CUSTOM, id: "custom1", name: "thinking" }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "..." }, + { type: EventType.CUSTOM, id: "custom2", name: "done-thinking" }, + { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, + ]; + + const compacted = compactEvents(events); + + expect(compacted).toHaveLength(5); + // Text message events should come first + expect(compacted[0].type).toBe(EventType.TEXT_MESSAGE_START); + expect(compacted[1].type).toBe(EventType.TEXT_MESSAGE_CONTENT); + expect((compacted[1] as TextMessageContentEvent).delta).toBe("Processing..."); + expect(compacted[2].type).toBe(EventType.TEXT_MESSAGE_END); + // Other events should come after + expect(compacted[3].type).toBe(EventType.CUSTOM); + expect((compacted[3] as CustomEvent & { id: string }).id).toBe("custom1"); + expect(compacted[4].type).toBe(EventType.CUSTOM); + expect((compacted[4] as CustomEvent & { id: string }).id).toBe("custom2"); + }); + + it("should handle multiple messages independently", () => { + const events = [ + { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "user" }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Hi" }, + { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, + { type: EventType.TEXT_MESSAGE_START, messageId: "msg2", role: "assistant" }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg2", delta: "Hello" }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg2", delta: " there" }, + { type: EventType.TEXT_MESSAGE_END, messageId: "msg2" }, + ]; + + const compacted = compactEvents(events); + + expect(compacted).toHaveLength(6); + // First message + expect(compacted[0].type).toBe(EventType.TEXT_MESSAGE_START); + expect((compacted[0] as TextMessageStartEvent).messageId).toBe("msg1"); + expect(compacted[1].type).toBe(EventType.TEXT_MESSAGE_CONTENT); + expect((compacted[1] as TextMessageContentEvent).delta).toBe("Hi"); + expect(compacted[2].type).toBe(EventType.TEXT_MESSAGE_END); + // Second message + expect(compacted[3].type).toBe(EventType.TEXT_MESSAGE_START); + expect((compacted[3] as TextMessageStartEvent).messageId).toBe("msg2"); + expect(compacted[4].type).toBe(EventType.TEXT_MESSAGE_CONTENT); + expect((compacted[4] as TextMessageContentEvent).delta).toBe("Hello there"); + expect(compacted[5].type).toBe(EventType.TEXT_MESSAGE_END); + }); + + it("should handle incomplete messages", () => { + const events = [ + { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "user" }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Incomplete" }, + // No END event + ]; + + const compacted = compactEvents(events); + + expect(compacted).toHaveLength(2); + expect(compacted[0].type).toBe(EventType.TEXT_MESSAGE_START); + expect(compacted[1].type).toBe(EventType.TEXT_MESSAGE_CONTENT); + expect((compacted[1] as TextMessageContentEvent).delta).toBe("Incomplete"); + }); + + it("should pass through non-text-message events unchanged", () => { + const events = [ + { type: EventType.CUSTOM, id: "custom1", name: "event1" }, + { type: EventType.TOOL_CALL_START, toolCallId: "tool1", toolCallName: "search" }, + { type: EventType.TOOL_CALL_END, toolCallId: "tool1" }, + ]; + + const compacted = compactEvents(events); + + expect(compacted).toEqual(events); + }); + + it("should handle empty content deltas", () => { + const events = [ + { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "user" }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "" }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Hello" }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "" }, + { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, + ]; + + const compacted = compactEvents(events); + + expect(compacted).toHaveLength(3); + expect((compacted[1] as TextMessageContentEvent).delta).toBe("Hello"); + }); + }); + + describe("Tool Call Compaction", () => { + it("should compact multiple tool call args events into one", () => { + const events = [ + { + type: EventType.TOOL_CALL_START, + toolCallId: "tool1", + toolCallName: "search", + parentMessageId: "msg1", + }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: '{"query": "' }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: "weather" }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: ' today"' }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: "}" }, + { type: EventType.TOOL_CALL_END, toolCallId: "tool1" }, + ]; + + const compacted = compactEvents(events); + + expect(compacted).toHaveLength(3); + expect(compacted[0].type).toBe(EventType.TOOL_CALL_START); + expect(compacted[1].type).toBe(EventType.TOOL_CALL_ARGS); + expect((compacted[1] as ToolCallArgsEvent).delta).toBe('{"query": "weather today"}'); + expect(compacted[2].type).toBe(EventType.TOOL_CALL_END); + }); + + it("should move interleaved events to after tool call events", () => { + const events = [ + { + type: EventType.TOOL_CALL_START, + toolCallId: "tool1", + toolCallName: "calculate", + parentMessageId: "msg1", + }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: '{"a": ' }, + { type: EventType.CUSTOM, id: "custom1", name: "processing" }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: '10, "b": 20}' }, + { type: EventType.CUSTOM, id: "custom2", name: "calculating" }, + { type: EventType.TOOL_CALL_END, toolCallId: "tool1" }, + ]; + + const compacted = compactEvents(events); + + expect(compacted).toHaveLength(5); + // Tool call events should come first + expect(compacted[0].type).toBe(EventType.TOOL_CALL_START); + expect(compacted[1].type).toBe(EventType.TOOL_CALL_ARGS); + expect((compacted[1] as ToolCallArgsEvent).delta).toBe('{"a": 10, "b": 20}'); + expect(compacted[2].type).toBe(EventType.TOOL_CALL_END); + // Other events should come after + expect(compacted[3].type).toBe(EventType.CUSTOM); + expect((compacted[3] as CustomEvent & { id: string }).id).toBe("custom1"); + expect(compacted[4].type).toBe(EventType.CUSTOM); + expect((compacted[4] as CustomEvent & { id: string }).id).toBe("custom2"); + }); + + it("should handle multiple tool calls independently", () => { + const events = [ + { + type: EventType.TOOL_CALL_START, + toolCallId: "tool1", + toolCallName: "search", + parentMessageId: "msg1", + }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: '{"query": "test"}' }, + { type: EventType.TOOL_CALL_END, toolCallId: "tool1" }, + { + type: EventType.TOOL_CALL_START, + toolCallId: "tool2", + toolCallName: "calculate", + parentMessageId: "msg1", + }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool2", delta: '{"a": ' }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool2", delta: "5}" }, + { type: EventType.TOOL_CALL_END, toolCallId: "tool2" }, + ]; + + const compacted = compactEvents(events); + + expect(compacted).toHaveLength(6); + // First tool call + expect(compacted[0].type).toBe(EventType.TOOL_CALL_START); + expect((compacted[0] as ToolCallStartEvent).toolCallId).toBe("tool1"); + expect(compacted[1].type).toBe(EventType.TOOL_CALL_ARGS); + expect((compacted[1] as ToolCallArgsEvent).delta).toBe('{"query": "test"}'); + expect(compacted[2].type).toBe(EventType.TOOL_CALL_END); + // Second tool call + expect(compacted[3].type).toBe(EventType.TOOL_CALL_START); + expect((compacted[3] as ToolCallStartEvent).toolCallId).toBe("tool2"); + expect(compacted[4].type).toBe(EventType.TOOL_CALL_ARGS); + expect((compacted[4] as ToolCallArgsEvent).delta).toBe('{"a": 5}'); + expect(compacted[5].type).toBe(EventType.TOOL_CALL_END); + }); + + it("should handle incomplete tool calls", () => { + const events = [ + { + type: EventType.TOOL_CALL_START, + toolCallId: "tool1", + toolCallName: "search", + parentMessageId: "msg1", + }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: '{"incomplete": ' }, + // No END event + ]; + + const compacted = compactEvents(events); + + expect(compacted).toHaveLength(2); + expect(compacted[0].type).toBe(EventType.TOOL_CALL_START); + expect(compacted[1].type).toBe(EventType.TOOL_CALL_ARGS); + expect((compacted[1] as ToolCallArgsEvent).delta).toBe('{"incomplete": '); + }); + + it("should handle empty args deltas", () => { + const events = [ + { + type: EventType.TOOL_CALL_START, + toolCallId: "tool1", + toolCallName: "search", + parentMessageId: "msg1", + }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: "" }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: '{"test": true}' }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: "" }, + { type: EventType.TOOL_CALL_END, toolCallId: "tool1" }, + ]; + + const compacted = compactEvents(events); + + expect(compacted).toHaveLength(3); + expect((compacted[1] as ToolCallArgsEvent).delta).toBe('{"test": true}'); + }); + }); + + describe("Mixed Compaction", () => { + it("should handle text messages and tool calls together", () => { + const events = [ + { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "assistant" }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Let me " }, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "search for that" }, + { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, + { + type: EventType.TOOL_CALL_START, + toolCallId: "tool1", + toolCallName: "search", + parentMessageId: "msg1", + }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: '{"q": "' }, + { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool1", delta: 'test"}' }, + { type: EventType.TOOL_CALL_END, toolCallId: "tool1" }, + ]; + + const compacted = compactEvents(events); + + expect(compacted).toHaveLength(6); + // Text message + expect(compacted[0].type).toBe(EventType.TEXT_MESSAGE_START); + expect(compacted[1].type).toBe(EventType.TEXT_MESSAGE_CONTENT); + expect((compacted[1] as TextMessageContentEvent).delta).toBe("Let me search for that"); + expect(compacted[2].type).toBe(EventType.TEXT_MESSAGE_END); + // Tool call + expect(compacted[3].type).toBe(EventType.TOOL_CALL_START); + expect(compacted[4].type).toBe(EventType.TOOL_CALL_ARGS); + expect((compacted[4] as ToolCallArgsEvent).delta).toBe('{"q": "test"}'); + expect(compacted[5].type).toBe(EventType.TOOL_CALL_END); + }); + }); +}); diff --git a/sdks/typescript/packages/client/src/compact/compact.ts b/sdks/typescript/packages/client/src/compact/compact.ts new file mode 100644 index 000000000..b34c08e6a --- /dev/null +++ b/sdks/typescript/packages/client/src/compact/compact.ts @@ -0,0 +1,252 @@ +import { + BaseEvent, + EventType, + TextMessageStartEvent, + TextMessageContentEvent, + TextMessageEndEvent, + ToolCallStartEvent, + ToolCallArgsEvent, + ToolCallEndEvent, +} from "@ag-ui/core"; + +/** + * Compacts streaming events by consolidating multiple deltas into single events. + * For text messages: multiple content deltas become one concatenated delta. + * For tool calls: multiple args deltas become one concatenated delta. + * Events between related streaming events are reordered to keep streaming events together. + * + * @param events - Array of events to compact + * @returns Compacted array of events + */ +export function compactEvents(events: BaseEvent[]): BaseEvent[] { + const compacted: BaseEvent[] = []; + const pendingTextMessages = new Map< + string, + { + start?: TextMessageStartEvent; + contents: TextMessageContentEvent[]; + end?: TextMessageEndEvent; + otherEvents: BaseEvent[]; + } + >(); + const pendingToolCalls = new Map< + string, + { + start?: ToolCallStartEvent; + args: ToolCallArgsEvent[]; + end?: ToolCallEndEvent; + otherEvents: BaseEvent[]; + } + >(); + + for (const event of events) { + // Handle text message streaming events + if (event.type === EventType.TEXT_MESSAGE_START) { + const startEvent = event as TextMessageStartEvent; + const messageId = startEvent.messageId; + + if (!pendingTextMessages.has(messageId)) { + pendingTextMessages.set(messageId, { + contents: [], + otherEvents: [], + }); + } + + const pending = pendingTextMessages.get(messageId)!; + pending.start = startEvent; + } else if (event.type === EventType.TEXT_MESSAGE_CONTENT) { + const contentEvent = event as TextMessageContentEvent; + const messageId = contentEvent.messageId; + + if (!pendingTextMessages.has(messageId)) { + pendingTextMessages.set(messageId, { + contents: [], + otherEvents: [], + }); + } + + const pending = pendingTextMessages.get(messageId)!; + pending.contents.push(contentEvent); + } else if (event.type === EventType.TEXT_MESSAGE_END) { + const endEvent = event as TextMessageEndEvent; + const messageId = endEvent.messageId; + + if (!pendingTextMessages.has(messageId)) { + pendingTextMessages.set(messageId, { + contents: [], + otherEvents: [], + }); + } + + const pending = pendingTextMessages.get(messageId)!; + pending.end = endEvent; + + // Flush this message's events + flushTextMessage(messageId, pending, compacted); + pendingTextMessages.delete(messageId); + } else if (event.type === EventType.TOOL_CALL_START) { + const startEvent = event as ToolCallStartEvent; + const toolCallId = startEvent.toolCallId; + + if (!pendingToolCalls.has(toolCallId)) { + pendingToolCalls.set(toolCallId, { + args: [], + otherEvents: [], + }); + } + + const pending = pendingToolCalls.get(toolCallId)!; + pending.start = startEvent; + } else if (event.type === EventType.TOOL_CALL_ARGS) { + const argsEvent = event as ToolCallArgsEvent; + const toolCallId = argsEvent.toolCallId; + + if (!pendingToolCalls.has(toolCallId)) { + pendingToolCalls.set(toolCallId, { + args: [], + otherEvents: [], + }); + } + + const pending = pendingToolCalls.get(toolCallId)!; + pending.args.push(argsEvent); + } else if (event.type === EventType.TOOL_CALL_END) { + const endEvent = event as ToolCallEndEvent; + const toolCallId = endEvent.toolCallId; + + if (!pendingToolCalls.has(toolCallId)) { + pendingToolCalls.set(toolCallId, { + args: [], + otherEvents: [], + }); + } + + const pending = pendingToolCalls.get(toolCallId)!; + pending.end = endEvent; + + // Flush this tool call's events + flushToolCall(toolCallId, pending, compacted); + pendingToolCalls.delete(toolCallId); + } else { + // For non-streaming events, check if we're in the middle of any streaming sequences + let addedToBuffer = false; + + // Check text messages + for (const [messageId, pending] of pendingTextMessages) { + // If we have a start but no end yet, this event is "in between" + if (pending.start && !pending.end) { + pending.otherEvents.push(event); + addedToBuffer = true; + break; + } + } + + // Check tool calls if not already buffered + if (!addedToBuffer) { + for (const [toolCallId, pending] of pendingToolCalls) { + // If we have a start but no end yet, this event is "in between" + if (pending.start && !pending.end) { + pending.otherEvents.push(event); + addedToBuffer = true; + break; + } + } + } + + // If not in the middle of any streaming sequence, add directly to compacted + if (!addedToBuffer) { + compacted.push(event); + } + } + } + + // Flush any remaining incomplete messages + for (const [messageId, pending] of pendingTextMessages) { + flushTextMessage(messageId, pending, compacted); + } + + // Flush any remaining incomplete tool calls + for (const [toolCallId, pending] of pendingToolCalls) { + flushToolCall(toolCallId, pending, compacted); + } + + return compacted; +} + +function flushTextMessage( + messageId: string, + pending: { + start?: TextMessageStartEvent; + contents: TextMessageContentEvent[]; + end?: TextMessageEndEvent; + otherEvents: BaseEvent[]; + }, + compacted: BaseEvent[], +): void { + // Add start event if present + if (pending.start) { + compacted.push(pending.start); + } + + // Compact all content events into one + if (pending.contents.length > 0) { + const concatenatedDelta = pending.contents.map((c) => c.delta).join(""); + + const compactedContent: TextMessageContentEvent = { + type: EventType.TEXT_MESSAGE_CONTENT, + messageId: messageId, + delta: concatenatedDelta, + }; + + compacted.push(compactedContent); + } + + // Add end event if present + if (pending.end) { + compacted.push(pending.end); + } + + // Add any events that were in between + for (const otherEvent of pending.otherEvents) { + compacted.push(otherEvent); + } +} + +function flushToolCall( + toolCallId: string, + pending: { + start?: ToolCallStartEvent; + args: ToolCallArgsEvent[]; + end?: ToolCallEndEvent; + otherEvents: BaseEvent[]; + }, + compacted: BaseEvent[], +): void { + // Add start event if present + if (pending.start) { + compacted.push(pending.start); + } + + // Compact all args events into one + if (pending.args.length > 0) { + const concatenatedArgs = pending.args.map((a) => a.delta).join(""); + + const compactedArgs: ToolCallArgsEvent = { + type: EventType.TOOL_CALL_ARGS, + toolCallId: toolCallId, + delta: concatenatedArgs, + }; + + compacted.push(compactedArgs); + } + + // Add end event if present + if (pending.end) { + compacted.push(pending.end); + } + + // Add any events that were in between + for (const otherEvent of pending.otherEvents) { + compacted.push(otherEvent); + } +} diff --git a/sdks/typescript/packages/client/src/compact/index.ts b/sdks/typescript/packages/client/src/compact/index.ts new file mode 100644 index 000000000..bb036168b --- /dev/null +++ b/sdks/typescript/packages/client/src/compact/index.ts @@ -0,0 +1 @@ +export { compactEvents } from "./compact"; diff --git a/sdks/typescript/packages/client/src/index.ts b/sdks/typescript/packages/client/src/index.ts index aa4a0a5ef..a6300de99 100644 --- a/sdks/typescript/packages/client/src/index.ts +++ b/sdks/typescript/packages/client/src/index.ts @@ -5,5 +5,6 @@ export * from "./run"; export * from "./legacy"; export * from "./agent"; export * from "./utils"; +export * from "./compact"; export * from "@ag-ui/core"; export * from "./chunks"; diff --git a/sdks/typescript/packages/client/src/legacy/convert.ts b/sdks/typescript/packages/client/src/legacy/convert.ts index aa3ac5e6f..2f4cd6761 100644 --- a/sdks/typescript/packages/client/src/legacy/convert.ts +++ b/sdks/typescript/packages/client/src/legacy/convert.ts @@ -41,6 +41,27 @@ import { } from "./types"; import untruncateJson from "untruncate-json"; +const flattenMessageContentToText = (content: Message["content"]) => { + if (typeof content === "string") { + return content; + } + + if (!Array.isArray(content)) { + return undefined; + } + + const textParts = content + .filter((part): part is { type: "text"; text: string } => part.type === "text") + .map((part) => part.text) + .filter((text) => text.length > 0); + + if (textParts.length === 0) { + return undefined; + } + + return textParts.join("\n"); +}; + interface PredictStateValue { state_key: string; tool: string; @@ -392,11 +413,12 @@ export function convertMessagesToLegacyFormat(messages: Message[]): LegacyMessag for (const message of messages) { if (message.role === "assistant" || message.role === "user" || message.role === "system") { - if (message.content) { + const textContent = flattenMessageContentToText(message.content); + if (textContent) { const textMessage: LegacyTextMessage = { id: message.id, role: message.role, - content: message.content, + content: textContent, }; result.push(textMessage); } diff --git a/sdks/typescript/packages/core/package.json b/sdks/typescript/packages/core/package.json index d3626f998..5e5aba046 100644 --- a/sdks/typescript/packages/core/package.json +++ b/sdks/typescript/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@ag-ui/core", "author": "Markus Ecker ", - "version": "0.0.39", + "version": "0.0.40-alpha.11", "private": false, "publishConfig": { "access": "public" diff --git a/sdks/typescript/packages/core/src/__tests__/activity-events.test.ts b/sdks/typescript/packages/core/src/__tests__/activity-events.test.ts new file mode 100644 index 000000000..78b31d3a3 --- /dev/null +++ b/sdks/typescript/packages/core/src/__tests__/activity-events.test.ts @@ -0,0 +1,54 @@ +import { ActivitySnapshotEventSchema, ActivityDeltaEventSchema, EventType } from "../events"; +import { ActivityMessageSchema } from "../types"; + +describe("Activity events", () => { + it("parses ActivitySnapshotEvent", () => { + const result = ActivitySnapshotEventSchema.parse({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "msg_activity", + activityType: "PLAN", + content: { tasks: ["search"] }, + }); + + expect(result.type).toBe(EventType.ACTIVITY_SNAPSHOT); + expect(result.messageId).toBe("msg_activity"); + expect(result.content.tasks).toEqual(["search"]); + expect(result.replace).toBe(true); + }); + + it("respects replace flag in ActivitySnapshotEvent", () => { + const result = ActivitySnapshotEventSchema.parse({ + type: EventType.ACTIVITY_SNAPSHOT, + messageId: "msg_activity", + activityType: "PLAN", + content: { tasks: [] }, + replace: false, + }); + + expect(result.replace).toBe(false); + }); + + it("parses ActivityDeltaEvent", () => { + const result = ActivityDeltaEventSchema.parse({ + type: EventType.ACTIVITY_DELTA, + messageId: "msg_activity", + activityType: "PLAN", + patch: [{ op: "replace", path: "/tasks/0", value: "✓ search" }], + }); + + expect(result.type).toBe(EventType.ACTIVITY_DELTA); + expect(result.patch).toHaveLength(1); + }); + + it("parses ActivityMessage", () => { + const result = ActivityMessageSchema.parse({ + id: "activity_1", + role: "activity" as const, + activityType: "PLAN", + content: { tasks: [] }, + }); + + expect(result.activityType).toBe("PLAN"); + expect(result.content).toEqual({ tasks: [] }); + }); +}); diff --git a/sdks/typescript/packages/core/src/__tests__/backwards-compatibility.test.ts b/sdks/typescript/packages/core/src/__tests__/backwards-compatibility.test.ts new file mode 100644 index 000000000..34c47961f --- /dev/null +++ b/sdks/typescript/packages/core/src/__tests__/backwards-compatibility.test.ts @@ -0,0 +1,252 @@ +import { + UserMessageSchema, + AssistantMessageSchema, + RunAgentInputSchema, + TextMessageStartEventSchema, + RunStartedEventSchema, + ToolSchema, + ContextSchema, + EventType, +} from "../index"; + +describe("Backwards Compatibility", () => { + describe("Message Schemas", () => { + it("should accept UserMessage with extra fields from future versions", () => { + const messageWithExtraFields = { + id: "msg_1", + role: "user" as const, + content: "Hello", + futureField: "This is from a future version", + anotherNewProp: { nested: "data" }, + }; + + const result = UserMessageSchema.safeParse(messageWithExtraFields); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.id).toBe("msg_1"); + expect(result.data.role).toBe("user"); + expect(result.data.content).toBe("Hello"); + // Extra fields should be stripped (Zod default behavior) + expect('futureField' in result.data).toBe(false); + expect('anotherNewProp' in result.data).toBe(false); + } + }); + + it("should accept AssistantMessage with extra fields", () => { + const messageWithExtraFields = { + id: "msg_2", + role: "assistant" as const, + content: "Response", + newFeatureFlag: true, + experimentalData: [1, 2, 3], + }; + + const result = AssistantMessageSchema.safeParse(messageWithExtraFields); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.id).toBe("msg_2"); + expect(result.data.content).toBe("Response"); + } + }); + }); + + describe("RunAgentInput Schema", () => { + it("should accept RunAgentInput with extra fields at top level", () => { + const inputWithExtraFields = { + threadId: "thread_1", + runId: "run_1", + parentRunId: "parent_run_1", + state: {}, + messages: [], + tools: [], + context: [], + forwardedProps: {}, + // Extra fields from future version + newFeatureFlag: true, + experimentalTimeout: 5000, + futureConfig: { option: "value" }, + }; + + const result = RunAgentInputSchema.safeParse(inputWithExtraFields); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.threadId).toBe("thread_1"); + expect(result.data.runId).toBe("run_1"); + expect(result.data.parentRunId).toBe("parent_run_1"); + } + }); + + it("should accept RunAgentInput with messages containing extra fields", () => { + const inputWithExtraFieldsInMessages = { + threadId: "thread_2", + runId: "run_2", + state: {}, + messages: [ + { + id: "m1", + role: "user" as const, + content: "Hi", + extraProp: "value", + metadata: { source: "web" }, + }, + ], + tools: [], + context: [], + forwardedProps: {}, + }; + + const result = RunAgentInputSchema.safeParse(inputWithExtraFieldsInMessages); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.messages.length).toBe(1); + expect(result.data.messages[0].content).toBe("Hi"); + } + }); + }); + + describe("Event Schemas", () => { + it("should accept TextMessageStartEvent with extra fields", () => { + const eventWithExtraFields = { + type: EventType.TEXT_MESSAGE_START, + messageId: "msg_1", + role: "assistant" as const, + // Extra fields from future version + metadata: { tokenCount: 10 }, + experimentalFeature: true, + }; + + const result = TextMessageStartEventSchema.safeParse(eventWithExtraFields); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.type).toBe(EventType.TEXT_MESSAGE_START); + expect(result.data.messageId).toBe("msg_1"); + } + }); + + it("should accept RunStartedEvent with extra fields", () => { + const eventWithExtraFields = { + type: EventType.RUN_STARTED, + threadId: "thread_1", + runId: "run_1", + // Extra fields from future version + startTime: Date.now(), + priority: "high", + }; + + const result = RunStartedEventSchema.safeParse(eventWithExtraFields); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.threadId).toBe("thread_1"); + expect(result.data.runId).toBe("run_1"); + } + }); + }); + + describe("Tool and Context Schemas", () => { + it("should accept Tool with extra fields", () => { + const toolWithExtraFields = { + name: "calculator", + description: "Performs calculations", + parameters: { type: "object" }, + // Extra fields from future version + version: "2.0", + deprecationWarning: null, + }; + + const result = ToolSchema.safeParse(toolWithExtraFields); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.name).toBe("calculator"); + expect(result.data.description).toBe("Performs calculations"); + } + }); + + it("should accept Context with extra fields", () => { + const contextWithExtraFields = { + description: "User preferences", + value: '{"theme":"dark"}', + // Extra fields from future version + priority: 1, + expiresAt: Date.now() + 3600000, + }; + + const result = ContextSchema.safeParse(contextWithExtraFields); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.description).toBe("User preferences"); + expect(result.data.value).toBe('{"theme":"dark"}'); + } + }); + }); + + describe("Complex nested structures", () => { + it("should handle deeply nested objects with extra fields at multiple levels", () => { + const complexInput = { + threadId: "thread_complex", + runId: "run_complex", + state: { currentStep: 1 }, + messages: [ + { + id: "m1", + role: "user" as const, + content: "Hello", + extraUserProp: "value1", + }, + { + id: "m2", + role: "assistant" as const, + content: "Hi there", + toolCalls: [ + { + id: "tc1", + type: "function" as const, + function: { + name: "search", + arguments: "{}", + extraFunctionProp: "value2", + }, + extraToolCallProp: "value3", + }, + ], + extraAssistantProp: "value4", + }, + ], + tools: [ + { + name: "search", + description: "Search tool", + parameters: {}, + extraToolProp: "value5", + }, + ], + context: [ + { + description: "ctx", + value: "val", + extraContextProp: "value6", + }, + ], + forwardedProps: { custom: true }, + extraTopLevelProp: "value7", + }; + + const result = RunAgentInputSchema.safeParse(complexInput); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.messages.length).toBe(2); + expect(result.data.messages[1].toolCalls?.length).toBe(1); + expect(result.data.tools.length).toBe(1); + expect(result.data.context.length).toBe(1); + } + }); + }); +}); diff --git a/sdks/typescript/packages/core/src/__tests__/multimodal-messages.test.ts b/sdks/typescript/packages/core/src/__tests__/multimodal-messages.test.ts new file mode 100644 index 000000000..3bcca30e8 --- /dev/null +++ b/sdks/typescript/packages/core/src/__tests__/multimodal-messages.test.ts @@ -0,0 +1,52 @@ +import { + UserMessageSchema, + BinaryInputContentSchema, +} from "../types"; + +describe("Multimodal messages", () => { + it("parses user message with content array", () => { + const result = UserMessageSchema.parse({ + id: "user_multimodal", + role: "user" as const, + content: [ + { type: "text" as const, text: "Check this out" }, + { type: "binary" as const, mimeType: "image/png", url: "https://example.com/image.png" }, + ], + }); + + expect(Array.isArray(result.content)).toBe(true); + if (Array.isArray(result.content)) { + expect(result.content[0].type).toBe("text"); + expect(result.content[0].text).toBe("Check this out"); + expect(result.content[1].type).toBe("binary"); + expect(result.content[1].mimeType).toBe("image/png"); + expect(result.content[1].url).toBe("https://example.com/image.png"); + } + }); + + it("rejects binary content without payload source", () => { + const result = UserMessageSchema.safeParse({ + id: "user_invalid", + role: "user" as const, + content: [{ type: "binary" as const, mimeType: "image/png" }], + }); + + expect(result.success).toBe(false); + }); + + it("parses binary input with embedded data", () => { + const binary = BinaryInputContentSchema.parse({ + type: "binary" as const, + mimeType: "image/png", + data: "base64", + }); + + expect(binary.data).toBe("base64"); + }); + + it("requires binary payload source", () => { + expect(() => + BinaryInputContentSchema.parse({ type: "binary" as const, mimeType: "image/png" }), + ).toThrow(/id, url, or data/); + }); +}); diff --git a/sdks/typescript/packages/core/src/events.ts b/sdks/typescript/packages/core/src/events.ts index a95fc8e15..36076896c 100644 --- a/sdks/typescript/packages/core/src/events.ts +++ b/sdks/typescript/packages/core/src/events.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { MessageSchema, StateSchema } from "./types"; +import { MessageSchema, StateSchema, RunAgentInputSchema } from "./types"; // Text messages can have any role except "tool" const TextMessageRoleSchema = z.union([ @@ -27,6 +27,8 @@ export enum EventType { STATE_SNAPSHOT = "STATE_SNAPSHOT", STATE_DELTA = "STATE_DELTA", MESSAGES_SNAPSHOT = "MESSAGES_SNAPSHOT", + ACTIVITY_SNAPSHOT = "ACTIVITY_SNAPSHOT", + ACTIVITY_DELTA = "ACTIVITY_DELTA", RAW = "RAW", CUSTOM = "CUSTOM", RUN_STARTED = "RUN_STARTED", @@ -139,6 +141,21 @@ export const MessagesSnapshotEventSchema = BaseEventSchema.extend({ messages: z.array(MessageSchema), }); +export const ActivitySnapshotEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.ACTIVITY_SNAPSHOT), + messageId: z.string(), + activityType: z.string(), + content: z.record(z.any()), + replace: z.boolean().optional().default(true), +}); + +export const ActivityDeltaEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.ACTIVITY_DELTA), + messageId: z.string(), + activityType: z.string(), + patch: z.array(z.any()), +}); + export const RawEventSchema = BaseEventSchema.extend({ type: z.literal(EventType.RAW), event: z.any(), @@ -155,6 +172,8 @@ export const RunStartedEventSchema = BaseEventSchema.extend({ type: z.literal(EventType.RUN_STARTED), threadId: z.string(), runId: z.string(), + parentRunId: z.string().optional(), + input: RunAgentInputSchema.optional(), }); export const RunFinishedEventSchema = BaseEventSchema.extend({ @@ -198,6 +217,8 @@ export const EventSchemas = z.discriminatedUnion("type", [ StateSnapshotEventSchema, StateDeltaEventSchema, MessagesSnapshotEventSchema, + ActivitySnapshotEventSchema, + ActivityDeltaEventSchema, RawEventSchema, CustomEventSchema, RunStartedEventSchema, @@ -225,6 +246,8 @@ export type ThinkingEndEvent = z.infer; export type StateSnapshotEvent = z.infer; export type StateDeltaEvent = z.infer; export type MessagesSnapshotEvent = z.infer; +export type ActivitySnapshotEvent = z.infer; +export type ActivityDeltaEvent = z.infer; export type RawEvent = z.infer; export type CustomEvent = z.infer; export type RunStartedEvent = z.infer; diff --git a/sdks/typescript/packages/core/src/types.ts b/sdks/typescript/packages/core/src/types.ts index 1abb31a0b..6a8560293 100644 --- a/sdks/typescript/packages/core/src/types.ts +++ b/sdks/typescript/packages/core/src/types.ts @@ -18,6 +18,48 @@ export const BaseMessageSchema = z.object({ name: z.string().optional(), }); +export const TextInputContentSchema = z.object({ + type: z.literal("text"), + text: z.string(), +}); + +const BinaryInputContentObjectSchema = z.object({ + type: z.literal("binary"), + mimeType: z.string(), + id: z.string().optional(), + url: z.string().optional(), + data: z.string().optional(), + filename: z.string().optional(), +}); + +const ensureBinaryPayload = ( + value: { id?: string; url?: string; data?: string }, + ctx: z.RefinementCtx, +) => { + if (!value.id && !value.url && !value.data) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "BinaryInputContent requires at least one of id, url, or data.", + path: ["id"], + }); + } +}; + +export const BinaryInputContentSchema = BinaryInputContentObjectSchema.superRefine((value, ctx) => { + ensureBinaryPayload(value, ctx); +}); + +const InputContentBaseSchema = z.discriminatedUnion("type", [ + TextInputContentSchema, + BinaryInputContentObjectSchema, +]); + +export const InputContentSchema = InputContentBaseSchema.superRefine((value, ctx) => { + if (value.type === "binary") { + ensureBinaryPayload(value, ctx); + } +}); + export const DeveloperMessageSchema = BaseMessageSchema.extend({ role: z.literal("developer"), content: z.string(), @@ -36,7 +78,7 @@ export const AssistantMessageSchema = BaseMessageSchema.extend({ export const UserMessageSchema = BaseMessageSchema.extend({ role: z.literal("user"), - content: z.string(), + content: z.union([z.string(), z.array(InputContentSchema)]), }); export const ToolMessageSchema = z.object({ @@ -47,12 +89,20 @@ export const ToolMessageSchema = z.object({ error: z.string().optional(), }); +export const ActivityMessageSchema = z.object({ + id: z.string(), + role: z.literal("activity"), + activityType: z.string(), + content: z.record(z.any()), +}); + export const MessageSchema = z.discriminatedUnion("role", [ DeveloperMessageSchema, SystemMessageSchema, AssistantMessageSchema, UserMessageSchema, ToolMessageSchema, + ActivityMessageSchema, ]); export const RoleSchema = z.union([ @@ -61,6 +111,7 @@ export const RoleSchema = z.union([ z.literal("assistant"), z.literal("user"), z.literal("tool"), + z.literal("activity"), ]); export const ContextSchema = z.object({ @@ -77,6 +128,7 @@ export const ToolSchema = z.object({ export const RunAgentInputSchema = z.object({ threadId: z.string(), runId: z.string(), + parentRunId: z.string().optional(), state: z.any(), messages: z.array(MessageSchema), tools: z.array(ToolSchema), @@ -88,11 +140,15 @@ export const StateSchema = z.any(); export type ToolCall = z.infer; export type FunctionCall = z.infer; +export type TextInputContent = z.infer; +export type BinaryInputContent = z.infer; +export type InputContent = z.infer; export type DeveloperMessage = z.infer; export type SystemMessage = z.infer; export type AssistantMessage = z.infer; export type UserMessage = z.infer; export type ToolMessage = z.infer; +export type ActivityMessage = z.infer; export type Message = z.infer; export type Context = z.infer; export type Tool = z.infer; @@ -105,3 +161,9 @@ export class AGUIError extends Error { super(message); } } + +export class AGUIConnectNotImplementedError extends AGUIError { + constructor() { + super("Connect not implemented. This method is not supported by the current agent."); + } +} diff --git a/sdks/typescript/packages/encoder/package.json b/sdks/typescript/packages/encoder/package.json index b95c5ee70..2167df068 100644 --- a/sdks/typescript/packages/encoder/package.json +++ b/sdks/typescript/packages/encoder/package.json @@ -1,7 +1,7 @@ { "name": "@ag-ui/encoder", "author": "Markus Ecker ", - "version": "0.0.39", + "version": "0.0.40-alpha.11", "private": false, "publishConfig": { "access": "public" diff --git a/sdks/typescript/packages/proto/package.json b/sdks/typescript/packages/proto/package.json index 7c1fed72b..f485dcfea 100644 --- a/sdks/typescript/packages/proto/package.json +++ b/sdks/typescript/packages/proto/package.json @@ -1,7 +1,7 @@ { "name": "@ag-ui/proto", "author": "Markus Ecker ", - "version": "0.0.39", + "version": "0.0.40-alpha.11", "private": false, "publishConfig": { "access": "public"