diff --git a/.gitignore b/.gitignore index 7167d0b1..d12cb157 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ storybook-static # Angular .angular/ .playwright-mcp/ + +.pnpm-store diff --git a/apps/angular/demo-server/package.json b/apps/angular/demo-server/package.json index 4aff6a1e..b47cc43f 100644 --- a/apps/angular/demo-server/package.json +++ b/apps/angular/demo-server/package.json @@ -8,7 +8,7 @@ "start": "node --env-file=.env --loader tsx src/index.ts" }, "dependencies": { - "@ag-ui/client": "0.0.40-alpha.3", + "@ag-ui/client": "0.0.40-alpha.6", "@ag-ui/langgraph": "^0.0.11", "@copilotkitnext/demo-agents": "workspace:^", "@copilotkitnext/runtime": "workspace:^", diff --git a/apps/angular/demo-server/src/index.ts b/apps/angular/demo-server/src/index.ts index 5c105349..9f93ae6a 100644 --- a/apps/angular/demo-server/src/index.ts +++ b/apps/angular/demo-server/src/index.ts @@ -1,13 +1,20 @@ import { serve } from "@hono/node-server"; import { Hono } from "hono"; import { cors } from "hono/cors"; -import { CopilotRuntime, createCopilotEndpoint, InMemoryAgentRunner } from "@copilotkitnext/runtime"; -import { OpenAIAgent, SlowToolCallStreamingAgent } from "@copilotkitnext/demo-agents"; +import { + CopilotRuntime, + createCopilotEndpoint, + InMemoryAgentRunner, +} from "@copilotkitnext/runtime"; +import { + OpenAIAgent, + SlowToolCallStreamingAgent, +} from "@copilotkitnext/demo-agents"; const runtime = new CopilotRuntime({ agents: { // @ts-ignore - default: new OpenAIAgent(), + default: new SlowToolCallStreamingAgent(), }, runner: new InMemoryAgentRunner(), }); @@ -25,7 +32,7 @@ app.use( exposeHeaders: ["Content-Type"], credentials: true, maxAge: 86400, - }), + }) ); // Create the CopilotKit endpoint @@ -39,4 +46,6 @@ app.route("/", copilotApp); const port = Number(process.env.PORT || 3001); serve({ fetch: app.fetch, port }); -console.log(`CopilotKit runtime listening at http://localhost:${port}/api/copilotkit`); +console.log( + `CopilotKit runtime listening at http://localhost:${port}/api/copilotkit` +); diff --git a/apps/angular/demo/package.json b/apps/angular/demo/package.json index ad81164b..a1a962af 100644 --- a/apps/angular/demo/package.json +++ b/apps/angular/demo/package.json @@ -22,7 +22,6 @@ "@copilotkitnext/angular": "workspace:*", "rxjs": "^7.8.1", "tslib": "^2.8.1", - "zod": "^3.25.75", "zone.js": "^0.14.0" }, "devDependencies": { diff --git a/apps/angular/demo/src/app/app.config.ts b/apps/angular/demo/src/app/app.config.ts index 081f8f88..d2c2747d 100644 --- a/apps/angular/demo/src/app/app.config.ts +++ b/apps/angular/demo/src/app/app.config.ts @@ -1,8 +1,10 @@ import { ApplicationConfig, importProvidersFrom } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; -import { provideCopilotKit, provideCopilotChatLabels } from "@copilotkitnext/angular"; +import { + provideCopilotKit, + provideCopilotChatLabels, +} from "@copilotkitnext/angular"; import { WildcardToolRenderComponent } from "./components/wildcard-tool-render.component"; -import { helloWorldToolConfig } from "./tools/hello-world"; export const appConfig: ApplicationConfig = { providers: [ @@ -15,12 +17,13 @@ export const appConfig: ApplicationConfig = { component: WildcardToolRenderComponent, } as any, ], - frontendTools: [helloWorldToolConfig], + frontendTools: [], humanInTheLoop: [], }), provideCopilotChatLabels({ chatInputPlaceholder: "Ask me anything...", - chatDisclaimerText: "CopilotKit Angular Demo - AI responses may need verification.", + chatDisclaimerText: + "CopilotKit Angular Demo - AI responses may need verification.", }), ], }; diff --git a/apps/angular/demo/src/app/tools/hello-world.ts b/apps/angular/demo/src/app/tools/hello-world.ts deleted file mode 100644 index 6a989527..00000000 --- a/apps/angular/demo/src/app/tools/hello-world.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Component, input } from "@angular/core"; -import { FrontendToolConfig, ToolRenderer } from "@copilotkitnext/angular"; -import { JsonPipe } from "@angular/common"; -import { AngularToolCall } from "@copilotkitnext/angular"; -import { z } from "zod"; - -@Component({ - selector: "app-hello-world-tool", - template: `
{{ toolCall() | json }}
`, - standalone: true, - imports: [JsonPipe], -}) -export class HelloWorldTool implements ToolRenderer { - toolCall = input.required(); -} - -export const helloWorldToolConfig: FrontendToolConfig = { - name: "hello_world", - description: "Says hello to the world", - args: z.object({ - name: z.string(), - }), - component: HelloWorldTool, - handler: async (args) => { - return `Hello ${args.name}!`; - }, -}; diff --git a/apps/angular/storybook/package.json b/apps/angular/storybook/package.json index d668c9f2..c53ccbac 100644 --- a/apps/angular/storybook/package.json +++ b/apps/angular/storybook/package.json @@ -9,7 +9,7 @@ "storybook:build": "ng run storybook-angular:build-storybook" }, "dependencies": { - "@ag-ui/client": "0.0.40-alpha.3", + "@ag-ui/client": "0.0.40-alpha.6", "@angular/animations": "^18.2.0", "@angular/common": "^18.2.0", "@angular/compiler": "^18.2.0", diff --git a/apps/react/demo/package.json b/apps/react/demo/package.json index 7fe4923d..60652968 100644 --- a/apps/react/demo/package.json +++ b/apps/react/demo/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "@ag-ui/client": "0.0.40-alpha.3", + "@ag-ui/client": "0.0.40-alpha.6", "@copilotkitnext/agent": "workspace:*", "@copilotkitnext/core": "workspace:*", "@copilotkitnext/react": "workspace:*", diff --git a/apps/react/demo/src/app/page.tsx b/apps/react/demo/src/app/page.tsx index 983daba5..a6107430 100644 --- a/apps/react/demo/src/app/page.tsx +++ b/apps/react/demo/src/app/page.tsx @@ -1,14 +1,13 @@ "use client"; -import { HttpAgent } from "@ag-ui/client"; import { CopilotChat, CopilotKitProvider, useFrontendTool, defineToolCallRenderer, useConfigureSuggestions, - type ToolsMenuItem, } from "@copilotkitnext/react"; +import type { ToolsMenuItem } from "@copilotkitnext/react"; import { z } from "zod"; import { useMemo } from "react"; @@ -76,7 +75,7 @@ function Chat() { return `Hello ${name}`; }, }); - const toolsMenu = useMemo<("-" | ToolsMenuItem)[]>( + const toolsMenu = useMemo<(ToolsMenuItem | "-")[]>( () => [ { label: "Say hi to CopilotKit", @@ -108,5 +107,5 @@ function Chat() { [], ); - return ; + return ; } diff --git a/package.json b/package.json index 180efc13..96d30e4e 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,11 @@ "name": "CopilotKitNext", "private": true, "scripts": { - "predev": "turbo run build --filter='@copilotkitnext/core' --filter='@copilotkitnext/shared' --filter='@copilotkitnext/web-inspector' --filter='@copilotkitnext/react' --filter='@copilotkitnext/angular' --filter='@copilotkitnext/agent'", + "predev": "turbo run build --filter='@copilotkitnext/core' --filter='@copilotkitnext/shared' --filter='@copilotkitnext/web-inspector' --filter='@copilotkitnext/react' --filter='@copilotkitnext/angular' --filter='@copilotkitnext/agent' --filter='@copilotkitnext/sqlite-runner'", "build": "turbo run build", "clean": "turbo run clean", - "dev": "turbo run dev --filter='@copilotkitnext/core' --filter='@copilotkitnext/shared' --filter='@copilotkitnext/web-inspector' --filter='@copilotkitnext/runtime' --filter='@copilotkitnext/react' --filter='@copilotkitnext/angular' --filter='@copilotkitnext/agent' --concurrency=15", - "dev:packages": "turbo run dev --filter='@copilotkitnext/core' --filter='@copilotkitnext/shared' --filter='@copilotkitnext/web-inspector' --filter='@copilotkitnext/runtime' --filter='@copilotkitnext/react' --filter='@copilotkitnext/angular' --filter='@copilotkitnext/agent'", + "dev": "turbo run dev --filter='@copilotkitnext/core' --filter='@copilotkitnext/shared' --filter='@copilotkitnext/web-inspector' --filter='@copilotkitnext/runtime' --filter='@copilotkitnext/sqlite-runner' --filter='@copilotkitnext/react' --filter='@copilotkitnext/angular' --filter='@copilotkitnext/agent' --concurrency=15", + "dev:packages": "turbo run dev --filter='@copilotkitnext/core' --filter='@copilotkitnext/shared' --filter='@copilotkitnext/web-inspector' --filter='@copilotkitnext/runtime' --filter='@copilotkitnext/sqlite-runner' --filter='@copilotkitnext/react' --filter='@copilotkitnext/angular' --filter='@copilotkitnext/agent'", "demo:angular": "turbo run dev --filter='@copilotkitnext/angular-demo-server' --filter='@copilotkitnext/angular-demo'", "storybook:angular": "pnpm -C apps/angular/storybook dev", "demo:react": "pnpm -C apps/react/demo dev", @@ -22,9 +22,9 @@ "storybook": "pnpm storybook:react", "storybook:all": "echo 'Run storybooks in separate terminals: pnpm storybook:react | pnpm storybook:angular'", "build-storybook": "turbo run build --filter=storybook --filter=storybook-angular", - "bump:prerelease": "pnpm -r --filter='@copilotkitnext/agent' --filter='@copilotkitnext/angular' --filter='@copilotkitnext/core' --filter='@copilotkitnext/react' --filter='@copilotkitnext/runtime' --filter='@copilotkitnext/shared' --filter='@copilotkitnext/web-inspector' exec pnpm version prerelease --preid=alpha", - "bump:release": "pnpm -r --filter='@copilotkitnext/agent' --filter='@copilotkitnext/angular' --filter='@copilotkitnext/core' --filter='@copilotkitnext/react' --filter='@copilotkitnext/runtime' --filter='@copilotkitnext/shared' --filter='@copilotkitnext/web-inspector' exec pnpm version patch", - "publish:prerelease": "pnpm -r clean && pnpm install && turbo build && pnpm publish -r --no-git-checks --filter='@copilotkitnext/agent' --filter='@copilotkitnext/angular' --filter='@copilotkitnext/core' --filter='@copilotkitnext/react' --filter='@copilotkitnext/runtime' --filter='@copilotkitnext/shared' --filter='@copilotkitnext/web-inspector' --tag alpha", + "bump:prerelease": "pnpm -r --filter='@copilotkitnext/agent' --filter='@copilotkitnext/angular' --filter='@copilotkitnext/core' --filter='@copilotkitnext/react' --filter='@copilotkitnext/runtime' --filter='@copilotkitnext/sqlite-runner' --filter='@copilotkitnext/shared' --filter='@copilotkitnext/web-inspector' exec pnpm version prerelease --preid=alpha", + "bump:release": "pnpm -r --filter='@copilotkitnext/agent' --filter='@copilotkitnext/angular' --filter='@copilotkitnext/core' --filter='@copilotkitnext/react' --filter='@copilotkitnext/runtime' --filter='@copilotkitnext/sqlite-runner' --filter='@copilotkitnext/shared' --filter='@copilotkitnext/web-inspector' exec pnpm version patch", + "publish:prerelease": "pnpm -r clean && pnpm install && turbo build && pnpm publish -r --no-git-checks --filter='@copilotkitnext/agent' --filter='@copilotkitnext/angular' --filter='@copilotkitnext/core' --filter='@copilotkitnext/react' --filter='@copilotkitnext/runtime' --filter='@copilotkitnext/sqlite-runner' --filter='@copilotkitnext/shared' --filter='@copilotkitnext/web-inspector' --tag alpha", "publish:release:dry": "pnpm -r --filter '@copilotkitnext/*' publish --access public --dry-run", "publish:release": "pnpm -r --filter '@copilotkitnext/*' publish --access public" }, diff --git a/packages/agent/package.json b/packages/agent/package.json index 5a0d82e9..740dfee6 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@copilotkitnext/agent", - "version": "0.0.15", + "version": "0.0.14", "description": "Basic Agent for CopilotKit", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -36,7 +36,7 @@ "vitest": "^3.0.5" }, "dependencies": { - "@ag-ui/client": "0.0.40-alpha.3", + "@ag-ui/client": "0.0.40-alpha.6", "@ai-sdk/anthropic": "^2.0.22", "@ai-sdk/google": "^2.0.17", "@ai-sdk/openai": "^2.0.42", diff --git a/packages/angular/package.json b/packages/angular/package.json index d7e7e573..7320f831 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@copilotkitnext/angular", - "version": "0.0.15", + "version": "0.0.14", "description": "Angular library for CopilotKit", "main": "dist/fesm2022/copilotkitnext-angular.mjs", "module": "dist/fesm2022/copilotkitnext-angular.mjs", @@ -31,8 +31,8 @@ "test:watch": "vitest --watch" }, "dependencies": { - "@ag-ui/client": "0.0.40-alpha.3", - "@ag-ui/core": "0.0.40-alpha.3", + "@ag-ui/client": "0.0.40-alpha.6", + "@ag-ui/core": "0.0.40-alpha.6", "@copilotkitnext/core": "workspace:*", "@copilotkitnext/shared": "workspace:*", "clsx": "^2.1.1", diff --git a/packages/angular/src/lib/agent.ts b/packages/angular/src/lib/agent.ts index 5bf71f0a..02eedc89 100644 --- a/packages/angular/src/lib/agent.ts +++ b/packages/angular/src/lib/agent.ts @@ -1,4 +1,11 @@ -import { DestroyRef, Injectable, inject, signal, computed, Signal } from "@angular/core"; +import { + DestroyRef, + Injectable, + inject, + signal, + computed, + Signal, +} from "@angular/core"; import { CopilotKit } from "./copilotkit"; import type { AbstractAgent } from "@ag-ui/client"; import type { Message } from "@ag-ui/client"; @@ -54,7 +61,10 @@ export class AgentStore { export class CopilotkitAgentFactory { readonly #copilotkit = inject(CopilotKit); - createAgentStoreSignal(agentId: Signal, destroyRef: DestroyRef): Signal { + createAgentStoreSignal( + agentId: Signal, + destroyRef: DestroyRef + ): Signal { let lastAgentStore: AgentStore | undefined; return computed(() => { @@ -65,7 +75,9 @@ export class CopilotkitAgentFactory { lastAgentStore = undefined; } - const abstractAgent = this.#copilotkit.getAgent(agentId() || DEFAULT_AGENT_ID); + const abstractAgent = this.#copilotkit.getAgent( + agentId() || DEFAULT_AGENT_ID + ); if (!abstractAgent) return undefined; lastAgentStore = new AgentStore(abstractAgent, destroyRef); @@ -74,10 +86,13 @@ export class CopilotkitAgentFactory { } } -export function injectAgentStore(agentId: string | Signal): Signal { +export function injectAgentStore( + agentId: string | Signal +): Signal { const agentFactory = inject(CopilotkitAgentFactory); const destroyRef = inject(DestroyRef); - const agentIdSignal = typeof agentId === "function" ? agentId : computed(() => agentId); + const agentIdSignal = + typeof agentId === "function" ? agentId : computed(() => agentId); return agentFactory.createAgentStoreSignal(agentIdSignal, destroyRef); } diff --git a/packages/angular/src/lib/copilotkit.ts b/packages/angular/src/lib/copilotkit.ts index 1190ea0e..8df87fd3 100644 --- a/packages/angular/src/lib/copilotkit.ts +++ b/packages/angular/src/lib/copilotkit.ts @@ -1,7 +1,19 @@ import { AbstractAgent } from "@ag-ui/client"; import { FrontendTool, CopilotKitCore } from "@copilotkitnext/core"; -import { Injectable, Injector, Signal, WritableSignal, runInInjectionContext, signal, inject } from "@angular/core"; -import { FrontendToolConfig, HumanInTheLoopConfig, RenderToolCallConfig } from "./tools"; +import { + Injectable, + Injector, + Signal, + WritableSignal, + runInInjectionContext, + signal, + inject, +} from "@angular/core"; +import { + FrontendToolConfig, + HumanInTheLoopConfig, + RenderToolCallConfig, +} from "./tools"; import { injectCopilotKitConfig } from "./config"; import { HumanInTheLoop } from "./human-in-the-loop"; @@ -10,7 +22,9 @@ export class CopilotKit { readonly #config = injectCopilotKitConfig(); readonly #hitl = inject(HumanInTheLoop); readonly #rootInjector = inject(Injector); - readonly #agents = signal>(this.#config.agents ?? {}); + readonly #agents = signal>( + this.#config.agents ?? {} + ); readonly agents = this.#agents.asReadonly(); readonly core = new CopilotKitCore({ @@ -21,12 +35,18 @@ export class CopilotKit { tools: this.#config.tools, }); - readonly #toolCallRenderConfigs: WritableSignal = signal([]); - readonly #clientToolCallRenderConfigs: WritableSignal = signal([]); - readonly #humanInTheLoopToolRenderConfigs: WritableSignal = signal([]); - - readonly toolCallRenderConfigs: Signal = this.#toolCallRenderConfigs.asReadonly(); - readonly clientToolCallRenderConfigs: Signal = this.#clientToolCallRenderConfigs.asReadonly(); + readonly #toolCallRenderConfigs: WritableSignal = + signal([]); + readonly #clientToolCallRenderConfigs: WritableSignal = + signal([]); + readonly #humanInTheLoopToolRenderConfigs: WritableSignal< + HumanInTheLoopConfig[] + > = signal([]); + + readonly toolCallRenderConfigs: Signal = + this.#toolCallRenderConfigs.asReadonly(); + readonly clientToolCallRenderConfigs: Signal = + this.#clientToolCallRenderConfigs.asReadonly(); readonly humanInTheLoopToolRenderConfigs: Signal = this.#humanInTheLoopToolRenderConfigs.asReadonly(); @@ -64,36 +84,38 @@ export class CopilotKit { #bindClientTool( clientToolWithInjector: FrontendToolConfig & { injector: Injector; - }, + } ): FrontendTool { - const { injector, handler, args, description, name, agentId } = clientToolWithInjector; + const { injector, handler, ...frontendCandidate } = clientToolWithInjector; return { - description, - name, - agentId, + ...frontendCandidate, handler: (args) => runInInjectionContext(injector, () => handler(args)), - parameters: args, }; } addFrontendTool( clientToolWithInjector: FrontendToolConfig & { injector: Injector; - }, + } ): void { const tool = this.#bindClientTool(clientToolWithInjector); this.core.addTool(tool); - this.#clientToolCallRenderConfigs.update((current) => [...current, clientToolWithInjector]); + this.#clientToolCallRenderConfigs.update((current) => [ + ...current, + clientToolWithInjector, + ]); } addRenderToolCall(renderConfig: RenderToolCallConfig): void { this.#toolCallRenderConfigs.update((current) => [...current, renderConfig]); } - #bindHumanInTheLoopTool(humanInTheLoopTool: HumanInTheLoopConfig): FrontendTool { + #bindHumanInTheLoopTool( + humanInTheLoopTool: HumanInTheLoopConfig + ): FrontendTool { return { ...humanInTheLoopTool, handler: (args, toolCall) => { @@ -103,14 +125,20 @@ export class CopilotKit { } addHumanInTheLoop(humanInTheLoopTool: HumanInTheLoopConfig): void { - this.#humanInTheLoopToolRenderConfigs.update((current) => [...current, humanInTheLoopTool]); + this.#humanInTheLoopToolRenderConfigs.update((current) => [ + ...current, + humanInTheLoopTool, + ]); const tool = this.#bindHumanInTheLoopTool(humanInTheLoopTool); this.core.addTool(tool); } - #isSameAgentId(target: T, agentId?: string): boolean { + #isSameAgentId( + target: T, + agentId?: string + ): boolean { if (agentId) { return target.agentId === agentId; } @@ -121,13 +149,25 @@ export class CopilotKit { removeTool(toolName: string, agentId?: string): void { this.core.removeTool(toolName); this.#clientToolCallRenderConfigs.update((current) => - current.filter((renderConfig) => renderConfig.name !== toolName && this.#isSameAgentId(renderConfig, agentId)), + current.filter( + (renderConfig) => + renderConfig.name !== toolName && + this.#isSameAgentId(renderConfig, agentId) + ) ); this.#humanInTheLoopToolRenderConfigs.update((current) => - current.filter((renderConfig) => renderConfig.name !== toolName && this.#isSameAgentId(renderConfig, agentId)), + current.filter( + (renderConfig) => + renderConfig.name !== toolName && + this.#isSameAgentId(renderConfig, agentId) + ) ); this.#toolCallRenderConfigs.update((current) => - current.filter((renderConfig) => renderConfig.name !== toolName && this.#isSameAgentId(renderConfig, agentId)), + current.filter( + (renderConfig) => + renderConfig.name !== toolName && + this.#isSameAgentId(renderConfig, agentId) + ) ); } diff --git a/packages/core/package.json b/packages/core/package.json index 4987b8e3..f372c3a3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@copilotkitnext/core", - "version": "0.0.15", + "version": "0.0.14", "description": "Core web utilities for CopilotKit2", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -36,7 +36,7 @@ "vitest": "^3.2.4" }, "dependencies": { - "@ag-ui/client": "0.0.40-alpha.3", + "@ag-ui/client": "0.0.40-alpha.6", "@copilotkitnext/shared": "workspace:*", "rxjs": "7.8.1", "zod": "^3.25.75", diff --git a/packages/react/package.json b/packages/react/package.json index 6ebd551e..95a46049 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@copilotkitnext/react", - "version": "0.0.15", + "version": "0.0.14", "description": "React components for CopilotKit2", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -53,8 +53,8 @@ "vitest": "^3.2.4" }, "dependencies": { - "@ag-ui/client": "0.0.40-alpha.3", - "@ag-ui/core": "0.0.40-alpha.3", + "@ag-ui/client": "0.0.40-alpha.6", + "@ag-ui/core": "0.0.40-alpha.6", "@copilotkitnext/core": "workspace:*", "@copilotkitnext/shared": "workspace:*", "@copilotkitnext/web-inspector": "workspace:*", diff --git a/packages/react/src/__tests__/setup.ts b/packages/react/src/__tests__/setup.ts index 4a2184dc..bd80a7cc 100644 --- a/packages/react/src/__tests__/setup.ts +++ b/packages/react/src/__tests__/setup.ts @@ -16,6 +16,9 @@ global.ResizeObserver = class ResizeObserver { disconnect() {} }; +// Mock scrollIntoView which is not available in jsdom +HTMLElement.prototype.scrollIntoView = vi.fn(); + // Ensure we cleanup between tests to avoid lingering handles afterEach(() => { cleanup(); diff --git a/packages/react/src/components/chat/CopilotChatAssistantMessage.tsx b/packages/react/src/components/chat/CopilotChatAssistantMessage.tsx index 1e54fcc9..393a77da 100644 --- a/packages/react/src/components/chat/CopilotChatAssistantMessage.tsx +++ b/packages/react/src/components/chat/CopilotChatAssistantMessage.tsx @@ -155,7 +155,9 @@ export function CopilotChatAssistantMessage({ // Don't show toolbar if message has no content (only tool calls) const hasContent = !!(message.content && message.content.trim().length > 0); - const shouldShowToolbar = toolbarVisible && hasContent; + const isLatestAssistantMessage = + message.role === "assistant" && messages?.[messages.length - 1]?.id === message.id; + const shouldShowToolbar = toolbarVisible && hasContent && !(isRunning && isLatestAssistantMessage); if (children) { return ( diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 59a81999..a8258e28 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@copilotkitnext/runtime", - "version": "0.0.15", + "version": "0.0.14", "description": "Server-side runtime package for CopilotKit2", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,35 +28,25 @@ "devDependencies": { "@copilotkitnext/eslint-config": "workspace:*", "@copilotkitnext/typescript-config": "workspace:*", - "@types/better-sqlite3": "^7.6.13", "@types/node": "^22.15.3", - "better-sqlite3": "^12.2.0", "eslint": "^9.30.0", - "ioredis-mock": "^8.9.0", "openai": "^5.9.0", "tsup": "^8.5.0", "typescript": "5.8.2", "vitest": "^3.0.5" }, "dependencies": { - "@ag-ui/client": "0.0.40-alpha.3", - "@ag-ui/core": "0.0.40-alpha.3", - "@ag-ui/encoder": "0.0.40-alpha.3", + "@ag-ui/client": "0.0.40-alpha.6", + "@ag-ui/core": "0.0.40-alpha.6", + "@ag-ui/encoder": "0.0.40-alpha.6", "@copilotkitnext/shared": "workspace:*", "hono": "^4.6.13", - "ioredis": "^5.7.0", - "kysely": "^0.28.5", "rxjs": "7.8.1" }, "peerDependencies": { - "better-sqlite3": "^12.2.0", "openai": "^5.9.0" }, - "peerDependenciesMeta": { - "better-sqlite3": { - "optional": true - } - }, + "peerDependenciesMeta": {}, "engines": { "node": ">=18" } diff --git a/packages/runtime/src/__tests__/handle-run.test.ts b/packages/runtime/src/__tests__/handle-run.test.ts index 5ee1a108..570c7fba 100644 --- a/packages/runtime/src/__tests__/handle-run.test.ts +++ b/packages/runtime/src/__tests__/handle-run.test.ts @@ -1,5 +1,5 @@ import { Observable } from "rxjs"; -import { describe, it, expect } from "vitest"; +import { describe, it, expect, vi } from "vitest"; import { AbstractAgent, BaseEvent, HttpAgent } from "@ag-ui/client"; import { handleRunAgent } from "../handlers/handle-run"; import { CopilotRuntime } from "../runtime"; @@ -53,20 +53,26 @@ describe("handleRunAgent", () => { const request = createMockRequest(); const agentId = "test-agent"; - const response = await handleRunAgent({ - runtime, - request, - agentId, - }); - - expect(response.status).toBe(500); - expect(response.headers.get("Content-Type")).toBe("application/json"); - - const body = await response.json(); - expect(body).toEqual({ - error: "Failed to run agent", - message: "Database connection failed", - }); + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + try { + const response = await handleRunAgent({ + runtime, + request, + agentId, + }); + + expect(response.status).toBe(500); + expect(response.headers.get("Content-Type")).toBe("application/json"); + + const body = await response.json(); + expect(body).toEqual({ + error: "Failed to run agent", + message: "Database connection failed", + }); + } finally { + errorSpy.mockRestore(); + } }); it("forwards only authorization and custom x- headers to HttpAgent runs", async () => { diff --git a/packages/runtime/src/__tests__/in-process-agent-runner-messages.test.ts b/packages/runtime/src/__tests__/in-process-agent-runner-messages.test.ts index df1f67b0..073713d8 100644 --- a/packages/runtime/src/__tests__/in-process-agent-runner-messages.test.ts +++ b/packages/runtime/src/__tests__/in-process-agent-runner-messages.test.ts @@ -3,597 +3,202 @@ import { InMemoryAgentRunner } from "../runner/in-memory"; import { AbstractAgent, BaseEvent, - RunAgentInput, + EventType, Message, + RunAgentInput, RunStartedEvent, - EventType, } from "@ag-ui/client"; -import { EMPTY, firstValueFrom, Observable } from "rxjs"; +import { EMPTY, Observable, firstValueFrom } from "rxjs"; import { toArray } from "rxjs/operators"; -// Type helpers for event filtering -type EventWithType = BaseEvent & { type: string }; -type EventWithId = BaseEvent & { id: string }; -type EventWithMessageId = BaseEvent & { messageId: string }; -type EventWithMessageIdAndDelta = BaseEvent & { - messageId: string; - delta: string; +type RunAgentCallbacks = { + onEvent: (event: { event: BaseEvent }) => void; + onNewMessage?: (args: { message: Message }) => void; + onRunStartedEvent?: (args: { event: BaseEvent }) => void; }; -// Mock agent that can handle messages and callbacks class MessageAwareAgent extends AbstractAgent { - private events: BaseEvent[] = []; - public receivedMessages: Message[] = []; - public onNewMessageCalled = 0; - public onRunStartedCalled = 0; - - constructor(events: BaseEvent[] = []) { + constructor( + private readonly events: BaseEvent[] = [], + private readonly emitDefaultRunStarted = true, + ) { super(); - this.events = events; } - protected run(input: RunAgentInput): Observable { + protected run(_input: RunAgentInput): Observable { return EMPTY; } async runAgent( input: RunAgentInput, - options: { - onEvent: (event: { event: BaseEvent }) => void; - onNewMessage?: (args: { message: Message }) => void; - onRunStartedEvent?: (args: { event: BaseEvent }) => void; - } - // @ts-expect-error - ): Promise { - // Call onRunStartedEvent if provided - if (options.onRunStartedEvent) { - this.onRunStartedCalled++; - const runStartedEvent = { + callbacks: RunAgentCallbacks, + ): Promise { + if (this.emitDefaultRunStarted) { + const runStarted: RunStartedEvent = { type: EventType.RUN_STARTED, threadId: input.threadId, runId: input.runId, - } as RunStartedEvent; - options.onRunStartedEvent({ event: runStartedEvent }); + }; + + callbacks.onEvent({ event: runStarted }); + callbacks.onRunStartedEvent?.({ event: runStarted }); } - // Emit the agent's own events for (const event of this.events) { - options.onEvent({ event }); + callbacks.onEvent({ event }); } - - // Return a promise as expected - return Promise.resolve(); } } -describe("InMemoryAgentRunner - Message Injection", () => { +describe("InMemoryAgentRunner – run started inputs", () => { let runner: InMemoryAgentRunner; beforeEach(() => { runner = new InMemoryAgentRunner(); }); - describe("Message Injection on Run", () => { - it("should inject user messages as events when running an agent", async () => { - const threadId = "test-thread-messages-1"; - const userMessage: Message = { - id: "user-msg-1", - role: "user", - content: "Hello, agent!", - }; - - const agentEvents: BaseEvent[] = [ - { - type: EventType.CUSTOM, - id: "agent-response-1", - timestamp: Date.now(), - name: "agent-response", - value: { text: "Hello, user!" }, - } as BaseEvent, - ]; - - const agent = new MessageAwareAgent(agentEvents); - const input: RunAgentInput = { - messages: [userMessage], - state: {}, - threadId, - runId: "run-1", - tools: [], - context: [], - }; - - // Run the agent - const runObservable = runner.run({ threadId, agent, input }); - const runEvents = await firstValueFrom(runObservable.pipe(toArray())); - - // run() should only return agent events, not injected messages - expect(runEvents).toHaveLength(1); - expect((runEvents[0] as EventWithId).id).toBe("agent-response-1"); - - // connect() should have all events including injected messages - const connectObservable = runner.connect({ threadId }); - const allEvents = await firstValueFrom(connectObservable.pipe(toArray())); - - // Should have: user message events (Start, Content, End) + agent event = 4 events - expect(allEvents.length).toBe(4); - - // Find the injected user message events - const textStartEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TEXT_MESSAGE_START - ); - const textContentEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TEXT_MESSAGE_CONTENT - ); - const textEndEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TEXT_MESSAGE_END - ); - - expect(textStartEvents).toHaveLength(1); - expect(textStartEvents[0]).toMatchObject({ - type: EventType.TEXT_MESSAGE_START, - messageId: "user-msg-1", - }); - - expect(textContentEvents).toHaveLength(1); - expect(textContentEvents[0]).toMatchObject({ - type: EventType.TEXT_MESSAGE_CONTENT, - messageId: "user-msg-1", - delta: "Hello, agent!", - }); - - expect(textEndEvents).toHaveLength(1); - expect(textEndEvents[0]).toMatchObject({ - type: EventType.TEXT_MESSAGE_END, - messageId: "user-msg-1", - }); - - // Verify the agent callbacks - expect(agent.onNewMessageCalled).toBe(0); // onNewMessage is not called by the test agent - expect(agent.onRunStartedCalled).toBe(1); - }); - - it("should inject assistant messages as proper TextMessage events", async () => { - const threadId = "test-thread-messages-2"; - const assistantMessage: Message = { - id: "assistant-msg-1", - role: "assistant", - content: "I can help you with that!", - }; - - const agent = new MessageAwareAgent([]); - const input: RunAgentInput = { - messages: [assistantMessage], - state: {}, - threadId, - runId: "run-2", - tools: [], - context: [], - }; - - // Run the agent - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Check events via connect - const allEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Find the injected message events - const textStartEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TEXT_MESSAGE_START - ); - const textContentEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TEXT_MESSAGE_CONTENT - ); - const textEndEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TEXT_MESSAGE_END - ); - - expect(textStartEvents).toHaveLength(1); - expect(textStartEvents[0]).toMatchObject({ - type: EventType.TEXT_MESSAGE_START, - messageId: "assistant-msg-1", - role: "assistant", - }); - - expect(textContentEvents).toHaveLength(1); - expect(textContentEvents[0]).toMatchObject({ - type: EventType.TEXT_MESSAGE_CONTENT, - messageId: "assistant-msg-1", - delta: "I can help you with that!", - }); - - expect(textEndEvents).toHaveLength(1); - expect(textEndEvents[0]).toMatchObject({ - type: EventType.TEXT_MESSAGE_END, - messageId: "assistant-msg-1", - }); - }); - - it("should inject tool call messages as proper ToolCall events", async () => { - const threadId = "test-thread-messages-3"; - const toolCallMessage: Message = { - id: "assistant-msg-2", + it("attaches every input message to the emitted RUN_STARTED event", async () => { + const threadId = "thread-all-messages"; + const messages: Message[] = [ + { id: "user-1", role: "user", content: "User message" }, + { id: "assistant-1", role: "assistant", content: "Assistant message" }, + { id: "developer-1", role: "developer", content: "Developer hint" }, + { id: "system-1", role: "system", content: "System prompt" }, + { + id: "tool-call-1", role: "assistant", content: "", toolCalls: [ { id: "tool-call-1", type: "function", - function: { - name: "get_weather", - arguments: '{"location": "New York"}', - }, + function: { name: "calculator", arguments: "{\"a\":1}" }, }, ], - }; - - const agent = new MessageAwareAgent([]); - const input: RunAgentInput = { - messages: [toolCallMessage], - state: {}, - threadId, - runId: "run-3", - tools: [], - context: [], - }; - - // Run the agent - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Check events via connect - const allEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Find the injected tool call events - const toolStartEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TOOL_CALL_START - ); - const toolArgsEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TOOL_CALL_ARGS - ); - const toolEndEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TOOL_CALL_END - ); - - expect(toolStartEvents).toHaveLength(1); - expect(toolStartEvents[0]).toMatchObject({ - type: EventType.TOOL_CALL_START, - toolCallId: "tool-call-1", - toolCallName: "get_weather", - parentMessageId: "assistant-msg-2", - }); - - expect(toolArgsEvents).toHaveLength(1); - expect(toolArgsEvents[0]).toMatchObject({ - type: EventType.TOOL_CALL_ARGS, - toolCallId: "tool-call-1", - delta: '{"location": "New York"}', - }); - - expect(toolEndEvents).toHaveLength(1); - expect(toolEndEvents[0]).toMatchObject({ - type: EventType.TOOL_CALL_END, - toolCallId: "tool-call-1", - }); - }); - - it("should inject developer and system messages as TextMessage events", async () => { - const threadId = "test-thread-messages-dev-sys"; - const developerMessage: Message = { - id: "dev-msg-1", - role: "developer", - content: "You are a helpful assistant.", - }; - const systemMessage: Message = { - id: "sys-msg-1", - role: "system", - content: "System prompt: Be concise.", - }; - - const agent = new MessageAwareAgent([]); - const input: RunAgentInput = { - messages: [developerMessage, systemMessage], - state: {}, - threadId, - runId: "run-dev-sys", - tools: [], - context: [], - }; - - // Run the agent - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Check events via connect - const allEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Find the injected message events - const textStartEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TEXT_MESSAGE_START - ); - const textContentEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TEXT_MESSAGE_CONTENT - ); - const textEndEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TEXT_MESSAGE_END - ); - - // Should have 2 sets of text message events (one for each message) - expect(textStartEvents).toHaveLength(2); - expect(textContentEvents).toHaveLength(2); - expect(textEndEvents).toHaveLength(2); - - // Verify developer message events - expect( - textStartEvents.some( - (e) => (e as EventWithMessageId).messageId === "dev-msg-1" - ) - ).toBe(true); - expect( - textContentEvents.some((e) => { - const evt = e as EventWithMessageIdAndDelta; - return ( - evt.messageId === "dev-msg-1" && - evt.delta === "You are a helpful assistant." - ); - }) - ).toBe(true); - - // Verify system message events - expect( - textStartEvents.some( - (e) => (e as EventWithMessageId).messageId === "sys-msg-1" - ) - ).toBe(true); - expect( - textContentEvents.some((e) => { - const evt = e as EventWithMessageIdAndDelta; - return ( - evt.messageId === "sys-msg-1" && - evt.delta === "System prompt: Be concise." - ); - }) - ).toBe(true); - }); - - it("should inject tool result messages as ToolCallResult events", async () => { - const threadId = "test-thread-messages-4"; - const toolResultMessage: Message = { + }, + { id: "tool-result-1", role: "tool", - content: "72°F and sunny", + content: "result", toolCallId: "tool-call-1", - }; - - const agent = new MessageAwareAgent([]); - const input: RunAgentInput = { - messages: [toolResultMessage], - state: {}, - threadId, - runId: "run-4", - tools: [], - context: [], - }; + }, + ]; + + const agent = new MessageAwareAgent(); + const input: RunAgentInput = { + threadId, + runId: "run-1", + state: {}, + messages, + }; + + const runEvents = await firstValueFrom( + runner.run({ threadId, agent, input }).pipe(toArray()), + ); + + expect(runEvents).toHaveLength(1); + const runStarted = runEvents[0] as RunStartedEvent; + expect(runStarted.type).toBe(EventType.RUN_STARTED); + expect(runStarted.input?.messages).toEqual(messages); + + const connectEvents = await firstValueFrom( + runner.connect({ threadId }).pipe(toArray()), + ); + + expect(connectEvents).toHaveLength(1); + const connectRunStarted = connectEvents[0] as RunStartedEvent; + expect(connectRunStarted.input?.messages).toEqual(messages); + }); - // Run the agent - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); + it("only includes new messages on subsequent runs", async () => { + const threadId = "thread-new-messages"; + const existing: Message = { + id: "existing-msg", + role: "user", + content: "Hi there", + }; + + await firstValueFrom( + runner + .run({ + threadId, + agent: new MessageAwareAgent(), + input: { threadId, runId: "run-0", state: {}, messages: [existing] }, + }) + .pipe(toArray()), + ); + + const newMessage: Message = { + id: "new-msg", + role: "user", + content: "Second question", + }; + + const secondRunEvents = await firstValueFrom( + runner + .run({ + threadId, + agent: new MessageAwareAgent(), + input: { + threadId, + runId: "run-1", + state: {}, + messages: [existing, newMessage], + }, + }) + .pipe(toArray()), + ); - // Check events via connect - const allEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); + const runStarted = secondRunEvents[0] as RunStartedEvent; + expect(runStarted.input?.messages).toEqual([newMessage]); - // Find the injected tool result events - const toolResultEvents = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TOOL_CALL_RESULT - ); + const connectEvents = await firstValueFrom( + runner.connect({ threadId }).pipe(toArray()), + ); - expect(toolResultEvents).toHaveLength(1); - expect(toolResultEvents[0]).toMatchObject({ - type: EventType.TOOL_CALL_RESULT, - messageId: "tool-result-1", - toolCallId: "tool-call-1", - content: "72°F and sunny", - role: "tool", - }); - }); + const latestRunStarted = connectEvents + .filter((event) => event.type === EventType.RUN_STARTED) + .pop() as RunStartedEvent; + expect(latestRunStarted.input?.messages).toEqual([newMessage]); }); - describe("Consecutive Runs with Different Messages", () => { - it("should accumulate messages across multiple runs", async () => { - const threadId = "test-thread-consecutive-1"; - - // First run with a user message - const userMessage1: Message = { - id: "user-msg-1", - role: "user", - content: "What's the weather?", - }; - - const agent1 = new MessageAwareAgent([ + it("preserves agent-provided RUN_STARTED input", async () => { + const threadId = "thread-agent-input"; + const providedInput: RunAgentInput = { + threadId, + runId: "run-preserve", + state: { injected: true }, + messages: [], + }; + + const agent = new MessageAwareAgent( + [ { - type: EventType.CUSTOM, - id: "agent-1-response", - timestamp: Date.now(), - name: "agent-response", - value: { text: "Let me check..." }, - } as BaseEvent, - ]); - - const input1: RunAgentInput = { - messages: [userMessage1], - state: {}, - threadId, - runId: "run-1", - tools: [], - context: [], - }; - - await firstValueFrom( - runner.run({ threadId, agent: agent1, input: input1 }).pipe(toArray()) - ); - - // Second run with assistant and tool messages - const assistantMessage: Message = { - id: "assistant-msg-1", - role: "assistant", - content: "", - toolCalls: [ - { - id: "tool-call-1", - type: "function", - function: { - name: "get_weather", - arguments: '{"location": "current"}', - }, + type: EventType.RUN_STARTED, + threadId, + runId: "run-preserve", + input: providedInput, + } as RunStartedEvent, + ], + false, + ); + + const runEvents = await firstValueFrom( + runner + .run({ + threadId, + agent, + input: { + threadId, + runId: "run-preserve", + state: {}, + messages: [{ id: "extra", role: "user", content: "Hello" }], }, - ], - }; - - const toolResultMessage: Message = { - id: "tool-result-1", - role: "tool", - content: "72°F and sunny in New York", - toolCallId: "tool-call-1", - }; - - const agent2 = new MessageAwareAgent([ - { - type: EventType.CUSTOM, - id: "agent-2-response", - timestamp: Date.now(), - name: "agent-response", - value: { text: "It's 72°F and sunny!" }, - } as BaseEvent, - ]); - - const input2: RunAgentInput = { - messages: [userMessage1, assistantMessage, toolResultMessage], - state: {}, - threadId, - runId: "run-2", - tools: [], - context: [], - }; - - await firstValueFrom( - runner.run({ threadId, agent: agent2, input: input2 }).pipe(toArray()) - ); - - // Connect should have all events from both runs - const allEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Should have events from both runs plus injected message events - expect(allEvents.length).toBeGreaterThan(4); - - // Verify we have both agent responses - const agentResponses = allEvents.filter( - (e) => e.type === EventType.CUSTOM - ); - expect( - agentResponses.some((e) => (e as EventWithId).id === "agent-1-response") - ).toBe(true); - expect( - agentResponses.some((e) => (e as EventWithId).id === "agent-2-response") - ).toBe(true); - - // Verify we have the tool call events - const toolCallStarts = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TOOL_CALL_START - ); - expect(toolCallStarts).toHaveLength(1); - - // Verify we have the tool result event - const toolResults = allEvents.filter( - (e) => (e as EventWithType).type === EventType.TOOL_CALL_RESULT - ); - expect(toolResults).toHaveLength(1); - }); - - it("should track seen message IDs and not duplicate messages", async () => { - const threadId = "test-thread-duplicate-1"; - - const sharedMessage: Message = { - id: "shared-msg-1", - role: "assistant", - content: "This message appears in both runs", - }; - - const newMessage: Message = { - id: "new-msg-1", - role: "assistant", - content: "This is a new message", - }; - - // First run with shared message - const agent1 = new MessageAwareAgent([]); - const input1: RunAgentInput = { - messages: [sharedMessage], - state: {}, - threadId, - runId: "run-1", - tools: [], - context: [], - }; - - await firstValueFrom( - runner.run({ threadId, agent: agent1, input: input1 }).pipe(toArray()) - ); - - // Second run with shared message and new message - const agent2 = new MessageAwareAgent([]); - const input2: RunAgentInput = { - messages: [sharedMessage, newMessage], - state: {}, - threadId, - runId: "run-2", - tools: [], - context: [], - }; - - await firstValueFrom( - runner.run({ threadId, agent: agent2, input: input2 }).pipe(toArray()) - ); - - // Connect should have events without duplicates - const allEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Count TextMessageStart events for our messages - const textStartEvents = allEvents.filter((e) => { - const evt = e as EventWithType & { messageId?: string }; - return ( - evt.type === EventType.TEXT_MESSAGE_START && - (evt.messageId === "shared-msg-1" || evt.messageId === "new-msg-1") - ); - }); + }) + .pipe(toArray()), + ); - // Should have exactly 2 TextMessageStart events (one for each unique message) - expect(textStartEvents).toHaveLength(2); - expect( - textStartEvents.some( - (e) => (e as EventWithMessageId).messageId === "shared-msg-1" - ) - ).toBe(true); - expect( - textStartEvents.some( - (e) => (e as EventWithMessageId).messageId === "new-msg-1" - ) - ).toBe(true); - }); + const runStarted = runEvents[0] as RunStartedEvent; + expect(runStarted.input).toBe(providedInput); }); }); diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index e1fefbbd..f4cb5fa7 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -1,8 +1,6 @@ export * from "./runtime"; export * from "./endpoint"; -// Export agent runners +// Export agent runners and base types +export * from "./runner/agent-runner"; export { InMemoryAgentRunner } from "./runner/in-memory"; -export { SqliteAgentRunner } from "./runner/sqlite"; -export { EnterpriseAgentRunner } from "./runner/enterprise"; -export type { EnterpriseAgentRunnerOptions } from "./runner/enterprise"; diff --git a/packages/runtime/src/runner/__tests__/enterprise-runner.test.ts b/packages/runtime/src/runner/__tests__/enterprise-runner.test.ts deleted file mode 100644 index ee82f303..00000000 --- a/packages/runtime/src/runner/__tests__/enterprise-runner.test.ts +++ /dev/null @@ -1,992 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { Kysely, SqliteDialect } from 'kysely'; -import Database from 'better-sqlite3'; -import IORedisMock from 'ioredis-mock'; -import { EnterpriseAgentRunner } from '../enterprise'; -import { EventType } from '@ag-ui/client'; -import type { AbstractAgent, RunAgentInput, BaseEvent, Message } from '@ag-ui/client'; -import { firstValueFrom, toArray } from 'rxjs'; - -// Mock agent that takes custom events -class CustomEventAgent implements AbstractAgent { - private events: BaseEvent[]; - - constructor(events: BaseEvent[] = []) { - this.events = events; - } - - async runAgent( - input: RunAgentInput, - callbacks: { - onEvent: (params: { event: any }) => void | Promise; - onNewMessage?: (params: { message: any }) => void | Promise; - onRunStartedEvent?: () => void | Promise; - } - ): Promise { - if (callbacks.onRunStartedEvent) { - await callbacks.onRunStartedEvent(); - } - - for (const event of this.events) { - await callbacks.onEvent({ event }); - } - } -} - -// Mock agent for testing -class MockAgent implements AbstractAgent { - async runAgent( - input: RunAgentInput, - callbacks: { - onEvent: (params: { event: any }) => void | Promise; - onNewMessage?: (params: { message: any }) => void | Promise; - onRunStartedEvent?: () => void | Promise; - } - ): Promise { - // Emit run started - if (callbacks.onRunStartedEvent) { - await callbacks.onRunStartedEvent(); - } - - // Emit some events - await callbacks.onEvent({ - event: { - type: EventType.RUN_STARTED, - threadId: input.threadId, - runId: input.runId, - } - }); - - // Emit a text message - await callbacks.onEvent({ - event: { - type: EventType.TEXT_MESSAGE_START, - messageId: 'test-msg-1', - role: 'assistant', - } - }); - - await callbacks.onEvent({ - event: { - type: EventType.TEXT_MESSAGE_CONTENT, - messageId: 'test-msg-1', - delta: 'Hello from agent', - } - }); - - await callbacks.onEvent({ - event: { - type: EventType.TEXT_MESSAGE_END, - messageId: 'test-msg-1', - } - }); - - // Emit run finished - await callbacks.onEvent({ - event: { - type: EventType.RUN_FINISHED, - threadId: input.threadId, - runId: input.runId, - } - }); - } -} - -// Error agent for testing -class ErrorAgent implements AbstractAgent { - async runAgent( - input: RunAgentInput, - callbacks: { - onEvent: (params: { event: any }) => void | Promise; - onNewMessage?: (params: { message: any }) => void | Promise; - onRunStartedEvent?: () => void | Promise; - } - ): Promise { - await callbacks.onEvent({ - event: { - type: EventType.RUN_STARTED, - threadId: input.threadId, - runId: input.runId, - } - }); - - await callbacks.onEvent({ - event: { - type: EventType.RUN_ERROR, - error: 'Test error', - } - }); - } -} - -describe('EnterpriseAgentRunner', () => { - let db: Kysely; - let redis: any; - let redisSub: any; - let runner: EnterpriseAgentRunner; - - beforeEach(async () => { - // In-memory SQLite for testing - db = new Kysely({ - dialect: new SqliteDialect({ - database: new Database(':memory:') - }) - }); - - // Mock Redis for unit tests - redis = new IORedisMock(); - redisSub = redis.duplicate(); - - runner = new EnterpriseAgentRunner({ - kysely: db, - redis, - redisSub, - streamRetentionMs: 60000, // 1 minute for tests - streamActiveTTLMs: 10000, // 10 seconds for tests - lockTTLMs: 30000 // 30 seconds for tests - }); - - // Allow schema to initialize - await new Promise(resolve => setTimeout(resolve, 100)); - }); - - afterEach(async () => { - await runner.close(); - }); - - it('should prevent concurrent runs on same thread', async () => { - // Create a slow agent that takes time to complete - const slowAgent: AbstractAgent = { - async runAgent(input, callbacks) { - if (callbacks.onRunStartedEvent) { - await callbacks.onRunStartedEvent(); - } - - await callbacks.onEvent({ - event: { - type: EventType.RUN_STARTED, - threadId: input.threadId, - runId: input.runId, - } - }); - - // Simulate long running task - await new Promise(resolve => setTimeout(resolve, 500)); - - await callbacks.onEvent({ - event: { - type: EventType.RUN_FINISHED, - threadId: input.threadId, - runId: input.runId, - } - }); - } - }; - - const threadId = 'test-thread-1'; - const input1: RunAgentInput = { - runId: 'run-1', - threadId, - messages: [], - }; - const input2: RunAgentInput = { - runId: 'run-2', - threadId, - messages: [], - }; - - // Start first run - const run1 = runner.run({ threadId, agent: slowAgent, input: input1 }); - - // Wait a bit for first run to acquire lock - await new Promise(resolve => setTimeout(resolve, 100)); - - // Try to start second run on same thread - should error - const run2 = runner.run({ threadId, agent: slowAgent, input: input2 }); - - let errorReceived = false; - let completedWithoutError = false; - - await new Promise((resolve) => { - run2.subscribe({ - next: () => {}, - error: (err) => { - errorReceived = true; - expect(err.message).toBe('Thread already running'); - resolve(); - }, - complete: () => { - completedWithoutError = true; - resolve(); - } - }); - }); - - expect(errorReceived).toBe(true); - expect(completedWithoutError).toBe(false); - - // Let first run complete - const events1 = await firstValueFrom(run1.pipe(toArray())); - expect(events1.length).toBeGreaterThan(0); - }); - - it('should handle RUN_FINISHED event correctly', async () => { - const agent = new MockAgent(); - const threadId = 'test-thread-2'; - const input: RunAgentInput = { - runId: 'run-finished', - threadId, - messages: [], - }; - - const events = await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Should contain RUN_FINISHED event - const runFinishedEvent = events.find(e => e.type === EventType.RUN_FINISHED); - expect(runFinishedEvent).toBeDefined(); - - // Thread should not be running after completion - const isRunning = await runner.isRunning({ threadId }); - expect(isRunning).toBe(false); - - // Stream should have retention TTL - const streamKey = `stream:${threadId}:${input.runId}`; - const ttl = await redis.pttl(streamKey); - expect(ttl).toBeGreaterThan(0); - expect(ttl).toBeLessThanOrEqual(60000); // retention period - }); - - it('should handle RUN_ERROR event correctly', async () => { - const agent = new ErrorAgent(); - const threadId = 'test-thread-3'; - const input: RunAgentInput = { - runId: 'run-error', - threadId, - messages: [], - }; - - const events = await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Should contain RUN_ERROR event - const runErrorEvent = events.find(e => e.type === EventType.RUN_ERROR); - expect(runErrorEvent).toBeDefined(); - - // Thread should not be running after error - const isRunning = await runner.isRunning({ threadId }); - expect(isRunning).toBe(false); - }); - - it('should allow late readers to catch up during retention period', async () => { - const agent = new MockAgent(); - const threadId = 'test-thread-4'; - const input: RunAgentInput = { - runId: 'run-retention', - threadId, - messages: [], - }; - - // Start and complete a run - const runEvents = await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Wait a bit - await new Promise(resolve => setTimeout(resolve, 100)); - - // Connect should still get all events - const connectEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Should get the same events (after compaction) - expect(connectEvents.length).toBeGreaterThan(0); - - // Should include text message events - const textStartEvents = connectEvents.filter(e => e.type === EventType.TEXT_MESSAGE_START); - expect(textStartEvents.length).toBeGreaterThan(0); - }); - - it('should handle connect() with no active runs', async () => { - const threadId = 'test-thread-5'; - - // Connect to thread with no runs - const events = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Should complete with empty array - expect(events).toEqual([]); - }); - - it('should handle connect() during active run', async () => { - const agent = new MockAgent(); - const threadId = 'test-thread-6'; - const input: RunAgentInput = { - runId: 'run-active', - threadId, - messages: [], - }; - - // Start a run but don't wait for it - runner.run({ threadId, agent, input }); - - // Wait for run to start - await new Promise(resolve => setTimeout(resolve, 50)); - - // Connect while run is active - const connectPromise = firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Should eventually complete when run finishes - const events = await connectPromise; - expect(events.length).toBeGreaterThan(0); - - // Should include RUN_FINISHED - const finishedEvent = events.find(e => e.type === EventType.RUN_FINISHED); - expect(finishedEvent).toBeDefined(); - }); - - it('should handle stop() correctly', async () => { - const agent = new MockAgent(); - const threadId = 'test-thread-7'; - const input: RunAgentInput = { - runId: 'run-stop', - threadId, - messages: [], - }; - - // Mock a slow agent - const slowAgent: AbstractAgent = { - async runAgent(input, callbacks) { - await callbacks.onEvent({ - event: { - type: EventType.RUN_STARTED, - threadId: input.threadId, - runId: input.runId, - } - }); - - // Simulate long running task - await new Promise(resolve => setTimeout(resolve, 1000)); - } - }; - - // Start run - runner.run({ threadId, agent: slowAgent, input }); - - // Wait for run to start - await new Promise(resolve => setTimeout(resolve, 50)); - - // Stop the run - const stopped = await runner.stop({ threadId }); - expect(stopped).toBe(true); - - // Should not be running - const isRunning = await runner.isRunning({ threadId }); - expect(isRunning).toBe(false); - - // Stream should contain RUN_ERROR event - const streamKey = `stream:${threadId}:${input.runId}`; - const stream = await redis.xrange(streamKey, '-', '+'); - const errorEvent = stream.find((entry: any) => { - const fields = entry[1]; - for (let i = 0; i < fields.length; i += 2) { - if (fields[i] === 'type' && fields[i + 1] === EventType.RUN_ERROR) { - return true; - } - } - return false; - }); - expect(errorEvent).toBeDefined(); - }); - - it('should handle multiple sequential runs on same thread', async () => { - const agent = new MockAgent(); - const threadId = 'test-thread-8'; - - // First run - const input1: RunAgentInput = { - runId: 'run-seq-1', - threadId, - messages: [], - }; - - const events1 = await firstValueFrom( - runner.run({ threadId, agent, input: input1 }).pipe(toArray()) - ); - expect(events1.length).toBeGreaterThan(0); - - // Second run - const input2: RunAgentInput = { - runId: 'run-seq-2', - threadId, - messages: [], - }; - - const events2 = await firstValueFrom( - runner.run({ threadId, agent, input: input2 }).pipe(toArray()) - ); - expect(events2.length).toBeGreaterThan(0); - - // Connect should get both runs' events - const allEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Should have events from both runs - expect(allEvents.length).toBeGreaterThan(events1.length); - }); - - it('should handle input messages correctly', async () => { - const agent = new MockAgent(); - const threadId = 'test-thread-9'; - const input: RunAgentInput = { - runId: 'run-messages', - threadId, - messages: [ - { - id: 'user-msg-1', - role: 'user', - content: 'Hello', - } - ], - }; - - const events = await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Run events should not include input messages - const userMessages = events.filter((e: any) => - e.type === EventType.TEXT_MESSAGE_START && e.role === 'user' - ); - expect(userMessages.length).toBe(0); - - // But connect should include them - const connectEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - const userMessagesInConnect = connectEvents.filter((e: any) => - e.type === EventType.TEXT_MESSAGE_START && e.role === 'user' - ); - expect(userMessagesInConnect.length).toBe(1); - }); - - // Additional comprehensive tests to match SQLite/InMemory coverage - - it('should persist events across runner instances', async () => { - const threadId = 'test-thread-persistence'; - const events: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: 'msg1', role: 'assistant' }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: 'msg1', delta: 'Persisted' }, - { type: EventType.TEXT_MESSAGE_END, messageId: 'msg1' }, - { type: EventType.RUN_FINISHED, threadId, runId: 'run1' }, - ]; - - const agent = new CustomEventAgent(events); - const input: RunAgentInput = { - threadId, - runId: 'run1', - messages: [], - }; - - // Run with first instance - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Don't close the first runner's DB connection since we share it - // Just disconnect Redis - runner.redis.disconnect(); - runner.redisSub.disconnect(); - - // Create new runner instance with same DB but new Redis - const newRunner = new EnterpriseAgentRunner({ - kysely: db, // Reuse same DB connection - redis: new IORedisMock(), - streamRetentionMs: 60000, - streamActiveTTLMs: 10000, - lockTTLMs: 30000 - }); - - // Connect should get persisted events - const persistedEvents = await firstValueFrom( - newRunner.connect({ threadId }).pipe(toArray()) - ); - - expect(persistedEvents.length).toBeGreaterThan(0); - const textContent = persistedEvents.find( - e => e.type === EventType.TEXT_MESSAGE_CONTENT - ) as any; - expect(textContent?.delta).toBe('Persisted'); - - // Clean up new runner's Redis connections only - newRunner.redis.disconnect(); - newRunner.redisSub.disconnect(); - }); - - it('should handle concurrent connections', async () => { - const threadId = 'test-thread-concurrent'; - const agent = new MockAgent(); - const input: RunAgentInput = { - threadId, - runId: 'run1', - messages: [], - }; - - // Start a run - const runPromise = firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Start multiple connections while run is active - await new Promise(resolve => setTimeout(resolve, 50)); - - const conn1Promise = firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - const conn2Promise = firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - const conn3Promise = firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Wait for all to complete - const [runEvents, conn1Events, conn2Events, conn3Events] = await Promise.all([ - runPromise, - conn1Promise, - conn2Promise, - conn3Promise, - ]); - - // All connections should receive events - expect(conn1Events.length).toBeGreaterThan(0); - expect(conn2Events.length).toBeGreaterThan(0); - expect(conn3Events.length).toBeGreaterThan(0); - }); - - it('should store compacted events in the database', async () => { - const threadId = 'test-thread-compaction'; - const messageId = 'msg-compact'; - - // Create events that will be compacted - const events: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId, role: 'assistant' }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId, delta: 'Hello' }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId, delta: ' ' }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId, delta: 'World' }, - { type: EventType.TEXT_MESSAGE_END, messageId }, - { type: EventType.RUN_FINISHED, threadId, runId: 'run1' }, - ]; - - const agent = new CustomEventAgent(events); - const input: RunAgentInput = { - threadId, - runId: 'run1', - messages: [], - }; - - // Run the agent - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Check database has compacted events - const dbRuns = await db - .selectFrom('agent_runs') - .where('thread_id', '=', threadId) - .selectAll() - .execute(); - - expect(dbRuns).toHaveLength(1); - const storedEvents = JSON.parse(dbRuns[0].events); - - // Should have compacted content into single delta - const contentEvents = storedEvents.filter( - (e: any) => e.type === EventType.TEXT_MESSAGE_CONTENT - ); - expect(contentEvents).toHaveLength(1); - expect(contentEvents[0].delta).toBe('Hello World'); - }); - - it('should not store duplicate message IDs across multiple runs', async () => { - const threadId = 'test-thread-nodupe'; - const messageId = 'shared-msg'; - - // First run with a message - const input1: RunAgentInput = { - threadId, - runId: 'run1', - messages: [{ - id: messageId, - role: 'user', - content: 'First message', - }], - }; - - const agent = new CustomEventAgent([ - { type: EventType.RUN_FINISHED, threadId, runId: 'run1' }, - ]); - - await firstValueFrom( - runner.run({ threadId, agent, input: input1 }).pipe(toArray()) - ); - - // Second run with same message ID - const input2: RunAgentInput = { - threadId, - runId: 'run2', - messages: [{ - id: messageId, - role: 'user', - content: 'First message', - }], - }; - - await firstValueFrom( - runner.run({ threadId, agent, input: input2 }).pipe(toArray()) - ); - - // Check database - message should only be stored in first run - const dbRuns = await db - .selectFrom('agent_runs') - .where('thread_id', '=', threadId) - .selectAll() - .orderBy('created_at', 'asc') - .execute(); - - expect(dbRuns).toHaveLength(2); - - const run1Events = JSON.parse(dbRuns[0].events); - const run2Events = JSON.parse(dbRuns[1].events); - - // First run should have the message events - const run1MessageEvents = run1Events.filter( - (e: any) => e.messageId === messageId - ); - expect(run1MessageEvents.length).toBeGreaterThan(0); - - // Second run should NOT have the message events - const run2MessageEvents = run2Events.filter( - (e: any) => e.messageId === messageId - ); - expect(run2MessageEvents.length).toBe(0); - }); - - it('should handle all message types (user, assistant, tool, system, developer)', async () => { - const threadId = 'test-thread-alltypes'; - const messages: Message[] = [ - { id: 'user-1', role: 'user', content: 'User message' }, - { id: 'assistant-1', role: 'assistant', content: 'Assistant message' }, - { id: 'system-1', role: 'system', content: 'System message' }, - { id: 'developer-1', role: 'developer', content: 'Developer message' }, - { - id: 'tool-1', - role: 'tool', - content: 'Tool result', - toolCallId: 'call-1' - }, - ]; - - const input: RunAgentInput = { - threadId, - runId: 'run1', - messages, - }; - - const agent = new CustomEventAgent([ - { type: EventType.RUN_FINISHED, threadId, runId: 'run1' }, - ]); - - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Connect should get all message types - const connectEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Check each message type is present - const userEvents = connectEvents.filter( - (e: any) => e.type === EventType.TEXT_MESSAGE_START && e.role === 'user' - ); - expect(userEvents.length).toBe(1); - - const assistantEvents = connectEvents.filter( - (e: any) => e.type === EventType.TEXT_MESSAGE_START && e.role === 'assistant' - ); - expect(assistantEvents.length).toBe(1); - - const systemEvents = connectEvents.filter( - (e: any) => e.type === EventType.TEXT_MESSAGE_START && e.role === 'system' - ); - expect(systemEvents.length).toBe(1); - - const developerEvents = connectEvents.filter( - (e: any) => e.type === EventType.TEXT_MESSAGE_START && e.role === 'developer' - ); - expect(developerEvents.length).toBe(1); - - const toolEvents = connectEvents.filter( - (e: any) => e.type === EventType.TOOL_CALL_RESULT - ); - expect(toolEvents.length).toBe(1); - }); - - it('should handle tool calls correctly', async () => { - const threadId = 'test-thread-tools'; - const messageId = 'assistant-msg'; - const toolCallId = 'tool-call-1'; - - const messages: Message[] = [ - { - id: messageId, - role: 'assistant', - content: 'Let me help', - toolCalls: [{ - id: toolCallId, - function: { - name: 'calculator', - arguments: '{"a": 1, "b": 2}' - } - }] - }, - { - id: 'tool-result-1', - role: 'tool', - content: '3', - toolCallId: toolCallId - } - ]; - - const input: RunAgentInput = { - threadId, - runId: 'run1', - messages, - }; - - const agent = new CustomEventAgent([ - { type: EventType.RUN_FINISHED, threadId, runId: 'run1' }, - ]); - - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - const connectEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Check tool call events - const toolCallStart = connectEvents.find( - (e: any) => e.type === EventType.TOOL_CALL_START && e.toolCallId === toolCallId - ) as any; - expect(toolCallStart).toBeDefined(); - expect(toolCallStart.toolCallName).toBe('calculator'); - - const toolCallArgs = connectEvents.find( - (e: any) => e.type === EventType.TOOL_CALL_ARGS && e.toolCallId === toolCallId - ) as any; - expect(toolCallArgs).toBeDefined(); - expect(toolCallArgs.delta).toBe('{"a": 1, "b": 2}'); - - const toolCallEnd = connectEvents.find( - (e: any) => e.type === EventType.TOOL_CALL_END && e.toolCallId === toolCallId - ); - expect(toolCallEnd).toBeDefined(); - - const toolResult = connectEvents.find( - (e: any) => e.type === EventType.TOOL_CALL_RESULT && e.toolCallId === toolCallId - ) as any; - expect(toolResult).toBeDefined(); - expect(toolResult.content).toBe('3'); - }); - - it('should track running state correctly', async () => { - const threadId = 'test-thread-state'; - - // Use a slow agent to ensure we can check running state - const slowAgent: AbstractAgent = { - async runAgent(input, callbacks) { - if (callbacks.onRunStartedEvent) { - await callbacks.onRunStartedEvent(); - } - - await callbacks.onEvent({ - event: { - type: EventType.RUN_STARTED, - threadId: input.threadId, - runId: input.runId, - } - }); - - // Delay to ensure we can check running state - await new Promise(resolve => setTimeout(resolve, 200)); - - await callbacks.onEvent({ - event: { - type: EventType.RUN_FINISHED, - threadId: input.threadId, - runId: input.runId, - } - }); - } - }; - - const input: RunAgentInput = { - threadId, - runId: 'run1', - messages: [], - }; - - // Check not running initially - let isRunning = await runner.isRunning({ threadId }); - expect(isRunning).toBe(false); - - // Start run - const runPromise = runner.run({ threadId, agent: slowAgent, input }); - - // Check running state during execution - await new Promise(resolve => setTimeout(resolve, 50)); - isRunning = await runner.isRunning({ threadId }); - expect(isRunning).toBe(true); - - // Wait for completion - await firstValueFrom(runPromise.pipe(toArray())); - - // Check not running after completion - isRunning = await runner.isRunning({ threadId }); - expect(isRunning).toBe(false); - }); - - it('should handle empty events arrays correctly', async () => { - const threadId = 'test-thread-empty'; - const agent = new CustomEventAgent([]); // No events - const input: RunAgentInput = { - threadId, - runId: 'run1', - messages: [], - }; - - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Check database - should still create a run record - const dbRuns = await db - .selectFrom('agent_runs') - .where('thread_id', '=', threadId) - .selectAll() - .execute(); - - expect(dbRuns).toHaveLength(1); - const storedEvents = JSON.parse(dbRuns[0].events); - expect(storedEvents).toEqual([]); - }); - - it('should handle parent-child run relationships', async () => { - const threadId = 'test-thread-parent-child'; - const agent = new CustomEventAgent([ - { type: EventType.RUN_FINISHED, threadId, runId: 'run1' }, - ]); - - // First run (parent) - await firstValueFrom( - runner.run({ - threadId, - agent, - input: { threadId, runId: 'run1', messages: [] } - }).pipe(toArray()) - ); - - // Second run (child) - await firstValueFrom( - runner.run({ - threadId, - agent, - input: { threadId, runId: 'run2', messages: [] } - }).pipe(toArray()) - ); - - // Check parent-child relationship in database - const dbRuns = await db - .selectFrom('agent_runs') - .where('thread_id', '=', threadId) - .selectAll() - .orderBy('created_at', 'asc') - .execute(); - - expect(dbRuns).toHaveLength(2); - expect(dbRuns[0].parent_run_id).toBeNull(); - expect(dbRuns[1].parent_run_id).toBe('run1'); - }); - - it('should handle database initialization correctly', async () => { - // Check all tables exist - const tables = await db - .selectFrom('sqlite_master') - .where('type', '=', 'table') - .select('name') - .execute(); - - const tableNames = tables.map(t => t.name); - expect(tableNames).toContain('agent_runs'); - expect(tableNames).toContain('run_state'); - expect(tableNames).toContain('schema_version'); - - // Check schema version - const schemaVersion = await db - .selectFrom('schema_version') - .selectAll() - .executeTakeFirst(); - - expect(schemaVersion).toBeDefined(); - expect(schemaVersion?.version).toBe(1); - }); - - it('should handle Redis stream expiry correctly', async () => { - const threadId = 'test-thread-expiry'; - const agent = new MockAgent(); - const input: RunAgentInput = { - threadId, - runId: 'run1', - messages: [], - }; - - // Run with short TTL - const shortTTLRunner = new EnterpriseAgentRunner({ - kysely: db, - redis, - redisSub, - streamRetentionMs: 100, // 100ms retention - streamActiveTTLMs: 50, // 50ms active TTL - lockTTLMs: 1000 - }); - - await firstValueFrom( - shortTTLRunner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Stream should exist immediately after run - const streamKey = `stream:${threadId}:run1`; - let exists = await redis.exists(streamKey); - expect(exists).toBe(1); - - // Wait for retention period to expire - await new Promise(resolve => setTimeout(resolve, 150)); - - // Stream should be gone - exists = await redis.exists(streamKey); - expect(exists).toBe(0); - - await shortTTLRunner.close(); - }); -}); \ No newline at end of file diff --git a/packages/runtime/src/runner/__tests__/event-compaction.test.ts b/packages/runtime/src/runner/__tests__/event-compaction.test.ts deleted file mode 100644 index fe1e40af..00000000 --- a/packages/runtime/src/runner/__tests__/event-compaction.test.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { compactEvents } from "../event-compaction"; -import { BaseEvent, EventType } from "@ag-ui/client"; - -describe("Event Compaction", () => { - describe("Text Message Compaction", () => { - it("should compact multiple text message content events into one", () => { - const events: BaseEvent[] = [ - { 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 any).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: BaseEvent[] = [ - { 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 any).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 any).id).toBe("custom1"); - expect(compacted[4].type).toBe(EventType.CUSTOM); - expect((compacted[4] as any).id).toBe("custom2"); - }); - - it("should handle multiple messages independently", () => { - const events: BaseEvent[] = [ - { 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 any).messageId).toBe("msg1"); - expect(compacted[1].type).toBe(EventType.TEXT_MESSAGE_CONTENT); - expect((compacted[1] as any).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 any).messageId).toBe("msg2"); - expect(compacted[4].type).toBe(EventType.TEXT_MESSAGE_CONTENT); - expect((compacted[4] as any).delta).toBe("Hello there"); - expect(compacted[5].type).toBe(EventType.TEXT_MESSAGE_END); - }); - - it("should handle incomplete messages", () => { - const events: BaseEvent[] = [ - { 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 any).delta).toBe("Incomplete"); - }); - - it("should pass through non-text-message events unchanged", () => { - const events: BaseEvent[] = [ - { 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: BaseEvent[] = [ - { 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 any).delta).toBe("Hello"); - }); - }); - - describe("Tool Call Compaction", () => { - it("should compact multiple tool call args events into one", () => { - const events: BaseEvent[] = [ - { 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 any).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: BaseEvent[] = [ - { 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 any).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 any).id).toBe("custom1"); - expect(compacted[4].type).toBe(EventType.CUSTOM); - expect((compacted[4] as any).id).toBe("custom2"); - }); - - it("should handle multiple tool calls independently", () => { - const events: BaseEvent[] = [ - { 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 any).toolCallId).toBe("tool1"); - expect(compacted[1].type).toBe(EventType.TOOL_CALL_ARGS); - expect((compacted[1] as any).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 any).toolCallId).toBe("tool2"); - expect(compacted[4].type).toBe(EventType.TOOL_CALL_ARGS); - expect((compacted[4] as any).delta).toBe('{"a": 5}'); - expect(compacted[5].type).toBe(EventType.TOOL_CALL_END); - }); - - it("should handle incomplete tool calls", () => { - const events: BaseEvent[] = [ - { 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 any).delta).toBe('{"incomplete": '); - }); - - it("should handle empty args deltas", () => { - const events: BaseEvent[] = [ - { 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 any).delta).toBe('{"test": true}'); - }); - }); - - describe("Mixed Compaction", () => { - it("should handle text messages and tool calls together", () => { - const events: BaseEvent[] = [ - { 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 any).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 any).delta).toBe('{"q": "test"}'); - expect(compacted[5].type).toBe(EventType.TOOL_CALL_END); - }); - }); -}); \ No newline at end of file diff --git a/packages/runtime/src/runner/__tests__/in-memory-runner.e2e.test.ts b/packages/runtime/src/runner/__tests__/in-memory-runner.e2e.test.ts new file mode 100644 index 00000000..bfd87a72 --- /dev/null +++ b/packages/runtime/src/runner/__tests__/in-memory-runner.e2e.test.ts @@ -0,0 +1,750 @@ +import { describe, it, expect } from "vitest"; +import { InMemoryAgentRunner } from "../in-memory"; +import { + AbstractAgent, + BaseEvent, + EventType, + Message, + RunAgentInput, + RunErrorEvent, + RunFinishedEvent, + RunStartedEvent, +} from "@ag-ui/client"; +import { EMPTY, Subscription, firstValueFrom, from } from "rxjs"; +import { toArray } from "rxjs/operators"; + +type RunCallbacks = { + onEvent: (event: { event: BaseEvent }) => void; + onNewMessage?: (args: { message: Message }) => void; + onRunStartedEvent?: () => void; +}; + +interface EmitAgentOptions { + events?: BaseEvent[]; + emitDefaultRunStarted?: boolean; + includeRunFinished?: boolean; + runFinishedEvent?: RunFinishedEvent; + afterEvent?: (args: { event: BaseEvent; index: number }) => void | Promise; +} + +class EmitAgent extends AbstractAgent { + constructor(private readonly options: EmitAgentOptions = {}) { + super(); + } + + async runAgent(input: RunAgentInput, callbacks: RunCallbacks): Promise { + const { + emitDefaultRunStarted = true, + includeRunFinished = true, + runFinishedEvent, + afterEvent, + } = this.options; + const scriptedEvents = this.options.events ?? []; + + let index = 0; + const emit = async (event: BaseEvent) => { + callbacks.onEvent({ event }); + if (event.type === EventType.RUN_STARTED) { + callbacks.onRunStartedEvent?.(); + } + await afterEvent?.({ event, index }); + index += 1; + }; + + if (emitDefaultRunStarted) { + const runStarted: RunStartedEvent = { + type: EventType.RUN_STARTED, + threadId: input.threadId, + runId: input.runId, + parentRunId: input.parentRunId, + }; + await emit(runStarted); + } + + for (const event of scriptedEvents) { + await emit(event); + } + + const hasRunFinishedEvent = + scriptedEvents.some((event) => event.type === EventType.RUN_FINISHED) || + runFinishedEvent?.type === EventType.RUN_FINISHED; + + if (includeRunFinished && !hasRunFinishedEvent) { + const finishEvent: RunFinishedEvent = + runFinishedEvent ?? { + type: EventType.RUN_FINISHED, + threadId: input.threadId, + runId: input.runId, + }; + await emit(finishEvent); + } + } + + clone(): AbstractAgent { + return new EmitAgent({ + ...this.options, + events: this.options.events ? [...this.options.events] : undefined, + }); + } + + protected run(): ReturnType { + return EMPTY; + } + + protected connect(): ReturnType { + return EMPTY; + } +} + +class ReplayAgent extends AbstractAgent { + constructor(private readonly replayEvents: BaseEvent[], threadId: string) { + super({ threadId }); + } + + async runAgent(): Promise { + throw new Error("not used"); + } + + protected run(): ReturnType { + return EMPTY; + } + + protected connect(): ReturnType { + return from(this.replayEvents); + } +} + +class RunnerConnectAgent extends AbstractAgent { + constructor(private readonly runner: InMemoryAgentRunner, threadId: string) { + super({ threadId }); + } + + async runAgent(): Promise { + throw new Error("not used"); + } + + protected run(): ReturnType { + return EMPTY; + } + + protected connect(input: RunAgentInput): ReturnType { + return this.runner.connect({ threadId: input.threadId }); + } +} + +type Deferred = { + promise: Promise; + resolve: (value: T) => void; + reject: (reason?: unknown) => void; +}; + +function createDeferred(): Deferred { + let resolve!: (value: T) => void; + let reject!: (reason?: unknown) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +} + +async function collectEvents(observable: ReturnType | ReturnType) { + return firstValueFrom(observable.pipe(toArray())); +} + +function createRunInput({ + threadId, + runId, + messages, + state, + parentRunId, +}: { + threadId: string; + runId: string; + messages: Message[]; + state?: Record; + parentRunId?: string | null; +}): RunAgentInput { + return { + threadId, + runId, + parentRunId: parentRunId ?? undefined, + state: state ?? {}, + messages, + tools: [], + context: [], + forwardedProps: undefined, + }; +} + +function expectRunStartedEvent(event: BaseEvent, expectedMessages: Message[]) { + expect(event.type).toBe(EventType.RUN_STARTED); + const runStarted = event as RunStartedEvent; + expect(runStarted.input?.messages).toEqual(expectedMessages); +} + +function createTextMessageEvents({ + messageId, + role = "assistant", + content, +}: { + messageId: string; + role?: "assistant" | "developer" | "system" | "user"; + content: string; +}): BaseEvent[] { + return [ + { + type: EventType.TEXT_MESSAGE_START, + messageId, + role, + }, + { + type: EventType.TEXT_MESSAGE_CONTENT, + messageId, + delta: content, + }, + { + type: EventType.TEXT_MESSAGE_END, + messageId, + }, + ] as BaseEvent[]; +} + +function createToolCallEvents({ + toolCallId, + parentMessageId, + toolName, + argsJson, + resultMessageId, + resultContent, +}: { + toolCallId: string; + parentMessageId: string; + toolName: string; + argsJson: string; + resultMessageId: string; + resultContent: string; +}): BaseEvent[] { + return [ + { + type: EventType.TOOL_CALL_START, + toolCallId, + toolCallName: toolName, + parentMessageId, + }, + { + type: EventType.TOOL_CALL_ARGS, + toolCallId, + delta: argsJson, + }, + { + type: EventType.TOOL_CALL_END, + toolCallId, + }, + { + type: EventType.TOOL_CALL_RESULT, + toolCallId, + messageId: resultMessageId, + content: resultContent, + role: "tool", + }, + ] as BaseEvent[]; +} + +describe("InMemoryAgentRunner e2e", () => { + describe("Fresh Replay After Single Run", () => { + it("replays sanitized message history on connectAgent", async () => { + const runner = new InMemoryAgentRunner(); + const threadId = "thread-fresh-replay"; + const existingMessage: Message = { + id: "message-existing", + role: "user", + content: "Hello there", + }; + + const runEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent(), + input: createRunInput({ + threadId, + runId: "run-0", + messages: [existingMessage], + }), + }), + ); + + expectRunStartedEvent(runEvents[0], [existingMessage]); + expect(runEvents.at(-1)?.type).toBe(EventType.RUN_FINISHED); + + const replayEvents = await collectEvents(runner.connect({ threadId })); + const replayAgent = new ReplayAgent(replayEvents, threadId); + await replayAgent.connectAgent({ runId: "replay-run" }); + + expect(replayAgent.messages).toEqual([existingMessage]); + }); + }); + + describe("New Messages on Subsequent Runs", () => { + it("merges new message IDs without duplicating history", async () => { + const runner = new InMemoryAgentRunner(); + const threadId = "thread-subsequent-runs"; + const existingMessage: Message = { + id: "msg-existing", + role: "user", + content: "First turn", + }; + + const initialRunEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent(), + input: createRunInput({ + threadId, + runId: "run-0", + messages: [existingMessage], + }), + }), + ); + expectRunStartedEvent(initialRunEvents[0], [existingMessage]); + + const newMessage: Message = { + id: "msg-new", + role: "user", + content: "Second turn", + }; + + const secondRunEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent(), + input: createRunInput({ + threadId, + runId: "run-1", + messages: [existingMessage, newMessage], + }), + }), + ); + + expectRunStartedEvent(secondRunEvents[0], [newMessage]); + + const replayEvents = await collectEvents(runner.connect({ threadId })); + const replayAgent = new ReplayAgent(replayEvents, threadId); + await replayAgent.connectAgent({ runId: "replay-run" }); + + expect(replayAgent.messages).toEqual([existingMessage, newMessage]); + expect(new Set(replayAgent.messages.map((message) => message.id)).size).toBe( + replayAgent.messages.length, + ); + }); + }); + + describe("Fresh Agent Connection After Prior Runs", () => { + it("hydrates a brand-new agent via connect()", async () => { + const runner = new InMemoryAgentRunner(); + const threadId = "thread-new-agent-connection"; + const existingMessage: Message = { + id: "existing-connection", + role: "user", + content: "Persist me", + }; + + const runEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent(), + input: createRunInput({ + threadId, + runId: "run-0", + messages: [existingMessage], + }), + }), + ); + expectRunStartedEvent(runEvents[0], [existingMessage]); + + const connectingAgent = new RunnerConnectAgent(runner, threadId); + await connectingAgent.connectAgent({ runId: "connect-run" }); + + expect(connectingAgent.messages).toEqual([existingMessage]); + }); + }); + + describe("Mixed Roles and Tool Results", () => { + it("preserves agent-emitted tool events alongside heterogeneous inputs", async () => { + const runner = new InMemoryAgentRunner(); + const threadId = "thread-mixed-roles"; + + const systemMessage: Message = { + id: "sys-1", + role: "system", + content: "Global directive", + }; + const developerMessage: Message = { + id: "dev-1", + role: "developer", + content: "Internal guidance", + }; + const userMessage: Message = { + id: "user-1", + role: "user", + content: "Need the weather", + }; + const baseMessages = [systemMessage, developerMessage, userMessage]; + + const assistantMessageId = "assistant-1"; + const toolCallId = "tool-call-1"; + const toolMessageId = "tool-msg-1"; + + const agentEvents: BaseEvent[] = [ + ...createTextMessageEvents({ + messageId: assistantMessageId, + content: "Calling the weather tool", + }), + ...createToolCallEvents({ + toolCallId, + parentMessageId: assistantMessageId, + toolName: "getWeather", + argsJson: '{"location":"NYC"}', + resultMessageId: toolMessageId, + resultContent: '{"temp":72}', + }), + ]; + + const runEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent({ events: agentEvents }), + input: createRunInput({ + threadId, + runId: "run-0", + messages: baseMessages, + }), + }), + ); + + expectRunStartedEvent(runEvents[0], baseMessages); + expect(runEvents.filter((event) => event.type === EventType.TOOL_CALL_RESULT)).toHaveLength(1); + + const replayEvents = await collectEvents(runner.connect({ threadId })); + const replayAgent = new ReplayAgent(replayEvents, threadId); + await replayAgent.connectAgent({ runId: "replay-run" }); + + expect(replayAgent.messages).toEqual([ + systemMessage, + developerMessage, + userMessage, + { + id: assistantMessageId, + role: "assistant", + content: "Calling the weather tool", + toolCalls: [ + { + id: toolCallId, + type: "function", + function: { + name: "getWeather", + arguments: '{"location":"NYC"}', + }, + }, + ], + }, + { + id: toolMessageId, + role: "tool", + content: '{"temp":72}', + toolCallId, + }, + ]); + expect(replayAgent.messages.filter((message) => message.role === "tool")).toHaveLength(1); + }); + }); + + describe("Multiple Consecutive Runs with Agent Output", () => { + it("deduplicates input history while emitting each agent message once", async () => { + const runner = new InMemoryAgentRunner(); + const threadId = "thread-multi-runs"; + const systemMessage: Message = { + id: "system-shared", + role: "system", + content: "System context", + }; + const userMessages: Message[] = []; + + for (let index = 0; index < 3; index += 1) { + const userMessage: Message = { + id: `user-${index + 1}`, + role: "user", + content: `User message ${index + 1}`, + }; + userMessages.push(userMessage); + + const messagesForRun = [systemMessage, ...userMessages]; + const assistantId = `assistant-${index + 1}`; + const toolCallId = `tool-call-${index + 1}`; + const toolMessageId = `tool-msg-${index + 1}`; + + const events: BaseEvent[] = [ + ...createTextMessageEvents({ + messageId: assistantId, + content: `Assistant reply ${index + 1}`, + }), + ...createToolCallEvents({ + toolCallId, + parentMessageId: assistantId, + toolName: `tool-${index + 1}`, + argsJson: `{"step":${index + 1}}`, + resultMessageId: toolMessageId, + resultContent: `{"ok":${index + 1}}`, + }), + ]; + + const runEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent({ events }), + input: createRunInput({ + threadId, + runId: `run-${index}`, + messages: messagesForRun, + }), + }), + ); + + if (index === 0) { + expectRunStartedEvent(runEvents[0], messagesForRun); + } else { + expectRunStartedEvent(runEvents[0], [userMessage]); + } + expect(runEvents.at(-1)?.type).toBe(EventType.RUN_FINISHED); + } + + const replayEvents = await collectEvents(runner.connect({ threadId })); + const replayAgent = new ReplayAgent(replayEvents, threadId); + await replayAgent.connectAgent({ runId: "replay-final" }); + + const finalMessages = replayAgent.messages; + expect(new Set(finalMessages.map((message) => message.id)).size).toBe(finalMessages.length); + const roleCounts = finalMessages.reduce>((counts, message) => { + counts[message.role] = (counts[message.role] ?? 0) + 1; + return counts; + }, {}); + expect(roleCounts.system).toBe(1); + expect(roleCounts.user).toBe(3); + expect(roleCounts.assistant).toBe(3); + expect(roleCounts.tool).toBe(3); + }); + }); + + describe("Agent-Provided RUN_STARTED input", () => { + it("forwards the agent-specified payload without sanitizing", async () => { + const runner = new InMemoryAgentRunner(); + const threadId = "thread-custom-run-started"; + const runId = "run-0"; + + const customMessages: Message[] = [ + { + id: "custom-user", + role: "user", + content: "Pre-sent content", + }, + ]; + const customInput: RunAgentInput = { + threadId, + runId, + parentRunId: undefined, + state: { injected: true }, + messages: customMessages, + tools: [], + context: [], + forwardedProps: { source: "agent" }, + }; + const customRunStarted: RunStartedEvent = { + type: EventType.RUN_STARTED, + threadId, + runId, + parentRunId: null, + input: customInput, + }; + + const agentEvents: BaseEvent[] = [ + customRunStarted, + ...createTextMessageEvents({ + messageId: "agent-message", + content: "Custom start acknowledged", + }), + ]; + + const runEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent({ + events: agentEvents, + emitDefaultRunStarted: false, + }), + input: createRunInput({ + threadId, + runId, + messages: [], + }), + }), + ); + + expect(runEvents[0]).toEqual(customRunStarted); + expect(runEvents.filter((event) => event.type === EventType.RUN_FINISHED)).toHaveLength(1); + + const replayEvents = await collectEvents(runner.connect({ threadId })); + const replayAgent = new ReplayAgent(replayEvents, threadId); + await replayAgent.connectAgent({ runId: "replay-run" }); + expect(replayAgent.messages.find((message) => message.id === "custom-user")).toEqual( + customMessages[0], + ); + }); + }); + + describe("Concurrent Connections During Run", () => { + it("streams in-flight events to live subscribers and persists final history", async () => { + const runner = new InMemoryAgentRunner(); + const threadId = "thread-concurrency"; + const runId = "run-live"; + const initialMessage: Message = { + id: "initial-user", + role: "user", + content: "Start run", + }; + + const runStartedSignal = createDeferred(); + const resumeSignal = createDeferred(); + + const agent = new EmitAgent({ + events: [ + ...createTextMessageEvents({ + messageId: "assistant-live", + content: "Streaming content", + }), + ], + afterEvent: async ({ event }) => { + if (event.type === EventType.RUN_STARTED) { + runStartedSignal.resolve(); + await resumeSignal.promise; + } + }, + }); + + const runEvents: BaseEvent[] = []; + const run$ = runner.run({ + threadId, + agent, + input: createRunInput({ + threadId, + runId, + messages: [initialMessage], + }), + }); + + let runSubscription: Subscription; + const runCompletion = new Promise((resolve, reject) => { + runSubscription = run$.subscribe({ + next: (event) => runEvents.push(event), + error: (error) => { + runSubscription.unsubscribe(); + reject(error); + }, + complete: () => { + runSubscription.unsubscribe(); + resolve(); + }, + }); + }); + + await runStartedSignal.promise; + + const liveEvents: BaseEvent[] = []; + const connect$ = runner.connect({ threadId }); + let connectSubscription: Subscription; + const connectCompletion = new Promise((resolve, reject) => { + connectSubscription = connect$.subscribe({ + next: (event) => liveEvents.push(event), + error: (error) => { + connectSubscription.unsubscribe(); + reject(error); + }, + complete: () => { + connectSubscription.unsubscribe(); + resolve(); + }, + }); + }); + + resumeSignal.resolve(); + + await Promise.all([runCompletion, connectCompletion]); + + expectRunStartedEvent(runEvents[0], [initialMessage]); + expect(runEvents.at(-1)?.type).toBe(EventType.RUN_FINISHED); + expect(liveEvents).toEqual(runEvents); + + const persistedEvents = await collectEvents(runner.connect({ threadId })); + expect(persistedEvents).toEqual(runEvents); + + const replayAgent = new ReplayAgent(persistedEvents, threadId); + await replayAgent.connectAgent({ runId: "replay-run" }); + expect(replayAgent.messages.map((message) => message.id)).toEqual([ + initialMessage.id, + "assistant-live", + ]); + }); + }); + + describe("Error Handling", () => { + it("propagates RUN_ERROR while retaining input history", async () => { + const runner = new InMemoryAgentRunner(); + const threadId = "thread-run-error"; + const userMessage: Message = { + id: "error-user", + role: "user", + content: "Trigger error", + }; + + const runErrorEvent: RunErrorEvent = { + type: EventType.RUN_ERROR, + message: "Agent failure", + }; + + const runEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent({ + events: [runErrorEvent], + includeRunFinished: false, + }), + input: createRunInput({ + threadId, + runId: "run-error", + messages: [userMessage], + }), + }), + ); + + expectRunStartedEvent(runEvents[0], [userMessage]); + expect(runEvents.at(-1)).toEqual(runErrorEvent); + + const replayEvents = await collectEvents(runner.connect({ threadId })); + const replayAgent = new ReplayAgent(replayEvents, threadId); + const capturedRunErrors: RunErrorEvent[] = []; + const result = await replayAgent.connectAgent( + { runId: "replay-run" }, + { + onRunErrorEvent: ({ event }) => { + capturedRunErrors.push(event); + }, + }, + ); + + expect(runEvents.at(-1)?.type).toBe(EventType.RUN_ERROR); + expect(capturedRunErrors).toHaveLength(1); + expect(capturedRunErrors[0]).toMatchObject(runErrorEvent); + expect(result.newMessages).toEqual([userMessage]); + expect(replayAgent.messages).toEqual([userMessage]); + }); + }); +}); diff --git a/packages/runtime/src/runner/__tests__/in-memory-runner.test.ts b/packages/runtime/src/runner/__tests__/in-memory-runner.test.ts index cc6e6035..d63d302f 100644 --- a/packages/runtime/src/runner/__tests__/in-memory-runner.test.ts +++ b/packages/runtime/src/runner/__tests__/in-memory-runner.test.ts @@ -6,20 +6,21 @@ import { EventType, Message, RunAgentInput, + RunStartedEvent, TextMessageContentEvent, TextMessageEndEvent, TextMessageStartEvent, ToolCallResultEvent, } from "@ag-ui/client"; -import { firstValueFrom } from "rxjs"; +import { EMPTY, firstValueFrom } from "rxjs"; import { toArray } from "rxjs/operators"; class TestAgent extends AbstractAgent { - private events: BaseEvent[] = []; - - constructor(events: BaseEvent[] = []) { + constructor( + private readonly events: BaseEvent[] = [], + private readonly emitDefaultRunStarted = true, + ) { super(); - this.events = events; } async runAgent( @@ -28,31 +29,33 @@ class TestAgent extends AbstractAgent { onEvent: (event: { event: BaseEvent }) => void; onNewMessage?: (args: { message: Message }) => void; onRunStartedEvent?: () => void; - } + }, ): Promise { - // Call onRunStartedEvent to trigger message injection - if (options.onRunStartedEvent) { - options.onRunStartedEvent(); + if (this.emitDefaultRunStarted) { + const runStarted: RunStartedEvent = { + type: EventType.RUN_STARTED, + threadId: input.threadId, + runId: input.runId, + }; + options.onEvent({ event: runStarted }); + options.onRunStartedEvent?.(); } - // Emit test events for (const event of this.events) { options.onEvent({ event }); } - - // Call onNewMessage for assistant messages we generate - if (options.onNewMessage) { - const assistantMessage: Message = { - id: "assistant-msg-1", - role: "assistant", - content: "Test response", - }; - options.onNewMessage({ message: assistantMessage }); - } } clone(): AbstractAgent { - return new TestAgent(this.events); + return new TestAgent(this.events, this.emitDefaultRunStarted); + } + + protected run(): ReturnType { + return EMPTY; + } + + protected connect(): ReturnType { + return EMPTY; } } @@ -63,35 +66,10 @@ describe("InMemoryAgentRunner", () => { runner = new InMemoryAgentRunner(); }); - describe("Event Storage", () => { - it("should not store empty events", async () => { - const threadId = "test-thread-no-empty"; - const agent = new TestAgent([]); - - const input: RunAgentInput = { - messages: [], - state: {}, - threadId, - runId: "run-1", - }; - - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Connect and get stored events - const storedEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // No events should be stored - expect(storedEvents).toHaveLength(0); - }); - - it("should store and compact text message events", async () => { - const threadId = "test-thread-compact"; - - const events: BaseEvent[] = [ + describe("RunStarted payload", () => { + it("emits RUN_STARTED before agent events", async () => { + const threadId = "in-memory-basic"; + const agent = new TestAgent([ { type: EventType.TEXT_MESSAGE_START, messageId: "msg-1", @@ -102,382 +80,185 @@ describe("InMemoryAgentRunner", () => { messageId: "msg-1", delta: "Hello", } as TextMessageContentEvent, - { - type: EventType.TEXT_MESSAGE_CONTENT, - messageId: "msg-1", - delta: " world", - } as TextMessageContentEvent, { type: EventType.TEXT_MESSAGE_END, messageId: "msg-1", } as TextMessageEndEvent, - ]; - - const agent = new TestAgent(events); - const input: RunAgentInput = { - messages: [], - state: {}, - threadId, - runId: "run-1", - }; - - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Connect and get stored events - const storedEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Should have start, single content (compacted), and end events - expect(storedEvents).toHaveLength(3); - expect(storedEvents[0].type).toBe(EventType.TEXT_MESSAGE_START); - expect(storedEvents[1].type).toBe(EventType.TEXT_MESSAGE_CONTENT); - expect((storedEvents[1] as TextMessageContentEvent).delta).toBe("Hello world"); - expect(storedEvents[2].type).toBe(EventType.TEXT_MESSAGE_END); - }); - - it("should not store duplicate message IDs across multiple runs", async () => { - const threadId = "test-thread-no-duplicates"; - const userMessage: Message = { - id: "user-msg-1", - role: "user", - content: "Hello", - }; - - // First run - const agent1 = new TestAgent([ - { - type: EventType.TEXT_MESSAGE_START, - messageId: "assistant-msg-1", - role: "assistant", - } as TextMessageStartEvent, - { - type: EventType.TEXT_MESSAGE_CONTENT, - messageId: "assistant-msg-1", - delta: "Hi from run 1", - } as TextMessageContentEvent, - { - type: EventType.TEXT_MESSAGE_END, - messageId: "assistant-msg-1", - } as TextMessageEndEvent, ]); - const input1: RunAgentInput = { - messages: [userMessage], - state: {}, - threadId, - runId: "run-1", - }; - - await firstValueFrom( - runner.run({ threadId, agent: agent1, input: input1 }).pipe(toArray()) + const events = await firstValueFrom( + runner + .run({ + threadId, + agent, + input: { threadId, runId: "run-1", messages: [], state: {} }, + }) + .pipe(toArray()), ); - // Second run with same user message plus new one - const newUserMessage: Message = { - id: "user-msg-2", - role: "user", - content: "How are you?", - }; - - const agent2 = new TestAgent([ - { - type: EventType.TEXT_MESSAGE_START, - messageId: "assistant-msg-2", - role: "assistant", - } as TextMessageStartEvent, - { - type: EventType.TEXT_MESSAGE_CONTENT, - messageId: "assistant-msg-2", - delta: "Hi from run 2", - } as TextMessageContentEvent, - { - type: EventType.TEXT_MESSAGE_END, - messageId: "assistant-msg-2", - } as TextMessageEndEvent, - ]); - - const input2: RunAgentInput = { - messages: [userMessage, newUserMessage], - state: {}, - threadId, - runId: "run-2", - }; - - await firstValueFrom( - runner.run({ threadId, agent: agent2, input: input2 }).pipe(toArray()) - ); - - // Connect and get all stored events - const storedEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Count unique message IDs - const messageIds = new Set(); - for (const event of storedEvents) { - if ('messageId' in event && event.messageId) { - messageIds.add(event.messageId); - } - } - - // Should have: user-msg-1, assistant-msg-1, user-msg-2, assistant-msg-2 - expect(messageIds.size).toBe(4); - expect(messageIds.has("user-msg-1")).toBe(true); - expect(messageIds.has("assistant-msg-1")).toBe(true); - expect(messageIds.has("user-msg-2")).toBe(true); - expect(messageIds.has("assistant-msg-2")).toBe(true); - - // Check that each message ID appears only once in start events - const startEvents = storedEvents.filter(e => e.type === EventType.TEXT_MESSAGE_START); - const startMessageIds = startEvents.map(e => (e as any).messageId); - const uniqueStartIds = new Set(startMessageIds); - expect(startMessageIds.length).toBe(uniqueStartIds.size); - }); - - it("should store all types of new messages (user, assistant, tool, system, developer)", async () => { - const threadId = "test-thread-all-messages"; - - const messages: Message[] = [ - { id: "user-1", role: "user", content: "User message" }, - { id: "system-1", role: "system", content: "System message" }, - { id: "developer-1", role: "developer", content: "Developer message" }, - { id: "tool-1", role: "tool", content: "Tool result", toolCallId: "tool-call-1" }, - ]; - - const agent = new TestAgent([ - { - type: EventType.TEXT_MESSAGE_START, - messageId: "assistant-1", - role: "assistant", - } as TextMessageStartEvent, - { - type: EventType.TEXT_MESSAGE_CONTENT, - messageId: "assistant-1", - delta: "Assistant response", - } as TextMessageContentEvent, - { - type: EventType.TEXT_MESSAGE_END, - messageId: "assistant-1", - } as TextMessageEndEvent, - ]); - - const input: RunAgentInput = { - messages, - state: {}, - threadId, - runId: "run-1", - }; - - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Connect and get stored events - const storedEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Collect all message IDs - const messageIds = new Set(); - for (const event of storedEvents) { - if ('messageId' in event && event.messageId) { - messageIds.add(event.messageId); - } - } - - // Should have all message types - expect(messageIds.has("user-1")).toBe(true); - expect(messageIds.has("system-1")).toBe(true); - expect(messageIds.has("developer-1")).toBe(true); - expect(messageIds.has("tool-1")).toBe(true); - expect(messageIds.has("assistant-1")).toBe(true); - - // Check tool result event - const toolEvents = storedEvents.filter(e => e.type === EventType.TOOL_CALL_RESULT); - expect(toolEvents).toHaveLength(1); - const toolEvent = toolEvents[0] as ToolCallResultEvent; - expect(toolEvent.messageId).toBe("tool-1"); - expect(toolEvent.content).toBe("Tool result"); - expect(toolEvent.toolCallId).toBe("tool-call-1"); + expect(events).toHaveLength(4); + expect(events[0].type).toBe(EventType.RUN_STARTED); + const compacted = events.slice(1); + 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"); + expect(compacted[2].type).toBe(EventType.TEXT_MESSAGE_END); }); - it("should only store new messages for each run", async () => { - const threadId = "test-thread-only-new"; - - // First run - const message1: Message = { id: "msg-1", role: "user", content: "First" }; - const agent1 = new TestAgent([]); - const input1: RunAgentInput = { - messages: [message1], - state: {}, - threadId, - runId: "run-1", - }; + it("attaches only new messages to the RUN_STARTED input", async () => { + const threadId = "in-memory-new-messages"; + const existing: Message = { id: "existing-msg", role: "user", content: "Hi" }; await firstValueFrom( - runner.run({ threadId, agent: agent1, input: input1 }).pipe(toArray()) + runner + .run({ + threadId, + agent: new TestAgent(), + input: { threadId, runId: "run-0", messages: [existing], state: {} }, + }) + .pipe(toArray()), ); - // Second run with old message and new message - const message2: Message = { id: "msg-2", role: "user", content: "Second" }; - const agent2 = new TestAgent([]); - const input2: RunAgentInput = { - messages: [message1, message2], // Include old message for context - state: {}, - threadId, - runId: "run-2", - }; - - await firstValueFrom( - runner.run({ threadId, agent: agent2, input: input2 }).pipe(toArray()) + const newMessage: Message = { id: "new-msg", role: "user", content: "Follow up" }; + + const secondRun = await firstValueFrom( + runner + .run({ + threadId, + agent: new TestAgent(), + input: { + threadId, + runId: "run-1", + messages: [existing, newMessage], + state: { counter: 1 }, + }, + }) + .pipe(toArray()), ); - // Third run with all old messages and one new - const message3: Message = { id: "msg-3", role: "user", content: "Third" }; - const agent3 = new TestAgent([]); - const input3: RunAgentInput = { - messages: [message1, message2, message3], - state: {}, - threadId, - runId: "run-3", - }; + const runStarted = secondRun[0] as RunStartedEvent; + expect(runStarted.input?.messages?.map((m) => m.id)).toEqual(["new-msg"]); + expect(runStarted.input?.state).toEqual({ counter: 1 }); - await firstValueFrom( - runner.run({ threadId, agent: agent3, input: input3 }).pipe(toArray()) + const connectEvents = await firstValueFrom( + runner.connect({ threadId }).pipe(toArray()), ); - - // Connect and verify each message appears only once - const storedEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Count start events for each message - const startEvents = storedEvents.filter(e => e.type === EventType.TEXT_MESSAGE_START); - const messageIdCounts = new Map(); - - for (const event of startEvents) { - const messageId = (event as any).messageId; - messageIdCounts.set(messageId, (messageIdCounts.get(messageId) || 0) + 1); - } - - // Each message should appear exactly once - expect(messageIdCounts.get("msg-1")).toBe(1); - expect(messageIdCounts.get("msg-2")).toBe(1); - expect(messageIdCounts.get("msg-3")).toBe(1); + const latestRunStarted = connectEvents + .filter((event): event is RunStartedEvent => event.type === EventType.RUN_STARTED) + .pop(); + expect(latestRunStarted?.input?.messages?.map((m) => m.id)).toEqual(["new-msg"]); }); - it("should handle tool messages correctly", async () => { - const threadId = "test-thread-tool-messages"; - - const toolMessage: Message = { - id: "tool-msg-1", - role: "tool", - content: "Tool execution result", - toolCallId: "tool-call-123", - }; - - const agent = new TestAgent([]); - const input: RunAgentInput = { - messages: [toolMessage], - state: {}, + it("preserves agent-provided RUN_STARTED input", async () => { + const threadId = "in-memory-agent-input"; + const providedInput: RunAgentInput = { threadId, - runId: "run-1", + runId: "run-preserve", + messages: [], + state: { fromAgent: true }, }; - await firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) + const agent = new TestAgent( + [ + { + type: EventType.RUN_STARTED, + threadId, + runId: "run-preserve", + input: providedInput, + } as RunStartedEvent, + ], + false, ); - // Connect and get stored events - const storedEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) + const events = await firstValueFrom( + runner + .run({ + threadId, + agent, + input: { + threadId, + runId: "run-preserve", + messages: [{ id: "extra", role: "user", content: "hi" }], + state: {}, + }, + }) + .pipe(toArray()), ); - // Should have the tool result event - const toolEvents = storedEvents.filter(e => e.type === EventType.TOOL_CALL_RESULT); - expect(toolEvents).toHaveLength(1); - - const toolEvent = toolEvents[0] as ToolCallResultEvent; - expect(toolEvent.messageId).toBe("tool-msg-1"); - expect(toolEvent.toolCallId).toBe("tool-call-123"); - expect(toolEvent.content).toBe("Tool execution result"); - expect(toolEvent.role).toBe("tool"); + expect(events).toHaveLength(1); + const runStarted = events[0] as RunStartedEvent; + expect(runStarted.input).toBe(providedInput); }); }); - describe("Run Isolation", () => { - it("should store each run's events separately", async () => { - const threadId = "test-thread-isolation"; - - // First run - const agent1 = new TestAgent([ + describe("Event propagation", () => { + it("replays agent events for new connections", async () => { + const threadId = "in-memory-replay"; + const agent = new TestAgent([ { type: EventType.TEXT_MESSAGE_START, - messageId: "run1-msg", + messageId: "msg-1", role: "assistant", } as TextMessageStartEvent, { type: EventType.TEXT_MESSAGE_CONTENT, - messageId: "run1-msg", - delta: "From run 1", + messageId: "msg-1", + delta: "Hello", } as TextMessageContentEvent, { type: EventType.TEXT_MESSAGE_END, - messageId: "run1-msg", + messageId: "msg-1", } as TextMessageEndEvent, ]); await firstValueFrom( - runner.run({ - threadId, - agent: agent1, - input: { messages: [], state: {}, threadId, runId: "run-1" }, - }).pipe(toArray()) + runner + .run({ + threadId, + agent, + input: { threadId, runId: "run-1", messages: [], state: {} }, + }) + .pipe(toArray()), ); - // Second run - const agent2 = new TestAgent([ - { - type: EventType.TEXT_MESSAGE_START, - messageId: "run2-msg", - role: "assistant", - } as TextMessageStartEvent, - { - type: EventType.TEXT_MESSAGE_CONTENT, - messageId: "run2-msg", - delta: "From run 2", - } as TextMessageContentEvent, - { - type: EventType.TEXT_MESSAGE_END, - messageId: "run2-msg", - } as TextMessageEndEvent, + const connectEvents = await firstValueFrom( + runner.connect({ threadId }).pipe(toArray()), + ); + + expect(connectEvents).toHaveLength(4); + expect(connectEvents[0].type).toBe(EventType.RUN_STARTED); + expect(connectEvents.slice(1).map((event) => event.type)).toEqual([ + EventType.TEXT_MESSAGE_START, + EventType.TEXT_MESSAGE_CONTENT, + EventType.TEXT_MESSAGE_END, ]); + }); - await firstValueFrom( - runner.run({ - threadId, - agent: agent2, - input: { messages: [], state: {}, threadId, runId: "run-2" }, - }).pipe(toArray()) - ); + it("keeps agent-generated tool results", async () => { + const threadId = "in-memory-tool-results"; + const agent = new TestAgent([ + { + type: EventType.TOOL_CALL_RESULT, + messageId: "tool-msg", + toolCallId: "tool-call", + content: "42", + role: "tool", + } as ToolCallResultEvent, + ]); - // Connect and verify both runs' events are present - const storedEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) + const events = await firstValueFrom( + runner + .run({ + threadId, + agent, + input: { threadId, runId: "run-1", messages: [], state: {} }, + }) + .pipe(toArray()), ); - const messageIds = new Set(); - for (const event of storedEvents) { - if ('messageId' in event && event.messageId) { - messageIds.add(event.messageId); - } - } - - expect(messageIds.has("run1-msg")).toBe(true); - expect(messageIds.has("run2-msg")).toBe(true); + expect(events).toHaveLength(2); + const [, toolResult] = events; + expect(toolResult.type).toBe(EventType.TOOL_CALL_RESULT); }); }); -}); \ No newline at end of file +}); diff --git a/packages/runtime/src/runner/__tests__/sqlite-runner.test.ts b/packages/runtime/src/runner/__tests__/sqlite-runner.test.ts deleted file mode 100644 index 46e2fd26..00000000 --- a/packages/runtime/src/runner/__tests__/sqlite-runner.test.ts +++ /dev/null @@ -1,975 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { SqliteAgentRunner } from "../sqlite"; -import { InMemoryAgentRunner } from "../in-memory"; -import { AbstractAgent, BaseEvent, RunAgentInput, EventType } from "@ag-ui/client"; -import { firstValueFrom } from "rxjs"; -import { toArray } from "rxjs/operators"; -import Database from "better-sqlite3"; -import * as fs from "fs"; -import * as path from "path"; -import * as os from "os"; - -// Mock agent for testing -class MockAgent extends AbstractAgent { - private events: BaseEvent[]; - - constructor(events: BaseEvent[] = []) { - super(); - this.events = events; - } - - async runAgent( - input: RunAgentInput, - options: { - onEvent: (event: { event: BaseEvent }) => void; - onNewMessage?: (args: { message: any }) => void; - onRunStartedEvent?: () => void; - } - ): Promise { - // Call onRunStartedEvent if provided - if (options.onRunStartedEvent) { - options.onRunStartedEvent(); - } - - // Emit all events - for (const event of this.events) { - options.onEvent({ event }); - } - } - - clone(): AbstractAgent { - return new MockAgent(this.events); - } -} - -describe("SqliteAgentRunner", () => { - let tempDir: string; - let dbPath: string; - let runner: SqliteAgentRunner; - - beforeEach(() => { - // Create a temporary directory for test database - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "sqlite-test-")); - dbPath = path.join(tempDir, "test.db"); - runner = new SqliteAgentRunner({ dbPath }); - }); - - afterEach(() => { - // Clean up test database - if (fs.existsSync(dbPath)) { - fs.unlinkSync(dbPath); - } - if (fs.existsSync(tempDir)) { - fs.rmdirSync(tempDir); - } - }); - - describe("Basic functionality", () => { - it("should run an agent and emit events", async () => { - const threadId = "test-thread-1"; - const events: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Hello" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, - ]; - - const agent = new MockAgent(events); - const input: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - const runObservable = runner.run({ threadId, agent, input }); - const emittedEvents = await firstValueFrom(runObservable.pipe(toArray())); - - expect(emittedEvents).toHaveLength(3); - expect(emittedEvents[0].type).toBe(EventType.TEXT_MESSAGE_START); - }); - - it("should persist events across runner instances", async () => { - const threadId = "test-thread-persistence"; - const events: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Persisted" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, - ]; - - const agent = new MockAgent(events); - const input: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - // Run with first instance - await firstValueFrom(runner.run({ threadId, agent, input }).pipe(toArray())); - - // Create new runner instance with same database - const newRunner = new SqliteAgentRunner({ dbPath }); - - // Connect should return persisted events - const persistedEvents = await firstValueFrom( - newRunner.connect({ threadId }).pipe(toArray()) - ); - - expect(persistedEvents).toHaveLength(3); - expect(persistedEvents[1].type).toBe(EventType.TEXT_MESSAGE_CONTENT); - expect((persistedEvents[1] as any).delta).toBe("Persisted"); - }); - - it("should handle concurrent connections", async () => { - const threadId = "test-thread-concurrent"; - const events: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Test" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, - ]; - - const agent = new MockAgent(events); - const input: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - // Run the agent - await firstValueFrom(runner.run({ threadId, agent, input }).pipe(toArray())); - - // Create multiple concurrent connections - const connection1 = runner.connect({ threadId }); - const connection2 = runner.connect({ threadId }); - const connection3 = runner.connect({ threadId }); - - const [events1, events2, events3] = await Promise.all([ - firstValueFrom(connection1.pipe(toArray())), - firstValueFrom(connection2.pipe(toArray())), - firstValueFrom(connection3.pipe(toArray())), - ]); - - // All connections should receive the same events - expect(events1).toHaveLength(3); - expect(events2).toHaveLength(3); - expect(events3).toHaveLength(3); - expect(events1).toEqual(events2); - expect(events2).toEqual(events3); - }); - - it("should track running state correctly", async () => { - const threadId = "test-thread-running"; - const agent = new MockAgent([]); - const input: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - // Initially not running - expect(await runner.isRunning({ threadId })).toBe(false); - - // Start running - const runPromise = firstValueFrom( - runner.run({ threadId, agent, input }).pipe(toArray()) - ); - - // Should be running now - expect(await runner.isRunning({ threadId })).toBe(true); - - // Wait for completion - await runPromise; - - // Should not be running after completion - expect(await runner.isRunning({ threadId })).toBe(false); - }); - - it("should prevent concurrent runs on same thread", async () => { - const threadId = "test-thread-no-concurrent"; - const agent = new MockAgent([]); - const input: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - // Start first run (don't await) - runner.run({ threadId, agent, input }).subscribe(); - - // Try to start second run immediately - expect(() => { - runner.run({ threadId, agent, input: { ...input, runId: "run2" } }); - }).toThrow("Thread already running"); - }); - }); - - describe("Event compaction", () => { - it("should store compacted events in the database", async () => { - const threadId = "test-thread-compaction"; - - // Create events that should be compacted - const events1: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Hello " }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "world" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, - ]; - - const agent1 = new MockAgent(events1); - const input1: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - // Run first agent - await firstValueFrom(runner.run({ threadId, agent: agent1, input: input1 }).pipe(toArray())); - - // Add more events - each run stores only its own events - const events2: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg2", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg2", delta: "Second " }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg2", delta: "message" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg2" }, - ]; - - const agent2 = new MockAgent(events2); - const input2: RunAgentInput = { - threadId, - runId: "run2", - messages: [], - }; - - // Run second agent - await firstValueFrom(runner.run({ threadId, agent: agent2, input: input2 }).pipe(toArray())); - - // Check database directly to verify compaction - const db = new Database(dbPath); - const rows = db.prepare("SELECT events FROM agent_runs WHERE thread_id = ?").all(threadId) as any[]; - db.close(); - - // Parse events from both runs - const run1Events = JSON.parse(rows[0].events); - const run2Events = JSON.parse(rows[1].events); - - // First run should have only its own compacted events - // We expect: START, single CONTENT with "Hello world", END - expect(run1Events).toHaveLength(3); - const contentEvents1 = run1Events.filter((e: any) => e.type === EventType.TEXT_MESSAGE_CONTENT); - expect(contentEvents1).toHaveLength(1); - expect(contentEvents1[0].delta).toBe("Hello world"); - expect(run1Events[0].messageId).toBe("msg1"); - - // Second run should have only its own compacted events - expect(run2Events).toHaveLength(3); - const contentEvents2 = run2Events.filter((e: any) => e.type === EventType.TEXT_MESSAGE_CONTENT); - expect(contentEvents2).toHaveLength(1); - expect(contentEvents2[0].delta).toBe("Second message"); - expect(run2Events[0].messageId).toBe("msg2"); - - // Verify runs have different message IDs (no cross-contamination) - const run1MessageIds = new Set(run1Events.filter((e: any) => e.messageId).map((e: any) => e.messageId)); - const run2MessageIds = new Set(run2Events.filter((e: any) => e.messageId).map((e: any) => e.messageId)); - - // Ensure no overlap between message IDs in different runs - for (const id of run1MessageIds) { - expect(run2MessageIds.has(id)).toBe(false); - } - }); - - it("should never store empty events after text message compaction", async () => { - const threadId = "test-thread-text-compaction-not-empty"; - - // First run: multiple text content events that will be compacted - const events1: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "user" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "H" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "e" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "l" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "l" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "o" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, - ]; - - const agent1 = new MockAgent(events1); - const input1: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent1, input: input1 }).pipe(toArray())); - - // Second run: more text content events - const events2: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg2", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg2", delta: "W" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg2", delta: "o" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg2", delta: "r" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg2", delta: "l" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg2", delta: "d" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg2" }, - ]; - - const agent2 = new MockAgent(events2); - const input2: RunAgentInput = { - threadId, - runId: "run2", - messages: [], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent2, input: input2 }).pipe(toArray())); - - // Check database directly - const db = new Database(dbPath); - const rows = db.prepare("SELECT run_id, events FROM agent_runs WHERE thread_id = ? ORDER BY created_at").all(threadId) as any[]; - db.close(); - - expect(rows).toHaveLength(2); - - // Both runs should have non-empty compacted events - const run1Events = JSON.parse(rows[0].events); - const run2Events = JSON.parse(rows[1].events); - - // Verify run1 events are not empty and properly compacted - expect(run1Events).not.toHaveLength(0); - expect(run1Events).toHaveLength(3); // START, compacted CONTENT, END - expect(run1Events[0].type).toBe(EventType.TEXT_MESSAGE_START); - expect(run1Events[1].type).toBe(EventType.TEXT_MESSAGE_CONTENT); - expect(run1Events[1].delta).toBe("Hello"); // All characters concatenated - expect(run1Events[2].type).toBe(EventType.TEXT_MESSAGE_END); - - // Verify run2 events are not empty and properly compacted - expect(run2Events).not.toHaveLength(0); - expect(run2Events).toHaveLength(3); // START, compacted CONTENT, END - expect(run2Events[0].type).toBe(EventType.TEXT_MESSAGE_START); - expect(run2Events[1].type).toBe(EventType.TEXT_MESSAGE_CONTENT); - expect(run2Events[1].delta).toBe("World"); // All characters concatenated - expect(run2Events[2].type).toBe(EventType.TEXT_MESSAGE_END); - }); - - it("should handle complex compaction scenarios with multiple message types", async () => { - const threadId = "test-thread-complex-compaction"; - - // First run: Mix of events including text messages and other events - const events1: BaseEvent[] = [ - { type: EventType.RUN_STARTED, runId: "run1" }, - { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "user" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Part " }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "1" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, - { type: EventType.RUN_FINISHED, runId: "run1" }, - ]; - - const agent1 = new MockAgent(events1); - const input1: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent1, input: input1 }).pipe(toArray())); - - // Second run: Another message that could potentially compact to empty - const events2: BaseEvent[] = [ - { type: EventType.RUN_STARTED, runId: "run2" }, - { type: EventType.TEXT_MESSAGE_START, messageId: "msg2", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg2", delta: "Part " }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg2", delta: "2" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg2" }, - { type: EventType.RUN_FINISHED, runId: "run2" }, - ]; - - const agent2 = new MockAgent(events2); - const input2: RunAgentInput = { - threadId, - runId: "run2", - messages: [], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent2, input: input2 }).pipe(toArray())); - - // Third run: Test with already compacted previous events - const events3: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg3", role: "user" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg3", delta: "Part " }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg3", delta: "3" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg3" }, - ]; - - const agent3 = new MockAgent(events3); - const input3: RunAgentInput = { - threadId, - runId: "run3", - messages: [], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent3, input: input3 }).pipe(toArray())); - - // Check database directly - const db = new Database(dbPath); - const rows = db.prepare("SELECT run_id, events FROM agent_runs WHERE thread_id = ? ORDER BY created_at").all(threadId) as any[]; - db.close(); - - expect(rows).toHaveLength(3); - - // All runs should have non-empty events - for (let i = 0; i < rows.length; i++) { - const events = JSON.parse(rows[i].events); - expect(events).not.toHaveLength(0); - expect(events.length).toBeGreaterThan(0); - - // Verify text messages are properly compacted - const textContentEvents = events.filter((e: any) => e.type === EventType.TEXT_MESSAGE_CONTENT); - textContentEvents.forEach((event: any) => { - expect(event.delta).toBeTruthy(); - expect(event.delta).not.toBe(""); - }); - } - }); - - it("should retrieve already-compacted events without re-compacting", async () => { - const threadId = "test-thread-no-recompact"; - - // Create events that would be compacted - const events: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Part 1 " }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Part 2 " }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Part 3" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, - ]; - - const agent = new MockAgent(events); - const input: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - // Run and store compacted events - await firstValueFrom(runner.run({ threadId, agent, input }).pipe(toArray())); - - // Connect and retrieve events - const retrievedEvents = await firstValueFrom( - runner.connect({ threadId }).pipe(toArray()) - ); - - // Should have compacted format: START, single CONTENT, END - expect(retrievedEvents).toHaveLength(3); - expect(retrievedEvents[0].type).toBe(EventType.TEXT_MESSAGE_START); - expect(retrievedEvents[1].type).toBe(EventType.TEXT_MESSAGE_CONTENT); - expect((retrievedEvents[1] as any).delta).toBe("Part 1 Part 2 Part 3"); - expect(retrievedEvents[2].type).toBe(EventType.TEXT_MESSAGE_END); - }); - - it("should handle edge case where new events are identical to compacted previous events", async () => { - const threadId = "test-thread-identical-after-compaction"; - - // First run: send a compacted-looking message - const events1: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "user" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Hello World" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, - ]; - - const agent1 = new MockAgent(events1); - const input1: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent1, input: input1 }).pipe(toArray())); - - // Second run: send events that would compact to the same thing - const events2: BaseEvent[] = [ - { 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: "World" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg2" }, - ]; - - const agent2 = new MockAgent(events2); - const input2: RunAgentInput = { - threadId, - runId: "run2", - messages: [], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent2, input: input2 }).pipe(toArray())); - - // Check database directly - const db = new Database(dbPath); - const rows = db.prepare("SELECT run_id, events FROM agent_runs WHERE thread_id = ? ORDER BY created_at").all(threadId) as any[]; - db.close(); - - expect(rows).toHaveLength(2); - - // Both runs should have events stored - const run1Events = JSON.parse(rows[0].events); - const run2Events = JSON.parse(rows[1].events); - - // Both should be non-empty - expect(run1Events).not.toHaveLength(0); - expect(run2Events).not.toHaveLength(0); - - // Both should have proper structure - expect(run1Events).toHaveLength(3); - expect(run2Events).toHaveLength(3); - - // Verify the content is correct - expect(run1Events[1].delta).toBe("Hello World"); - expect(run2Events[1].delta).toBe("Hello World"); - - // Messages should have different IDs - expect(run1Events[0].messageId).toBe("msg1"); - expect(run2Events[0].messageId).toBe("msg2"); - }); - }); - - describe("Comparison with InMemoryAgentRunner", () => { - it("should behave identically to InMemoryAgentRunner for basic operations", async () => { - const threadId = "test-thread-comparison"; - const events: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "user" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Test message" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, - ]; - - const agent = new MockAgent(events); - const input: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - // Run with SQLite runner - const sqliteRunner = new SqliteAgentRunner({ dbPath }); - const sqliteRunEvents = await firstValueFrom( - sqliteRunner.run({ threadId, agent: agent.clone(), input }).pipe(toArray()) - ); - const sqliteConnectEvents = await firstValueFrom( - sqliteRunner.connect({ threadId }).pipe(toArray()) - ); - - // Run with InMemory runner - const memoryRunner = new InMemoryAgentRunner(); - const memoryRunEvents = await firstValueFrom( - memoryRunner.run({ threadId, agent: agent.clone(), input }).pipe(toArray()) - ); - const memoryConnectEvents = await firstValueFrom( - memoryRunner.connect({ threadId }).pipe(toArray()) - ); - - // Both should emit the same events - expect(sqliteRunEvents).toEqual(memoryRunEvents); - expect(sqliteConnectEvents).toEqual(memoryConnectEvents); - }); - }); - - describe("Input message handling", () => { - it("should store NEW input messages but NOT old ones", async () => { - const threadId = "test-thread-input-storage"; - - // First run: create some messages - const events1: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "first-run-msg", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "first-run-msg", delta: "First run message" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "first-run-msg" }, - ]; - - const agent1 = new MockAgent(events1); - const input1: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent1, input: input1 }).pipe(toArray())); - - // Second run: pass OLD message and a NEW message as input - const events2: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "second-run-msg", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "second-run-msg", delta: "Second run message" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "second-run-msg" }, - ]; - - const agent2 = new MockAgent(events2); - const input2: RunAgentInput = { - threadId, - runId: "run2", - messages: [ - { - id: "first-run-msg", // This is OLD - from run1, should NOT be stored again - role: "assistant", - content: "First run message" - }, - { - id: "new-user-msg", // This is NEW - should be stored in run2 - role: "user", - content: "This is a NEW user message that SHOULD be stored in run2" - } - ], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent2, input: input2 }).pipe(toArray())); - - // Check database directly - const db = new Database(dbPath); - const rows = db.prepare("SELECT run_id, events FROM agent_runs WHERE thread_id = ? ORDER BY created_at").all(threadId) as any[]; - db.close(); - - expect(rows).toHaveLength(2); - - // First run should only have its own message - const run1Events = JSON.parse(rows[0].events); - const run1MessageIds = run1Events.filter((e: any) => e.messageId).map((e: any) => e.messageId); - expect(run1MessageIds).toContain("first-run-msg"); - expect(run1MessageIds).not.toContain("new-user-msg"); - expect(run1MessageIds).not.toContain("second-run-msg"); - - // Second run should have the NEW user message and agent response, but NOT the old message - const run2Events = JSON.parse(rows[1].events); - const run2MessageIds = run2Events.filter((e: any) => e.messageId).map((e: any) => e.messageId); - expect(run2MessageIds).toContain("second-run-msg"); // Agent's response - expect(run2MessageIds).toContain("new-user-msg"); // NEW user message - SHOULD be stored - expect(run2MessageIds).not.toContain("first-run-msg"); // OLD message - should NOT be stored again - - // Verify the second run has the right messages - const uniqueRun2MessageIds = [...new Set(run2MessageIds)]; - expect(uniqueRun2MessageIds).toHaveLength(2); // Should have exactly 2 message IDs - expect(uniqueRun2MessageIds).toContain("new-user-msg"); - expect(uniqueRun2MessageIds).toContain("second-run-msg"); - }); - }); - - describe("Complete conversation flow", () => { - it("should store ALL types of NEW messages including tool results", async () => { - const threadId = "test-thread-all-message-types"; - - // Run 1: User message with tool call and result - const agent1 = new MockAgent([ - { type: EventType.TOOL_CALL_START, toolCallId: "tool-1", toolName: "calculator" }, - { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool-1", delta: '{"a": 1, "b": 2}' }, - { type: EventType.TOOL_CALL_END, toolCallId: "tool-1" }, - ]); - - const input1: RunAgentInput = { - threadId, - runId: "run1", - messages: [ - { id: "user-1", role: "user", content: "Calculate 1+2" }, - { id: "assistant-1", role: "assistant", content: "Let me calculate that", toolCalls: [ - { id: "tool-1", type: "function", function: { name: "calculator", arguments: JSON.stringify({ a: 1, b: 2 }) } } - ]}, - { id: "tool-result-1", role: "tool", toolCallId: "tool-1", content: "3" } - ], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent1, input: input1 }).pipe(toArray())); - - // Run 2: Add more messages including system and developer - const agent2 = new MockAgent([ - { type: EventType.TEXT_MESSAGE_START, messageId: "assistant-2", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "assistant-2", delta: "The answer is 3" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "assistant-2" }, - ]); - - const input2: RunAgentInput = { - threadId, - runId: "run2", - messages: [ - // Old messages from run 1 - { id: "user-1", role: "user", content: "Calculate 1+2" }, - { id: "assistant-1", role: "assistant", content: "Let me calculate that", toolCalls: [ - { id: "tool-1", type: "function", function: { name: "calculator", arguments: JSON.stringify({ a: 1, b: 2 }) } } - ]}, - { id: "tool-result-1", role: "tool", toolCallId: "tool-1", content: "3" }, - // New messages for run 2 - { id: "system-1", role: "system", content: "Be concise" }, - { id: "developer-1", role: "developer", content: "Use simple language" }, - { id: "user-2", role: "user", content: "What was the result?" } - ], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent2, input: input2 }).pipe(toArray())); - - // Check database - const db = new Database(dbPath); - const rows = db.prepare("SELECT run_id, events FROM agent_runs WHERE thread_id = ? ORDER BY created_at").all(threadId) as any[]; - db.close(); - - expect(rows).toHaveLength(2); - - // Run 1 should have all the initial messages - const run1Events = JSON.parse(rows[0].events); - const run1MessageIds = [...new Set(run1Events.filter((e: any) => e.messageId).map((e: any) => e.messageId))]; - const run1ToolIds = [...new Set(run1Events.filter((e: any) => e.toolCallId).map((e: any) => e.toolCallId))]; - - expect(run1MessageIds).toContain("user-1"); - expect(run1MessageIds).toContain("assistant-1"); - expect(run1ToolIds).toContain("tool-1"); // Tool events from both input and agent - - // Verify tool result event is stored - const toolResultEvents = run1Events.filter((e: any) => e.type === EventType.TOOL_CALL_RESULT); - expect(toolResultEvents).toHaveLength(1); - expect(toolResultEvents[0].toolCallId).toBe("tool-1"); - expect(toolResultEvents[0].content).toBe("3"); - - // Run 2 should have ONLY the new messages - const run2Events = JSON.parse(rows[1].events); - const run2MessageIds = [...new Set(run2Events.filter((e: any) => e.messageId).map((e: any) => e.messageId))]; - - expect(run2MessageIds).toContain("system-1"); // NEW system message - expect(run2MessageIds).toContain("developer-1"); // NEW developer message - expect(run2MessageIds).toContain("user-2"); // NEW user message - expect(run2MessageIds).toContain("assistant-2"); // NEW assistant response - - // Should NOT contain old messages - expect(run2MessageIds).not.toContain("user-1"); - expect(run2MessageIds).not.toContain("assistant-1"); - - // Should NOT contain old tool results - const run2ToolResults = run2Events.filter((e: any) => e.type === EventType.TOOL_CALL_RESULT); - expect(run2ToolResults.filter((e: any) => e.toolCallId === "tool-1")).toHaveLength(0); - - // Verify we captured all 4 new message types in run 2 - const run2EventTypes = new Set(run2Events.map((e: any) => e.type)); - expect(run2EventTypes.has(EventType.TEXT_MESSAGE_START)).toBe(true); - expect(run2EventTypes.has(EventType.TEXT_MESSAGE_CONTENT)).toBe(true); - expect(run2EventTypes.has(EventType.TEXT_MESSAGE_END)).toBe(true); - }); - - it("should correctly store a multi-turn conversation", async () => { - const threadId = "test-thread-conversation"; - - // Run 1: Initial user message and agent response - const agent1 = new MockAgent([ - { type: EventType.TEXT_MESSAGE_START, messageId: "agent-1", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "agent-1", delta: "Hello! How can I help?" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "agent-1" }, - ]); - - const input1: RunAgentInput = { - threadId, - runId: "run1", - messages: [ - { id: "user-1", role: "user", content: "Hi!" } - ], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent1, input: input1 }).pipe(toArray())); - - // Run 2: Second user message and agent response - const agent2 = new MockAgent([ - { type: EventType.TEXT_MESSAGE_START, messageId: "agent-2", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "agent-2", delta: "The weather is nice today!" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "agent-2" }, - ]); - - const input2: RunAgentInput = { - threadId, - runId: "run2", - messages: [ - { id: "user-1", role: "user", content: "Hi!" }, - { id: "agent-1", role: "assistant", content: "Hello! How can I help?" }, - { id: "user-2", role: "user", content: "What's the weather?" } - ], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent2, input: input2 }).pipe(toArray())); - - // Check database - const db = new Database(dbPath); - const rows = db.prepare("SELECT run_id, events FROM agent_runs WHERE thread_id = ? ORDER BY created_at").all(threadId) as any[]; - db.close(); - - expect(rows).toHaveLength(2); - - // Run 1 should have user-1 and agent-1 - const run1Events = JSON.parse(rows[0].events); - const run1MessageIds = [...new Set(run1Events.filter((e: any) => e.messageId).map((e: any) => e.messageId))]; - expect(run1MessageIds).toHaveLength(2); - expect(run1MessageIds).toContain("user-1"); - expect(run1MessageIds).toContain("agent-1"); - - // Run 2 should have ONLY user-2 and agent-2 (not the old messages) - const run2Events = JSON.parse(rows[1].events); - const run2MessageIds = [...new Set(run2Events.filter((e: any) => e.messageId).map((e: any) => e.messageId))]; - expect(run2MessageIds).toHaveLength(2); - expect(run2MessageIds).toContain("user-2"); - expect(run2MessageIds).toContain("agent-2"); - expect(run2MessageIds).not.toContain("user-1"); - expect(run2MessageIds).not.toContain("agent-1"); - }); - }); - - describe("Database integrity", () => { - it("should create all required tables", () => { - const db = new Database(dbPath); - - // Check agent_runs table exists - const agentRunsTable = db.prepare( - "SELECT name FROM sqlite_master WHERE type='table' AND name='agent_runs'" - ).get(); - expect(agentRunsTable).toBeDefined(); - - // Check run_state table exists - const runStateTable = db.prepare( - "SELECT name FROM sqlite_master WHERE type='table' AND name='run_state'" - ).get(); - expect(runStateTable).toBeDefined(); - - // Check schema_version table exists - const schemaVersionTable = db.prepare( - "SELECT name FROM sqlite_master WHERE type='table' AND name='schema_version'" - ).get(); - expect(schemaVersionTable).toBeDefined(); - - db.close(); - }); - - it("should handle database file creation", () => { - // Database file should be created - expect(fs.existsSync(dbPath)).toBe(true); - }); - - it("should never store empty events array", async () => { - const threadId = "test-thread-no-empty"; - - // Test with events that should be stored - const events: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Test" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, - ]; - - const agent = new MockAgent(events); - const input: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - // Run the agent - await firstValueFrom(runner.run({ threadId, agent, input }).pipe(toArray())); - - // Check database directly - const db = new Database(dbPath); - const rows = db.prepare("SELECT events FROM agent_runs WHERE thread_id = ?").all(threadId) as any[]; - db.close(); - - // Should have one run - expect(rows).toHaveLength(1); - - // Parse and check events are not empty - const storedEvents = JSON.parse(rows[0].events); - expect(storedEvents).not.toHaveLength(0); - expect(storedEvents.length).toBeGreaterThan(0); - expect(storedEvents).toEqual(expect.arrayContaining([ - expect.objectContaining({ type: EventType.TEXT_MESSAGE_START }), - expect.objectContaining({ type: EventType.TEXT_MESSAGE_CONTENT }), - expect.objectContaining({ type: EventType.TEXT_MESSAGE_END }), - ])); - }); - - it("should store correct events after compaction on subsequent runs", async () => { - const threadId = "test-thread-subsequent-runs"; - - // First run with initial events - const events1: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg1", role: "user" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg1", delta: "Hello" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg1" }, - ]; - - const agent1 = new MockAgent(events1); - const input1: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent1, input: input1 }).pipe(toArray())); - - // Second run with new events - const events2: BaseEvent[] = [ - { type: EventType.TEXT_MESSAGE_START, messageId: "msg2", role: "assistant" }, - { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg2", delta: "World" }, - { type: EventType.TEXT_MESSAGE_END, messageId: "msg2" }, - ]; - - const agent2 = new MockAgent(events2); - const input2: RunAgentInput = { - threadId, - runId: "run2", - messages: [], - }; - - await firstValueFrom(runner.run({ threadId, agent: agent2, input: input2 }).pipe(toArray())); - - // Check database directly - const db = new Database(dbPath); - const rows = db.prepare("SELECT run_id, events FROM agent_runs WHERE thread_id = ? ORDER BY created_at").all(threadId) as any[]; - db.close(); - - // Should have two runs - expect(rows).toHaveLength(2); - - // Both runs should have non-empty events - const run1Events = JSON.parse(rows[0].events); - const run2Events = JSON.parse(rows[1].events); - - expect(run1Events).not.toHaveLength(0); - expect(run2Events).not.toHaveLength(0); - - // First run should have ONLY the first message events - expect(run1Events).toEqual(expect.arrayContaining([ - expect.objectContaining({ messageId: "msg1" }), - ])); - expect(run1Events.every((e: any) => !e.messageId || e.messageId === "msg1")).toBe(true); - - // Second run should have ONLY the second message events - expect(run2Events).toEqual(expect.arrayContaining([ - expect.objectContaining({ messageId: "msg2" }), - ])); - expect(run2Events.every((e: any) => !e.messageId || e.messageId === "msg2")).toBe(true); - - // Verify no message duplication across runs - const run1MessageIds = new Set(run1Events.filter((e: any) => e.messageId).map((e: any) => e.messageId)); - const run2MessageIds = new Set(run2Events.filter((e: any) => e.messageId).map((e: any) => e.messageId)); - const intersection = [...run1MessageIds].filter(id => run2MessageIds.has(id)); - expect(intersection).toHaveLength(0); - }); - - it("should handle edge case with no new events after compaction", async () => { - const threadId = "test-thread-edge-case"; - - // Run with duplicate events that might compact to nothing new - const events: BaseEvent[] = [ - { type: EventType.RUN_STARTED, runId: "run1" }, - { type: EventType.RUN_FINISHED, runId: "run1" }, - ]; - - const agent = new MockAgent(events); - const input: RunAgentInput = { - threadId, - runId: "run1", - messages: [], - }; - - await firstValueFrom(runner.run({ threadId, agent, input }).pipe(toArray())); - - // Check database - const db = new Database(dbPath); - const rows = db.prepare("SELECT events FROM agent_runs WHERE thread_id = ?").all(threadId) as any[]; - db.close(); - - // Should have stored the run - expect(rows).toHaveLength(1); - - // Events should be stored (even if they are minimal after compaction) - const storedEvents = JSON.parse(rows[0].events); - expect(Array.isArray(storedEvents)).toBe(true); - }); - }); -}); \ No newline at end of file diff --git a/packages/runtime/src/runner/enterprise.ts b/packages/runtime/src/runner/enterprise.ts deleted file mode 100644 index b5b29b6d..00000000 --- a/packages/runtime/src/runner/enterprise.ts +++ /dev/null @@ -1,653 +0,0 @@ -import { - AgentRunner, - AgentRunnerConnectRequest, - AgentRunnerIsRunningRequest, - AgentRunnerRunRequest, - type AgentRunnerStopRequest, -} from "./agent-runner"; -import { Observable, ReplaySubject } from "rxjs"; -import { - BaseEvent, - RunAgentInput, - Message, - EventType, - TextMessageStartEvent, - TextMessageContentEvent, - TextMessageEndEvent, - ToolCallStartEvent, - ToolCallArgsEvent, - ToolCallEndEvent, - ToolCallResultEvent, -} from "@ag-ui/client"; -import { compactEvents } from "./event-compaction"; -import { Kysely, Generated } from "kysely"; -import { Redis } from "ioredis"; - -const SCHEMA_VERSION = 1; - -interface AgentDatabase { - agent_runs: { - id: Generated; - thread_id: string; - run_id: string; - parent_run_id: string | null; - events: string; - input: string; - created_at: number; - version: number; - }; - - run_state: { - thread_id: string; - is_running: number; - current_run_id: string | null; - server_id: string | null; - updated_at: number; - }; - - schema_version: { - version: number; - applied_at: number; - }; -} - -interface AgentRunRecord { - id: number; - thread_id: string; - run_id: string; - parent_run_id: string | null; - events: string; - input: string; - created_at: number; - version: number; -} - -const redisKeys = { - stream: (threadId: string, runId: string) => `stream:${threadId}:${runId}`, - active: (threadId: string) => `active:${threadId}`, - lock: (threadId: string) => `lock:${threadId}`, -}; - -export interface EnterpriseAgentRunnerOptions { - kysely: Kysely; - redis: Redis; - redisSub?: Redis; - streamRetentionMs?: number; - streamActiveTTLMs?: number; - lockTTLMs?: number; - serverId?: string; -} - -export class EnterpriseAgentRunner extends AgentRunner { - private db: Kysely; - public redis: Redis; - public redisSub: Redis; - private serverId: string; - private streamRetentionMs: number; - private streamActiveTTLMs: number; - private lockTTLMs: number; - - constructor(options: EnterpriseAgentRunnerOptions) { - super(); - this.db = options.kysely; - this.redis = options.redis; - this.redisSub = options.redisSub || options.redis.duplicate(); - this.serverId = options.serverId || this.generateServerId(); - this.streamRetentionMs = options.streamRetentionMs ?? 3600000; // 1 hour - this.streamActiveTTLMs = options.streamActiveTTLMs ?? 300000; // 5 minutes - this.lockTTLMs = options.lockTTLMs ?? 300000; // 5 minutes - - this.initializeSchema(); - } - - run(request: AgentRunnerRunRequest): Observable { - const runSubject = new ReplaySubject(Infinity); - - const executeRun = async () => { - const { threadId, input, agent } = request; - const runId = input.runId; - const streamKey = redisKeys.stream(threadId, runId); - - // Check if thread already running (do this check synchronously for consistency with SQLite) - // For now we'll just check after, but in production you might want a sync check - const activeRunId = await this.redis.get(redisKeys.active(threadId)); - if (activeRunId) { - throw new Error("Thread already running"); - } - - // Acquire distributed lock - const lockAcquired = await this.redis.set( - redisKeys.lock(threadId), - this.serverId, - 'PX', this.lockTTLMs, - 'NX' - ); - - if (!lockAcquired) { - throw new Error("Thread already running"); - } - - // Mark as active - await this.redis.setex( - redisKeys.active(threadId), - Math.floor(this.lockTTLMs / 1000), - runId - ); - - // Update database state - await this.setRunState(threadId, true, runId); - - // Track events and message IDs - const currentRunEvents: BaseEvent[] = []; - const seenMessageIds = new Set(); - - // Get historic message IDs - const historicRuns = await this.getHistoricRuns(threadId); - const historicMessageIds = new Set(); - for (const run of historicRuns) { - const events = JSON.parse(run.events) as BaseEvent[]; - for (const event of events) { - if ('messageId' in event && typeof event.messageId === 'string') { - historicMessageIds.add(event.messageId); - } - } - } - - const parentRunId = historicRuns[historicRuns.length - 1]?.run_id ?? null; - - try { - await agent.runAgent(input, { - onEvent: async ({ event }) => { - // Emit to run() caller - runSubject.next(event); - - // Collect for database - currentRunEvents.push(event); - - // Stream to Redis for connect() subscribers - await this.redis.xadd( - streamKey, - 'MAXLEN', '~', '10000', - '*', - 'type', event.type, - 'data', JSON.stringify(event) - ); - - // Refresh TTL with sliding window during active writes - await this.redis.pexpire(streamKey, this.streamActiveTTLMs); - - // Check for completion events - if (event.type === EventType.RUN_FINISHED || - event.type === EventType.RUN_ERROR) { - // Switch to retention TTL for late readers - await this.redis.pexpire(streamKey, this.streamRetentionMs); - } - }, - - onNewMessage: ({ message }) => { - if (!seenMessageIds.has(message.id)) { - seenMessageIds.add(message.id); - } - }, - - onRunStartedEvent: async () => { - // Process input messages - if (input.messages) { - for (const message of input.messages) { - if (!seenMessageIds.has(message.id)) { - seenMessageIds.add(message.id); - const events = this.convertMessageToEvents(message); - const isNewMessage = !historicMessageIds.has(message.id); - - for (const event of events) { - // Stream to Redis for context - await this.redis.xadd( - streamKey, - 'MAXLEN', '~', '10000', - '*', - 'type', event.type, - 'data', JSON.stringify(event) - ); - - if (isNewMessage) { - currentRunEvents.push(event); - } - } - } - } - } - - // Refresh TTL - await this.redis.pexpire(streamKey, this.streamActiveTTLMs); - }, - }); - - // Store to database - const compactedEvents = compactEvents(currentRunEvents); - await this.storeRun(threadId, runId, compactedEvents, input, parentRunId); - - } finally { - // Clean up (even on error) - await this.setRunState(threadId, false); - await this.redis.del(redisKeys.active(threadId)); - await this.redis.del(redisKeys.lock(threadId)); - - // Ensure stream has retention TTL for late readers - const exists = await this.redis.exists(streamKey); - if (exists) { - await this.redis.pexpire(streamKey, this.streamRetentionMs); - } - - runSubject.complete(); - } - }; - - executeRun().catch((error) => { - runSubject.error(error); - }); - - return runSubject.asObservable(); - } - - connect(request: AgentRunnerConnectRequest): Observable { - const connectionSubject = new ReplaySubject(Infinity); - - const streamConnection = async () => { - const { threadId } = request; - - // Load and emit historic runs from database - const historicRuns = await this.getHistoricRuns(threadId); - const allHistoricEvents: BaseEvent[] = []; - - for (const run of historicRuns) { - const events = JSON.parse(run.events) as BaseEvent[]; - allHistoricEvents.push(...events); - } - - // Compact and emit historic events - const compactedEvents = compactEvents(allHistoricEvents); - const emittedMessageIds = new Set(); - - for (const event of compactedEvents) { - connectionSubject.next(event); - if ('messageId' in event && typeof event.messageId === 'string') { - emittedMessageIds.add(event.messageId); - } - } - - // Check for active run - const activeRunId = await this.redis.get(redisKeys.active(threadId)); - - if (activeRunId) { - // Tail the run-specific Redis stream - const streamKey = redisKeys.stream(threadId, activeRunId); - let lastId = '0-0'; - let consecutiveEmptyReads = 0; - - while (true) { - try { - // Read with blocking using call method for better compatibility - const result = await this.redis.call( - 'XREAD', - 'BLOCK', '5000', - 'COUNT', '100', - 'STREAMS', streamKey, lastId - ) as [string, [string, string[]][]][] | null; - - if (!result || result.length === 0) { - consecutiveEmptyReads++; - - // Check if stream still exists - const exists = await this.redis.exists(streamKey); - if (!exists) { - // Stream expired, we're done - break; - } - - // Check if thread still active - const stillActive = await this.redis.get(redisKeys.active(threadId)); - if (stillActive !== activeRunId) { - // Different run started or thread stopped - break; - } - - // After multiple empty reads, assume completion - if (consecutiveEmptyReads > 3) { - break; - } - - continue; - } - - consecutiveEmptyReads = 0; - const [, messages] = result[0] || [null, []]; - - for (const [id, fields] of messages || []) { - lastId = id; - - // Extract event data (fields is array: [key, value, key, value, ...]) - let eventData: string | null = null; - let eventType: string | null = null; - - for (let i = 0; i < fields.length; i += 2) { - if (fields[i] === 'data') { - eventData = fields[i + 1] ?? null; - } else if (fields[i] === 'type') { - eventType = fields[i + 1] ?? null; - } - } - - if (eventData) { - const event = JSON.parse(eventData) as BaseEvent; - - // Skip already emitted messages - if ('messageId' in event && - typeof event.messageId === 'string' && - emittedMessageIds.has(event.messageId)) { - continue; - } - - connectionSubject.next(event); - - // Check for completion events - if (eventType === EventType.RUN_FINISHED || - eventType === EventType.RUN_ERROR) { - connectionSubject.complete(); - return; - } - } - } - } catch { - // Redis error, complete the stream - break; - } - } - } - - connectionSubject.complete(); - }; - - streamConnection().catch(() => connectionSubject.complete()); - return connectionSubject.asObservable(); - } - - async isRunning(request: AgentRunnerIsRunningRequest): Promise { - const { threadId } = request; - - // Check Redis first for speed - const activeRunId = await this.redis.get(redisKeys.active(threadId)); - if (activeRunId) return true; - - // Check lock - const lockExists = await this.redis.exists(redisKeys.lock(threadId)); - if (lockExists) return true; - - // Fallback to database - const state = await this.db - .selectFrom('run_state') - .where('thread_id', '=', threadId) - .selectAll() - .executeTakeFirst(); - - return state?.is_running === 1; - } - - async stop(request: AgentRunnerStopRequest): Promise { - const { threadId } = request; - - // Get active run ID - const activeRunId = await this.redis.get(redisKeys.active(threadId)); - if (!activeRunId) { - return false; - } - - // Add RUN_ERROR event to stream - const streamKey = redisKeys.stream(threadId, activeRunId); - await this.redis.xadd( - streamKey, - '*', - 'type', EventType.RUN_ERROR, - 'data', JSON.stringify({ - type: EventType.RUN_ERROR, - error: 'Run stopped by user' - }) - ); - - // Set retention TTL - await this.redis.pexpire(streamKey, this.streamRetentionMs); - - // Clean up - await this.setRunState(threadId, false); - await this.redis.del(redisKeys.active(threadId)); - await this.redis.del(redisKeys.lock(threadId)); - - return true; - } - - // Helper methods - private convertMessageToEvents(message: Message): BaseEvent[] { - const events: BaseEvent[] = []; - - if ( - (message.role === "assistant" || - message.role === "user" || - message.role === "developer" || - message.role === "system") && - message.content - ) { - const textStartEvent: TextMessageStartEvent = { - type: EventType.TEXT_MESSAGE_START, - messageId: message.id, - role: message.role, - }; - events.push(textStartEvent); - - const textContentEvent: TextMessageContentEvent = { - type: EventType.TEXT_MESSAGE_CONTENT, - messageId: message.id, - delta: message.content, - }; - events.push(textContentEvent); - - const textEndEvent: TextMessageEndEvent = { - type: EventType.TEXT_MESSAGE_END, - messageId: message.id, - }; - events.push(textEndEvent); - } - - if (message.role === "assistant" && message.toolCalls) { - for (const toolCall of message.toolCalls) { - const toolStartEvent: ToolCallStartEvent = { - type: EventType.TOOL_CALL_START, - toolCallId: toolCall.id, - toolCallName: toolCall.function.name, - parentMessageId: message.id, - }; - events.push(toolStartEvent); - - const toolArgsEvent: ToolCallArgsEvent = { - type: EventType.TOOL_CALL_ARGS, - toolCallId: toolCall.id, - delta: toolCall.function.arguments, - }; - events.push(toolArgsEvent); - - const toolEndEvent: ToolCallEndEvent = { - type: EventType.TOOL_CALL_END, - toolCallId: toolCall.id, - }; - events.push(toolEndEvent); - } - } - - if (message.role === "tool" && message.toolCallId) { - const toolResultEvent: ToolCallResultEvent = { - type: EventType.TOOL_CALL_RESULT, - messageId: message.id, - toolCallId: message.toolCallId, - content: message.content, - role: "tool", - }; - events.push(toolResultEvent); - } - - return events; - } - - private async initializeSchema(): Promise { - try { - // Create agent_runs table - await this.db.schema - .createTable('agent_runs') - .ifNotExists() - .addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement()) - .addColumn('thread_id', 'text', (col) => col.notNull()) - .addColumn('run_id', 'text', (col) => col.notNull().unique()) - .addColumn('parent_run_id', 'text') - .addColumn('events', 'text', (col) => col.notNull()) - .addColumn('input', 'text', (col) => col.notNull()) - .addColumn('created_at', 'integer', (col) => col.notNull()) - .addColumn('version', 'integer', (col) => col.notNull()) - .execute() - .catch(() => {}); // Ignore if already exists - - // Create run_state table - await this.db.schema - .createTable('run_state') - .ifNotExists() - .addColumn('thread_id', 'text', (col) => col.primaryKey()) - .addColumn('is_running', 'integer', (col) => col.defaultTo(0)) - .addColumn('current_run_id', 'text') - .addColumn('server_id', 'text') - .addColumn('updated_at', 'integer', (col) => col.notNull()) - .execute() - .catch(() => {}); // Ignore if already exists - - // Create schema_version table - await this.db.schema - .createTable('schema_version') - .ifNotExists() - .addColumn('version', 'integer', (col) => col.primaryKey()) - .addColumn('applied_at', 'integer', (col) => col.notNull()) - .execute() - .catch(() => {}); // Ignore if already exists - - // Create indexes - await this.db.schema - .createIndex('idx_thread_id') - .ifNotExists() - .on('agent_runs') - .column('thread_id') - .execute() - .catch(() => {}); - - await this.db.schema - .createIndex('idx_parent_run_id') - .ifNotExists() - .on('agent_runs') - .column('parent_run_id') - .execute() - .catch(() => {}); - - // Check and set schema version - const currentVersion = await this.db - .selectFrom('schema_version') - .orderBy('version', 'desc') - .limit(1) - .selectAll() - .executeTakeFirst(); - - if (!currentVersion || currentVersion.version < SCHEMA_VERSION) { - await this.db - .insertInto('schema_version') - .values({ - version: SCHEMA_VERSION, - applied_at: Date.now() - }) - .onConflict((oc) => oc - .column('version') - .doUpdateSet({ applied_at: Date.now() }) - ) - .execute(); - } - } catch { - // Schema initialization might fail if DB is closed, ignore - } - } - - private async storeRun( - threadId: string, - runId: string, - events: BaseEvent[], - input: RunAgentInput, - parentRunId: string | null - ): Promise { - await this.db.insertInto('agent_runs') - .values({ - thread_id: threadId, - run_id: runId, - parent_run_id: parentRunId, - events: JSON.stringify(events), - input: JSON.stringify(input), - created_at: Date.now(), - version: SCHEMA_VERSION - }) - .execute(); - } - - private async getHistoricRuns(threadId: string): Promise { - const rows = await this.db - .selectFrom('agent_runs') - .where('thread_id', '=', threadId) - .orderBy('created_at', 'asc') - .selectAll() - .execute(); - - return rows.map(row => ({ - id: Number(row.id), - thread_id: row.thread_id, - run_id: row.run_id, - parent_run_id: row.parent_run_id, - events: row.events, - input: row.input, - created_at: row.created_at, - version: row.version - })); - } - - private async setRunState( - threadId: string, - isRunning: boolean, - runId?: string - ): Promise { - await this.db.insertInto('run_state') - .values({ - thread_id: threadId, - is_running: isRunning ? 1 : 0, - current_run_id: runId ?? null, - server_id: this.serverId, - updated_at: Date.now() - }) - .onConflict((oc) => oc - .column('thread_id') - .doUpdateSet({ - is_running: isRunning ? 1 : 0, - current_run_id: runId ?? null, - server_id: this.serverId, - updated_at: Date.now() - }) - ) - .execute(); - } - - async close(): Promise { - await this.db.destroy(); - this.redis.disconnect(); - this.redisSub.disconnect(); - } - - private generateServerId(): string { - return `server-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - } -} \ No newline at end of file diff --git a/packages/runtime/src/runner/event-compaction.ts b/packages/runtime/src/runner/event-compaction.ts deleted file mode 100644 index 082a293e..00000000 --- a/packages/runtime/src/runner/event-compaction.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { - BaseEvent, - EventType, - TextMessageStartEvent, - TextMessageContentEvent, - TextMessageEndEvent, - ToolCallStartEvent, - ToolCallArgsEvent, - ToolCallEndEvent, -} from "@ag-ui/client"; - -/** - * 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(); - const pendingToolCalls = new Map(); - - 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); - } -} \ No newline at end of file diff --git a/packages/runtime/src/runner/in-memory.ts b/packages/runtime/src/runner/in-memory.ts index a57baca5..6625bc1a 100644 --- a/packages/runtime/src/runner/in-memory.ts +++ b/packages/runtime/src/runner/in-memory.ts @@ -8,18 +8,11 @@ import { import { Observable, ReplaySubject } from "rxjs"; import { BaseEvent, - Message, EventType, - TextMessageStartEvent, - TextMessageContentEvent, - TextMessageEndEvent, - ToolCallStartEvent, - ToolCallArgsEvent, - ToolCallEndEvent, - ToolCallResultEvent, MessagesSnapshotEvent, + RunStartedEvent, + compactEvents, } from "@ag-ui/client"; -import { compactEvents } from "./event-compaction"; interface HistoricRun { threadId: string; @@ -51,76 +44,6 @@ class InMemoryEventStore { const GLOBAL_STORE = new Map(); export class InMemoryAgentRunner extends AgentRunner { - private convertMessageToEvents(message: Message): BaseEvent[] { - const events: BaseEvent[] = []; - - if ( - (message.role === "assistant" || - message.role === "user" || - message.role === "developer" || - message.role === "system") && - message.content - ) { - const textStartEvent: TextMessageStartEvent = { - type: EventType.TEXT_MESSAGE_START, - messageId: message.id, - role: message.role, - }; - events.push(textStartEvent); - - const textContentEvent: TextMessageContentEvent = { - type: EventType.TEXT_MESSAGE_CONTENT, - messageId: message.id, - delta: message.content, - }; - events.push(textContentEvent); - - const textEndEvent: TextMessageEndEvent = { - type: EventType.TEXT_MESSAGE_END, - messageId: message.id, - }; - events.push(textEndEvent); - } - - if (message.role === "assistant" && message.toolCalls) { - for (const toolCall of message.toolCalls) { - const toolStartEvent: ToolCallStartEvent = { - type: EventType.TOOL_CALL_START, - toolCallId: toolCall.id, - toolCallName: toolCall.function.name, - parentMessageId: message.id, - }; - events.push(toolStartEvent); - - const toolArgsEvent: ToolCallArgsEvent = { - type: EventType.TOOL_CALL_ARGS, - toolCallId: toolCall.id, - delta: toolCall.function.arguments, - }; - events.push(toolArgsEvent); - - const toolEndEvent: ToolCallEndEvent = { - type: EventType.TOOL_CALL_END, - toolCallId: toolCall.id, - }; - events.push(toolEndEvent); - } - } - - if (message.role === "tool" && message.toolCallId) { - const toolResultEvent: ToolCallResultEvent = { - type: EventType.TOOL_CALL_RESULT, - messageId: message.id, - toolCallId: message.toolCallId, - content: message.content, - role: "tool", - }; - events.push(toolResultEvent); - } - - return events; - } - run(request: AgentRunnerRunRequest): Observable { let existingStore = GLOBAL_STORE.get(request.threadId); if (!existingStore) { @@ -146,6 +69,13 @@ export class InMemoryAgentRunner extends AgentRunner { if ("messageId" in event && typeof event.messageId === "string") { historicMessageIds.add(event.messageId); } + if (event.type === EventType.RUN_STARTED) { + const runStarted = event as RunStartedEvent; + const messages = runStarted.input?.messages ?? []; + for (const message of messages) { + historicMessageIds.add(message.id); + } + } } } @@ -168,9 +98,31 @@ export class InMemoryAgentRunner extends AgentRunner { try { await request.agent.runAgent(request.input, { onEvent: ({ event }) => { - runSubject.next(event); // For run() return - only agent events - nextSubject.next(event); // For connect() / store - all events - currentRunEvents.push(event); // Accumulate for storage + let processedEvent: BaseEvent = event; + if (event.type === EventType.RUN_STARTED) { + const runStartedEvent = event as RunStartedEvent; + if (!runStartedEvent.input) { + const sanitizedMessages = request.input.messages + ? request.input.messages.filter( + (message) => !historicMessageIds.has(message.id), + ) + : undefined; + const updatedInput = { + ...request.input, + ...(sanitizedMessages !== undefined + ? { messages: sanitizedMessages } + : {}), + }; + processedEvent = { + ...runStartedEvent, + input: updatedInput, + } as RunStartedEvent; + } + } + + runSubject.next(processedEvent); // For run() return - only agent events + nextSubject.next(processedEvent); // For connect() / store - all events + currentRunEvents.push(processedEvent); // Accumulate for storage }, onNewMessage: ({ message }) => { // Called for each new message @@ -179,25 +131,11 @@ export class InMemoryAgentRunner extends AgentRunner { } }, onRunStartedEvent: () => { - // Process input messages (same logic as SQLite) + // Mark any messages from the input as seen so they aren't emitted twice if (request.input.messages) { for (const message of request.input.messages) { if (!seenMessageIds.has(message.id)) { seenMessageIds.add(message.id); - const events = this.convertMessageToEvents(message); - - // Check if this message is NEW (not in historic runs) - const isNewMessage = !historicMessageIds.has(message.id); - - for (const event of events) { - // Always emit to stream for context - nextSubject.next(event); - - // Store if this is a NEW message for this run - if (isNewMessage) { - currentRunEvents.push(event); - } - } } } } diff --git a/packages/shared/package.json b/packages/shared/package.json index d9dec245..c08c6c02 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@copilotkitnext/shared", - "version": "0.0.15", + "version": "0.0.14", "description": "Shared utilities and types for CopilotKit2", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/sqlite-runner/eslint.config.mjs b/packages/sqlite-runner/eslint.config.mjs new file mode 100644 index 00000000..daa75ba7 --- /dev/null +++ b/packages/sqlite-runner/eslint.config.mjs @@ -0,0 +1,3 @@ +import { config as baseConfig } from "@copilotkitnext/eslint-config/base"; + +export default [...baseConfig]; diff --git a/packages/sqlite-runner/package.json b/packages/sqlite-runner/package.json new file mode 100644 index 00000000..3898ad9e --- /dev/null +++ b/packages/sqlite-runner/package.json @@ -0,0 +1,55 @@ +{ + "name": "@copilotkitnext/sqlite-runner", + "version": "0.0.14", + "description": "SQLite-backed agent runner for CopilotKit2", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup", + "prepublishOnly": "pnpm run build", + "dev": "tsup --watch", + "lint": "eslint . --max-warnings 0", + "check-types": "tsc --noEmit", + "clean": "rm -rf dist", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" + }, + "devDependencies": { + "@copilotkitnext/eslint-config": "workspace:*", + "@copilotkitnext/typescript-config": "workspace:*", + "@types/better-sqlite3": "^7.6.13", + "@types/node": "^22.15.3", + "better-sqlite3": "^12.2.0", + "eslint": "^9.30.0", + "tsup": "^8.5.0", + "typescript": "5.8.2", + "vitest": "^3.0.5" + }, + "dependencies": { + "@ag-ui/client": "0.0.40-alpha.6", + "@copilotkitnext/runtime": "workspace:*", + "rxjs": "7.8.1" + }, + "peerDependencies": { + "better-sqlite3": "^12.2.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + } + }, + "engines": { + "node": ">=18" + } +} diff --git a/packages/sqlite-runner/src/__tests__/sqlite-runner.e2e.test.ts b/packages/sqlite-runner/src/__tests__/sqlite-runner.e2e.test.ts new file mode 100644 index 00000000..e968eef0 --- /dev/null +++ b/packages/sqlite-runner/src/__tests__/sqlite-runner.e2e.test.ts @@ -0,0 +1,765 @@ +import { describe, it, expect, afterEach } from "vitest"; +import { SqliteAgentRunner } from ".."; +import { + AbstractAgent, + BaseEvent, + EventType, + Message, + RunAgentInput, + RunErrorEvent, + RunFinishedEvent, + RunStartedEvent, +} from "@ag-ui/client"; +import { EMPTY, Subscription, firstValueFrom, from } from "rxjs"; +import { toArray } from "rxjs/operators"; + +type RunCallbacks = { + onEvent: (event: { event: BaseEvent }) => void; + onNewMessage?: (args: { message: Message }) => void; + onRunStartedEvent?: () => void; +}; + +const createdRunners: SqliteAgentRunner[] = []; + +afterEach(() => { + while (createdRunners.length > 0) { + const runner = createdRunners.pop(); + runner?.close(); + } +}); + +function createRunner(): SqliteAgentRunner { + const runner = new SqliteAgentRunner(); + createdRunners.push(runner); + return runner; +} + +interface EmitAgentOptions { + events?: BaseEvent[]; + emitDefaultRunStarted?: boolean; + includeRunFinished?: boolean; + runFinishedEvent?: RunFinishedEvent; + afterEvent?: (args: { event: BaseEvent; index: number }) => void | Promise; +} + +class EmitAgent extends AbstractAgent { + constructor(private readonly options: EmitAgentOptions = {}) { + super(); + } + + async runAgent(input: RunAgentInput, callbacks: RunCallbacks): Promise { + const { + emitDefaultRunStarted = true, + includeRunFinished = true, + runFinishedEvent, + afterEvent, + } = this.options; + const scriptedEvents = this.options.events ?? []; + + let index = 0; + const emit = async (event: BaseEvent) => { + callbacks.onEvent({ event }); + if (event.type === EventType.RUN_STARTED) { + callbacks.onRunStartedEvent?.(); + } + await afterEvent?.({ event, index }); + index += 1; + }; + + if (emitDefaultRunStarted) { + const runStarted: RunStartedEvent = { + type: EventType.RUN_STARTED, + threadId: input.threadId, + runId: input.runId, + parentRunId: input.parentRunId, + }; + await emit(runStarted); + } + + for (const event of scriptedEvents) { + await emit(event); + } + + const hasRunFinishedEvent = + scriptedEvents.some((event) => event.type === EventType.RUN_FINISHED) || + runFinishedEvent?.type === EventType.RUN_FINISHED; + + if (includeRunFinished && !hasRunFinishedEvent) { + const finishEvent: RunFinishedEvent = + runFinishedEvent ?? { + type: EventType.RUN_FINISHED, + threadId: input.threadId, + runId: input.runId, + }; + await emit(finishEvent); + } + } + + clone(): AbstractAgent { + return new EmitAgent({ + ...this.options, + events: this.options.events ? [...this.options.events] : undefined, + }); + } + + protected run(): ReturnType { + return EMPTY; + } + + protected connect(): ReturnType { + return EMPTY; + } +} + +class ReplayAgent extends AbstractAgent { + constructor(private readonly replayEvents: BaseEvent[], threadId: string) { + super({ threadId }); + } + + async runAgent(): Promise { + throw new Error("not used"); + } + + protected run(): ReturnType { + return EMPTY; + } + + protected connect(): ReturnType { + return from(this.replayEvents); + } +} + +class RunnerConnectAgent extends AbstractAgent { + constructor(private readonly runner: SqliteAgentRunner, threadId: string) { + super({ threadId }); + } + + async runAgent(): Promise { + throw new Error("not used"); + } + + protected run(): ReturnType { + return EMPTY; + } + + protected connect(input: RunAgentInput): ReturnType { + return this.runner.connect({ threadId: input.threadId }); + } +} + +type Deferred = { + promise: Promise; + resolve: (value: T) => void; + reject: (reason?: unknown) => void; +}; + +function createDeferred(): Deferred { + let resolve!: (value: T) => void; + let reject!: (reason?: unknown) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +} + +async function collectEvents(observable: ReturnType | ReturnType) { + return firstValueFrom(observable.pipe(toArray())); +} + +function createRunInput({ + threadId, + runId, + messages, + state, + parentRunId, +}: { + threadId: string; + runId: string; + messages: Message[]; + state?: Record; + parentRunId?: string | null; +}): RunAgentInput { + return { + threadId, + runId, + parentRunId: parentRunId ?? undefined, + state: state ?? {}, + messages, + tools: [], + context: [], + forwardedProps: undefined, + }; +} + +function expectRunStartedEvent(event: BaseEvent, expectedMessages: Message[]) { + expect(event.type).toBe(EventType.RUN_STARTED); + const runStarted = event as RunStartedEvent; + expect(runStarted.input?.messages).toEqual(expectedMessages); +} + +function createTextMessageEvents({ + messageId, + role = "assistant", + content, +}: { + messageId: string; + role?: "assistant" | "developer" | "system" | "user"; + content: string; +}): BaseEvent[] { + return [ + { + type: EventType.TEXT_MESSAGE_START, + messageId, + role, + }, + { + type: EventType.TEXT_MESSAGE_CONTENT, + messageId, + delta: content, + }, + { + type: EventType.TEXT_MESSAGE_END, + messageId, + }, + ] as BaseEvent[]; +} + +function createToolCallEvents({ + toolCallId, + parentMessageId, + toolName, + argsJson, + resultMessageId, + resultContent, +}: { + toolCallId: string; + parentMessageId: string; + toolName: string; + argsJson: string; + resultMessageId: string; + resultContent: string; +}): BaseEvent[] { + return [ + { + type: EventType.TOOL_CALL_START, + toolCallId, + toolCallName: toolName, + parentMessageId, + }, + { + type: EventType.TOOL_CALL_ARGS, + toolCallId, + delta: argsJson, + }, + { + type: EventType.TOOL_CALL_END, + toolCallId, + }, + { + type: EventType.TOOL_CALL_RESULT, + toolCallId, + messageId: resultMessageId, + content: resultContent, + role: "tool", + }, + ] as BaseEvent[]; +} + +describe("SqliteAgentRunner e2e", () => { + describe("Fresh Replay After Single Run", () => { + it("replays sanitized message history on connectAgent", async () => { + const runner = createRunner(); + const threadId = "thread-fresh-replay"; + const existingMessage: Message = { + id: "message-existing", + role: "user", + content: "Hello there", + }; + + const runEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent(), + input: createRunInput({ + threadId, + runId: "run-0", + messages: [existingMessage], + }), + }), + ); + + expectRunStartedEvent(runEvents[0], [existingMessage]); + expect(runEvents.at(-1)?.type).toBe(EventType.RUN_FINISHED); + + const replayEvents = await collectEvents(runner.connect({ threadId })); + const replayAgent = new ReplayAgent(replayEvents, threadId); + await replayAgent.connectAgent({ runId: "replay-run" }); + + expect(replayAgent.messages).toEqual([existingMessage]); + }); + }); + + describe("New Messages on Subsequent Runs", () => { + it("merges new message IDs without duplicating history", async () => { + const runner = createRunner(); + const threadId = "thread-subsequent-runs"; + const existingMessage: Message = { + id: "msg-existing", + role: "user", + content: "First turn", + }; + + const initialRunEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent(), + input: createRunInput({ + threadId, + runId: "run-0", + messages: [existingMessage], + }), + }), + ); + expectRunStartedEvent(initialRunEvents[0], [existingMessage]); + + const newMessage: Message = { + id: "msg-new", + role: "user", + content: "Second turn", + }; + + const secondRunEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent(), + input: createRunInput({ + threadId, + runId: "run-1", + messages: [existingMessage, newMessage], + }), + }), + ); + + expectRunStartedEvent(secondRunEvents[0], [newMessage]); + + const replayEvents = await collectEvents(runner.connect({ threadId })); + const replayAgent = new ReplayAgent(replayEvents, threadId); + await replayAgent.connectAgent({ runId: "replay-run" }); + + expect(replayAgent.messages).toEqual([existingMessage, newMessage]); + expect(new Set(replayAgent.messages.map((message) => message.id)).size).toBe( + replayAgent.messages.length, + ); + }); + }); + + describe("Fresh Agent Connection After Prior Runs", () => { + it("hydrates a brand-new agent via connect()", async () => { + const runner = createRunner(); + const threadId = "thread-new-agent-connection"; + const existingMessage: Message = { + id: "existing-connection", + role: "user", + content: "Persist me", + }; + + const runEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent(), + input: createRunInput({ + threadId, + runId: "run-0", + messages: [existingMessage], + }), + }), + ); + expectRunStartedEvent(runEvents[0], [existingMessage]); + + const connectingAgent = new RunnerConnectAgent(runner, threadId); + await connectingAgent.connectAgent({ runId: "connect-run" }); + + expect(connectingAgent.messages).toEqual([existingMessage]); + }); + }); + + describe("Mixed Roles and Tool Results", () => { + it("preserves agent-emitted tool events alongside heterogeneous inputs", async () => { + const runner = createRunner(); + const threadId = "thread-mixed-roles"; + + const systemMessage: Message = { + id: "sys-1", + role: "system", + content: "Global directive", + }; + const developerMessage: Message = { + id: "dev-1", + role: "developer", + content: "Internal guidance", + }; + const userMessage: Message = { + id: "user-1", + role: "user", + content: "Need the weather", + }; + const baseMessages = [systemMessage, developerMessage, userMessage]; + + const assistantMessageId = "assistant-1"; + const toolCallId = "tool-call-1"; + const toolMessageId = "tool-msg-1"; + + const agentEvents: BaseEvent[] = [ + ...createTextMessageEvents({ + messageId: assistantMessageId, + content: "Calling the weather tool", + }), + ...createToolCallEvents({ + toolCallId, + parentMessageId: assistantMessageId, + toolName: "getWeather", + argsJson: '{"location":"NYC"}', + resultMessageId: toolMessageId, + resultContent: '{"temp":72}', + }), + ]; + + const runEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent({ events: agentEvents }), + input: createRunInput({ + threadId, + runId: "run-0", + messages: baseMessages, + }), + }), + ); + + expectRunStartedEvent(runEvents[0], baseMessages); + expect(runEvents.filter((event) => event.type === EventType.TOOL_CALL_RESULT)).toHaveLength(1); + + const replayEvents = await collectEvents(runner.connect({ threadId })); + const replayAgent = new ReplayAgent(replayEvents, threadId); + await replayAgent.connectAgent({ runId: "replay-run" }); + + expect(replayAgent.messages).toEqual([ + systemMessage, + developerMessage, + userMessage, + { + id: assistantMessageId, + role: "assistant", + content: "Calling the weather tool", + toolCalls: [ + { + id: toolCallId, + type: "function", + function: { + name: "getWeather", + arguments: '{"location":"NYC"}', + }, + }, + ], + }, + { + id: toolMessageId, + role: "tool", + content: '{"temp":72}', + toolCallId, + }, + ]); + expect(replayAgent.messages.filter((message) => message.role === "tool")).toHaveLength(1); + }); + }); + + describe("Multiple Consecutive Runs with Agent Output", () => { + it("deduplicates input history while emitting each agent message once", async () => { + const runner = createRunner(); + const threadId = "thread-multi-runs"; + const systemMessage: Message = { + id: "system-shared", + role: "system", + content: "System context", + }; + const userMessages: Message[] = []; + + for (let index = 0; index < 3; index += 1) { + const userMessage: Message = { + id: `user-${index + 1}`, + role: "user", + content: `User message ${index + 1}`, + }; + userMessages.push(userMessage); + + const messagesForRun = [systemMessage, ...userMessages]; + const assistantId = `assistant-${index + 1}`; + const toolCallId = `tool-call-${index + 1}`; + const toolMessageId = `tool-msg-${index + 1}`; + + const events: BaseEvent[] = [ + ...createTextMessageEvents({ + messageId: assistantId, + content: `Assistant reply ${index + 1}`, + }), + ...createToolCallEvents({ + toolCallId, + parentMessageId: assistantId, + toolName: `tool-${index + 1}`, + argsJson: `{"step":${index + 1}}`, + resultMessageId: toolMessageId, + resultContent: `{"ok":${index + 1}}`, + }), + ]; + + const runEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent({ events }), + input: createRunInput({ + threadId, + runId: `run-${index}`, + messages: messagesForRun, + }), + }), + ); + + if (index === 0) { + expectRunStartedEvent(runEvents[0], messagesForRun); + } else { + expectRunStartedEvent(runEvents[0], [userMessage]); + } + expect(runEvents.at(-1)?.type).toBe(EventType.RUN_FINISHED); + } + + const replayEvents = await collectEvents(runner.connect({ threadId })); + const replayAgent = new ReplayAgent(replayEvents, threadId); + await replayAgent.connectAgent({ runId: "replay-final" }); + + const finalMessages = replayAgent.messages; + expect(new Set(finalMessages.map((message) => message.id)).size).toBe(finalMessages.length); + const roleCounts = finalMessages.reduce>((counts, message) => { + counts[message.role] = (counts[message.role] ?? 0) + 1; + return counts; + }, {}); + expect(roleCounts.system).toBe(1); + expect(roleCounts.user).toBe(3); + expect(roleCounts.assistant).toBe(3); + expect(roleCounts.tool).toBe(3); + }); + }); + + describe("Agent-Provided RUN_STARTED input", () => { + it("forwards the agent-specified payload without sanitizing", async () => { + const runner = createRunner(); + const threadId = "thread-custom-run-started"; + const runId = "run-0"; + + const customMessages: Message[] = [ + { + id: "custom-user", + role: "user", + content: "Pre-sent content", + }, + ]; + const customInput: RunAgentInput = { + threadId, + runId, + parentRunId: undefined, + state: { injected: true }, + messages: customMessages, + tools: [], + context: [], + forwardedProps: { source: "agent" }, + }; + const customRunStarted: RunStartedEvent = { + type: EventType.RUN_STARTED, + threadId, + runId, + parentRunId: null, + input: customInput, + }; + + const agentEvents: BaseEvent[] = [ + customRunStarted, + ...createTextMessageEvents({ + messageId: "agent-message", + content: "Custom start acknowledged", + }), + ]; + + const runEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent({ + events: agentEvents, + emitDefaultRunStarted: false, + }), + input: createRunInput({ + threadId, + runId, + messages: [], + }), + }), + ); + + expect(runEvents[0]).toEqual(customRunStarted); + expect(runEvents.filter((event) => event.type === EventType.RUN_FINISHED)).toHaveLength(1); + + const replayEvents = await collectEvents(runner.connect({ threadId })); + const replayAgent = new ReplayAgent(replayEvents, threadId); + await replayAgent.connectAgent({ runId: "replay-run" }); + expect(replayAgent.messages.find((message) => message.id === "custom-user")).toEqual( + customMessages[0], + ); + }); + }); + + describe("Concurrent Connections During Run", () => { + it("streams in-flight events to live subscribers and persists final history", async () => { + const runner = createRunner(); + const threadId = "thread-concurrency"; + const runId = "run-live"; + const initialMessage: Message = { + id: "initial-user", + role: "user", + content: "Start run", + }; + + const runStartedSignal = createDeferred(); + const resumeSignal = createDeferred(); + + const agent = new EmitAgent({ + events: [ + ...createTextMessageEvents({ + messageId: "assistant-live", + content: "Streaming content", + }), + ], + afterEvent: async ({ event }) => { + if (event.type === EventType.RUN_STARTED) { + runStartedSignal.resolve(); + await resumeSignal.promise; + } + }, + }); + + const runEvents: BaseEvent[] = []; + const run$ = runner.run({ + threadId, + agent, + input: createRunInput({ + threadId, + runId, + messages: [initialMessage], + }), + }); + + let runSubscription: Subscription; + const runCompletion = new Promise((resolve, reject) => { + runSubscription = run$.subscribe({ + next: (event) => runEvents.push(event), + error: (error) => { + runSubscription.unsubscribe(); + reject(error); + }, + complete: () => { + runSubscription.unsubscribe(); + resolve(); + }, + }); + }); + + await runStartedSignal.promise; + + const liveEvents: BaseEvent[] = []; + const connect$ = runner.connect({ threadId }); + let connectSubscription: Subscription; + const connectCompletion = new Promise((resolve, reject) => { + connectSubscription = connect$.subscribe({ + next: (event) => liveEvents.push(event), + error: (error) => { + connectSubscription.unsubscribe(); + reject(error); + }, + complete: () => { + connectSubscription.unsubscribe(); + resolve(); + }, + }); + }); + + resumeSignal.resolve(); + + await Promise.all([runCompletion, connectCompletion]); + + expectRunStartedEvent(runEvents[0], [initialMessage]); + expect(runEvents.at(-1)?.type).toBe(EventType.RUN_FINISHED); + expect(liveEvents).toEqual(runEvents); + + const persistedEvents = await collectEvents(runner.connect({ threadId })); + expect(persistedEvents).toEqual(runEvents); + + const replayAgent = new ReplayAgent(persistedEvents, threadId); + await replayAgent.connectAgent({ runId: "replay-run" }); + expect(replayAgent.messages.map((message) => message.id)).toEqual([ + initialMessage.id, + "assistant-live", + ]); + }); + }); + + describe("Error Handling", () => { + it("propagates RUN_ERROR while retaining input history", async () => { + const runner = createRunner(); + const threadId = "thread-run-error"; + const userMessage: Message = { + id: "error-user", + role: "user", + content: "Trigger error", + }; + + const runErrorEvent: RunErrorEvent = { + type: EventType.RUN_ERROR, + message: "Agent failure", + }; + + const runEvents = await collectEvents( + runner.run({ + threadId, + agent: new EmitAgent({ + events: [runErrorEvent], + includeRunFinished: false, + }), + input: createRunInput({ + threadId, + runId: "run-error", + messages: [userMessage], + }), + }), + ); + + expectRunStartedEvent(runEvents[0], [userMessage]); + expect(runEvents.at(-1)).toEqual(runErrorEvent); + + const replayEvents = await collectEvents(runner.connect({ threadId })); + const replayAgent = new ReplayAgent(replayEvents, threadId); + const capturedRunErrors: RunErrorEvent[] = []; + const result = await replayAgent.connectAgent( + { runId: "replay-run" }, + { + onRunErrorEvent: ({ event }) => { + capturedRunErrors.push(event); + }, + }, + ); + + expect(runEvents.at(-1)?.type).toBe(EventType.RUN_ERROR); + expect(capturedRunErrors).toHaveLength(1); + expect(capturedRunErrors[0]).toMatchObject(runErrorEvent); + expect(result.newMessages).toEqual([userMessage]); + expect(replayAgent.messages).toEqual([userMessage]); + }); + }); +}); diff --git a/packages/sqlite-runner/src/__tests__/sqlite-runner.test.ts b/packages/sqlite-runner/src/__tests__/sqlite-runner.test.ts new file mode 100644 index 00000000..dd2f60d6 --- /dev/null +++ b/packages/sqlite-runner/src/__tests__/sqlite-runner.test.ts @@ -0,0 +1,226 @@ +import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import { SqliteAgentRunner } from ".."; +import { + AbstractAgent, + BaseEvent, + EventType, + Message, + RunAgentInput, + RunStartedEvent, + TextMessageContentEvent, + TextMessageEndEvent, + TextMessageStartEvent, +} from "@ag-ui/client"; +import { EMPTY, firstValueFrom } from "rxjs"; +import { toArray } from "rxjs/operators"; +import Database from "better-sqlite3"; +import fs from "fs"; +import os from "os"; +import path from "path"; + +type RunCallbacks = { + onEvent: (event: { event: BaseEvent }) => void | Promise; + onNewMessage?: (args: { message: Message }) => void | Promise; + onRunStartedEvent?: () => void | Promise; +}; + +class MockAgent extends AbstractAgent { + constructor( + private readonly events: BaseEvent[] = [], + private readonly emitDefaultRunStarted = true, + ) { + super(); + } + + async runAgent(input: RunAgentInput, callbacks: RunCallbacks): Promise { + if (this.emitDefaultRunStarted) { + const runStarted: RunStartedEvent = { + type: EventType.RUN_STARTED, + threadId: input.threadId, + runId: input.runId, + }; + await callbacks.onEvent({ event: runStarted }); + await callbacks.onRunStartedEvent?.(); + } + + for (const event of this.events) { + await callbacks.onEvent({ event }); + } + } + + protected run(): ReturnType { + return EMPTY; + } + + protected connect(): ReturnType { + return EMPTY; + } + + clone(): AbstractAgent { + return new MockAgent(this.events, this.emitDefaultRunStarted); + } +} + +describe("SqliteAgentRunner", () => { + let tempDir: string; + let dbPath: string; + let runner: SqliteAgentRunner; + + beforeEach(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "sqlite-runner-test-")); + dbPath = path.join(tempDir, "test.db"); + runner = new SqliteAgentRunner({ dbPath }); + }); + + afterEach(() => { + if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath); + if (fs.existsSync(tempDir)) fs.rmdirSync(tempDir); + }); + + it("emits RUN_STARTED and agent events", async () => { + const threadId = "sqlite-basic"; + const agent = new MockAgent([ + { type: EventType.TEXT_MESSAGE_START, messageId: "msg-1", role: "assistant" } as TextMessageStartEvent, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg-1", delta: "Hello" } as TextMessageContentEvent, + { type: EventType.TEXT_MESSAGE_END, messageId: "msg-1" } as TextMessageEndEvent, + ]); + + const events = await firstValueFrom( + runner + .run({ + threadId, + agent, + input: { threadId, runId: "run-1", messages: [], state: {} }, + }) + .pipe(toArray()), + ); + + expect(events.map((event) => event.type)).toEqual([ + EventType.RUN_STARTED, + EventType.TEXT_MESSAGE_START, + EventType.TEXT_MESSAGE_CONTENT, + EventType.TEXT_MESSAGE_END, + ]); + }); + + it("attaches only new messages on subsequent runs", async () => { + const threadId = "sqlite-new-messages"; + const existing: Message = { id: "existing", role: "user", content: "hi" }; + + await firstValueFrom( + runner + .run({ + threadId, + agent: new MockAgent(), + input: { threadId, runId: "run-0", messages: [existing], state: {} }, + }) + .pipe(toArray()), + ); + + const newMessage: Message = { id: "new", role: "user", content: "follow up" }; + + const secondRun = await firstValueFrom( + runner + .run({ + threadId, + agent: new MockAgent(), + input: { + threadId, + runId: "run-1", + messages: [existing, newMessage], + state: { counter: 1 }, + }, + }) + .pipe(toArray()), + ); + + const runStarted = secondRun[0] as RunStartedEvent; + expect(runStarted.input?.messages?.map((m) => m.id)).toEqual(["new"]); + + const db = new Database(dbPath); + const rows = db + .prepare("SELECT events FROM agent_runs WHERE thread_id = ? ORDER BY created_at") + .all(threadId) as { events: string }[]; + db.close(); + + expect(rows).toHaveLength(2); + const run1Stored = JSON.parse(rows[0].events) as BaseEvent[]; + const run2Stored = JSON.parse(rows[1].events) as BaseEvent[]; + + const run1Started = run1Stored.find((event) => event.type === EventType.RUN_STARTED) as RunStartedEvent; + expect(run1Started.input?.messages?.map((m) => m.id)).toEqual(["existing"]); + + const run2Started = run2Stored.find((event) => event.type === EventType.RUN_STARTED) as RunStartedEvent; + expect(run2Started.input?.messages?.map((m) => m.id)).toEqual(["new"]); + }); + + it("preserves agent-provided input", async () => { + const threadId = "sqlite-agent-input"; + const providedInput: RunAgentInput = { + threadId, + runId: "run-keep", + messages: [], + state: { fromAgent: true }, + }; + + const agent = new MockAgent( + [ + { + type: EventType.RUN_STARTED, + threadId, + runId: "run-keep", + input: providedInput, + } as RunStartedEvent, + ], + false, + ); + + const events = await firstValueFrom( + runner + .run({ + threadId, + agent, + input: { + threadId, + runId: "run-keep", + messages: [{ id: "ignored", role: "user", content: "hi" }], + state: {}, + }, + }) + .pipe(toArray()), + ); + + expect(events).toHaveLength(1); + const runStarted = events[0] as RunStartedEvent; + expect(runStarted.input).toBe(providedInput); + }); + + it("persists events across runner instances", async () => { + const threadId = "sqlite-persist"; + const agent = new MockAgent([ + { type: EventType.TEXT_MESSAGE_START, messageId: "msg", role: "assistant" } as TextMessageStartEvent, + { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg", delta: "hi" } as TextMessageContentEvent, + { type: EventType.TEXT_MESSAGE_END, messageId: "msg" } as TextMessageEndEvent, + ]); + + await firstValueFrom( + runner + .run({ + threadId, + agent, + input: { threadId, runId: "run-1", messages: [], state: {} }, + }) + .pipe(toArray()), + ); + + const newRunner = new SqliteAgentRunner({ dbPath }); + const replayed = await firstValueFrom(newRunner.connect({ threadId }).pipe(toArray())); + + expect(replayed[0].type).toBe(EventType.RUN_STARTED); + expect(replayed.slice(1).map((event) => event.type)).toEqual([ + EventType.TEXT_MESSAGE_START, + EventType.TEXT_MESSAGE_CONTENT, + EventType.TEXT_MESSAGE_END, + ]); + }); +}); diff --git a/packages/sqlite-runner/src/index.ts b/packages/sqlite-runner/src/index.ts new file mode 100644 index 00000000..9c2b44b8 --- /dev/null +++ b/packages/sqlite-runner/src/index.ts @@ -0,0 +1 @@ +export * from "./sqlite-runner"; diff --git a/packages/runtime/src/runner/sqlite.ts b/packages/sqlite-runner/src/sqlite-runner.ts similarity index 78% rename from packages/runtime/src/runner/sqlite.ts rename to packages/sqlite-runner/src/sqlite-runner.ts index 895835ed..2ba165ad 100644 --- a/packages/runtime/src/runner/sqlite.ts +++ b/packages/sqlite-runner/src/sqlite-runner.ts @@ -1,25 +1,18 @@ import { AgentRunner, - AgentRunnerConnectRequest, - AgentRunnerIsRunningRequest, - AgentRunnerRunRequest, + type AgentRunnerConnectRequest, + type AgentRunnerIsRunningRequest, + type AgentRunnerRunRequest, type AgentRunnerStopRequest, -} from "./agent-runner"; +} from "@copilotkitnext/runtime"; import { Observable, ReplaySubject } from "rxjs"; import { BaseEvent, RunAgentInput, - Message, EventType, - TextMessageStartEvent, - TextMessageContentEvent, - TextMessageEndEvent, - ToolCallStartEvent, - ToolCallArgsEvent, - ToolCallEndEvent, - ToolCallResultEvent, + RunStartedEvent, + compactEvents, } from "@ag-ui/client"; -import { compactEvents } from "./event-compaction"; import Database from "better-sqlite3"; const SCHEMA_VERSION = 1; @@ -67,76 +60,6 @@ export class SqliteAgentRunner extends AgentRunner { this.initializeSchema(); } - private convertMessageToEvents(message: Message): BaseEvent[] { - const events: BaseEvent[] = []; - - if ( - (message.role === "assistant" || - message.role === "user" || - message.role === "developer" || - message.role === "system") && - message.content - ) { - const textStartEvent: TextMessageStartEvent = { - type: EventType.TEXT_MESSAGE_START, - messageId: message.id, - role: message.role, - }; - events.push(textStartEvent); - - const textContentEvent: TextMessageContentEvent = { - type: EventType.TEXT_MESSAGE_CONTENT, - messageId: message.id, - delta: message.content, - }; - events.push(textContentEvent); - - const textEndEvent: TextMessageEndEvent = { - type: EventType.TEXT_MESSAGE_END, - messageId: message.id, - }; - events.push(textEndEvent); - } - - if (message.role === "assistant" && message.toolCalls) { - for (const toolCall of message.toolCalls) { - const toolStartEvent: ToolCallStartEvent = { - type: EventType.TOOL_CALL_START, - toolCallId: toolCall.id, - toolCallName: toolCall.function.name, - parentMessageId: message.id, - }; - events.push(toolStartEvent); - - const toolArgsEvent: ToolCallArgsEvent = { - type: EventType.TOOL_CALL_ARGS, - toolCallId: toolCall.id, - delta: toolCall.function.arguments, - }; - events.push(toolArgsEvent); - - const toolEndEvent: ToolCallEndEvent = { - type: EventType.TOOL_CALL_END, - toolCallId: toolCall.id, - }; - events.push(toolEndEvent); - } - } - - if (message.role === "tool" && message.toolCallId) { - const toolResultEvent: ToolCallResultEvent = { - type: EventType.TOOL_CALL_RESULT, - messageId: message.id, - toolCallId: message.toolCallId, - content: message.content, - role: "tool", - }; - events.push(toolResultEvent); - } - - return events; - } - private initializeSchema(): void { // Create the agent_runs table this.db.exec(` @@ -300,6 +223,13 @@ export class SqliteAgentRunner extends AgentRunner { if ('messageId' in event && typeof event.messageId === 'string') { historicMessageIds.add(event.messageId); } + if (event.type === EventType.RUN_STARTED) { + const runStarted = event as RunStartedEvent; + const messages = runStarted.input?.messages ?? []; + for (const message of messages) { + historicMessageIds.add(message.id); + } + } } } @@ -321,9 +251,31 @@ export class SqliteAgentRunner extends AgentRunner { try { await request.agent.runAgent(request.input, { onEvent: ({ event }) => { - runSubject.next(event); // For run() return - only agent events - nextSubject.next(event); // For connect() / store - all events - currentRunEvents.push(event); // Accumulate for database storage + let processedEvent: BaseEvent = event; + if (event.type === EventType.RUN_STARTED) { + const runStartedEvent = event as RunStartedEvent; + if (!runStartedEvent.input) { + const sanitizedMessages = request.input.messages + ? request.input.messages.filter( + (message) => !historicMessageIds.has(message.id), + ) + : undefined; + const updatedInput = { + ...request.input, + ...(sanitizedMessages !== undefined + ? { messages: sanitizedMessages } + : {}), + }; + processedEvent = { + ...runStartedEvent, + input: updatedInput, + } as RunStartedEvent; + } + } + + runSubject.next(processedEvent); // For run() return - only agent events + nextSubject.next(processedEvent); // For connect() / store - all events + currentRunEvents.push(processedEvent); // Accumulate for database storage }, onNewMessage: ({ message }) => { // Called for each new message @@ -332,25 +284,11 @@ export class SqliteAgentRunner extends AgentRunner { } }, onRunStartedEvent: () => { - // Process input messages + // Mark input messages as seen without emitting duplicates if (request.input.messages) { for (const message of request.input.messages) { if (!seenMessageIds.has(message.id)) { seenMessageIds.add(message.id); - const events = this.convertMessageToEvents(message); - - // Check if this message is NEW (not in historic runs) - const isNewMessage = !historicMessageIds.has(message.id); - - for (const event of events) { - // Always emit to stream for context - nextSubject.next(event); - - // Store if this is a NEW message for this run - if (isNewMessage) { - currentRunEvents.push(event); - } - } } } } @@ -478,4 +416,4 @@ export class SqliteAgentRunner extends AgentRunner { this.db.close(); } } -} \ No newline at end of file +} diff --git a/packages/sqlite-runner/tsconfig.json b/packages/sqlite-runner/tsconfig.json new file mode 100644 index 00000000..806f1abd --- /dev/null +++ b/packages/sqlite-runner/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@copilotkitnext/typescript-config/base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "types": ["vitest/globals"] + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "src/**/__tests__/**", "src/**/*.test.ts"] +} diff --git a/packages/sqlite-runner/tsup.config.ts b/packages/sqlite-runner/tsup.config.ts new file mode 100644 index 00000000..3cc0459b --- /dev/null +++ b/packages/sqlite-runner/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['cjs', 'esm'], + dts: true, + sourcemap: true, + clean: true, + target: 'es2022', + outDir: 'dist', +}); diff --git a/packages/sqlite-runner/vitest.config.mjs b/packages/sqlite-runner/vitest.config.mjs new file mode 100644 index 00000000..a0886b47 --- /dev/null +++ b/packages/sqlite-runner/vitest.config.mjs @@ -0,0 +1,15 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + globals: true, + include: ["src/**/__tests__/**/*.{test,spec}.ts"], + coverage: { + reporter: ["text", "lcov", "html"], + include: ["src/**/*.ts"], + exclude: ["src/**/*.d.ts", "src/**/index.ts"], + }, + setupFiles: [], + }, +}); diff --git a/packages/web-inspector/package.json b/packages/web-inspector/package.json index b0894759..feb29bde 100644 --- a/packages/web-inspector/package.json +++ b/packages/web-inspector/package.json @@ -1,6 +1,6 @@ { "name": "@copilotkitnext/web-inspector", - "version": "0.0.15", + "version": "0.0.14", "description": "Lit-based web component for the CopilotKit web inspector", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -25,7 +25,7 @@ "clean": "rm -rf dist src/styles/generated.css" }, "dependencies": { - "@ag-ui/client": "0.0.40-alpha.3", + "@ag-ui/client": "0.0.40-alpha.6", "@copilotkitnext/core": "workspace:*", "lit": "^3.2.0", "lucide": "^0.525.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e08fa9c9..75b45e76 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,10 +13,10 @@ importers: version: 8.6.14(@types/react@19.1.0)(storybook@8.6.14(prettier@3.6.0)) '@storybook/addon-webpack5-compiler-swc': specifier: ^1.0.5 - version: 1.0.6(@swc/helpers@0.5.15)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + version: 1.0.6(webpack@5.100.0(@swc/core@1.12.11)) '@storybook/react-webpack5': specifier: ^8 - version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) prettier: specifier: ^3.6.0 version: 3.6.0 @@ -65,19 +65,16 @@ importers: tslib: specifier: ^2.8.1 version: 2.8.1 - zod: - specifier: ^3.25.75 - version: 3.25.75 zone.js: specifier: ^0.14.0 version: 0.14.10 devDependencies: '@angular-devkit/build-angular': specifier: ^18.2.0 - version: 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.12)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.12)(typescript@5.4.5) + version: 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5) '@angular/cli': specifier: ^18.2.0 - version: 18.2.20(chokidar@4.0.3) + version: 18.2.20(chokidar@3.6.0) '@angular/compiler-cli': specifier: ^18.2.0 version: 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) @@ -94,11 +91,11 @@ importers: apps/angular/demo-server: dependencies: '@ag-ui/client': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@ag-ui/langgraph': specifier: ^0.0.11 - version: 0.0.11(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.75))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.0.11(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.75))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@copilotkitnext/demo-agents': specifier: workspace:^ version: link:../../../packages/demo-agents @@ -131,8 +128,8 @@ importers: apps/angular/storybook: dependencies: '@ag-ui/client': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@angular/animations': specifier: ^18.2.0 version: 18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)) @@ -172,10 +169,10 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: ^18.2.0 - version: 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5) + version: 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5) '@angular/cli': specifier: ^18.2.0 - version: 18.2.20(chokidar@4.0.3) + version: 18.2.20 '@angular/compiler-cli': specifier: ^18.2.0 version: 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) @@ -196,7 +193,7 @@ importers: version: 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@storybook/angular': specifier: ^8 - version: 8.6.14(@angular-devkit/architect@0.1902.15(chokidar@4.0.3))(@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5))(@angular-devkit/core@19.2.15(chokidar@4.0.3))(@angular/animations@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/cli@18.2.20(chokidar@4.0.3))(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(@angular/forms@18.2.13(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.13(@angular/animations@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1))(@angular/platform-browser-dynamic@18.2.13(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.13(@angular/animations@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))))(@angular/platform-browser@18.2.13(@angular/animations@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(rxjs@7.8.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.4.5)(zone.js@0.14.10) + version: 8.6.14(yd4v6lxii4rfhetgylkhgsqlgq) '@storybook/test': specifier: ^8 version: 8.6.14(storybook@8.6.14(prettier@3.6.0)) @@ -211,19 +208,19 @@ importers: version: 10.4.21(postcss@8.5.6) css-loader: specifier: ^7.1.2 - version: 7.1.2(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + version: 7.1.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) postcss: specifier: ^8.4.31 version: 8.5.6 postcss-loader: specifier: ^8.1.1 - version: 8.1.1(postcss@8.5.6)(typescript@5.4.5)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + version: 8.1.1(postcss@8.5.6)(typescript@5.4.5)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) storybook: specifier: ^8 version: 8.6.14(prettier@3.6.0) style-loader: specifier: ^4.0.0 - version: 4.0.0(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + version: 4.0.0(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) tailwindcss: specifier: ^4.1.11 version: 4.1.11 @@ -269,8 +266,8 @@ importers: apps/react/demo: dependencies: '@ag-ui/client': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@copilotkitnext/agent': specifier: workspace:* version: link:../../../packages/agent @@ -294,7 +291,7 @@ importers: version: 4.8.10 next: specifier: 15.4.4 - version: 15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0) + version: 15.4.4(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0) openai: specifier: ^5.9.0 version: 5.9.0(ws@8.18.3)(zod@3.25.75) @@ -352,7 +349,7 @@ importers: version: 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@storybook/nextjs': specifier: ^8 - version: 8.6.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)(storybook@8.6.14(prettier@3.6.0))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + version: 8.6.14(@swc/core@1.12.11)(next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)(storybook@8.6.14(prettier@3.6.0))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)) '@storybook/react': specifier: ^8 version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) @@ -406,8 +403,8 @@ importers: packages/agent: dependencies: '@ag-ui/client': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@ai-sdk/anthropic': specifier: ^2.0.22 version: 2.0.23(zod@3.25.75) @@ -447,7 +444,7 @@ importers: version: 9.30.0(jiti@2.5.1) tsup: specifier: ^8.5.0 - version: 8.5.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) + version: 8.5.0(@swc/core@1.12.11)(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) typescript: specifier: 5.8.2 version: 5.8.2 @@ -458,11 +455,11 @@ importers: packages/angular: dependencies: '@ag-ui/client': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@ag-ui/core': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@copilotkitnext/core': specifier: workspace:* version: link:../core @@ -496,10 +493,10 @@ importers: devDependencies: '@analogjs/vite-plugin-angular': specifier: ^1.20.2 - version: 1.20.2(@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5))(@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)) + version: 1.20.2(@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@4.0.3)(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5))(@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)) '@analogjs/vitest-angular': specifier: ^1.20.2 - version: 1.20.2(@analogjs/vite-plugin-angular@1.20.2(@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5))(@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)))(@angular-devkit/architect@0.1902.15)(vitest@2.1.9(@types/node@22.15.3)(@vitest/ui@2.1.9)(jsdom@24.1.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)) + version: 1.20.2(@analogjs/vite-plugin-angular@1.20.2(@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@4.0.3)(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5))(@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)))(@angular-devkit/architect@0.1902.15(chokidar@4.0.3))(vitest@2.1.9(@types/node@22.15.3)(@vitest/ui@2.1.9)(jsdom@24.1.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)) '@angular/cdk': specifier: ^18.0.0 version: 18.2.14(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1) @@ -600,8 +597,8 @@ importers: packages/core: dependencies: '@ag-ui/client': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@copilotkitnext/shared': specifier: workspace:* version: link:../shared @@ -632,7 +629,7 @@ importers: version: 9.30.0(jiti@2.5.1) tsup: specifier: ^8.5.0 - version: 8.5.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) + version: 8.5.0(@swc/core@1.12.11)(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) typescript: specifier: 5.8.2 version: 5.8.2 @@ -666,13 +663,13 @@ importers: version: 9.30.0(jiti@2.5.1) tsup: specifier: ^8.0.1 - version: 8.5.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) + version: 8.5.0(@swc/core@1.12.11)(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) typescript: specifier: ^5.8.2 version: 5.8.2 vitest: specifier: ^2.1.8 - version: 2.1.9(@types/node@22.15.3)(@vitest/ui@3.2.4)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + version: 2.1.9(@types/node@22.15.3)(@vitest/ui@2.1.9)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) packages/eslint-config: devDependencies: @@ -713,11 +710,11 @@ importers: packages/react: dependencies: '@ag-ui/client': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@ag-ui/core': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@copilotkitnext/core': specifier: workspace:* version: link:../core @@ -832,7 +829,7 @@ importers: version: 4.1.11 tsup: specifier: ^8.5.0 - version: 8.5.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) + version: 8.5.0(@swc/core@1.12.11)(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) typescript: specifier: 5.8.2 version: 5.8.2 @@ -843,26 +840,20 @@ importers: packages/runtime: dependencies: '@ag-ui/client': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@ag-ui/core': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@ag-ui/encoder': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@copilotkitnext/shared': specifier: workspace:* version: link:../shared hono: specifier: ^4.6.13 version: 4.8.10 - ioredis: - specifier: ^5.7.0 - version: 5.7.0 - kysely: - specifier: ^0.28.5 - version: 0.28.5 rxjs: specifier: 7.8.1 version: 7.8.1 @@ -873,27 +864,18 @@ importers: '@copilotkitnext/typescript-config': specifier: workspace:* version: link:../typescript-config - '@types/better-sqlite3': - specifier: ^7.6.13 - version: 7.6.13 '@types/node': specifier: ^22.15.3 version: 22.15.3 - better-sqlite3: - specifier: ^12.2.0 - version: 12.2.0 eslint: specifier: ^9.30.0 version: 9.30.0(jiti@2.5.1) - ioredis-mock: - specifier: ^8.9.0 - version: 8.9.0(@types/ioredis-mock@8.2.6(ioredis@5.7.0))(ioredis@5.7.0) openai: specifier: ^5.9.0 version: 5.9.0(ws@8.18.3)(zod@3.25.75) tsup: specifier: ^8.5.0 - version: 8.5.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) + version: 8.5.0(@swc/core@1.12.11)(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) typescript: specifier: 5.8.2 version: 5.8.2 @@ -924,18 +906,58 @@ importers: version: 9.30.0(jiti@2.5.1) tsup: specifier: ^8.5.0 - version: 8.5.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) + version: 8.5.0(@swc/core@1.12.11)(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) + typescript: + specifier: 5.8.2 + version: 5.8.2 + + packages/sqlite-runner: + dependencies: + '@ag-ui/client': + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 + '@copilotkitnext/runtime': + specifier: workspace:* + version: link:../runtime + rxjs: + specifier: 7.8.1 + version: 7.8.1 + devDependencies: + '@copilotkitnext/eslint-config': + specifier: workspace:* + version: link:../eslint-config + '@copilotkitnext/typescript-config': + specifier: workspace:* + version: link:../typescript-config + '@types/better-sqlite3': + specifier: ^7.6.13 + version: 7.6.13 + '@types/node': + specifier: ^22.15.3 + version: 22.15.3 + better-sqlite3: + specifier: ^12.2.0 + version: 12.2.0 + eslint: + specifier: ^9.30.0 + version: 9.30.0(jiti@2.5.1) + tsup: + specifier: ^8.5.0 + version: 8.5.0(@swc/core@1.12.11)(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) typescript: specifier: 5.8.2 version: 5.8.2 + vitest: + specifier: ^3.0.5 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.3)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) packages/typescript-config: {} packages/web-inspector: dependencies: '@ag-ui/client': - specifier: 0.0.40-alpha.3 - version: 0.0.40-alpha.3 + specifier: 0.0.40-alpha.6 + version: 0.0.40-alpha.6 '@copilotkitnext/core': specifier: workspace:* version: link:../core @@ -969,7 +991,7 @@ importers: version: 4.1.12 tsup: specifier: ^8.5.0 - version: 8.5.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) + version: 8.5.0(@swc/core@1.12.11)(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0) typescript: specifier: 5.8.2 version: 5.8.2 @@ -982,20 +1004,20 @@ packages: '@ag-ui/client@0.0.36': resolution: {integrity: sha512-1Ey2KqK9KQpRJcnJvKPfVyLiTK4+CLBQZ085oJvr6T1nznw224j0KyzXNJ7cRjXeEGnuafmXTgpU+xEbN3xuYQ==} - '@ag-ui/client@0.0.40-alpha.3': - resolution: {integrity: sha512-Za1j4r/9MZoic1DCmPGiRFIjSkGgr5sqnadYjQV6v/qvbv+cx4+8GX8K3snBcRX6H3EceOtQ9eY2BSZcR+Nc1g==} + '@ag-ui/client@0.0.40-alpha.6': + resolution: {integrity: sha512-QIgOY8nJvv3mrDz9OXyaoxwj3Js28YPYXz+myRQ2idR1ghoKq0V9MEZrXoHOE4O6Z7PnUSw5z7zZIemh7YK/5w==} '@ag-ui/core@0.0.36': resolution: {integrity: sha512-uYUrzw6uxuw4qVQ61mdSeiG0mFh2n/VAWmWsWzwETDuhqJZT7rFmd07IajcFWcyItMr1wjqxFDdlklucAyEYNA==} - '@ag-ui/core@0.0.40-alpha.3': - resolution: {integrity: sha512-35ctuwk4/799c7uCSYfwHLmy5tUFg35VONqdmv6n1xl3kbH7qYA4L6xaGeCaI/YtTAVDs1KuUb/alSifrfPSAA==} + '@ag-ui/core@0.0.40-alpha.6': + resolution: {integrity: sha512-XkZAnmtewucr0yayWxK2GEcOoxaHZ6cSZZKkVFKAEOPJuUwFCFstqS3vh0Z5rUy2ZE8eh8WJylBHidiCRW4yuA==} '@ag-ui/encoder@0.0.36': resolution: {integrity: sha512-p8UNh6a77G/oe/4EZmwkTeYCN/5SnqSY2Cz8f8psZpk4LKzzrPkRNykrUAIBsi1wMp50/VQiM27oTRaade/Qkw==} - '@ag-ui/encoder@0.0.40-alpha.3': - resolution: {integrity: sha512-hFCkR9x409ZzU9a0u9WTAnaWWAIRTpos4X9GfDXlrNXzmkHr1zwpzmJsvTbmnpAtg/stpLQuWebej4dk/8QBzg==} + '@ag-ui/encoder@0.0.40-alpha.6': + resolution: {integrity: sha512-q+bpJGp4fMjQplHowmbNaOQrICHzmk1J394jZXmTv/RjysNHtcDrWHythXBiPAnNRutYeqsG6bQJ5PASbQYiqg==} '@ag-ui/langgraph@0.0.11': resolution: {integrity: sha512-3xUkaOelnpQ5tbsbuoOTin71tTgWEN0GDZBjGs/7xAwly2Dn4fahbBAoscXullO/pH9kTGGgbuJ0rWDUgo6fKQ==} @@ -1003,8 +1025,8 @@ packages: '@ag-ui/proto@0.0.36': resolution: {integrity: sha512-yaWLwJQmBaCtFstSoZEALztVckCYv+RD8guU91kL5AvywRXvZPP5mjiN+bEwvtw8VU3idXoee1ZbJGpSlSAQ8A==} - '@ag-ui/proto@0.0.40-alpha.3': - resolution: {integrity: sha512-VS9R2SGMJ+fbNq4hRT3jKe/FKeqRRev+DtKd1JIkwYiR+CaXF2h+gxi5xypCMiSyHzrH7QtftXI5nWU871HxAA==} + '@ag-ui/proto@0.0.40-alpha.6': + resolution: {integrity: sha512-xFw9igIC7OGdq/t1biUXRMinaC0sPLkavjj2oz820KaJe1uZOTSRfAK1qEgswoHJU1N4vRg0RQDEhd1wN8+GGg==} '@ai-sdk/anthropic@2.0.23': resolution: {integrity: sha512-ZEBiiv1UhjGjBwUU63pFhLK5LCSlNDb1idY9K1oZHm5/Fda1cuTojf32tOp0opH0RPbPAN/F8fyyNjbU33n9Kw==} @@ -3160,12 +3182,6 @@ packages: '@types/node': optional: true - '@ioredis/as-callback@3.0.0': - resolution: {integrity: sha512-Kqv1rZ3WbgOrS+hgzJ5xG5WQuhvzzSTRYvNeyPMLOAM78MHSnuKI20JeJGbpuAt//LCuP0vsexZcorqW7kWhJg==} - - '@ioredis/commands@1.3.0': - resolution: {integrity: sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==} - '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -5295,11 +5311,6 @@ packages: '@types/http-proxy@1.17.16': resolution: {integrity: sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==} - '@types/ioredis-mock@8.2.6': - resolution: {integrity: sha512-5heqtZMvQ4nXARY0o8rc8cjkJjct2ScM12yCJ/h731S9He93a2cv+kAhwPCNwTKDfNH9gjRfLG4VpAEYJU0/gQ==} - peerDependencies: - ioredis: '>=5' - '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -6426,10 +6437,6 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - cluster-key-slot@1.1.2: - resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} - engines: {node: '>=0.10.0'} - code-block-writer@12.0.0: resolution: {integrity: sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==} @@ -6989,10 +6996,6 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -7631,14 +7634,6 @@ packages: picomatch: optional: true - fengari-interop@0.1.3: - resolution: {integrity: sha512-EtZ+oTu3kEwVJnoymFPBVLIbQcCoy9uWCVnMA6h3M/RqHkUBsLYp29+RRHf9rKr6GwjubWREU1O7RretFIXjHw==} - peerDependencies: - fengari: ^0.1.0 - - fengari@0.1.4: - resolution: {integrity: sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g==} - fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} @@ -8299,17 +8294,6 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - ioredis-mock@8.9.0: - resolution: {integrity: sha512-yIglcCkI1lvhwJVoMsR51fotZVsPsSk07ecTCgRTRlicG0Vq3lke6aAaHklyjmRNRsdYAgswqC2A0bPtQK4LSw==} - engines: {node: '>=12.22'} - peerDependencies: - '@types/ioredis-mock': ^8 - ioredis: ^5 - - ioredis@5.7.0: - resolution: {integrity: sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==} - engines: {node: '>=12.22.0'} - ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -8762,10 +8746,6 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - kysely@0.28.5: - resolution: {integrity: sha512-rlB0I/c6FBDWPcQoDtkxi9zIvpmnV5xoIalfCMSMCa7nuA6VGA3F54TW9mEgX4DVf10sXAWCF5fDbamI/5ZpKA==} - engines: {node: '>=20.0.0'} - langium@3.3.1: resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} engines: {node: '>=16.0.0'} @@ -8978,12 +8958,6 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - lodash.defaults@4.2.0: - resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - - lodash.isarguments@3.1.0: - resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} - lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} @@ -10526,10 +10500,6 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - readline-sync@1.4.10: - resolution: {integrity: sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==} - engines: {node: '>= 0.8.0'} - recast@0.23.11: resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} engines: {node: '>= 4'} @@ -10550,14 +10520,6 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} - redis-errors@1.2.0: - resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} - engines: {node: '>=4'} - - redis-parser@3.0.0: - resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} - engines: {node: '>=4'} - reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -11184,9 +11146,6 @@ packages: stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} - standard-as-callback@2.1.0: - resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -12544,11 +12503,11 @@ snapshots: uuid: 11.1.0 zod: 3.25.75 - '@ag-ui/client@0.0.40-alpha.3': + '@ag-ui/client@0.0.40-alpha.6': dependencies: - '@ag-ui/core': 0.0.40-alpha.3 - '@ag-ui/encoder': 0.0.40-alpha.3 - '@ag-ui/proto': 0.0.40-alpha.3 + '@ag-ui/core': 0.0.40-alpha.6 + '@ag-ui/encoder': 0.0.40-alpha.6 + '@ag-ui/proto': 0.0.40-alpha.6 '@types/uuid': 10.0.0 fast-json-patch: 3.1.1 rxjs: 7.8.1 @@ -12561,7 +12520,7 @@ snapshots: rxjs: 7.8.1 zod: 3.25.75 - '@ag-ui/core@0.0.40-alpha.3': + '@ag-ui/core@0.0.40-alpha.6': dependencies: rxjs: 7.8.1 zod: 3.25.75 @@ -12571,16 +12530,16 @@ snapshots: '@ag-ui/core': 0.0.36 '@ag-ui/proto': 0.0.36 - '@ag-ui/encoder@0.0.40-alpha.3': + '@ag-ui/encoder@0.0.40-alpha.6': dependencies: - '@ag-ui/core': 0.0.40-alpha.3 - '@ag-ui/proto': 0.0.40-alpha.3 + '@ag-ui/core': 0.0.40-alpha.6 + '@ag-ui/proto': 0.0.40-alpha.6 - '@ag-ui/langgraph@0.0.11(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.75))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@ag-ui/langgraph@0.0.11(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.75))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@ag-ui/client': 0.0.36 '@langchain/core': 0.3.73(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.75)) - '@langchain/langgraph-sdk': 0.0.105(@langchain/core@0.3.73(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.75)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@langchain/langgraph-sdk': 0.0.105(@langchain/core@0.3.73(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.75)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) partial-json: 0.1.7 rxjs: 7.8.1 transitivePeerDependencies: @@ -12597,9 +12556,9 @@ snapshots: '@bufbuild/protobuf': 2.6.0 '@protobuf-ts/protoc': 2.11.1 - '@ag-ui/proto@0.0.40-alpha.3': + '@ag-ui/proto@0.0.40-alpha.6': dependencies: - '@ag-ui/core': 0.0.40-alpha.3 + '@ag-ui/core': 0.0.40-alpha.6 '@bufbuild/protobuf': 2.6.0 '@protobuf-ts/protoc': 2.11.1 @@ -12650,20 +12609,27 @@ snapshots: '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.30 - '@analogjs/vite-plugin-angular@1.20.2(@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5))(@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5))': + '@analogjs/vite-plugin-angular@1.20.2(@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@4.0.3)(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5))(@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5))': dependencies: ts-morph: 21.0.1 vfile: 6.0.3 optionalDependencies: - '@angular-devkit/build-angular': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5) - '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5) + '@angular-devkit/build-angular': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@4.0.3)(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5) + '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5) - ? '@analogjs/vitest-angular@1.20.2(@analogjs/vite-plugin-angular@1.20.2(@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5))(@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)))(@angular-devkit/architect@0.1902.15)(vitest@2.1.9(@types/node@22.15.3)(@vitest/ui@2.1.9)(jsdom@24.1.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1))' - : dependencies: - '@analogjs/vite-plugin-angular': 1.20.2(@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5))(@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)) + '@analogjs/vitest-angular@1.20.2(@analogjs/vite-plugin-angular@1.20.2(@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@4.0.3)(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5))(@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)))(@angular-devkit/architect@0.1902.15(chokidar@4.0.3))(vitest@2.1.9(@types/node@22.15.3)(@vitest/ui@2.1.9)(jsdom@24.1.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1))': + dependencies: + '@analogjs/vite-plugin-angular': 1.20.2(@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@4.0.3)(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5))(@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)) '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) vitest: 2.1.9(@types/node@22.15.3)(@vitest/ui@2.1.9)(jsdom@24.1.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + '@angular-devkit/architect@0.1802.20(chokidar@3.6.0)': + dependencies: + '@angular-devkit/core': 18.2.20(chokidar@3.6.0) + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + '@angular-devkit/architect@0.1802.20(chokidar@4.0.3)': dependencies: '@angular-devkit/core': 18.2.20(chokidar@4.0.3) @@ -12678,13 +12644,13 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': + '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) - '@angular-devkit/build-webpack': 0.1802.20(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) - '@angular-devkit/core': 18.2.20(chokidar@4.0.3) - '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) + '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) + '@angular-devkit/build-webpack': 0.1802.20(chokidar@3.6.0)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + '@angular-devkit/core': 18.2.20(chokidar@3.6.0) + '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@3.6.0)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@3.4.17)(terser@5.31.6)(typescript@5.4.5) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -12696,14 +12662,14 @@ snapshots: '@babel/preset-env': 7.26.9(@babel/core@7.26.10) '@babel/runtime': 7.26.10 '@discoveryjs/json-ext': 0.6.1 - '@ngtools/webpack': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + '@ngtools/webpack': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.4.41) - babel-loader: 9.1.3(@babel/core@7.26.10)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + babel-loader: 9.1.3(@babel/core@7.26.10)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) browserslist: 4.25.1 - copy-webpack-plugin: 12.0.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + copy-webpack-plugin: 12.0.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) critters: 0.0.24 - css-loader: 7.1.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + css-loader: 7.1.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) esbuild-wasm: 0.23.0 fast-glob: 3.3.2 http-proxy-middleware: 3.0.5 @@ -12712,11 +12678,11 @@ snapshots: jsonc-parser: 3.3.1 karma-source-map-support: 1.4.0 less: 4.2.0 - less-loader: 12.2.0(less@4.2.0)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) - license-webpack-plugin: 4.0.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + less-loader: 12.2.0(less@4.2.0)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + license-webpack-plugin: 4.0.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) loader-utils: 3.3.1 magic-string: 0.30.11 - mini-css-extract-plugin: 2.9.0(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + mini-css-extract-plugin: 2.9.0(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) mrmime: 2.0.0 open: 10.1.0 ora: 5.4.1 @@ -12724,28 +12690,28 @@ snapshots: picomatch: 4.0.2 piscina: 4.6.1 postcss: 8.4.41 - postcss-loader: 8.1.1(postcss@8.4.41)(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + postcss-loader: 8.1.1(postcss@8.4.41)(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.77.6 - sass-loader: 16.0.0(sass@1.77.6)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + sass-loader: 16.0.0(sass@1.77.6)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) semver: 7.6.3 - source-map-loader: 5.0.0(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + source-map-loader: 5.0.0(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) source-map-support: 0.5.21 terser: 5.31.6 tree-kill: 1.2.2 tslib: 2.6.3 typescript: 5.4.5 watchpack: 2.4.1 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) - webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) - webpack-dev-server: 5.2.2(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) + webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) optionalDependencies: esbuild: 0.23.0 - ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5) - tailwindcss: 4.1.11 + ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5) + tailwindcss: 3.4.17 transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -12764,13 +12730,13 @@ snapshots: - utf-8-validate - webpack-cli - '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.12)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.12)(typescript@5.4.5)': + '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@4.0.3)(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) - '@angular-devkit/build-webpack': 0.1802.20(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + '@angular-devkit/build-webpack': 0.1802.20(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) '@angular-devkit/core': 18.2.20(chokidar@4.0.3) - '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.12)(terser@5.31.6)(typescript@5.4.5) + '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -12782,14 +12748,14 @@ snapshots: '@babel/preset-env': 7.26.9(@babel/core@7.26.10) '@babel/runtime': 7.26.10 '@discoveryjs/json-ext': 0.6.1 - '@ngtools/webpack': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + '@ngtools/webpack': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.4.41) - babel-loader: 9.1.3(@babel/core@7.26.10)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + babel-loader: 9.1.3(@babel/core@7.26.10)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) browserslist: 4.25.1 - copy-webpack-plugin: 12.0.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + copy-webpack-plugin: 12.0.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) critters: 0.0.24 - css-loader: 7.1.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + css-loader: 7.1.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) esbuild-wasm: 0.23.0 fast-glob: 3.3.2 http-proxy-middleware: 3.0.5 @@ -12798,11 +12764,11 @@ snapshots: jsonc-parser: 3.3.1 karma-source-map-support: 1.4.0 less: 4.2.0 - less-loader: 12.2.0(less@4.2.0)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) - license-webpack-plugin: 4.0.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + less-loader: 12.2.0(less@4.2.0)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + license-webpack-plugin: 4.0.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) loader-utils: 3.3.1 magic-string: 0.30.11 - mini-css-extract-plugin: 2.9.0(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + mini-css-extract-plugin: 2.9.0(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) mrmime: 2.0.0 open: 10.1.0 ora: 5.4.1 @@ -12810,28 +12776,28 @@ snapshots: picomatch: 4.0.2 piscina: 4.6.1 postcss: 8.4.41 - postcss-loader: 8.1.1(postcss@8.4.41)(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + postcss-loader: 8.1.1(postcss@8.4.41)(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.77.6 - sass-loader: 16.0.0(sass@1.77.6)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + sass-loader: 16.0.0(sass@1.77.6)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) semver: 7.6.3 - source-map-loader: 5.0.0(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + source-map-loader: 5.0.0(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) source-map-support: 0.5.21 terser: 5.31.6 tree-kill: 1.2.2 tslib: 2.6.3 typescript: 5.4.5 watchpack: 2.4.1 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) - webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) - webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) + webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) optionalDependencies: esbuild: 0.23.0 - ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.12)(tslib@2.8.1)(typescript@5.4.5) - tailwindcss: 4.1.12 + ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5) + tailwindcss: 4.1.11 transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -12849,14 +12815,15 @@ snapshots: - uglify-js - utf-8-validate - webpack-cli + optional: true - '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': + '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) - '@angular-devkit/build-webpack': 0.1802.20(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + '@angular-devkit/build-webpack': 0.1802.20(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) '@angular-devkit/core': 18.2.20(chokidar@4.0.3) - '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) + '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -12868,14 +12835,14 @@ snapshots: '@babel/preset-env': 7.26.9(@babel/core@7.26.10) '@babel/runtime': 7.26.10 '@discoveryjs/json-ext': 0.6.1 - '@ngtools/webpack': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + '@ngtools/webpack': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.4.41) - babel-loader: 9.1.3(@babel/core@7.26.10)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + babel-loader: 9.1.3(@babel/core@7.26.10)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) browserslist: 4.25.1 - copy-webpack-plugin: 12.0.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + copy-webpack-plugin: 12.0.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) critters: 0.0.24 - css-loader: 7.1.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + css-loader: 7.1.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) esbuild-wasm: 0.23.0 fast-glob: 3.3.2 http-proxy-middleware: 3.0.5 @@ -12884,11 +12851,11 @@ snapshots: jsonc-parser: 3.3.1 karma-source-map-support: 1.4.0 less: 4.2.0 - less-loader: 12.2.0(less@4.2.0)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) - license-webpack-plugin: 4.0.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + less-loader: 12.2.0(less@4.2.0)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + license-webpack-plugin: 4.0.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) loader-utils: 3.3.1 magic-string: 0.30.11 - mini-css-extract-plugin: 2.9.0(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + mini-css-extract-plugin: 2.9.0(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) mrmime: 2.0.0 open: 10.1.0 ora: 5.4.1 @@ -12896,24 +12863,24 @@ snapshots: picomatch: 4.0.2 piscina: 4.6.1 postcss: 8.4.41 - postcss-loader: 8.1.1(postcss@8.4.41)(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + postcss-loader: 8.1.1(postcss@8.4.41)(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.77.6 - sass-loader: 16.0.0(sass@1.77.6)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + sass-loader: 16.0.0(sass@1.77.6)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) semver: 7.6.3 - source-map-loader: 5.0.0(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + source-map-loader: 5.0.0(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) source-map-support: 0.5.21 terser: 5.31.6 tree-kill: 1.2.2 tslib: 2.6.3 typescript: 5.4.5 watchpack: 2.4.1 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) - webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) - webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) + webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) optionalDependencies: esbuild: 0.23.0 ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5) @@ -12935,17 +12902,46 @@ snapshots: - uglify-js - utf-8-validate - webpack-cli + + '@angular-devkit/build-webpack@0.1802.20(chokidar@3.6.0)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0))': + dependencies: + '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) + rxjs: 7.8.1 + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) + webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + transitivePeerDependencies: + - chokidar + + '@angular-devkit/build-webpack@0.1802.20(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0))': + dependencies: + '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) + rxjs: 7.8.1 + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) + webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + transitivePeerDependencies: + - chokidar optional: true - '@angular-devkit/build-webpack@0.1802.20(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0))': + '@angular-devkit/build-webpack@0.1802.20(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0))': dependencies: '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) rxjs: 7.8.1 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) - webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) + webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) transitivePeerDependencies: - chokidar + '@angular-devkit/core@18.2.20(chokidar@3.6.0)': + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + jsonc-parser: 3.3.1 + picomatch: 4.0.2 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 3.6.0 + '@angular-devkit/core@18.2.20(chokidar@4.0.3)': dependencies: ajv: 8.17.1 @@ -12968,7 +12964,7 @@ snapshots: optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/schematics@18.2.20(chokidar@4.0.3)': + '@angular-devkit/schematics@18.2.20': dependencies: '@angular-devkit/core': 18.2.20(chokidar@4.0.3) jsonc-parser: 3.3.1 @@ -12978,15 +12974,25 @@ snapshots: transitivePeerDependencies: - chokidar + '@angular-devkit/schematics@18.2.20(chokidar@3.6.0)': + dependencies: + '@angular-devkit/core': 18.2.20(chokidar@3.6.0) + jsonc-parser: 3.3.1 + magic-string: 0.30.11 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + '@angular/animations@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))': dependencies: '@angular/core': 18.2.13(rxjs@7.8.1)(zone.js@0.14.10) tslib: 2.8.1 - '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5)': + '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@3.6.0)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@3.4.17)(terser@5.31.6)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) + '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.25.2 '@babel/helper-annotate-as-pure': 7.24.7 @@ -13015,7 +13021,7 @@ snapshots: optionalDependencies: less: 4.2.0 postcss: 8.4.41 - tailwindcss: 4.1.11 + tailwindcss: 3.4.17 transitivePeerDependencies: - '@types/node' - chokidar @@ -13026,7 +13032,7 @@ snapshots: - supports-color - terser - '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.12)(terser@5.31.6)(typescript@5.4.5)': + '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) @@ -13058,7 +13064,7 @@ snapshots: optionalDependencies: less: 4.2.0 postcss: 8.4.41 - tailwindcss: 4.1.12 + tailwindcss: 4.1.11 transitivePeerDependencies: - '@types/node' - chokidar @@ -13068,8 +13074,9 @@ snapshots: - sugarss - supports-color - terser + optional: true - '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)': + '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) @@ -13113,6 +13120,49 @@ snapshots: - terser optional: true + '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5)': + dependencies: + '@ampproject/remapping': 2.3.0 + '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) + '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) + '@babel/core': 7.25.2 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.25.2) + '@inquirer/confirm': 3.1.22 + '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.19(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.31.6)) + browserslist: 4.25.1 + critters: 0.0.24 + esbuild: 0.23.0 + fast-glob: 3.3.2 + https-proxy-agent: 7.0.5 + listr2: 8.2.4 + lmdb: 3.0.13 + magic-string: 0.30.11 + mrmime: 2.0.0 + parse5-html-rewriting-stream: 7.0.0 + picomatch: 4.0.2 + piscina: 4.6.1 + rollup: 4.22.4 + sass: 1.77.6 + semver: 7.6.3 + typescript: 5.4.5 + vite: 5.4.19(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.31.6) + watchpack: 2.4.1 + optionalDependencies: + less: 4.2.0 + postcss: 8.4.41 + tailwindcss: 4.1.11 + transitivePeerDependencies: + - '@types/node' + - chokidar + - lightningcss + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + '@angular/cdk@18.2.14(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1)': dependencies: '@angular/common': 18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1) @@ -13122,14 +13172,38 @@ snapshots: optionalDependencies: parse5: 7.3.0 - '@angular/cli@18.2.20(chokidar@4.0.3)': + '@angular/cli@18.2.20': dependencies: '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) '@angular-devkit/core': 18.2.20(chokidar@4.0.3) - '@angular-devkit/schematics': 18.2.20(chokidar@4.0.3) + '@angular-devkit/schematics': 18.2.20 + '@inquirer/prompts': 5.3.8 + '@listr2/prompt-adapter-inquirer': 2.0.15(@inquirer/prompts@5.3.8) + '@schematics/angular': 18.2.20 + '@yarnpkg/lockfile': 1.1.0 + ini: 4.1.3 + jsonc-parser: 3.3.1 + listr2: 8.2.4 + npm-package-arg: 11.0.3 + npm-pick-manifest: 9.1.0 + pacote: 18.0.6 + resolve: 1.22.8 + semver: 7.6.3 + symbol-observable: 4.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - chokidar + - supports-color + + '@angular/cli@18.2.20(chokidar@3.6.0)': + dependencies: + '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) + '@angular-devkit/core': 18.2.20(chokidar@3.6.0) + '@angular-devkit/schematics': 18.2.20(chokidar@3.6.0) '@inquirer/prompts': 5.3.8 '@listr2/prompt-adapter-inquirer': 2.0.15(@inquirer/prompts@5.3.8) - '@schematics/angular': 18.2.20(chokidar@4.0.3) + '@schematics/angular': 18.2.20(chokidar@3.6.0) '@yarnpkg/lockfile': 1.1.0 ini: 4.1.3 jsonc-parser: 3.3.1 @@ -15541,10 +15615,6 @@ snapshots: optionalDependencies: '@types/node': 22.15.3 - '@ioredis/as-callback@3.0.0': {} - - '@ioredis/commands@1.3.0': {} - '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -15661,7 +15731,7 @@ snapshots: - '@opentelemetry/sdk-trace-base' - openai - '@langchain/langgraph-sdk@0.0.105(@langchain/core@0.3.73(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.75)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@langchain/langgraph-sdk@0.0.105(@langchain/core@0.3.73(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.75)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@types/json-schema': 7.0.15 p-queue: 6.6.2 @@ -15669,8 +15739,8 @@ snapshots: uuid: 9.0.1 optionalDependencies: '@langchain/core': 0.3.73(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.75)) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) '@leichtgewicht/ip-codec@2.0.5': {} @@ -16186,11 +16256,11 @@ snapshots: '@next/swc-win32-x64-msvc@15.4.4': optional: true - '@ngtools/webpack@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0))': + '@ngtools/webpack@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0))': dependencies: '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) typescript: 5.4.5 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) '@nodelib/fs.scandir@2.1.5': dependencies: @@ -16364,7 +16434,7 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@pmmmwh/react-refresh-webpack-plugin@0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6))': + '@pmmmwh/react-refresh-webpack-plugin@0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11))': dependencies: ansi-html: 0.0.9 core-js-pure: 3.44.0 @@ -16374,10 +16444,10 @@ snapshots: react-refresh: 0.14.2 schema-utils: 4.3.2 source-map: 0.7.4 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) optionalDependencies: type-fest: 4.41.0 - webpack-dev-server: 5.2.2(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + webpack-dev-server: 5.2.2(webpack@5.100.0(@swc/core@1.12.11)) webpack-hot-middleware: 2.26.1 '@polka/url@1.0.0-next.29': {} @@ -16881,10 +16951,18 @@ snapshots: '@rushstack/eslint-patch@1.12.0': {} - '@schematics/angular@18.2.20(chokidar@4.0.3)': + '@schematics/angular@18.2.20': dependencies: '@angular-devkit/core': 18.2.20(chokidar@4.0.3) - '@angular-devkit/schematics': 18.2.20(chokidar@4.0.3) + '@angular-devkit/schematics': 18.2.20 + jsonc-parser: 3.3.1 + transitivePeerDependencies: + - chokidar + + '@schematics/angular@18.2.20(chokidar@3.6.0)': + dependencies: + '@angular-devkit/core': 18.2.20(chokidar@3.6.0) + '@angular-devkit/schematics': 18.2.20(chokidar@3.6.0) jsonc-parser: 3.3.1 transitivePeerDependencies: - chokidar @@ -17249,18 +17327,18 @@ snapshots: memoizerific: 1.11.3 storybook: 8.6.14(prettier@3.6.0) - '@storybook/addon-webpack5-compiler-swc@1.0.6(@swc/helpers@0.5.15)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6))': + '@storybook/addon-webpack5-compiler-swc@1.0.6(webpack@5.100.0(@swc/core@1.12.11))': dependencies: - '@swc/core': 1.12.11(@swc/helpers@0.5.15) - swc-loader: 0.2.6(@swc/core@1.12.11(@swc/helpers@0.5.15))(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + '@swc/core': 1.12.11 + swc-loader: 0.2.6(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)) transitivePeerDependencies: - '@swc/helpers' - webpack - ? '@storybook/angular@8.6.14(@angular-devkit/architect@0.1902.15(chokidar@4.0.3))(@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5))(@angular-devkit/core@19.2.15(chokidar@4.0.3))(@angular/animations@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/cli@18.2.20(chokidar@4.0.3))(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(@angular/forms@18.2.13(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.13(@angular/animations@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1))(@angular/platform-browser-dynamic@18.2.13(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.13(@angular/animations@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))))(@angular/platform-browser@18.2.13(@angular/animations@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(rxjs@7.8.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.4.5)(zone.js@0.14.10)' - : dependencies: + '@storybook/angular@8.6.14(yd4v6lxii4rfhetgylkhgsqlgq)': + dependencies: '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) - '@angular-devkit/build-angular': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11(@swc/helpers@0.5.15))(@types/node@22.15.3)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5) + '@angular-devkit/build-angular': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5) '@angular-devkit/core': 19.2.15(chokidar@4.0.3) '@angular/common': 18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1) '@angular/compiler': 18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)) @@ -17269,7 +17347,7 @@ snapshots: '@angular/forms': 18.2.13(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.13(@angular/animations@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1) '@angular/platform-browser': 18.2.13(@angular/animations@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)) '@angular/platform-browser-dynamic': 18.2.13(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.13(@angular/animations@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10))) - '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.4.5) + '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11)(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.4.5) '@storybook/components': 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@storybook/core-webpack': 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@storybook/global': 5.0.0 @@ -17290,10 +17368,10 @@ snapshots: tsconfig-paths-webpack-plugin: 4.2.0 typescript: 5.4.5 util-deprecate: 1.0.2 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) optionalDependencies: '@angular/animations': 18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)) - '@angular/cli': 18.2.20(chokidar@4.0.3) + '@angular/cli': 18.2.20 zone.js: 0.14.10 transitivePeerDependencies: - '@rspack/core' @@ -17311,7 +17389,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-webpack5@8.6.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.4.5)': + '@storybook/builder-webpack5@8.6.14(@swc/core@1.12.11)(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.4.5)': dependencies: '@storybook/core-webpack': 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@types/semver': 7.7.0 @@ -17319,23 +17397,23 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.3 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) es-module-lexer: 1.7.0 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.4.5)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) - html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.4.5)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) magic-string: 0.30.17 path-browserify: 1.0.1 process: 0.11.10 semver: 7.7.2 storybook: 8.6.14(prettier@3.6.0) - style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) - terser-webpack-plugin: 5.3.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) - webpack-dev-middleware: 6.1.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack-dev-middleware: 6.1.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -17347,7 +17425,7 @@ snapshots: - uglify-js - webpack-cli - '@storybook/builder-webpack5@8.6.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': + '@storybook/builder-webpack5@8.6.14(@swc/core@1.12.11)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': dependencies: '@storybook/core-webpack': 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@types/semver': 7.7.0 @@ -17355,23 +17433,23 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.3 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11)) es-module-lexer: 1.7.0 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) - html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)) + html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11)) magic-string: 0.30.17 path-browserify: 1.0.1 process: 0.11.10 semver: 7.7.2 storybook: 8.6.14(prettier@3.6.0) - style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) - terser-webpack-plugin: 5.3.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11)) + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) - webpack-dev-middleware: 6.1.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + webpack: 5.100.0(@swc/core@1.12.11) + webpack-dev-middleware: 6.1.3(webpack@5.100.0(@swc/core@1.12.11)) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -17435,7 +17513,7 @@ snapshots: dependencies: storybook: 8.6.14(prettier@3.6.0) - '@storybook/nextjs@8.6.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)(storybook@8.6.14(prettier@3.6.0))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6))': + '@storybook/nextjs@8.6.14(@swc/core@1.12.11)(next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)(storybook@8.6.14(prettier@3.6.0))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.0) @@ -17450,30 +17528,30 @@ snapshots: '@babel/preset-react': 7.27.1(@babel/core@7.28.0) '@babel/preset-typescript': 7.27.1(@babel/core@7.28.0) '@babel/runtime': 7.27.6 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) - '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) - '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)) + '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@types/semver': 7.7.0 - babel-loader: 9.2.1(@babel/core@7.28.0)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) - css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + babel-loader: 9.2.1(@babel/core@7.28.0)(webpack@5.100.0(@swc/core@1.12.11)) + css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11)) find-up: 5.0.0 image-size: 1.2.1 loader-utils: 3.3.1 next: 15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) - node-polyfill-webpack-plugin: 2.0.1(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + node-polyfill-webpack-plugin: 2.0.1(webpack@5.100.0(@swc/core@1.12.11)) pnp-webpack-plugin: 1.7.0(typescript@5.8.2) postcss: 8.5.6 - postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-refresh: 0.14.2 resolve-url-loader: 5.0.0 - sass-loader: 14.2.1(sass@1.90.0)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + sass-loader: 14.2.1(sass@1.90.0)(webpack@5.100.0(@swc/core@1.12.11)) semver: 7.7.2 storybook: 8.6.14(prettier@3.6.0) - style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11)) styled-jsx: 5.1.7(@babel/core@7.28.0)(react@18.3.1) ts-dedent: 2.2.0 tsconfig-paths: 4.2.0 @@ -17481,7 +17559,7 @@ snapshots: optionalDependencies: sharp: 0.33.5 typescript: 5.8.2 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -17500,11 +17578,11 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@storybook/preset-react-webpack@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': + '@storybook/preset-react-webpack@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': dependencies: '@storybook/core-webpack': 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)) '@types/semver': 7.7.0 find-up: 5.0.0 magic-string: 0.30.17 @@ -17515,7 +17593,7 @@ snapshots: semver: 7.7.2 storybook: 8.6.14(prettier@3.6.0) tsconfig-paths: 4.2.0 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) optionalDependencies: typescript: 5.8.2 transitivePeerDependencies: @@ -17530,7 +17608,7 @@ snapshots: dependencies: storybook: 8.6.14(prettier@3.6.0) - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11))': dependencies: debug: 4.4.1(supports-color@5.5.0) endent: 2.1.0 @@ -17540,7 +17618,7 @@ snapshots: react-docgen-typescript: 2.4.0(typescript@5.8.2) tslib: 2.8.1 typescript: 5.8.2 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) transitivePeerDependencies: - supports-color @@ -17550,10 +17628,10 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.6.14(prettier@3.6.0) - '@storybook/react-webpack5@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': + '@storybook/react-webpack5@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': dependencies: - '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) - '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -17629,7 +17707,7 @@ snapshots: '@swc/core-win32-x64-msvc@1.12.11': optional: true - '@swc/core@1.12.11(@swc/helpers@0.5.15)': + '@swc/core@1.12.11': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.23 @@ -17644,7 +17722,6 @@ snapshots: '@swc/core-win32-arm64-msvc': 1.12.11 '@swc/core-win32-ia32-msvc': 1.12.11 '@swc/core-win32-x64-msvc': 1.12.11 - '@swc/helpers': 0.5.15 '@swc/counter@0.1.3': {} @@ -18124,10 +18201,6 @@ snapshots: dependencies: '@types/node': 22.15.3 - '@types/ioredis-mock@8.2.6(ioredis@5.7.0)': - dependencies: - ioredis: 5.7.0 - '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -18616,18 +18689,6 @@ snapshots: tinyrainbow: 1.2.0 vitest: 2.1.9(@types/node@22.15.3)(@vitest/ui@2.1.9)(jsdom@24.1.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) - '@vitest/ui@3.2.4(vitest@2.1.9)': - dependencies: - '@vitest/utils': 3.2.4 - fflate: 0.8.2 - flatted: 3.3.3 - pathe: 2.0.3 - sirv: 3.0.1 - tinyglobby: 0.2.14 - tinyrainbow: 2.0.0 - vitest: 2.1.9(@types/node@22.15.3)(@vitest/ui@3.2.4)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) - optional: true - '@vitest/ui@3.2.4(vitest@3.2.4)': dependencies: '@vitest/utils': 3.2.4 @@ -19047,19 +19108,19 @@ snapshots: b4a@1.6.7: {} - babel-loader@9.1.3(@babel/core@7.26.10)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + babel-loader@9.1.3(@babel/core@7.26.10)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: '@babel/core': 7.26.10 find-cache-dir: 4.0.0 schema-utils: 4.3.2 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) - babel-loader@9.2.1(@babel/core@7.28.0)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + babel-loader@9.2.1(@babel/core@7.28.0)(webpack@5.100.0(@swc/core@1.12.11)): dependencies: '@babel/core': 7.28.0 find-cache-dir: 4.0.0 schema-utils: 4.3.2 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.26.10): dependencies: @@ -19511,8 +19572,6 @@ snapshots: clsx@2.1.1: {} - cluster-key-slot@1.1.2: {} - code-block-writer@12.0.0: {} code-excerpt@4.0.0: @@ -19631,7 +19690,7 @@ snapshots: dependencies: is-what: 3.14.1 - copy-webpack-plugin@12.0.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + copy-webpack-plugin@12.0.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: fast-glob: 3.3.3 glob-parent: 6.0.2 @@ -19639,7 +19698,7 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.3.2 serialize-javascript: 6.0.2 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) core-js-compat@3.44.0: dependencies: @@ -19748,7 +19807,20 @@ snapshots: randombytes: 2.1.0 randomfill: 1.0.4 - css-loader@6.11.0(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + css-loader@6.11.0(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): + dependencies: + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.6) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.6) + postcss-modules-scope: 3.2.1(postcss@8.5.6) + postcss-modules-values: 4.0.0(postcss@8.5.6) + postcss-value-parser: 4.2.0 + semver: 7.7.2 + optionalDependencies: + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + + css-loader@6.11.0(webpack@5.100.0(@swc/core@1.12.11)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -19759,9 +19831,9 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.2 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) - css-loader@7.1.2(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + css-loader@7.1.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -19772,9 +19844,9 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.2 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - css-loader@7.1.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + css-loader@7.1.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -19785,7 +19857,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.2 optionalDependencies: - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) css-select@4.3.0: dependencies: @@ -20131,8 +20203,6 @@ snapshots: delayed-stream@1.0.0: {} - denque@2.1.0: {} - depd@1.1.2: {} depd@2.0.0: {} @@ -21072,16 +21142,6 @@ snapshots: optionalDependencies: picomatch: 4.0.3 - fengari-interop@0.1.3(fengari@0.1.4): - dependencies: - fengari: 0.1.4 - - fengari@0.1.4: - dependencies: - readline-sync: 1.4.10 - sprintf-js: 1.1.3 - tmp: 0.0.33 - fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -21184,7 +21244,7 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.4.5)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.4.5)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: '@babel/code-frame': 7.27.1 chalk: 4.1.2 @@ -21199,9 +21259,9 @@ snapshots: semver: 7.7.2 tapable: 2.2.2 typescript: 5.4.5 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)): dependencies: '@babel/code-frame': 7.27.1 chalk: 4.1.2 @@ -21216,7 +21276,7 @@ snapshots: semver: 7.7.2 tapable: 2.2.2 typescript: 5.8.2 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) form-data-encoder@1.7.2: {} @@ -21735,7 +21795,17 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): + dependencies: + '@types/html-minifier-terser': 6.1.0 + html-minifier-terser: 6.1.0 + lodash: 4.17.21 + pretty-error: 4.0.0 + tapable: 2.2.2 + optionalDependencies: + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + + html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -21743,9 +21813,9 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.2 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) - html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -21753,7 +21823,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.2 optionalDependencies: - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) optional: true htmlparser2@6.1.0: @@ -21988,30 +22058,6 @@ snapshots: internmap@2.0.3: {} - ioredis-mock@8.9.0(@types/ioredis-mock@8.2.6(ioredis@5.7.0))(ioredis@5.7.0): - dependencies: - '@ioredis/as-callback': 3.0.0 - '@ioredis/commands': 1.3.0 - '@types/ioredis-mock': 8.2.6(ioredis@5.7.0) - fengari: 0.1.4 - fengari-interop: 0.1.3(fengari@0.1.4) - ioredis: 5.7.0 - semver: 7.7.2 - - ioredis@5.7.0: - dependencies: - '@ioredis/commands': 1.3.0 - cluster-key-slot: 1.1.2 - debug: 4.4.1(supports-color@5.5.0) - denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - ip-address@9.0.5: dependencies: jsbn: 1.1.0 @@ -22455,8 +22501,6 @@ snapshots: kolorist@1.8.0: {} - kysely@0.28.5: {} - langium@3.3.1: dependencies: chevrotain: 11.0.3 @@ -22497,11 +22541,11 @@ snapshots: dependencies: gcd: 0.0.1 - less-loader@12.2.0(less@4.2.0)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + less-loader@12.2.0(less@4.2.0)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: less: 4.2.0 optionalDependencies: - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) less@4.2.0: dependencies: @@ -22540,11 +22584,11 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - license-webpack-plugin@4.0.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + license-webpack-plugin@4.0.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: webpack-sources: 3.3.3 optionalDependencies: - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) lightningcss-darwin-arm64@1.30.1: optional: true @@ -22671,10 +22715,6 @@ snapshots: lodash.debounce@4.0.8: {} - lodash.defaults@4.2.0: {} - - lodash.isarguments@3.1.0: {} - lodash.isplainobject@4.0.6: {} lodash.merge@4.6.2: {} @@ -23369,11 +23409,11 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.9.0(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + mini-css-extract-plugin@2.9.0(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: schema-utils: 4.3.2 tapable: 2.2.2 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) minimalistic-assert@1.0.1: {} @@ -23578,7 +23618,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0): + next@15.4.4(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0): dependencies: '@next/env': 15.4.4 '@swc/helpers': 0.5.15 @@ -23586,7 +23626,7 @@ snapshots: postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.6(@babel/core@7.28.0)(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) optionalDependencies: '@next/swc-darwin-arm64': 15.4.4 '@next/swc-darwin-x64': 15.4.4 @@ -23603,7 +23643,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5): + ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5): dependencies: '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@rollup/plugin-json': 6.1.0(rollup@4.45.1) @@ -23632,9 +23672,10 @@ snapshots: typescript: 5.4.5 optionalDependencies: rollup: 4.45.1 - tailwindcss: 4.1.11 + tailwindcss: 3.4.17 + optional: true - ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.12)(tslib@2.8.1)(typescript@5.4.5): + ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5): dependencies: '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@rollup/plugin-json': 6.1.0(rollup@4.45.1) @@ -23663,8 +23704,7 @@ snapshots: typescript: 5.4.5 optionalDependencies: rollup: 4.45.1 - tailwindcss: 4.1.12 - optional: true + tailwindcss: 4.1.11 nice-napi@1.0.2: dependencies: @@ -23748,7 +23788,7 @@ snapshots: transitivePeerDependencies: - supports-color - node-polyfill-webpack-plugin@2.0.1(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + node-polyfill-webpack-plugin@2.0.1(webpack@5.100.0(@swc/core@1.12.11)): dependencies: assert: 2.1.0 browserify-zlib: 0.2.0 @@ -23775,7 +23815,7 @@ snapshots: url: 0.11.4 util: 0.12.5 vm-browserify: 1.1.2 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) node-releases@2.0.19: {} @@ -24347,36 +24387,36 @@ snapshots: tsx: 4.20.5 yaml: 2.8.0 - postcss-loader@8.1.1(postcss@8.4.41)(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + postcss-loader@8.1.1(postcss@8.4.41)(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: cosmiconfig: 9.0.0(typescript@5.4.5) jiti: 1.21.7 postcss: 8.4.41 semver: 7.7.2 optionalDependencies: - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) transitivePeerDependencies: - typescript - postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.4.5)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.4.5)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: cosmiconfig: 9.0.0(typescript@5.4.5) jiti: 1.21.7 postcss: 8.5.6 semver: 7.7.2 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) transitivePeerDependencies: - typescript - postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)): dependencies: cosmiconfig: 9.0.0(typescript@5.8.2) jiti: 1.21.7 postcss: 8.5.6 semver: 7.7.2 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) transitivePeerDependencies: - typescript @@ -24759,8 +24799,6 @@ snapshots: readdirp@4.1.2: {} - readline-sync@1.4.10: {} - recast@0.23.11: dependencies: ast-types: 0.16.1 @@ -24804,12 +24842,6 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 - redis-errors@1.2.0: {} - - redis-parser@3.0.0: - dependencies: - redis-errors: 1.2.0 - reflect-metadata@0.2.2: {} reflect.getprototypeof@1.0.10: @@ -25230,19 +25262,19 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@14.2.1(sass@1.90.0)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + sass-loader@14.2.1(sass@1.90.0)(webpack@5.100.0(@swc/core@1.12.11)): dependencies: neo-async: 2.6.2 optionalDependencies: sass: 1.90.0 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) - sass-loader@16.0.0(sass@1.77.6)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + sass-loader@16.0.0(sass@1.77.6)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: neo-async: 2.6.2 optionalDependencies: sass: 1.77.6 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) sass@1.77.6: dependencies: @@ -25655,11 +25687,11 @@ snapshots: source-map-js@1.2.1: {} - source-map-loader@5.0.0(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + source-map-loader@5.0.0(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) source-map-support@0.5.21: dependencies: @@ -25729,8 +25761,6 @@ snapshots: stackframe@1.3.4: {} - standard-as-callback@2.1.0: {} - statuses@1.5.0: {} statuses@2.0.1: {} @@ -25900,13 +25930,17 @@ snapshots: dependencies: js-tokens: 9.0.1 - style-loader@3.3.4(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + style-loader@3.3.4(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - style-loader@4.0.0(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + style-loader@3.3.4(webpack@5.100.0(@swc/core@1.12.11)): dependencies: - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) + + style-loader@4.0.0(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): + dependencies: + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) style-to-js@1.1.17: dependencies: @@ -25923,12 +25957,10 @@ snapshots: optionalDependencies: '@babel/core': 7.28.0 - styled-jsx@5.1.6(@babel/core@7.28.0)(react@19.1.0): + styled-jsx@5.1.6(react@19.1.0): dependencies: client-only: 0.0.1 react: 19.1.0 - optionalDependencies: - '@babel/core': 7.28.0 styled-jsx@5.1.7(@babel/core@7.28.0)(react@18.3.1): dependencies: @@ -25978,11 +26010,11 @@ snapshots: deep-rename-keys: 0.2.1 xml-reader: 2.4.3 - swc-loader@0.2.6(@swc/core@1.12.11(@swc/helpers@0.5.15))(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + swc-loader@0.2.6(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)): dependencies: - '@swc/core': 1.12.11(@swc/helpers@0.5.15) + '@swc/core': 1.12.11 '@swc/counter': 0.1.3 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) symbol-observable@4.0.0: {} @@ -26078,30 +26110,41 @@ snapshots: dependencies: memoizerific: 1.11.3 - terser-webpack-plugin@5.3.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + terser-webpack-plugin@5.3.14(@swc/core@1.12.11)(esbuild@0.23.0)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: '@jridgewell/trace-mapping': 0.3.29 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.43.1 - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) optionalDependencies: - '@swc/core': 1.12.11(@swc/helpers@0.5.15) - esbuild: 0.25.6 + '@swc/core': 1.12.11 + esbuild: 0.23.0 - terser-webpack-plugin@5.3.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + terser-webpack-plugin@5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: '@jridgewell/trace-mapping': 0.3.29 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.43.1 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) optionalDependencies: - '@swc/core': 1.12.11(@swc/helpers@0.5.15) + '@swc/core': 1.12.11 esbuild: 0.25.6 + terser-webpack-plugin@5.3.14(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)): + dependencies: + '@jridgewell/trace-mapping': 0.3.29 + jest-worker: 27.5.1 + schema-utils: 4.3.2 + serialize-javascript: 6.0.2 + terser: 5.43.1 + webpack: 5.100.0(@swc/core@1.12.11) + optionalDependencies: + '@swc/core': 1.12.11 + terser@5.31.6: dependencies: '@jridgewell/source-map': 0.3.10 @@ -26277,7 +26320,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0): + tsup@8.5.0(@swc/core@1.12.11)(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.5)(typescript@5.8.2)(yaml@2.8.0): dependencies: bundle-require: 5.1.0(esbuild@0.25.6) cac: 6.7.14 @@ -26297,7 +26340,7 @@ snapshots: tinyglobby: 0.2.14 tree-kill: 1.2.2 optionalDependencies: - '@swc/core': 1.12.11(@swc/helpers@0.5.15) + '@swc/core': 1.12.11 postcss: 8.5.6 typescript: 5.8.2 transitivePeerDependencies: @@ -26859,7 +26902,7 @@ snapshots: - supports-color - terser - vitest@2.1.9(@types/node@22.15.3)(@vitest/ui@3.2.4)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1): + vitest@2.1.9(@types/node@22.15.3)(@vitest/ui@2.1.9)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1): dependencies: '@vitest/expect': 2.1.9 '@vitest/mocker': 2.1.9(vite@5.4.19(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)) @@ -26883,7 +26926,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.15.3 - '@vitest/ui': 3.2.4(vitest@2.1.9) + '@vitest/ui': 2.1.9(vitest@2.1.9) jsdom: 26.1.0 transitivePeerDependencies: - less @@ -26997,7 +27040,7 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-dev-middleware@6.1.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + webpack-dev-middleware@6.1.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: colorette: 2.0.20 memfs: 3.5.3 @@ -27005,9 +27048,19 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.2 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - webpack-dev-middleware@7.4.2(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + webpack-dev-middleware@6.1.3(webpack@5.100.0(@swc/core@1.12.11)): + dependencies: + colorette: 2.0.20 + memfs: 3.5.3 + mime-types: 2.1.35 + range-parser: 1.2.1 + schema-utils: 4.3.2 + optionalDependencies: + webpack: 5.100.0(@swc/core@1.12.11) + + webpack-dev-middleware@7.4.2(webpack@5.100.0(@swc/core@1.12.11)): dependencies: colorette: 2.0.20 memfs: 4.36.3 @@ -27016,9 +27069,10 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.2 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) + optional: true - webpack-dev-middleware@7.4.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + webpack-dev-middleware@7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: colorette: 2.0.20 memfs: 4.36.3 @@ -27027,9 +27081,9 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.2 optionalDependencies: - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) - webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)): + webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -27057,17 +27111,18 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + webpack-dev-middleware: 7.4.2(webpack@5.100.0(@swc/core@1.12.11)) ws: 8.18.3 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) transitivePeerDependencies: - bufferutil - debug - supports-color - utf-8-validate + optional: true - webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -27095,10 +27150,10 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) ws: 8.18.3 optionalDependencies: - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) transitivePeerDependencies: - bufferutil - debug @@ -27119,23 +27174,55 @@ snapshots: webpack-sources@3.3.3: {} - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: typed-assert: 1.0.9 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) optionalDependencies: - html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)): + webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: typed-assert: 1.0.9 - webpack: 5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) optionalDependencies: - html-webpack-plugin: 5.6.3(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + html-webpack-plugin: 5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-virtual-modules@0.6.2: {} - webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6): + webpack@5.100.0(@swc/core@1.12.11): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.15.0 + acorn-import-phases: 1.0.3(acorn@8.15.0) + browserslist: 4.25.1 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.2 + es-module-lexer: 1.7.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.2 + tapable: 2.2.2 + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)) + watchpack: 2.4.4 + webpack-sources: 3.3.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -27159,7 +27246,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.2 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)) + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -27167,7 +27254,7 @@ snapshots: - esbuild - uglify-js - webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6): + webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0): dependencies: '@types/estree': 1.0.8 '@webassemblyjs/ast': 1.14.1 @@ -27189,7 +27276,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.25.6)(webpack@5.94.0(@swc/core@1.12.11(@swc/helpers@0.5.15))(esbuild@0.23.0)) + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.23.0)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: