diff --git a/codemods/0.1.0-schemas-to-subpath.ts b/codemods/0.1.0-schemas-to-subpath.ts new file mode 100644 index 0000000000..1559d9d4ab --- /dev/null +++ b/codemods/0.1.0-schemas-to-subpath.ts @@ -0,0 +1,316 @@ +/** + * jscodeshift codemod: @ag-ui/core 0.0.x → 0.1.0 + * + * Moves every `*Schema` import (and `EventSchemas`) from `@ag-ui/core` to the + * new `@ag-ui/core/schemas` subpath. Type-only imports are preserved. Idempotent. + * + * Usage: + * npx jscodeshift -t 0.1.0-schemas-to-subpath.ts --parser=tsx --extensions=ts,tsx src/ + */ +import type { Transform, ImportSpecifier, ImportNamespaceSpecifier } from "jscodeshift"; + +// --------------------------------------------------------------------------- +// Curated list — the public *Schema exports from 0.0.x. +// The transform also matches anything whose imported name ends with "Schema" +// as a fallback, so this list does not need to be exhaustive. It exists to +// catch `EventSchemas` (the only "Schemas" plural export) and to make the +// intent of the transform explicit. +// Keep in sync with sdks/typescript/packages/core/src/schemas.ts +// --------------------------------------------------------------------------- +const SCHEMA_NAMES = new Set([ + // Discriminated event union + "EventSchemas", + // EventType enum schema + "EventTypeSchema", + // Base type schemas + "FunctionCallSchema", + "ToolCallSchema", + "TextInputContentSchema", + "InputContentDataSourceSchema", + "InputContentUrlSourceSchema", + "InputContentSourceSchema", + "ImageInputContentSchema", + "AudioInputContentSchema", + "VideoInputContentSchema", + "DocumentInputContentSchema", + "ImageInputPartSchema", + "AudioInputPartSchema", + "VideoInputPartSchema", + "DocumentInputPartSchema", + "BinaryInputContentSchema", + "InputContentSchema", + "InputContentPartSchema", + "DeveloperMessageSchema", + "SystemMessageSchema", + "AssistantMessageSchema", + "UserMessageSchema", + "ToolMessageSchema", + "ActivityMessageSchema", + "ReasoningMessageSchema", + "MessageSchema", + "RoleSchema", + "ContextSchema", + "ToolSchema", + "InterruptSchema", + "ResumeEntrySchema", + "RunAgentInputSchema", + "StateSchema", + // Event schemas + "BaseEventSchema", + "TextMessageStartEventSchema", + "TextMessageContentEventSchema", + "TextMessageEndEventSchema", + "TextMessageChunkEventSchema", + "ThinkingTextMessageStartEventSchema", + "ThinkingTextMessageContentEventSchema", + "ThinkingTextMessageEndEventSchema", + "ToolCallStartEventSchema", + "ToolCallArgsEventSchema", + "ToolCallEndEventSchema", + "ToolCallResultEventSchema", + "ToolCallChunkEventSchema", + "ThinkingStartEventSchema", + "ThinkingEndEventSchema", + "StateSnapshotEventSchema", + "StateDeltaEventSchema", + "MessagesSnapshotEventSchema", + "ActivitySnapshotEventSchema", + "ActivityDeltaEventSchema", + "RawEventSchema", + "CustomEventSchema", + "RunStartedEventSchema", + "RunFinishedSuccessOutcomeSchema", + "RunFinishedInterruptOutcomeSchema", + "RunFinishedOutcomeSchema", + "RunFinishedEventSchema", + "RunErrorEventSchema", + "StepStartedEventSchema", + "StepFinishedEventSchema", + "ReasoningEncryptedValueSubtypeSchema", + "ReasoningStartEventSchema", + "ReasoningMessageStartEventSchema", + "ReasoningMessageContentEventSchema", + "ReasoningMessageEndEventSchema", + "ReasoningMessageChunkEventSchema", + "ReasoningEndEventSchema", + "ReasoningEncryptedValueEventSchema", + // Capability schemas + "SubAgentInfoSchema", + "IdentityCapabilitiesSchema", + "TransportCapabilitiesSchema", + "ToolsCapabilitiesSchema", + "OutputCapabilitiesSchema", + "StateCapabilitiesSchema", + "MultiAgentCapabilitiesSchema", + "ReasoningCapabilitiesSchema", + "MultimodalInputCapabilitiesSchema", + "MultimodalOutputCapabilitiesSchema", + "MultimodalCapabilitiesSchema", + "ExecutionCapabilitiesSchema", + "HumanInTheLoopCapabilitiesSchema", + "AgentCapabilitiesSchema", +]); + +/** Returns true if this imported name should move to @ag-ui/core/schemas. */ +const isSchemaSpecifier = (importedName: string): boolean => + importedName.endsWith("Schema") || importedName === "EventSchemas" || SCHEMA_NAMES.has(importedName); + +const CORE_SOURCE = "@ag-ui/core"; +const SCHEMAS_SOURCE = "@ag-ui/core/schemas"; + +const transform: Transform = (file, api) => { + const j = api.jscodeshift; + const root = j(file.source); + + let dirty = false; + + // Collect all import declarations from @ag-ui/core + const coreImports = root.find(j.ImportDeclaration, { + source: { value: CORE_SOURCE }, + }); + + if (coreImports.length === 0) { + return file.source; + } + + // Collect any existing import from @ag-ui/core/schemas so we can merge into it + const existingSchemasImports = root.find(j.ImportDeclaration, { + source: { value: SCHEMAS_SOURCE }, + }); + + // We may need to merge schema specifiers into an existing schemas import. + // Build a set of (imported::local) pairs already imported from @ag-ui/core/schemas + // so we don't create duplicates. Keying by both imported name AND local alias ensures + // that `Foo` and `Foo as Bar` are treated as distinct specifiers and both preserved. + const alreadyInSchemas = new Set(); + existingSchemasImports.forEach((path) => { + (path.node.specifiers ?? []).forEach((spec) => { + if (spec.type === "ImportSpecifier") { + const s = spec as ImportSpecifier; + alreadyInSchemas.add(`${s.imported.name}::${s.local.name}`); + } + }); + }); + + // Specifiers to move to @ag-ui/core/schemas, accumulated across all @ag-ui/core imports. + // We separate value specs from type-only specs so we can emit correctly typed declarations. + const valueSpecsToMove: ImportSpecifier[] = []; + const typeSpecsToMove: ImportSpecifier[] = []; + + coreImports.forEach((path) => { + const specifiers = path.node.specifiers ?? []; + // A whole-declaration `import type { ... }` makes all specifiers type-only. + const declIsTypeOnly = path.node.importKind === "type"; + + // Partition: stay vs. move + const staySpecs: typeof specifiers = []; + const moveSpecs: ImportSpecifier[] = []; + + for (const spec of specifiers) { + if (spec.type !== "ImportSpecifier") { + // Default or namespace imports — always stay on @ag-ui/core + if (spec.type === "ImportNamespaceSpecifier") { + console.warn( + `[codemod 0.1.0-schemas-to-subpath] ${file.path}: namespace import "import * as ${(spec as ImportNamespaceSpecifier).local.name} from "@ag-ui/core"" cannot be automatically migrated. Schema references via ${(spec as ImportNamespaceSpecifier).local.name}. must be updated manually.` + ); + } + staySpecs.push(spec); + continue; + } + const named = spec as ImportSpecifier; + const importedName = named.imported.name; + + if (isSchemaSpecifier(importedName)) { + moveSpecs.push(named); + } else { + staySpecs.push(named); + } + } + + if (moveSpecs.length === 0) { + // Nothing to move in this declaration — leave it untouched + return; + } + + dirty = true; + + // Only add to specsToMove if not already present in @ag-ui/core/schemas. + // Preserve type-only intent: a specifier is type-only if the whole declaration + // is `import type { ... }` OR if the individual specifier has importKind "type". + // Uniqueness key is `${imported.name}::${local.name}` so that the same schema + // imported under two different local aliases (e.g. `Foo` and `Foo as Bar`) + // are both preserved rather than the second being silently dropped. + for (const spec of moveSpecs) { + const importedName = spec.imported.name; + const localName = spec.local.name; + const dedupKey = `${importedName}::${localName}`; + if (!alreadyInSchemas.has(dedupKey)) { + const specIsTypeOnly = declIsTypeOnly || spec.importKind === "type"; + if (specIsTypeOnly) { + // Clone spec without per-specifier importKind — the declaration itself + // will be emitted as `import type { ... }`, so the per-specifier marker + // is redundant and would produce `import type { type Foo }`. + const cloned = j.importSpecifier( + j.identifier(importedName), + j.identifier(localName), + ); + typeSpecsToMove.push(cloned); + } else { + valueSpecsToMove.push(spec); + } + alreadyInSchemas.add(dedupKey); + } + } + + // Update or remove the original @ag-ui/core declaration + if (staySpecs.length === 0) { + // Nothing left on @ag-ui/core — remove the declaration entirely + j(path).remove(); + } else { + // Mutate the specifier list in-place + path.node.specifiers = staySpecs; + // If the declaration was `import type` but we stripped all type-only specs + // and only non-schema (value) specifiers remain, the importKind stays "type" + // which is still correct since all remaining specifiers are type imports. + } + }); + + const specsToMove = [...valueSpecsToMove, ...typeSpecsToMove]; + + if (!dirty || specsToMove.length === 0) { + return dirty ? root.toSource({ quote: "double" }) : file.source; + } + + // Helper to insert a new import declaration after the last remaining import. + const insertAfterLastImport = (newImport: ReturnType) => { + const allImports = root.find(j.ImportDeclaration); + if (allImports.length > 0) { + allImports.at(allImports.length - 1).insertAfter(newImport); + } else { + const body = root.find(j.Program).get("body"); + body.value.unshift(newImport); + } + }; + + if (existingSchemasImports.length > 0) { + // Merge specifiers only into a declaration of the matching kind to avoid + // accidentally making value imports type-only (erased at runtime) or + // making type imports lose their type-only status. + // + // Strategy: + // - typeSpecsToMove → merge into an existing `import type` declaration, + // or create a new one if none exists. + // - valueSpecsToMove → merge into an existing value import declaration, + // or create a new one if none exists. + // + // We never mix kinds within a single declaration. + + let mergedValue = false; + let mergedType = false; + + existingSchemasImports.forEach((path) => { + if (path.node.importKind === "type") { + // Type-only declaration: only merge type specs here. + if (typeSpecsToMove.length > 0 && !mergedType) { + path.node.specifiers = [...(path.node.specifiers ?? []), ...typeSpecsToMove]; + mergedType = true; + } + // Do NOT merge value specs — they would become type-only and be erased at runtime. + } else { + // Value import declaration: only merge value specs here. + if (valueSpecsToMove.length > 0 && !mergedValue) { + path.node.specifiers = [...(path.node.specifiers ?? []), ...valueSpecsToMove]; + mergedValue = true; + } + // Do NOT merge type specs into a value import — emit a separate `import type` below. + } + }); + + // Anything not yet merged needs a fresh declaration. + if (!mergedValue && valueSpecsToMove.length > 0) { + const newImport = j.importDeclaration(valueSpecsToMove, j.stringLiteral(SCHEMAS_SOURCE)); + insertAfterLastImport(newImport); + } + if (!mergedType && typeSpecsToMove.length > 0) { + const typeImport = j.importDeclaration(typeSpecsToMove, j.stringLiteral(SCHEMAS_SOURCE)); + typeImport.importKind = "type"; + insertAfterLastImport(typeImport); + } + } else { + // No existing @ag-ui/core/schemas import. Emit value and type declarations separately. + if (valueSpecsToMove.length > 0) { + const newImport = j.importDeclaration(valueSpecsToMove, j.stringLiteral(SCHEMAS_SOURCE)); + insertAfterLastImport(newImport); + } + if (typeSpecsToMove.length > 0) { + const typeImport = j.importDeclaration(typeSpecsToMove, j.stringLiteral(SCHEMAS_SOURCE)); + typeImport.importKind = "type"; + insertAfterLastImport(typeImport); + } + } + + return root.toSource({ quote: "double" }); +}; + +export default transform; +export const parser = "tsx"; diff --git a/codemods/README.md b/codemods/README.md new file mode 100644 index 0000000000..457db8765a --- /dev/null +++ b/codemods/README.md @@ -0,0 +1,56 @@ +# @ag-ui/core codemods + +Automated transforms for upgrading code that depends on `@ag-ui/core`. + +--- + +## 0.1.0-schemas-to-subpath + +**What it does.** Moves every `*Schema` import (and `EventSchemas`, the only "Schemas" plural export) from `@ag-ui/core` to the new `@ag-ui/core/schemas` subpath introduced in 0.1.0. If a file already has an import from `@ag-ui/core/schemas`, the moved specifiers are merged into it rather than creating a duplicate declaration. Type-only imports (`import type { ... }`) are preserved on the appropriate side. The transform is idempotent — running it twice produces the same output. + +**How to run it.** + +```bash +npx jscodeshift -t https://raw.githubusercontent.com/ag-ui-protocol/ag-ui/main/codemods/0.1.0-schemas-to-subpath.ts \ + --parser=tsx \ + --extensions=ts,tsx \ + src/ +``` + +To do a dry run (print changes without writing): + +```bash +npx jscodeshift --dry --print \ + -t https://raw.githubusercontent.com/ag-ui-protocol/ag-ui/main/codemods/0.1.0-schemas-to-subpath.ts \ + --parser=tsx \ + --extensions=ts,tsx \ + src/ +``` + +**What it does NOT do.** + +- It does not add `zod` to your `package.json`. After running the codemod, run `npm install zod` (or `pnpm add zod` / `yarn add zod`) if any file imports from `@ag-ui/core/schemas`. +- It does not update the `@ag-ui/core` version constraint in `package.json`. Update it to `^0.1.0` manually. + +**Recognized schema names.** + +The transform uses two complementary heuristics: + +1. Any imported name that ends with `"Schema"` is moved (e.g. `UserMessageSchema`, `AgentCapabilitiesSchema`). +2. The name `EventSchemas` is explicitly matched (the only "Schemas" plural export). + +The curated list in `SCHEMA_NAMES` inside the transform source mirrors the full public schema surface of `@ag-ui/core/schemas`. Both heuristics are applied, so unknown future schema additions (if they follow the naming convention) are also covered. + +**Aliasing behavior.** + +**Aliased imports work correctly.** Detection uses the *imported* name (the name on the `@ag-ui/core` side), so `import { UserMessageSchema as Foo } from "@ag-ui/core"` is recognized regardless of the local alias. The alias is preserved in the moved declaration: `import { UserMessageSchema as Foo } from "@ag-ui/core/schemas"`. + +**Known limitations.** + +- **Namespace imports** — `import * as core from "@ag-ui/core"` cannot be automatically migrated. The codemod will emit a warning to stderr and leave the import untouched. You must manually split `core.` references into a named import from `@ag-ui/core/schemas` and update all usages. +- **Re-exports** — `export { UserMessageSchema } from "@ag-ui/core"` is not handled; only `import` declarations are transformed. Re-export-from syntax will need to be updated manually. +- **Dynamic imports** — `import("@ag-ui/core")` and `require("@ag-ui/core")` calls are not transformed; only static `import` declarations are handled. + +--- + +For more context, see the [0.1.0 migration guide](https://docs.ag-ui.com/sdk/js/core/migration-0-1-0). diff --git a/codemods/__fixtures__/0.1.0-schemas-to-subpath.expected.ts b/codemods/__fixtures__/0.1.0-schemas-to-subpath.expected.ts new file mode 100644 index 0000000000..2be7a52340 --- /dev/null +++ b/codemods/__fixtures__/0.1.0-schemas-to-subpath.expected.ts @@ -0,0 +1,59 @@ +// ── 2. Pure type import (should be untouched) ───────────────────────────────── +import type { Message, Tool, EventType } from "@ag-ui/core"; + +// ── 3. Mixed types and schemas ──────────────────────────────────────────────── +import { Message as CoreMessage } from "@ag-ui/core"; + +// ── 4. Already-present @ag-ui/core/schemas import (new specifiers should merge) ─ +import { + BaseEventSchema, + UserMessageSchema, + EventSchemas, + AgentCapabilitiesSchema, + RunAgentInputSchema, + RunAgentInputSchema as RunInput, + RunAgentInputSchema as RunInputAlias, + ContextSchema as Ctx, +} from "@ag-ui/core/schemas"; + +// ── 5. Non-schema import that must stay on @ag-ui/core ─────────────────────── +import { EventType as ET } from "@ag-ui/core"; + +// ── 8. Namespace import — must be warned about and left untouched ───────────── +import * as core from "@ag-ui/core"; + +// ── 10. Pre-existing type-only schemas import + value import from @ag-ui/core ── +// The value spec must NOT be merged into the type-only declaration. +import type { BaseEventSchema as BaseEvent, ToolSchema, ContextSchema, StateSchema } from "@ag-ui/core/schemas"; + +// ── Unrelated import — must not be touched ──────────────────────────────────── +import { z } from "zod"; + +export function validate(raw: unknown) { + return EventSchemas.safeParse(raw); +} + +export function parseUser(raw: unknown) { + return UserMessageSchema.safeParse(raw); +} + +export function useMessage(msg: Message) { + return msg; +} + +export { CoreMessage }; + +// Case 7 usage — ensures RunInput alias is visible and would break if dropped +export function checkRunInput(raw: unknown) { + return RunInput.safeParse(raw); +} + +// Case 9 usage — both names must be usable after migration +export function checkRunInputAliased(raw: unknown) { + return RunInputAlias.safeParse(raw); +} + +// Case 10 usage — Ctx is a value import and must remain callable at runtime +export function checkCtx(raw: unknown) { + return Ctx.safeParse(raw); +} diff --git a/codemods/__fixtures__/0.1.0-schemas-to-subpath.input.ts b/codemods/__fixtures__/0.1.0-schemas-to-subpath.input.ts new file mode 100644 index 0000000000..8f693e97ea --- /dev/null +++ b/codemods/__fixtures__/0.1.0-schemas-to-subpath.input.ts @@ -0,0 +1,70 @@ +// Fixture: input for the 0.1.0-schemas-to-subpath codemod +// Covers: pure schema import, pure type import, mixed import, +// existing @ag-ui/core/schemas import that should be merged into, +// type-only schema imports, per-specifier type imports, +// and namespace imports (which should be warned about and left alone). + +// ── 1. Schemas only ────────────────────────────────────────────────────────── +import { UserMessageSchema, EventSchemas } from "@ag-ui/core"; + +// ── 2. Pure type import (should be untouched) ───────────────────────────────── +import type { Message, Tool, EventType } from "@ag-ui/core"; + +// ── 3. Mixed types and schemas ──────────────────────────────────────────────── +import { Message as CoreMessage, AgentCapabilitiesSchema, RunAgentInputSchema } from "@ag-ui/core"; + +// ── 4. Already-present @ag-ui/core/schemas import (new specifiers should merge) ─ +import { BaseEventSchema } from "@ag-ui/core/schemas"; + +// ── 5. Non-schema import that must stay on @ag-ui/core ─────────────────────── +import { EventType as ET } from "@ag-ui/core"; + +// ── 6. Type-only schema import — must emit `import type` on schemas subpath ─── +import type { ToolSchema, ContextSchema } from "@ag-ui/core"; + +// ── 7. Per-specifier type import mixed with value import ────────────────────── +// RunInput is a local alias — it must survive dedup and appear in the output. +import { type StateSchema, RunAgentInputSchema as RunInput } from "@ag-ui/core"; + +// ── 8. Namespace import — must be warned about and left untouched ───────────── +import * as core from "@ag-ui/core"; + +// ── 9. Same schema imported twice with different aliases ───────────────────── +import { RunAgentInputSchema as RunInputAlias } from "@ag-ui/core"; + +// ── 10. Pre-existing type-only schemas import + value import from @ag-ui/core ── +// The value spec must NOT be merged into the type-only declaration. +import type { BaseEventSchema as BaseEvent } from "@ag-ui/core/schemas"; +import { ContextSchema as Ctx } from "@ag-ui/core"; + +// ── Unrelated import — must not be touched ──────────────────────────────────── +import { z } from "zod"; + +export function validate(raw: unknown) { + return EventSchemas.safeParse(raw); +} + +export function parseUser(raw: unknown) { + return UserMessageSchema.safeParse(raw); +} + +export function useMessage(msg: Message) { + return msg; +} + +export { CoreMessage }; + +// Case 7 usage — ensures RunInput alias is visible and would break if dropped +export function checkRunInput(raw: unknown) { + return RunInput.safeParse(raw); +} + +// Case 9 usage — both names must be usable after migration +export function checkRunInputAliased(raw: unknown) { + return RunInputAlias.safeParse(raw); +} + +// Case 10 usage — Ctx is a value import and must remain callable at runtime +export function checkCtx(raw: unknown) { + return Ctx.safeParse(raw); +} diff --git a/codemods/test.ts b/codemods/test.ts new file mode 100644 index 0000000000..541dcc76c2 --- /dev/null +++ b/codemods/test.ts @@ -0,0 +1,60 @@ +/** + * Smoke-test for the 0.1.0-schemas-to-subpath codemod. + * + * Runs the transform against the input fixture via jscodeshift subprocess and + * diffs the result against the expected fixture. Exits 0 on success, 1 on mismatch. + * + * Usage: + * npx ts-node codemods/test.ts + */ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, readFileSync, copyFileSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { tmpdir } from "node:os"; + +const FIXTURES_DIR = join(__dirname, "__fixtures__"); +const INPUT = join(FIXTURES_DIR, "0.1.0-schemas-to-subpath.input.ts"); +const EXPECTED = join(FIXTURES_DIR, "0.1.0-schemas-to-subpath.expected.ts"); +const CODEMOD = resolve(__dirname, "0.1.0-schemas-to-subpath.ts"); + +// Use a unique temp directory per run so concurrent invocations don't race. +const TMP_DIR = mkdtempSync(join(tmpdir(), "codemod-test-")); +const TEMP = join(TMP_DIR, "codemod-test.ts"); + +copyFileSync(INPUT, TEMP); + +// `shell: true` lets us invoke npx via the shell — required on Windows where +// the binary is `npx.cmd` and Node 24's execFileSync no longer performs +// PATHEXT resolution. Args are static literals so shell injection is not a +// concern; CODEMOD and TEMP are absolute paths derived from __dirname/tmpdir. +execFileSync( + "npx", + ["--yes", "jscodeshift", "-t", CODEMOD, "--parser=tsx", TEMP], + { stdio: "inherit", shell: true }, +); + +const actual = readFileSync(TEMP, "utf8"); +const expected = readFileSync(EXPECTED, "utf8"); + +// Normalize line endings and trailing whitespace for comparison +const normalize = (s: string) => s.replace(/\r\n/g, "\n").trimEnd() + "\n"; + +if (normalize(actual) === normalize(expected)) { + console.log("PASS — codemod output matches expected fixture."); + process.exit(0); +} else { + console.error("FAIL — codemod output differs from expected."); + const actualLines = normalize(actual).split("\n"); + const expectedLines = normalize(expected).split("\n"); + const maxLines = Math.max(actualLines.length, expectedLines.length); + for (let i = 0; i < maxLines; i++) { + const a = actualLines[i] ?? ""; + const e = expectedLines[i] ?? ""; + if (a !== e) { + console.error(` Line ${i + 1}:`); + console.error(` expected: ${JSON.stringify(e)}`); + console.error(` actual: ${JSON.stringify(a)}`); + } + } + process.exit(1); +} diff --git a/docs/docs.json b/docs/docs.json index 4e3f2a1040..4210b9c743 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -92,7 +92,8 @@ "sdk/js/core/overview", "sdk/js/core/types", "sdk/js/core/multimodal-inputs", - "sdk/js/core/events" + "sdk/js/core/events", + "sdk/js/core/migration-0-1-0" ] }, { diff --git a/docs/sdk/js/core/events.mdx b/docs/sdk/js/core/events.mdx index 14c1505c23..15fd543c77 100644 --- a/docs/sdk/js/core/events.mdx +++ b/docs/sdk/js/core/events.mdx @@ -4,6 +4,10 @@ description: "Documentation for the events used in the Agent User Interaction Protocol SDK" --- + +**Upgrading from 0.0.x?** See the [0.1.0 migration guide](/sdk/js/core/migration-0-1-0) — schemas moved to a new subpath and zod is now an optional peer dependency. + + # Events The Agent User Interaction Protocol SDK uses a streaming event-based @@ -589,43 +593,32 @@ The following event types are deprecated: See [Reasoning Migration](/concepts/reasoning#migration-from-thinking-events) for detailed migration guidance. -## Event Schemas +## Event validation -The SDK uses Zod schemas to validate events: +`@ag-ui/core` ships TypeScript types for every event but does not bundle a runtime +schema validator in its main entry. The zod schemas live on an opt-in subpath: ```typescript -const EventSchemas = z.discriminatedUnion("type", [ - TextMessageStartEventSchema, - TextMessageContentEventSchema, - TextMessageEndEventSchema, - ToolCallStartEventSchema, - ToolCallArgsEventSchema, - ToolCallEndEventSchema, - ToolCallResultEventSchema, - StateSnapshotEventSchema, - StateDeltaEventSchema, - MessagesSnapshotEventSchema, - ActivitySnapshotEventSchema, - ActivityDeltaEventSchema, - RawEventSchema, - CustomEventSchema, - RunStartedEventSchema, - RunFinishedEventSchema, - RunErrorEventSchema, - StepStartedEventSchema, - StepFinishedEventSchema, - ReasoningStartEventSchema, - ReasoningMessageStartEventSchema, - ReasoningMessageContentEventSchema, - ReasoningMessageEndEventSchema, - ReasoningMessageChunkEventSchema, - ReasoningEndEventSchema, - ReasoningEncryptedValueEventSchema, -]) -``` - -This allows for runtime validation of events and provides TypeScript type -inference. +import { EventSchemas } from "@ag-ui/core/schemas"; + +const result = EventSchemas.safeParse(incoming); +if (result.success) { + // result.data is typed as the discriminated AGUIEvent union +} else { + console.error(result.error); +} +``` + +zod is an optional peer dependency of `@ag-ui/core/schemas`. Install it explicitly +if you import from this module: + +```bash +npm install zod +# or pnpm/yarn — zod 3.24+ and zod 4 are both supported +``` + +If you'd rather use a different validation library, define your own schemas locally +using your library of choice. The TypeScript types from `@ag-ui/core` are the contract. ### ToolCallChunkEvent diff --git a/docs/sdk/js/core/migration-0-1-0.mdx b/docs/sdk/js/core/migration-0-1-0.mdx new file mode 100644 index 0000000000..af4b29a08e --- /dev/null +++ b/docs/sdk/js/core/migration-0-1-0.mdx @@ -0,0 +1,126 @@ +--- +title: "Migrating to @ag-ui/core 0.1.0" +description: "Update your code from 0.0.x to 0.1.0: schemas moved to a subpath, zod is now an optional peer dependency." +--- + +# Migrating to @ag-ui/core 0.1.0 + +`@ag-ui/core` 0.1.0 splits the package surface: TypeScript types stay on the main entry, zod schemas move to a new opt-in subpath `@ag-ui/core/schemas`, and zod becomes an optional peer dependency. + +If your code only imports types (`Message`, `Tool`, `EventType`, etc.) from `@ag-ui/core`, you don't need to change anything — just bump the version. If you import any `*Schema` constant (`UserMessageSchema`, `EventSchemas`, `AgentCapabilitiesSchema`, etc.), update the import path to `@ag-ui/core/schemas`. + +## Why this changed + +Prior versions shipped zod 3.x as a hard runtime dependency. Consumers running zod 4 in their app ended up with two zod copies in their dependency graph, which broke `instanceof ZodError` checks across module boundaries and inflated bundle size. Making zod a peer dependency (now accepting `^3.24.0 || ^4.0.0`) lets the consumer pick exactly one version. Moving schemas to an opt-in subpath means consumers who only use types pay no zod cost at all. + + +**Do you need to migrate?** +- **No** — if your code only imports types and value enums (`Message`, `EventType`, `AGUIError`, factory functions like `createTextMessageStartEvent`, etc.). Just bump the version. +- **Yes** — if your code imports any `*Schema` constant from `@ag-ui/core` (e.g., `UserMessageSchema`, `EventSchemas`, `AgentCapabilitiesSchema`). + + +## Manual migration steps + + + + Bump the version constraint to `^0.1.0`: + + ```json + { + "dependencies": { + "@ag-ui/core": "^0.1.0" + } + } + ``` + + + + If you import anything from `@ag-ui/core/schemas`, install zod as a direct dependency. Both zod 3.24+ and zod 4 are supported: + + ```bash + # Pick whichever major your project already uses + npm install zod # latest (zod 4) + npm install zod@^3.24.0 # stay on zod 3 + ``` + + The supported peer dependency range is `^3.24.0 || ^4.0.0`. If your project has no opinion on zod, installing the latest is fine. + + + + Every schema that was exported from `@ag-ui/core` in 0.0.x is now exported from `@ag-ui/core/schemas`. Names are unchanged — only the import source changes. + + + + ```ts + // Before + import { UserMessageSchema, EventSchemas } from "@ag-ui/core"; + + // After + import { UserMessageSchema, EventSchemas } from "@ag-ui/core/schemas"; + ``` + + + ```ts + // Before + import { Message, UserMessageSchema } from "@ag-ui/core"; + + // After + import { Message } from "@ag-ui/core"; + import { UserMessageSchema } from "@ag-ui/core/schemas"; + ``` + + + ```ts + // Before — unchanged + import type { Message, Tool, EventType } from "@ag-ui/core"; + + // After — unchanged + import type { Message, Tool, EventType } from "@ag-ui/core"; + ``` + + + + + + Reinstall and rebuild to confirm everything resolves correctly: + + ```bash + pnpm install && pnpm build + ``` + + If you're using npm or yarn, substitute the equivalent commands. The build should complete without unresolved-module or missing-peer errors. + + + +## Automated migration + +For codebases with many `*Schema` imports, a jscodeshift codemod is shipped in +the `ag-ui` repo at `codemods/0.1.0-schemas-to-subpath.ts`. It +splits combined imports and redirects every `*Schema` import to `@ag-ui/core/schemas`. + +Run it from your project root: + +```bash +npx jscodeshift -t https://raw.githubusercontent.com/ag-ui-protocol/ag-ui/main/codemods/0.1.0-schemas-to-subpath.ts \ + --parser=tsx \ + --extensions=ts,tsx \ + src/ +``` + +The transform is idempotent — running it twice has no extra effect. See +[the codemod README](https://github.com/ag-ui-protocol/ag-ui/blob/main/codemods/README.md) +for details and a list of recognized schemas. + +## FAQ + +**Q: I'm on zod 4. Can I use AG-UI now?** + +Yes — `@ag-ui/core/schemas` declares `zod ^3.24.0 || ^4.0.0` as an optional peer dependency. Install whichever major you prefer; there's no longer a hard pin to zod 3. + +**Q: I don't want zod at all. Can I still use AG-UI?** + +Yes — the main `@ag-ui/core` entry has zero runtime dependencies. Use the TypeScript types directly and validate runtime data with whatever library you prefer (valibot, arktype, hand-written checks, or no validation at all). + +**Q: Where can I find the full list of `*Schema` exports that moved?** + +Every schema that was exported from `@ag-ui/core` in 0.0.x is now exported from `@ag-ui/core/schemas`. Names are unchanged. The complete list lives in [`schemas.ts` in the source](https://github.com/ag-ui-protocol/ag-ui/blob/main/sdks/typescript/packages/core/src/schemas.ts). diff --git a/docs/sdk/js/core/multimodal-inputs.mdx b/docs/sdk/js/core/multimodal-inputs.mdx index af51fad4c0..f174318cde 100644 --- a/docs/sdk/js/core/multimodal-inputs.mdx +++ b/docs/sdk/js/core/multimodal-inputs.mdx @@ -11,7 +11,7 @@ description: multimodal content parts. ```typescript -import { UserMessage } from "@ag-ui/core" +import type { UserMessage } from "@ag-ui/core" const message: UserMessage = { id: "user-1", diff --git a/docs/sdk/js/core/overview.mdx b/docs/sdk/js/core/overview.mdx index ab0edd3420..5ed7388f65 100644 --- a/docs/sdk/js/core/overview.mdx +++ b/docs/sdk/js/core/overview.mdx @@ -3,6 +3,10 @@ title: "Overview" description: "Core concepts in the Agent User Interaction Protocol SDK" --- + +**Upgrading from 0.0.x?** See the [0.1.0 migration guide](/sdk/js/core/migration-0-1-0) — schemas moved to a new subpath and zod is now an optional peer dependency. + + # @ag-ui/core The Agent User Interaction Protocol SDK uses a streaming event-based diff --git a/integrations/adk-middleware/typescript/package.json b/integrations/adk-middleware/typescript/package.json index 3eb5f12561..dc09eec4ea 100644 --- a/integrations/adk-middleware/typescript/package.json +++ b/integrations/adk-middleware/typescript/package.json @@ -34,10 +34,12 @@ "@arethetypeswrong/cli": "^0.17.4", "tsdown": "^0.20.1", "typescript": "^5.3.3", - "vitest": "^4.0.18" + "vitest": "^4.0.18", + "zod": "^3.24.0 || ^4.0.0" }, "exports": { ".": { + "types": "./dist/index.d.ts", "require": "./dist/index.js", "import": "./dist/index.mjs" }, diff --git a/integrations/adk-middleware/typescript/src/index.ts b/integrations/adk-middleware/typescript/src/index.ts index 66cc39d6d3..f5a71f869e 100644 --- a/integrations/adk-middleware/typescript/src/index.ts +++ b/integrations/adk-middleware/typescript/src/index.ts @@ -1,5 +1,6 @@ import { HttpAgent } from "@ag-ui/client"; -import { AgentCapabilities, AgentCapabilitiesSchema } from "@ag-ui/core"; +import type { AgentCapabilities } from "@ag-ui/core"; +import { AgentCapabilitiesSchema } from "@ag-ui/core/schemas"; export class ADKAgent extends HttpAgent { /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c24bbe7979..69e0d5b4ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -437,6 +437,9 @@ importers: vitest: specifier: ^4.0.18 version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@20.19.21)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) + zod: + specifier: ^3.24.0 || ^4.0.0 + version: 3.25.76 integrations/ag2/typescript: dependencies: @@ -813,7 +816,7 @@ importers: version: 0.17.4 '@copilotkit/runtime': specifier: 0.0.0-mme-ag-ui-0-0-46-20260227141603 - version: 0.0.0-mme-ag-ui-0-0-46-20260227141603(d21f54b13b2c2aa4c046f6b9f7abd449) + version: 0.0.0-mme-ag-ui-0-0-46-20260227141603(e7e6345cef6c41890dc996975e0ebc23) '@copilotkit/shared': specifier: 0.0.0-mme-ag-ui-0-0-46-20260227141603 version: 0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/core@sdks+typescript+packages+core) @@ -1239,7 +1242,7 @@ importers: specifier: ^11.1.0 version: 11.1.0 zod: - specifier: ^3.22.4 + specifier: ^3.22.4 || ^4.0.0 version: 3.25.76 devDependencies: '@arethetypeswrong/cli': @@ -1265,10 +1268,6 @@ importers: version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@20.19.21)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) sdks/typescript/packages/core: - dependencies: - zod: - specifier: ^3.22.4 - version: 3.25.76 devDependencies: '@arethetypeswrong/cli': specifier: ^0.17.4 @@ -1288,6 +1287,9 @@ importers: vitest: specifier: ^4.0.18 version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@20.19.21)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) + zod: + specifier: ^3.24.0 || ^4.0.0 + version: 3.25.76 sdks/typescript/packages/encoder: dependencies: @@ -1328,6 +1330,9 @@ importers: '@protobuf-ts/protoc': specifier: ^2.11.1 version: 2.11.1 + zod: + specifier: ^3.24.0 || ^4.0.0 + version: 3.25.76 devDependencies: '@arethetypeswrong/cli': specifier: ^0.17.4 @@ -1389,12 +1394,18 @@ packages: '@ag-ui/core@0.0.52': resolution: {integrity: sha512-Xo0bUaNV56EqylzcrAuhUkQX7et7+SZIrqZZtEByGwEq/I1EHny6ZMkWHLkKR7UNi0FJZwJyhKYmKJS3B2SEgA==} + '@ag-ui/core@0.0.53': + resolution: {integrity: sha512-11UocR7fFdMWw503bWCX2IOK15vbWfxT11Mn9xOiPBVO/UVcn57ywGrlLL4UaBlPgmUTvuzr2yYR2ElSqiN2wQ==} + '@ag-ui/encoder@0.0.46': resolution: {integrity: sha512-XU6dTgUOFZsXeO+CxCMNl5R8NCbdUyifWP7sRNIi61Et3F/0d0JotLo1y1/9GMGfsJNnP7bjb4YYsx21R7YMlw==} '@ag-ui/encoder@0.0.52': resolution: {integrity: sha512-6GVDTb1dv2rjap7VVnmXYypDutZi6nrsTcdfxoP6ryDG5ynlXtmmS+FSDAt62JbIMD5CtEE963xNCb6d1iXw9g==} + '@ag-ui/encoder@0.0.53': + resolution: {integrity: sha512-bAOcfVdm6U4H6G6tW+DZfwPEQm1w/snVBTwaFn9nJcEMW69M7/HZuwvEc/7Zo0rK1jRL32N/j60PwTAeky19fw==} + '@ag-ui/langgraph@0.0.24': resolution: {integrity: sha512-ebTYpUw28fvbmhqbpAbmfsDTfEqm1gSeZaBcnxMGHFivJLCzsJ/C9hYw6aV8yRKV3lMFBwh/QFxn1eRcr7yRkQ==} peerDependencies: @@ -1418,6 +1429,9 @@ packages: '@ag-ui/proto@0.0.52': resolution: {integrity: sha512-+iCGzNUNL50YIoThVmsolWPjG4MJidl+R9k8QAGVwErEfHRtQ64KFyrdpeOXNVuWtM3SViJqPSgFyv7eGVS63A==} + '@ag-ui/proto@0.0.53': + resolution: {integrity: sha512-swjz22xWT8YUZt5OhmUwkARDQdwt8XM1hmGZbQrhRnNPXKwrKJX9ELlbnQ4iFUQIKkMWpphzE3vA3yNKs2bbKw==} + '@ai-sdk/anthropic@2.0.23': resolution: {integrity: sha512-ZEBiiv1UhjGjBwUU63pFhLK5LCSlNDb1idY9K1oZHm5/Fda1cuTojf32tOp0opH0RPbPAN/F8fyyNjbU33n9Kw==} engines: {node: '>=18'} @@ -11886,6 +11900,10 @@ snapshots: dependencies: zod: 3.25.76 + '@ag-ui/core@0.0.53': + dependencies: + zod: 3.25.76 + '@ag-ui/encoder@0.0.46': dependencies: '@ag-ui/core': 0.0.46 @@ -11896,6 +11914,11 @@ snapshots: '@ag-ui/core': 0.0.52 '@ag-ui/proto': 0.0.52 + '@ag-ui/encoder@0.0.53': + dependencies: + '@ag-ui/core': 0.0.53 + '@ag-ui/proto': 0.0.53 + '@ag-ui/langgraph@0.0.24(@ag-ui/client@0.0.46)(@ag-ui/core@0.0.46)(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76))(react-dom@19.2.1(react@19.2.3))(react@19.2.3)': dependencies: '@ag-ui/client': 0.0.46 @@ -11954,6 +11977,12 @@ snapshots: '@bufbuild/protobuf': 2.9.0 '@protobuf-ts/protoc': 2.11.1 + '@ag-ui/proto@0.0.53': + dependencies: + '@ag-ui/core': 0.0.53 + '@bufbuild/protobuf': 2.9.0 + '@protobuf-ts/protoc': 2.11.1 + '@ai-sdk/anthropic@2.0.23(zod@3.25.76)': dependencies: '@ai-sdk/provider': 2.0.0 @@ -13875,7 +13904,7 @@ snapshots: - encoding - graphql - '@copilotkit/runtime@0.0.0-mme-ag-ui-0-0-46-20260227141603(d21f54b13b2c2aa4c046f6b9f7abd449)': + '@copilotkit/runtime@0.0.0-mme-ag-ui-0-0-46-20260227141603(e7e6345cef6c41890dc996975e0ebc23)': dependencies: '@ag-ui/client': 0.0.46 '@ag-ui/core': 0.0.46 @@ -13884,7 +13913,7 @@ snapshots: '@ai-sdk/openai': 2.0.52(zod@3.25.76) '@copilotkit/shared': 0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/core@0.0.46) '@copilotkitnext/agent': 0.0.0-mme-ag-ui-0-0-46-20260227141603 - '@copilotkitnext/runtime': 0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/client@0.0.46)(@ag-ui/core@0.0.46)(@ag-ui/encoder@0.0.52)(@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603) + '@copilotkitnext/runtime': 0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/client@0.0.46)(@ag-ui/core@0.0.46)(@ag-ui/encoder@0.0.53)(@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603) '@graphql-yoga/plugin-defer-stream': 3.16.0(graphql-yoga@5.16.0(graphql@16.11.0))(graphql@16.11.0) '@hono/node-server': 1.19.7(hono@4.11.5) '@langchain/core': 0.3.80(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@4.104.0(ws@8.18.3)(zod@3.25.76)) @@ -14069,11 +14098,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@copilotkitnext/runtime@0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/client@0.0.46)(@ag-ui/core@0.0.46)(@ag-ui/encoder@0.0.52)(@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603)': + '@copilotkitnext/runtime@0.0.0-mme-ag-ui-0-0-46-20260227141603(@ag-ui/client@0.0.46)(@ag-ui/core@0.0.46)(@ag-ui/encoder@0.0.53)(@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603)': dependencies: '@ag-ui/client': 0.0.46 '@ag-ui/core': 0.0.46 - '@ag-ui/encoder': 0.0.52 + '@ag-ui/encoder': 0.0.53 '@copilotkitnext/shared': 0.0.0-mme-ag-ui-0-0-46-20260227141603 cors: 2.8.5 express: 4.21.2 @@ -19415,7 +19444,7 @@ snapshots: '@next/eslint-plugin-next': 16.0.7 eslint: 9.37.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.37.0(jiti@2.6.1)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.37.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.37.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.37.0(jiti@2.6.1)) @@ -19438,7 +19467,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.37.0(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -19453,14 +19482,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.37.0(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.48.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint: 9.37.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.37.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -19475,7 +19504,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.37.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.37.0(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 diff --git a/sdks/typescript/README.md b/sdks/typescript/README.md index ee7234f43c..bb1f4b6e8f 100644 --- a/sdks/typescript/README.md +++ b/sdks/typescript/README.md @@ -7,11 +7,11 @@ For more information visit the [official documentation](https://docs.ag-ui.com/) ## Multimodal user messages ```ts -import { UserMessageSchema } from "@ag-ui/core"; +import type { UserMessage } from "@ag-ui/core"; -const message = UserMessageSchema.parse({ +const message: UserMessage = { id: "user-123", - role: "user" as const, + role: "user", content: [ { type: "text", text: "Please describe this image" }, { @@ -23,7 +23,7 @@ const message = UserMessageSchema.parse({ }, }, ], -}); +}; console.log(message); // { id: "user-123", role: "user", content: [...] } diff --git a/sdks/typescript/packages/client/package.json b/sdks/typescript/packages/client/package.json index 69f8100b02..5a36848fdb 100644 --- a/sdks/typescript/packages/client/package.json +++ b/sdks/typescript/packages/client/package.json @@ -36,7 +36,7 @@ "rxjs": "7.8.1", "untruncate-json": "^0.0.1", "uuid": "^11.1.0", - "zod": "^3.22.4" + "zod": "^3.22.4 || ^4.0.0" }, "devDependencies": { "@types/node": "^20.11.19", @@ -49,6 +49,7 @@ }, "exports": { ".": { + "types": "./dist/index.d.ts", "require": "./dist/index.js", "import": "./dist/index.mjs" }, diff --git a/sdks/typescript/packages/client/src/transform/http.ts b/sdks/typescript/packages/client/src/transform/http.ts index 60446868f8..b75a74bed4 100644 --- a/sdks/typescript/packages/client/src/transform/http.ts +++ b/sdks/typescript/packages/client/src/transform/http.ts @@ -1,10 +1,11 @@ -import { BaseEvent, EventSchemas } from "@ag-ui/core"; +import type { BaseEvent } from "@ag-ui/core"; +import { EventType } from "@ag-ui/core"; +import { EventSchemas } from "@ag-ui/core/schemas"; import { Subject, ReplaySubject, Observable } from "rxjs"; import { HttpEvent, HttpEventType } from "../run/http-request"; import { parseSSEStream } from "./sse"; import { parseProtoStream } from "./proto"; import * as proto from "@ag-ui/proto"; -import { EventType } from "@ag-ui/core"; import { type DebugLoggerInput, resolveDebugLogger } from "@/debug-logger"; /** diff --git a/sdks/typescript/packages/client/vitest.config.ts b/sdks/typescript/packages/client/vitest.config.ts index 3e63f2f81c..49a2adcdb3 100644 --- a/sdks/typescript/packages/client/vitest.config.ts +++ b/sdks/typescript/packages/client/vitest.config.ts @@ -8,6 +8,7 @@ export default mergeConfig( resolve: { alias: { "@/": path.resolve(__dirname, "./src") + "/", + "@ag-ui/core/schemas": path.resolve(__dirname, "../core/src/schemas.ts"), "@ag-ui/core": path.resolve(__dirname, "../core/src/index.ts"), "@ag-ui/proto": path.resolve(__dirname, "../proto/src/index.ts"), "@ag-ui/encoder": path.resolve(__dirname, "../encoder/src/index.ts"), diff --git a/sdks/typescript/packages/core/CHANGELOG.md b/sdks/typescript/packages/core/CHANGELOG.md new file mode 100644 index 0000000000..2e176a0514 --- /dev/null +++ b/sdks/typescript/packages/core/CHANGELOG.md @@ -0,0 +1,23 @@ +# @ag-ui/core CHANGELOG + +## 0.1.0 + +### BREAKING CHANGES + +- `zod` is no longer a runtime dependency of `@ag-ui/core`. The package's main entry now ships only TypeScript types, value-level constants (`EventType`), error classes (`AGUIError`, `AGUIConnectNotImplementedError`), and event factories. No `*Schema` exports. +- The zod schemas have moved to a new opt-in subpath: `@ag-ui/core/schemas`. zod is an optional peer dependency on the subpath, accepting `^3.24.0 || ^4.0.0` — install whichever major you prefer. +- The internal `BinaryInputContentSchema` runtime check moved from `superRefine` to `.refine()`. Boolean validation is unchanged; the precise error path (`["id"]`) is no longer reported. + +### Migration + +```ts +// Before +import { UserMessageSchema } from "@ag-ui/core"; + +// After +import { UserMessageSchema } from "@ag-ui/core/schemas"; +``` + +### Internal package changes + +- `@ag-ui/client` and `@ag-ui/proto` now declare `zod` as a regular dependency and import `EventSchemas` from `@ag-ui/core/schemas` to validate incoming events. No public API change for consumers of these packages. diff --git a/sdks/typescript/packages/core/README.md b/sdks/typescript/packages/core/README.md index 72ad4c66cc..d8a3dcac22 100644 --- a/sdks/typescript/packages/core/README.md +++ b/sdks/typescript/packages/core/README.md @@ -22,14 +22,15 @@ yarn add @ag-ui/core ## Quick example ```ts -import { EventSchemas, EventType } from "@ag-ui/core"; +import type { TextMessageContentEvent } from "@ag-ui/core"; +import { EventType } from "@ag-ui/core"; -// Validate an incoming event -EventSchemas.parse({ +// Construct a typed event +const event: TextMessageContentEvent = { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg_123", delta: "Hello, world!", -}); +}; ``` ## Documentation diff --git a/sdks/typescript/packages/core/package.json b/sdks/typescript/packages/core/package.json index 076c684604..011a6cdc74 100644 --- a/sdks/typescript/packages/core/package.json +++ b/sdks/typescript/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@ag-ui/core", "author": "Markus Ecker ", - "version": "0.0.53", + "version": "0.1.0", "private": false, "publishConfig": { "access": "public" @@ -21,8 +21,13 @@ "link:global": "pnpm link --global", "unlink:global": "pnpm unlink --global" }, - "dependencies": { - "zod": "^3.22.4" + "peerDependencies": { + "zod": "^3.24.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } }, "devDependencies": { "@vitest/coverage-istanbul": "^4.0.18", @@ -30,13 +35,27 @@ "@arethetypeswrong/cli": "^0.17.4", "vitest": "^4.0.18", "tsdown": "^0.20.1", - "typescript": "^5.8.2" + "typescript": "^5.8.2", + "zod": "^3.24.0 || ^4.0.0" + }, + "typesVersions": { + "*": { + "schemas": [ + "./dist/schemas.d.ts" + ] + } }, "exports": { ".": { + "types": "./dist/index.d.ts", "require": "./dist/index.js", "import": "./dist/index.mjs" }, + "./schemas": { + "types": "./dist/schemas.d.ts", + "require": "./dist/schemas.js", + "import": "./dist/schemas.mjs" + }, "./package.json": "./package.json" } } diff --git a/sdks/typescript/packages/core/src/__tests__/activity-events.test.ts b/sdks/typescript/packages/core/src/__tests__/activity-events.test.ts index 78b31d3a37..e0bb13d4d9 100644 --- a/sdks/typescript/packages/core/src/__tests__/activity-events.test.ts +++ b/sdks/typescript/packages/core/src/__tests__/activity-events.test.ts @@ -1,5 +1,5 @@ -import { ActivitySnapshotEventSchema, ActivityDeltaEventSchema, EventType } from "../events"; -import { ActivityMessageSchema } from "../types"; +import { EventType } from "../events"; +import { ActivitySnapshotEventSchema, ActivityDeltaEventSchema, ActivityMessageSchema } from "../schemas"; describe("Activity events", () => { it("parses ActivitySnapshotEvent", () => { diff --git a/sdks/typescript/packages/core/src/__tests__/backwards-compatibility.test.ts b/sdks/typescript/packages/core/src/__tests__/backwards-compatibility.test.ts index 34c47961f2..4904170ade 100644 --- a/sdks/typescript/packages/core/src/__tests__/backwards-compatibility.test.ts +++ b/sdks/typescript/packages/core/src/__tests__/backwards-compatibility.test.ts @@ -1,3 +1,4 @@ +import { EventType } from "../events"; import { UserMessageSchema, AssistantMessageSchema, @@ -6,8 +7,7 @@ import { RunStartedEventSchema, ToolSchema, ContextSchema, - EventType, -} from "../index"; +} from "../schemas"; describe("Backwards Compatibility", () => { describe("Message Schemas", () => { @@ -243,7 +243,7 @@ describe("Backwards Compatibility", () => { expect(result.success).toBe(true); if (result.success) { expect(result.data.messages.length).toBe(2); - expect(result.data.messages[1].toolCalls?.length).toBe(1); + expect((result.data.messages[1] as any).toolCalls?.length).toBe(1); expect(result.data.tools.length).toBe(1); expect(result.data.context.length).toBe(1); } diff --git a/sdks/typescript/packages/core/src/__tests__/capabilities-interrupts.test.ts b/sdks/typescript/packages/core/src/__tests__/capabilities-interrupts.test.ts index 621d09ac23..6a6fda1d0d 100644 --- a/sdks/typescript/packages/core/src/__tests__/capabilities-interrupts.test.ts +++ b/sdks/typescript/packages/core/src/__tests__/capabilities-interrupts.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { HumanInTheLoopCapabilitiesSchema } from "../capabilities"; +import { HumanInTheLoopCapabilitiesSchema } from "../schemas"; describe("HumanInTheLoopCapabilities — interrupt flags", () => { it("accepts interrupts: true", () => { diff --git a/sdks/typescript/packages/core/src/__tests__/events-role-defaults.test.ts b/sdks/typescript/packages/core/src/__tests__/events-role-defaults.test.ts index 279206c92f..185db73e3f 100644 --- a/sdks/typescript/packages/core/src/__tests__/events-role-defaults.test.ts +++ b/sdks/typescript/packages/core/src/__tests__/events-role-defaults.test.ts @@ -1,4 +1,5 @@ -import { TextMessageStartEventSchema, TextMessageChunkEventSchema, EventType } from "../events"; +import { EventType } from "../events"; +import { TextMessageStartEventSchema, TextMessageChunkEventSchema } from "../schemas"; describe("Event role defaults", () => { it("should default TextMessageStartEvent role to 'assistant' when not provided", () => { @@ -9,7 +10,7 @@ describe("Event role defaults", () => { }; const parsed = TextMessageStartEventSchema.parse(eventData); - + expect(parsed.type).toBe(EventType.TEXT_MESSAGE_START); expect(parsed.messageId).toBe("test-msg"); expect(parsed.role).toBe("assistant"); // Should default to assistant @@ -23,7 +24,7 @@ describe("Event role defaults", () => { }; const parsed = TextMessageStartEventSchema.parse(eventData); - + expect(parsed.type).toBe(EventType.TEXT_MESSAGE_START); expect(parsed.messageId).toBe("test-msg"); expect(parsed.role).toBe("user"); // Should use provided role @@ -31,7 +32,7 @@ describe("Event role defaults", () => { it("should accept all valid text message roles in TextMessageStartEvent", () => { const textMessageRoles = ["developer", "system", "assistant", "user"]; - + textMessageRoles.forEach(role => { const eventData = { type: EventType.TEXT_MESSAGE_START, diff --git a/sdks/typescript/packages/core/src/__tests__/interrupts.test.ts b/sdks/typescript/packages/core/src/__tests__/interrupts.test.ts index 3122a2a123..2c98bfaace 100644 --- a/sdks/typescript/packages/core/src/__tests__/interrupts.test.ts +++ b/sdks/typescript/packages/core/src/__tests__/interrupts.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { InterruptSchema, ResumeEntrySchema, RunAgentInputSchema } from "../types"; +import { InterruptSchema, ResumeEntrySchema, RunAgentInputSchema } from "../schemas"; describe("InterruptSchema", () => { it("accepts an interrupt with only required fields", () => { diff --git a/sdks/typescript/packages/core/src/__tests__/multimodal-messages.test.ts b/sdks/typescript/packages/core/src/__tests__/multimodal-messages.test.ts index 213caa5a66..d32436bd89 100644 --- a/sdks/typescript/packages/core/src/__tests__/multimodal-messages.test.ts +++ b/sdks/typescript/packages/core/src/__tests__/multimodal-messages.test.ts @@ -8,7 +8,7 @@ import { InputContentDataSourceSchema, InputContentUrlSourceSchema, BinaryInputContentSchema, -} from "../types"; +} from "../schemas"; const MODALITIES = ["image", "audio", "video", "document"] as const; diff --git a/sdks/typescript/packages/core/src/__tests__/run-finished-event.test.ts b/sdks/typescript/packages/core/src/__tests__/run-finished-event.test.ts index 558ad2dd69..092ad018c7 100644 --- a/sdks/typescript/packages/core/src/__tests__/run-finished-event.test.ts +++ b/sdks/typescript/packages/core/src/__tests__/run-finished-event.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from "vitest"; +import { EventType } from "../events"; import { EventSchemas, - EventType, RunFinishedEventSchema, RunFinishedOutcomeSchema, -} from "../events"; +} from "../schemas"; describe("RunFinishedEventSchema — outcome is optional and back-compat", () => { it("parses a legacy event with no outcome", () => { diff --git a/sdks/typescript/packages/core/src/__tests__/schema-type-equality.test.ts b/sdks/typescript/packages/core/src/__tests__/schema-type-equality.test.ts new file mode 100644 index 0000000000..19dea93158 --- /dev/null +++ b/sdks/typescript/packages/core/src/__tests__/schema-type-equality.test.ts @@ -0,0 +1,491 @@ +/** + * Drift-prevention tests: assert that z.infer (from + * @ag-ui/core/schemas) exactly matches the hand-written types (from the + * main @ag-ui/core entry). Any divergence between a schema and its + * corresponding type will surface here as a compile error. + * + * Assertion strategy: + * - Most types use `toEqualTypeOf` for strict bidirectional identity. + * + * - Event schemas extend `BaseEventSchema.passthrough()` which adds an index + * signature `[x: string]: unknown` to every inferred event type. This is + * intentional forward-compat behavior (unknown wire fields are preserved). + * The inferred type is a SUPERSET of the hand-written event type, so strict + * identity fails. We use `toExtend()` on pre-evaluated + * type aliases — TypeScript must evaluate the complex generic before the + * constraint check, which is why the aliases are required. + * + * - `StateSnapshotEvent.snapshot`, `RawEvent.event`, `CustomEvent.value` use + * `z.any()` — same addQuestionMarks optionality quirk — so those event + * tests use the reverse `AsObject.toExtend<_ISchema>()` form. + * The hand-written types for Tool, RunAgentInput, ToolsCapabilities, and + * AgentCapabilities now declare those z.any() fields as optional, matching + * the schema inference exactly, so they use strict `toEqualTypeOf`. + */ +import { describe, it, expectTypeOf } from "vitest"; +import { z } from "zod"; + +// -------------------------------------------------------------------------- +// Schema imports (from the schemas subpath module) +// -------------------------------------------------------------------------- +import type { + ToolCallSchema, + FunctionCallSchema, + MessageSchema, + AssistantMessageSchema, + UserMessageSchema, + ToolMessageSchema, + ActivityMessageSchema, + ReasoningMessageSchema, + DeveloperMessageSchema, + SystemMessageSchema, + ToolSchema, + ContextSchema, + InterruptSchema, + ResumeEntrySchema, + RunAgentInputSchema, + RoleSchema, + InputContentSchema, + BinaryInputContentSchema, + // Event schemas + BaseEventSchema, + TextMessageStartEventSchema, + TextMessageContentEventSchema, + TextMessageEndEventSchema, + TextMessageChunkEventSchema, + ToolCallStartEventSchema, + ToolCallArgsEventSchema, + ToolCallEndEventSchema, + ToolCallChunkEventSchema, + ToolCallResultEventSchema, + StateSnapshotEventSchema, + StateDeltaEventSchema, + MessagesSnapshotEventSchema, + ActivitySnapshotEventSchema, + ActivityDeltaEventSchema, + RawEventSchema, + CustomEventSchema, + RunStartedEventSchema, + RunFinishedEventSchema, + RunFinishedOutcomeSchema, + RunFinishedSuccessOutcomeSchema, + RunFinishedInterruptOutcomeSchema, + RunErrorEventSchema, + StepStartedEventSchema, + StepFinishedEventSchema, + ReasoningStartEventSchema, + ReasoningMessageStartEventSchema, + ReasoningMessageContentEventSchema, + ReasoningMessageEndEventSchema, + ReasoningMessageChunkEventSchema, + ReasoningEndEventSchema, + ReasoningEncryptedValueEventSchema, + ThinkingStartEventSchema, + ThinkingEndEventSchema, + ThinkingTextMessageStartEventSchema, + ThinkingTextMessageContentEventSchema, + ThinkingTextMessageEndEventSchema, + // Capability schemas + SubAgentInfoSchema, + IdentityCapabilitiesSchema, + TransportCapabilitiesSchema, + ToolsCapabilitiesSchema, + OutputCapabilitiesSchema, + StateCapabilitiesSchema, + MultiAgentCapabilitiesSchema, + ReasoningCapabilitiesSchema, + MultimodalInputCapabilitiesSchema, + MultimodalOutputCapabilitiesSchema, + MultimodalCapabilitiesSchema, + ExecutionCapabilitiesSchema, + HumanInTheLoopCapabilitiesSchema, + AgentCapabilitiesSchema, +} from "../schemas"; + +// -------------------------------------------------------------------------- +// Type imports (from the canonical type modules) +// -------------------------------------------------------------------------- +import type { + ToolCall, + FunctionCall, + Message, + AssistantMessage, + UserMessage, + ToolMessage, + ActivityMessage, + ReasoningMessage, + DeveloperMessage, + SystemMessage, + Tool, + Context, + Interrupt, + ResumeEntry, + RunAgentInput, + Role, + InputContent, + BinaryInputContent, +} from "../types"; + +import type { + BaseEvent, + TextMessageStartEvent, + TextMessageContentEvent, + TextMessageEndEvent, + TextMessageChunkEvent, + ToolCallStartEvent, + ToolCallArgsEvent, + ToolCallEndEvent, + ToolCallChunkEvent, + ToolCallResultEvent, + StateSnapshotEvent, + StateDeltaEvent, + MessagesSnapshotEvent, + ActivitySnapshotEvent, + ActivityDeltaEvent, + RawEvent, + CustomEvent, + RunStartedEvent, + RunFinishedEvent, + RunFinishedOutcome, + RunFinishedSuccessOutcome, + RunFinishedInterruptOutcome, + RunErrorEvent, + StepStartedEvent, + StepFinishedEvent, + ReasoningStartEvent, + ReasoningMessageStartEvent, + ReasoningMessageContentEvent, + ReasoningMessageEndEvent, + ReasoningMessageChunkEvent, + ReasoningEndEvent, + ReasoningEncryptedValueEvent, + ThinkingStartEvent, + ThinkingEndEvent, + ThinkingTextMessageStartEvent, + ThinkingTextMessageContentEvent, + ThinkingTextMessageEndEvent, +} from "../events"; + +import type { + SubAgentInfo, + IdentityCapabilities, + TransportCapabilities, + ToolsCapabilities, + OutputCapabilities, + StateCapabilities, + MultiAgentCapabilities, + ReasoningCapabilities, + MultimodalInputCapabilities, + MultimodalOutputCapabilities, + MultimodalCapabilities, + ExecutionCapabilities, + HumanInTheLoopCapabilities, + AgentCapabilities, +} from "../capabilities"; + +// -------------------------------------------------------------------------- +// Helper: converts an interface to a mapped object type. +// +// TypeScript interfaces with no index signature cannot directly satisfy a type +// with `[x: string]: unknown` (which passthrough event schemas produce). +// Mapping through `{ [K in keyof T]: T[K] }` lifts the interface restriction +// so that the resulting object type CAN be used in `toExtend<_ISchema>()` calls. +// -------------------------------------------------------------------------- +type AsObject = { [K in keyof T]: T[K] }; + +// -------------------------------------------------------------------------- +// Pre-evaluated type aliases for event schema inferences. +// +// `toExtend>()` fails because TypeScript does not +// fully evaluate the complex generic `objectOutputType<..., "passthrough">` +// inside the constraint check. Pre-defining aliases forces evaluation first. +// -------------------------------------------------------------------------- +type _IBaseEvent = z.infer; +type _ITextMessageStartEvent = z.infer; +type _ITextMessageContentEvent = z.infer; +type _ITextMessageEndEvent = z.infer; +type _ITextMessageChunkEvent = z.infer; +type _IToolCallStartEvent = z.infer; +type _IToolCallArgsEvent = z.infer; +type _IToolCallEndEvent = z.infer; +type _IToolCallChunkEvent = z.infer; +type _IToolCallResultEvent = z.infer; +type _IStateSnapshotEvent = z.infer; +type _IStateDeltaEvent = z.infer; +type _IMessagesSnapshotEvent = z.infer; +type _IActivitySnapshotEvent = z.infer; +type _IActivityDeltaEvent = z.infer; +type _IRawEvent = z.infer; +type _ICustomEvent = z.infer; +type _IRunStartedEvent = z.infer; +type _IRunFinishedEvent = z.infer; +type _IRunErrorEvent = z.infer; +type _IStepStartedEvent = z.infer; +type _IStepFinishedEvent = z.infer; +type _IReasoningStartEvent = z.infer; +type _IReasoningMessageStartEvent = z.infer; +type _IReasoningMessageContentEvent = z.infer; +type _IReasoningMessageEndEvent = z.infer; +type _IReasoningMessageChunkEvent = z.infer; +type _IReasoningEndEvent = z.infer; +type _IReasoningEncryptedValueEvent = z.infer; +type _IThinkingStartEvent = z.infer; +type _IThinkingEndEvent = z.infer; +type _IThinkingTextMessageStartEvent = z.infer; +type _IThinkingTextMessageContentEvent = z.infer; +type _IThinkingTextMessageEndEvent = z.infer; + +// ========================================================================== +// Types tests +// ========================================================================== + +describe("schema inferred types match hand-written types (types.ts)", () => { + it("ToolCall", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("FunctionCall", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("Message", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("AssistantMessage", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("UserMessage", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("ToolMessage", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("ActivityMessage", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("ReasoningMessage", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("DeveloperMessage", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("SystemMessage", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("Tool", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("Context", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("Interrupt", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("ResumeEntry", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("RunAgentInput", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("Role", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("InputContent", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("BinaryInputContent", () => { + expectTypeOf>().toEqualTypeOf(); + }); +}); + +// ========================================================================== +// Event tests +// ========================================================================== + +describe("schema inferred types match hand-written types (events.ts)", () => { + // All event schemas extend BaseEventSchema.passthrough(), which adds + // [x: string]: unknown to z.infer<...>. This is intentional (forward-compat). + // Strict equality fails; we verify the schema-inferred type extends the + // hand-written type (the schema accepts everything the hand-written type + // represents, plus possibly extra wire fields). + // Type aliases are required — see module-level comment above. + it("BaseEvent", () => { + // BaseEventSchema uses z.enum([...]) with plain string literals for `type`, + // which infers as `"TEXT_MESSAGE_START" | ...` (string literal union). The + // hand-written BaseEvent has `type: EventType` (a TypeScript string enum). + // String literals do NOT extend enum types in TypeScript's type system, so + // the forward check `_IBaseEvent extends BaseEvent` fails. + // + // Enum members DO extend their corresponding string literals, so the reverse + // `BaseEvent extends _IBaseEvent` would pass for `type` — but TypeScript + // interfaces cannot satisfy index-signature types (`[x: string]: unknown`). + // `AsObject` converts the interface to a mapped type, removing that + // restriction. + expectTypeOf>().toExtend<_IBaseEvent>(); + }); + it("TextMessageStartEvent", () => { + expectTypeOf<_ITextMessageStartEvent>().toExtend(); + }); + it("TextMessageContentEvent", () => { + expectTypeOf<_ITextMessageContentEvent>().toExtend(); + }); + it("TextMessageEndEvent", () => { + expectTypeOf<_ITextMessageEndEvent>().toExtend(); + }); + it("TextMessageChunkEvent", () => { + expectTypeOf<_ITextMessageChunkEvent>().toExtend(); + }); + it("ToolCallStartEvent", () => { + expectTypeOf<_IToolCallStartEvent>().toExtend(); + }); + it("ToolCallArgsEvent", () => { + expectTypeOf<_IToolCallArgsEvent>().toExtend(); + }); + it("ToolCallEndEvent", () => { + expectTypeOf<_IToolCallEndEvent>().toExtend(); + }); + it("ToolCallChunkEvent", () => { + expectTypeOf<_IToolCallChunkEvent>().toExtend(); + }); + it("ToolCallResultEvent", () => { + expectTypeOf<_IToolCallResultEvent>().toExtend(); + }); + it("StateSnapshotEvent", () => { + // `snapshot: StateSchema` where `StateSchema = z.any()`. Zod 3's + // addQuestionMarks makes required `z.any()` fields optional in the inferred + // type (`snapshot?: any`). The hand-written type has `snapshot: any` + // (required). Required satisfies optional, so the reverse check passes. + expectTypeOf>().toExtend<_IStateSnapshotEvent>(); + }); + it("StateDeltaEvent", () => { + expectTypeOf<_IStateDeltaEvent>().toExtend(); + }); + it("MessagesSnapshotEvent", () => { + expectTypeOf<_IMessagesSnapshotEvent>().toExtend(); + }); + it("ActivitySnapshotEvent", () => { + expectTypeOf<_IActivitySnapshotEvent>().toExtend(); + }); + it("ActivityDeltaEvent", () => { + expectTypeOf<_IActivityDeltaEvent>().toExtend(); + }); + it("RawEvent", () => { + // `event: z.any()` — same addQuestionMarks quirk as StateSnapshotEvent above. + expectTypeOf>().toExtend<_IRawEvent>(); + }); + it("CustomEvent", () => { + // `value: z.any()` — same addQuestionMarks quirk as StateSnapshotEvent above. + expectTypeOf>().toExtend<_ICustomEvent>(); + }); + it("RunStartedEvent", () => { + // `input?: RunAgentInputSchema` — RunAgentInput has `state: any` and + // `forwardedProps: any` which become optional in the inferred type (same + // addQuestionMarks quirk). Reverse direction handles this. + expectTypeOf>().toExtend<_IRunStartedEvent>(); + }); + it("RunFinishedEvent", () => { + expectTypeOf<_IRunFinishedEvent>().toExtend(); + }); + it("RunFinishedOutcome", () => { + // Outcome schemas use .strict() without passthrough; strict equality holds. + expectTypeOf>().toEqualTypeOf(); + }); + it("RunFinishedSuccessOutcome", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("RunFinishedInterruptOutcome", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("RunErrorEvent", () => { + expectTypeOf<_IRunErrorEvent>().toExtend(); + }); + it("StepStartedEvent", () => { + expectTypeOf<_IStepStartedEvent>().toExtend(); + }); + it("StepFinishedEvent", () => { + expectTypeOf<_IStepFinishedEvent>().toExtend(); + }); + it("ReasoningStartEvent", () => { + expectTypeOf<_IReasoningStartEvent>().toExtend(); + }); + it("ReasoningMessageStartEvent", () => { + expectTypeOf<_IReasoningMessageStartEvent>().toExtend(); + }); + it("ReasoningMessageContentEvent", () => { + expectTypeOf<_IReasoningMessageContentEvent>().toExtend(); + }); + it("ReasoningMessageEndEvent", () => { + expectTypeOf<_IReasoningMessageEndEvent>().toExtend(); + }); + it("ReasoningMessageChunkEvent", () => { + expectTypeOf<_IReasoningMessageChunkEvent>().toExtend(); + }); + it("ReasoningEndEvent", () => { + expectTypeOf<_IReasoningEndEvent>().toExtend(); + }); + it("ReasoningEncryptedValueEvent", () => { + expectTypeOf<_IReasoningEncryptedValueEvent>().toExtend(); + }); + it("ThinkingStartEvent", () => { + expectTypeOf<_IThinkingStartEvent>().toExtend(); + }); + it("ThinkingEndEvent", () => { + expectTypeOf<_IThinkingEndEvent>().toExtend(); + }); + it("ThinkingTextMessageStartEvent", () => { + expectTypeOf<_IThinkingTextMessageStartEvent>().toExtend(); + }); + it("ThinkingTextMessageContentEvent", () => { + expectTypeOf<_IThinkingTextMessageContentEvent>().toExtend(); + }); + it("ThinkingTextMessageEndEvent", () => { + expectTypeOf<_IThinkingTextMessageEndEvent>().toExtend(); + }); +}); + +// ========================================================================== +// Capability tests +// ========================================================================== + +describe("schema inferred types match hand-written types (capabilities.ts)", () => { + it("SubAgentInfo", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("IdentityCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("TransportCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("ToolsCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("OutputCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("StateCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("MultiAgentCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("ReasoningCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("MultimodalInputCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("MultimodalOutputCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("MultimodalCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("ExecutionCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("HumanInTheLoopCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); + it("AgentCapabilities", () => { + expectTypeOf>().toEqualTypeOf(); + }); +}); diff --git a/sdks/typescript/packages/core/src/capabilities.ts b/sdks/typescript/packages/core/src/capabilities.ts index 6ad1d6dbe2..16ad90532e 100644 --- a/sdks/typescript/packages/core/src/capabilities.ts +++ b/sdks/typescript/packages/core/src/capabilities.ts @@ -1,210 +1,212 @@ -import { z } from "zod"; -import { ToolSchema } from "./types"; +// Canonical agent capability declarations. This is the authoritative +// capabilities-surface module of @ag-ui/core. + +import type { Tool } from "./types"; /** Describes a sub-agent that can be invoked by a parent agent. */ -export const SubAgentInfoSchema = z.object({ +export interface SubAgentInfo { /** Unique name or identifier of the sub-agent. */ - name: z.string(), + name: string; /** What this sub-agent specializes in. Helps clients build agent selection UIs. */ - description: z.string().optional(), -}); + description?: string; +} /** * Basic metadata about the agent. Useful for discovery UIs, agent marketplaces, * and debugging. Set these when you want clients to display agent information * or when multiple agents are available and users need to pick one. */ -export const IdentityCapabilitiesSchema = z.object({ +export interface IdentityCapabilities { /** Human-readable name shown in UIs and agent selectors. */ - name: z.string().optional(), + name?: string; /** The framework or platform powering this agent (e.g., "langgraph", "mastra", "crewai"). */ - type: z.string().optional(), + type?: string; /** What this agent does — helps users and routing logic decide when to use it. */ - description: z.string().optional(), + description?: string; /** Semantic version of the agent (e.g., "1.2.0"). Useful for compatibility checks. */ - version: z.string().optional(), + version?: string; /** Organization or team that maintains this agent. */ - provider: z.string().optional(), + provider?: string; /** URL to the agent's documentation or homepage. */ - documentationUrl: z.string().optional(), + documentationUrl?: string; /** Arbitrary key-value pairs for integration-specific identity info. */ - metadata: z.record(z.unknown()).optional(), -}); + metadata?: Record; +} /** * Declares which transport mechanisms the agent supports. Clients use this * to pick the best connection strategy. Only set flags to `true` for transports * your agent actually handles — omit or set `false` for unsupported ones. */ -export const TransportCapabilitiesSchema = z.object({ +export interface TransportCapabilities { /** Set `true` if the agent streams responses via SSE. Most agents enable this. */ - streaming: z.boolean().optional(), + streaming?: boolean; /** Set `true` if the agent accepts persistent WebSocket connections. */ - websocket: z.boolean().optional(), + websocket?: boolean; /** Set `true` if the agent supports the AG-UI binary protocol (protobuf over HTTP). */ - httpBinary: z.boolean().optional(), + httpBinary?: boolean; /** Set `true` if the agent can send async updates via webhooks after a run finishes. */ - pushNotifications: z.boolean().optional(), + pushNotifications?: boolean; /** Set `true` if the agent supports resuming interrupted streams via sequence numbers. */ - resumable: z.boolean().optional(), -}); + resumable?: boolean; +} /** * Tool calling capabilities. Distinguishes between tools the agent itself provides * (listed in `items`) and tools the client passes at runtime via `RunAgentInput.tools`. * Enable this when your agent can call functions, search the web, execute code, etc. */ -export const ToolsCapabilitiesSchema = z.object({ +export interface ToolsCapabilities { /** Set `true` if the agent can make tool calls at all. Set `false` to explicitly * signal tool calling is disabled even if items are present. */ - supported: z.boolean().optional(), + supported?: boolean; /** The tools this agent provides on its own (full JSON Schema definitions). * These are distinct from client-provided tools passed in `RunAgentInput.tools`. */ - items: z.array(ToolSchema).optional(), + items?: Tool[]; /** Set `true` if the agent can invoke multiple tools concurrently within a single step. */ - parallelCalls: z.boolean().optional(), + parallelCalls?: boolean; /** Set `true` if the agent accepts and uses tools provided by the client at runtime. */ - clientProvided: z.boolean().optional(), -}); + clientProvided?: boolean; +} /** * Output format support. Enable `structuredOutput` when your agent can return * responses conforming to a JSON schema, which is useful for programmatic consumption. */ -export const OutputCapabilitiesSchema = z.object({ +export interface OutputCapabilities { /** Set `true` if the agent can produce structured JSON output matching a provided schema. */ - structuredOutput: z.boolean().optional(), + structuredOutput?: boolean; /** MIME types the agent can produce (e.g., `["text/plain", "application/json"]`). * Omit if the agent only produces plain text. */ - supportedMimeTypes: z.array(z.string()).optional(), -}); + supportedMimeTypes?: string[]; +} /** * State and memory management capabilities. These tell the client how the agent * handles shared state and whether conversation context persists across runs. */ -export const StateCapabilitiesSchema = z.object({ +export interface StateCapabilities { /** Set `true` if the agent emits `STATE_SNAPSHOT` events (full state replacement). */ - snapshots: z.boolean().optional(), + snapshots?: boolean; /** Set `true` if the agent emits `STATE_DELTA` events (JSON Patch incremental updates). */ - deltas: z.boolean().optional(), + deltas?: boolean; /** Set `true` if the agent has long-term memory beyond the current thread * (e.g., vector store, knowledge base, or cross-session recall). */ - memory: z.boolean().optional(), + memory?: boolean; /** Set `true` if state is preserved across multiple runs within the same thread. * When `false`, state resets on each run. */ - persistentState: z.boolean().optional(), -}); + persistentState?: boolean; +} /** * Multi-agent coordination capabilities. Enable these when your agent can * orchestrate or hand off work to other agents. */ -export const MultiAgentCapabilitiesSchema = z.object({ +export interface MultiAgentCapabilities { /** Set `true` if the agent participates in any form of multi-agent coordination. */ - supported: z.boolean().optional(), + supported?: boolean; /** Set `true` if the agent can delegate subtasks to other agents while retaining control. */ - delegation: z.boolean().optional(), + delegation?: boolean; /** Set `true` if the agent can transfer the conversation entirely to another agent. */ - handoffs: z.boolean().optional(), + handoffs?: boolean; /** List of sub-agents this agent can invoke. Helps clients build agent selection UIs. */ - subAgents: z.array(SubAgentInfoSchema).optional(), -}); + subAgents?: SubAgentInfo[]; +} /** * Reasoning and thinking capabilities. Enable these when your agent exposes its * internal thought process (e.g., chain-of-thought, extended thinking). */ -export const ReasoningCapabilitiesSchema = z.object({ +export interface ReasoningCapabilities { /** Set `true` if the agent produces reasoning/thinking tokens visible to the client. */ - supported: z.boolean().optional(), + supported?: boolean; /** Set `true` if reasoning tokens are streamed incrementally (vs. returned all at once). */ - streaming: z.boolean().optional(), + streaming?: boolean; /** Set `true` if reasoning content is encrypted (zero-data-retention mode). * Clients should expect opaque `encryptedValue` fields instead of readable content. */ - encrypted: z.boolean().optional(), -}); + encrypted?: boolean; +} /** * Modalities the agent can accept as input. Clients use this to show/hide * file upload buttons, audio recorders, image pickers, etc. */ -export const MultimodalInputCapabilitiesSchema = z.object({ +export interface MultimodalInputCapabilities { /** Set `true` if the agent can process image inputs (e.g., screenshots, photos). */ - image: z.boolean().optional(), + image?: boolean; /** Set `true` if the agent can process audio inputs (speech, recordings). */ - audio: z.boolean().optional(), + audio?: boolean; /** Set `true` if the agent can process video inputs. */ - video: z.boolean().optional(), + video?: boolean; /** Set `true` if the agent can process PDF documents. */ - pdf: z.boolean().optional(), + pdf?: boolean; /** Set `true` if the agent can process arbitrary file uploads. */ - file: z.boolean().optional(), -}); + file?: boolean; +} /** * Modalities the agent can produce as output. Clients use this to anticipate * rich content in the agent's response. */ -export const MultimodalOutputCapabilitiesSchema = z.object({ +export interface MultimodalOutputCapabilities { /** Set `true` if the agent can generate images as part of its response. */ - image: z.boolean().optional(), + image?: boolean; /** Set `true` if the agent can produce audio output (text-to-speech, audio files). */ - audio: z.boolean().optional(), -}); + audio?: boolean; +} /** * Multimodal input and output support. Organized into `input` and `output` * sub-objects so clients can independently query what the agent accepts * versus what it produces. */ -export const MultimodalCapabilitiesSchema = z.object({ +export interface MultimodalCapabilities { /** Modalities the agent can accept as input (images, audio, video, PDFs, files). */ - input: MultimodalInputCapabilitiesSchema.optional(), + input?: MultimodalInputCapabilities; /** Modalities the agent can produce as output (images, audio). */ - output: MultimodalOutputCapabilitiesSchema.optional(), -}); + output?: MultimodalOutputCapabilities; +} /** * Execution control and limits. Declare these so clients can set expectations * about how long or how many steps an agent run might take. */ -export const ExecutionCapabilitiesSchema = z.object({ +export interface ExecutionCapabilities { /** Set `true` if the agent can execute code (e.g., Python, JavaScript) during a run. */ - codeExecution: z.boolean().optional(), + codeExecution?: boolean; /** Set `true` if code execution happens in a sandboxed/isolated environment. * Only meaningful when `codeExecution` is `true`. */ - sandboxed: z.boolean().optional(), + sandboxed?: boolean; /** Maximum number of tool-call/reasoning iterations the agent will perform per run. * Helps clients display progress or set timeout expectations. */ - maxIterations: z.number().optional(), + maxIterations?: number; /** Maximum wall-clock time (in milliseconds) the agent will run before timing out. */ - maxExecutionTime: z.number().optional(), -}); + maxExecutionTime?: number; +} /** * Human-in-the-loop interaction support. Enable these when your agent can pause * execution to request human input, approval, or feedback before continuing. */ -export const HumanInTheLoopCapabilitiesSchema = z.object({ +export interface HumanInTheLoopCapabilities { /** Set `true` if the agent supports any form of human-in-the-loop interaction. */ - supported: z.boolean().optional(), + supported?: boolean; /** Set `true` if the agent can pause and request explicit approval before * performing sensitive actions (e.g., sending emails, deleting data). */ - approvals: z.boolean().optional(), + approvals?: boolean; /** Set `true` if the agent allows humans to intervene and modify its plan mid-execution. */ - interventions: z.boolean().optional(), + interventions?: boolean; /** Set `true` if the agent can incorporate user feedback (thumbs up/down, corrections) * to improve its behavior within the current session. */ - feedback: z.boolean().optional(), + feedback?: boolean; /** Set `true` if the agent participates in the AG-UI interrupt protocol * (emits RUN_FINISHED with outcome={ type: "interrupt", interrupts: [...] }, * accepts resume[]). */ - interrupts: z.boolean().optional(), + interrupts?: boolean; /** Set `true` if tool-call interrupts accept editedArgs in the resume payload. * Only meaningful when interrupts is true. */ - approveWithEdits: z.boolean().optional(), -}); + approveWithEdits?: boolean; +} /** * A typed, categorized snapshot of an agent's current capabilities. @@ -217,56 +219,27 @@ export const HumanInTheLoopCapabilitiesSchema = z.object({ * The `custom` field is an escape hatch for integration-specific capabilities * that don't fit into the standard categories. */ -export const AgentCapabilitiesSchema = z.object({ +export interface AgentCapabilities { /** Agent identity and metadata. */ - identity: IdentityCapabilitiesSchema.optional(), + identity?: IdentityCapabilities; /** Supported transport mechanisms (SSE, WebSocket, binary, etc.). */ - transport: TransportCapabilitiesSchema.optional(), + transport?: TransportCapabilities; /** Tools the agent provides and tool calling configuration. */ - tools: ToolsCapabilitiesSchema.optional(), + tools?: ToolsCapabilities; /** Output format support (structured output, MIME types). */ - output: OutputCapabilitiesSchema.optional(), + output?: OutputCapabilities; /** State and memory management (snapshots, deltas, persistence). */ - state: StateCapabilitiesSchema.optional(), + state?: StateCapabilities; /** Multi-agent coordination (delegation, handoffs, sub-agents). */ - multiAgent: MultiAgentCapabilitiesSchema.optional(), + multiAgent?: MultiAgentCapabilities; /** Reasoning and thinking support (chain-of-thought, encrypted thinking). */ - reasoning: ReasoningCapabilitiesSchema.optional(), + reasoning?: ReasoningCapabilities; /** Multimodal input/output support (images, audio, video, files). */ - multimodal: MultimodalCapabilitiesSchema.optional(), + multimodal?: MultimodalCapabilities; /** Execution control and limits (code execution, timeouts, iteration caps). */ - execution: ExecutionCapabilitiesSchema.optional(), + execution?: ExecutionCapabilities; /** Human-in-the-loop support (approvals, interventions, feedback). */ - humanInTheLoop: HumanInTheLoopCapabilitiesSchema.optional(), + humanInTheLoop?: HumanInTheLoopCapabilities; /** Integration-specific capabilities not covered by the standard categories. */ - custom: z.record(z.unknown()).optional(), -}); - -/** Describes a sub-agent that can be invoked by a parent agent. */ -export type SubAgentInfo = z.infer; -/** Agent identity and metadata for discovery UIs, marketplaces, and debugging. */ -export type IdentityCapabilities = z.infer; -/** Supported transport mechanisms (SSE, WebSocket, binary protocol, push notifications). */ -export type TransportCapabilities = z.infer; -/** Tool calling support and agent-provided tool definitions. */ -export type ToolsCapabilities = z.infer; -/** Output format support (structured output, MIME types). */ -export type OutputCapabilities = z.infer; -/** State and memory management (snapshots, deltas, persistence, long-term memory). */ -export type StateCapabilities = z.infer; -/** Multi-agent coordination (delegation, handoffs, sub-agent orchestration). */ -export type MultiAgentCapabilities = z.infer; -/** Reasoning and thinking visibility (streaming, encrypted chain-of-thought). */ -export type ReasoningCapabilities = z.infer; -/** Modalities the agent can accept as input (images, audio, video, PDFs, files). */ -export type MultimodalInputCapabilities = z.infer; -/** Modalities the agent can produce as output (images, audio). */ -export type MultimodalOutputCapabilities = z.infer; -/** Multimodal input/output support (images, audio, video, PDFs, file uploads). */ -export type MultimodalCapabilities = z.infer; -/** Execution control and limits (code execution, sandboxing, iteration caps, timeouts). */ -export type ExecutionCapabilities = z.infer; -/** Human-in-the-loop interaction support (approvals, interventions, feedback). */ -export type HumanInTheLoopCapabilities = z.infer; -/** A typed, categorized snapshot of an agent's current capabilities. Returned by `getCapabilities()`. */ -export type AgentCapabilities = z.infer; + custom?: Record; +} diff --git a/sdks/typescript/packages/core/src/event-factories.ts b/sdks/typescript/packages/core/src/event-factories.ts index d8b8f163d8..fccd5aac77 100644 --- a/sdks/typescript/packages/core/src/event-factories.ts +++ b/sdks/typescript/packages/core/src/event-factories.ts @@ -1,367 +1,323 @@ -import { z } from "zod"; -import type { Interrupt } from "./types"; -import { +import { EventType } from "./events"; +import { AGUIError } from "./types"; +import type { ActivityDeltaEvent, ActivityDeltaEventProps, - ActivityDeltaEventSchema, ActivitySnapshotEvent, ActivitySnapshotEventProps, - ActivitySnapshotEventSchema, CustomEvent, CustomEventProps, - CustomEventSchema, - EventType, MessagesSnapshotEvent, MessagesSnapshotEventProps, - MessagesSnapshotEventSchema, RawEvent, RawEventProps, - RawEventSchema, RunErrorEvent, RunErrorEventProps, - RunErrorEventSchema, RunFinishedEvent, RunFinishedEventProps, - RunFinishedEventSchema, RunStartedEvent, RunStartedEventProps, - RunStartedEventSchema, StateDeltaEvent, StateDeltaEventProps, - StateDeltaEventSchema, StateSnapshotEvent, StateSnapshotEventProps, - StateSnapshotEventSchema, StepFinishedEvent, StepFinishedEventProps, - StepFinishedEventSchema, StepStartedEvent, StepStartedEventProps, - StepStartedEventSchema, TextMessageChunkEvent, TextMessageChunkEventProps, - TextMessageChunkEventSchema, TextMessageContentEvent, TextMessageContentEventProps, - TextMessageContentEventSchema, TextMessageEndEvent, TextMessageEndEventProps, - TextMessageEndEventSchema, TextMessageStartEvent, TextMessageStartEventProps, - TextMessageStartEventSchema, ThinkingEndEvent, ThinkingEndEventProps, - ThinkingEndEventSchema, ThinkingStartEvent, ThinkingStartEventProps, - ThinkingStartEventSchema, ThinkingTextMessageContentEvent, ThinkingTextMessageContentEventProps, - ThinkingTextMessageContentEventSchema, ThinkingTextMessageEndEvent, ThinkingTextMessageEndEventProps, - ThinkingTextMessageEndEventSchema, ThinkingTextMessageStartEvent, ThinkingTextMessageStartEventProps, - ThinkingTextMessageStartEventSchema, ToolCallArgsEvent, ToolCallArgsEventProps, - ToolCallArgsEventSchema, ToolCallChunkEvent, ToolCallChunkEventProps, - ToolCallChunkEventSchema, ToolCallEndEvent, ToolCallEndEventProps, - ToolCallEndEventSchema, ToolCallResultEvent, ToolCallResultEventProps, - ToolCallResultEventSchema, ToolCallStartEvent, ToolCallStartEventProps, - ToolCallStartEventSchema, ReasoningStartEvent, ReasoningStartEventProps, - ReasoningStartEventSchema, ReasoningMessageStartEvent, ReasoningMessageStartEventProps, - ReasoningMessageStartEventSchema, ReasoningMessageContentEvent, ReasoningMessageContentEventProps, - ReasoningMessageContentEventSchema, ReasoningMessageEndEvent, ReasoningMessageEndEventProps, - ReasoningMessageEndEventSchema, ReasoningMessageChunkEvent, ReasoningMessageChunkEventProps, - ReasoningMessageChunkEventSchema, ReasoningEndEvent, ReasoningEndEventProps, - ReasoningEndEventSchema, ReasoningEncryptedValueEvent, ReasoningEncryptedValueEventProps, - ReasoningEncryptedValueEventSchema, } from "./events"; +import type { Interrupt } from "./types"; -const buildEvent = ( - eventType: EventType, - schema: Schema, - props: Omit, "type">, -): z.infer => - schema.parse({ - type: eventType, - ...props, - }); - -/** - * Creates a TEXT_MESSAGE_START event. - */ +/** Creates a TEXT_MESSAGE_START event. `role` defaults to `"assistant"` when omitted. */ export const createTextMessageStartEvent = ( props: TextMessageStartEventProps, ): TextMessageStartEvent => - buildEvent(EventType.TEXT_MESSAGE_START, TextMessageStartEventSchema, props); + ({ + type: EventType.TEXT_MESSAGE_START, + ...props, + role: props.role ?? "assistant", + }) as TextMessageStartEvent; -/** - * Creates a TEXT_MESSAGE_CONTENT event. - */ +/** Creates a TEXT_MESSAGE_CONTENT event. */ export const createTextMessageContentEvent = ( props: TextMessageContentEventProps, ): TextMessageContentEvent => - buildEvent(EventType.TEXT_MESSAGE_CONTENT, TextMessageContentEventSchema, props); + ({ type: EventType.TEXT_MESSAGE_CONTENT, ...props }) as TextMessageContentEvent; -/** - * Creates a TEXT_MESSAGE_END event. - */ -export const createTextMessageEndEvent = (props: TextMessageEndEventProps): TextMessageEndEvent => - buildEvent(EventType.TEXT_MESSAGE_END, TextMessageEndEventSchema, props); +/** Creates a TEXT_MESSAGE_END event. */ +export const createTextMessageEndEvent = ( + props: TextMessageEndEventProps, +): TextMessageEndEvent => + ({ type: EventType.TEXT_MESSAGE_END, ...props }) as TextMessageEndEvent; -/** - * Creates a TEXT_MESSAGE_CHUNK event. - */ +/** Creates a TEXT_MESSAGE_CHUNK event. */ export const createTextMessageChunkEvent = ( props: TextMessageChunkEventProps, ): TextMessageChunkEvent => - buildEvent(EventType.TEXT_MESSAGE_CHUNK, TextMessageChunkEventSchema, props); + ({ type: EventType.TEXT_MESSAGE_CHUNK, ...props }) as TextMessageChunkEvent; -/** - * Creates a THINKING_TEXT_MESSAGE_START event. - */ +/** @deprecated Use `createReasoningMessageStartEvent` instead. Will be removed in 1.0.0. */ export const createThinkingTextMessageStartEvent = ( props: ThinkingTextMessageStartEventProps, ): ThinkingTextMessageStartEvent => - buildEvent(EventType.THINKING_TEXT_MESSAGE_START, ThinkingTextMessageStartEventSchema, props); + ({ + type: EventType.THINKING_TEXT_MESSAGE_START, + ...props, + }) as ThinkingTextMessageStartEvent; -/** - * Creates a THINKING_TEXT_MESSAGE_CONTENT event. - */ +/** @deprecated Use `createReasoningMessageContentEvent` instead. Will be removed in 1.0.0. */ export const createThinkingTextMessageContentEvent = ( props: ThinkingTextMessageContentEventProps, ): ThinkingTextMessageContentEvent => - buildEvent(EventType.THINKING_TEXT_MESSAGE_CONTENT, ThinkingTextMessageContentEventSchema, props); + ({ + type: EventType.THINKING_TEXT_MESSAGE_CONTENT, + ...props, + }) as ThinkingTextMessageContentEvent; -/** - * Creates a THINKING_TEXT_MESSAGE_END event. - */ +/** @deprecated Use `createReasoningMessageEndEvent` instead. Will be removed in 1.0.0. */ export const createThinkingTextMessageEndEvent = ( props: ThinkingTextMessageEndEventProps, ): ThinkingTextMessageEndEvent => - buildEvent(EventType.THINKING_TEXT_MESSAGE_END, ThinkingTextMessageEndEventSchema, props); - -/** - * Creates a TOOL_CALL_START event. - */ -export const createToolCallStartEvent = (props: ToolCallStartEventProps): ToolCallStartEvent => - buildEvent(EventType.TOOL_CALL_START, ToolCallStartEventSchema, props); - -/** - * Creates a TOOL_CALL_ARGS event. - */ -export const createToolCallArgsEvent = (props: ToolCallArgsEventProps): ToolCallArgsEvent => - buildEvent(EventType.TOOL_CALL_ARGS, ToolCallArgsEventSchema, props); - -/** - * Creates a TOOL_CALL_END event. - */ -export const createToolCallEndEvent = (props: ToolCallEndEventProps): ToolCallEndEvent => - buildEvent(EventType.TOOL_CALL_END, ToolCallEndEventSchema, props); - -/** - * Creates a TOOL_CALL_CHUNK event. - */ -export const createToolCallChunkEvent = (props: ToolCallChunkEventProps): ToolCallChunkEvent => - buildEvent(EventType.TOOL_CALL_CHUNK, ToolCallChunkEventSchema, props); - -/** - * Creates a TOOL_CALL_RESULT event. - */ -export const createToolCallResultEvent = (props: ToolCallResultEventProps): ToolCallResultEvent => - buildEvent(EventType.TOOL_CALL_RESULT, ToolCallResultEventSchema, props); - -/** - * Creates a THINKING_START event. - */ -export const createThinkingStartEvent = (props: ThinkingStartEventProps): ThinkingStartEvent => - buildEvent(EventType.THINKING_START, ThinkingStartEventSchema, props); - -/** - * Creates a THINKING_END event. - */ -export const createThinkingEndEvent = (props: ThinkingEndEventProps): ThinkingEndEvent => - buildEvent(EventType.THINKING_END, ThinkingEndEventSchema, props); - -/** - * Creates a STATE_SNAPSHOT event. - */ -export const createStateSnapshotEvent = (props: StateSnapshotEventProps): StateSnapshotEvent => - buildEvent(EventType.STATE_SNAPSHOT, StateSnapshotEventSchema, props); - -/** - * Creates a STATE_DELTA event. - */ -export const createStateDeltaEvent = (props: StateDeltaEventProps): StateDeltaEvent => - buildEvent(EventType.STATE_DELTA, StateDeltaEventSchema, props); - -/** - * Creates a MESSAGES_SNAPSHOT event. - */ + ({ + type: EventType.THINKING_TEXT_MESSAGE_END, + ...props, + }) as ThinkingTextMessageEndEvent; + +/** Creates a TOOL_CALL_START event. */ +export const createToolCallStartEvent = ( + props: ToolCallStartEventProps, +): ToolCallStartEvent => + ({ type: EventType.TOOL_CALL_START, ...props }) as ToolCallStartEvent; + +/** Creates a TOOL_CALL_ARGS event. */ +export const createToolCallArgsEvent = ( + props: ToolCallArgsEventProps, +): ToolCallArgsEvent => + ({ type: EventType.TOOL_CALL_ARGS, ...props }) as ToolCallArgsEvent; + +/** Creates a TOOL_CALL_END event. */ +export const createToolCallEndEvent = ( + props: ToolCallEndEventProps, +): ToolCallEndEvent => + ({ type: EventType.TOOL_CALL_END, ...props }) as ToolCallEndEvent; + +/** Creates a TOOL_CALL_CHUNK event. */ +export const createToolCallChunkEvent = ( + props: ToolCallChunkEventProps, +): ToolCallChunkEvent => + ({ type: EventType.TOOL_CALL_CHUNK, ...props }) as ToolCallChunkEvent; + +/** Creates a TOOL_CALL_RESULT event. */ +export const createToolCallResultEvent = ( + props: ToolCallResultEventProps, +): ToolCallResultEvent => + ({ type: EventType.TOOL_CALL_RESULT, ...props }) as ToolCallResultEvent; + +/** @deprecated Use `createReasoningStartEvent` instead. Will be removed in 1.0.0. */ +export const createThinkingStartEvent = ( + props: ThinkingStartEventProps, +): ThinkingStartEvent => + ({ type: EventType.THINKING_START, ...props }) as ThinkingStartEvent; + +/** @deprecated Use `createReasoningEndEvent` instead. Will be removed in 1.0.0. */ +export const createThinkingEndEvent = ( + props: ThinkingEndEventProps, +): ThinkingEndEvent => + ({ type: EventType.THINKING_END, ...props }) as ThinkingEndEvent; + +/** Creates a STATE_SNAPSHOT event. */ +export const createStateSnapshotEvent = ( + props: StateSnapshotEventProps, +): StateSnapshotEvent => + ({ type: EventType.STATE_SNAPSHOT, ...props }) as StateSnapshotEvent; + +/** Creates a STATE_DELTA event. */ +export const createStateDeltaEvent = ( + props: StateDeltaEventProps, +): StateDeltaEvent => + ({ type: EventType.STATE_DELTA, ...props }) as StateDeltaEvent; + +/** Creates a MESSAGES_SNAPSHOT event. */ export const createMessagesSnapshotEvent = ( props: MessagesSnapshotEventProps, ): MessagesSnapshotEvent => - buildEvent(EventType.MESSAGES_SNAPSHOT, MessagesSnapshotEventSchema, props); + ({ type: EventType.MESSAGES_SNAPSHOT, ...props }) as MessagesSnapshotEvent; -/** - * Creates an ACTIVITY_SNAPSHOT event. - */ +/** Creates an ACTIVITY_SNAPSHOT event. `replace` defaults to `true` when omitted. */ export const createActivitySnapshotEvent = ( props: ActivitySnapshotEventProps, ): ActivitySnapshotEvent => - buildEvent(EventType.ACTIVITY_SNAPSHOT, ActivitySnapshotEventSchema, props); + ({ + type: EventType.ACTIVITY_SNAPSHOT, + ...props, + replace: props.replace ?? true, + }) as ActivitySnapshotEvent; -/** - * Creates an ACTIVITY_DELTA event. - */ -export const createActivityDeltaEvent = (props: ActivityDeltaEventProps): ActivityDeltaEvent => - buildEvent(EventType.ACTIVITY_DELTA, ActivityDeltaEventSchema, props); +/** Creates an ACTIVITY_DELTA event. */ +export const createActivityDeltaEvent = ( + props: ActivityDeltaEventProps, +): ActivityDeltaEvent => + ({ type: EventType.ACTIVITY_DELTA, ...props }) as ActivityDeltaEvent; -/** - * Creates a RAW event. - */ +/** Creates a RAW event. */ export const createRawEvent = (props: RawEventProps): RawEvent => - buildEvent(EventType.RAW, RawEventSchema, props); + ({ + type: EventType.RAW, + ...props, + }) as RawEvent; -/** - * Creates a CUSTOM event. - */ +/** Creates a CUSTOM event. */ export const createCustomEvent = (props: CustomEventProps): CustomEvent => - buildEvent(EventType.CUSTOM, CustomEventSchema, props); + ({ + type: EventType.CUSTOM, + ...props, + }) as CustomEvent; -/** - * Creates a RUN_STARTED event. - */ -export const createRunStartedEvent = (props: RunStartedEventProps): RunStartedEvent => - buildEvent(EventType.RUN_STARTED, RunStartedEventSchema, props); +/** Creates a RUN_STARTED event. */ +export const createRunStartedEvent = ( + props: RunStartedEventProps, +): RunStartedEvent => + ({ type: EventType.RUN_STARTED, ...props }) as RunStartedEvent; /** - * Creates a RUN_FINISHED event. + * Creates a RUN_FINISHED event. `outcome` is optional; pass `{ type: "success" }` or + * `{ type: "interrupt", interrupts }` (or use the convenience helpers below). * - * `outcome` is optional. Omit it for legacy/back-compat behavior, or set it - * explicitly to `{ type: "success" }` or `{ type: "interrupt", interrupts }` - * — see `createRunFinishedSuccessEvent` and `createRunFinishedInterruptEvent` - * for convenience helpers. + * `outcome: null` is normalized to `outcome` being omitted. */ -export const createRunFinishedEvent = (props: RunFinishedEventProps): RunFinishedEvent => - buildEvent(EventType.RUN_FINISHED, RunFinishedEventSchema, props); +export const createRunFinishedEvent = ( + props: RunFinishedEventProps, +): RunFinishedEvent => { + const { outcome, ...rest } = props; + const event: RunFinishedEvent = { type: EventType.RUN_FINISHED, ...rest } as RunFinishedEvent; + if (outcome !== null && outcome !== undefined) { + event.outcome = outcome as RunFinishedEvent["outcome"]; + } + return event; +}; -/** - * Creates a RUN_FINISHED event with `outcome: { type: "success" }`. - */ +/** Creates a RUN_FINISHED event with `outcome: { type: "success" }`. */ export const createRunFinishedSuccessEvent = ( props: Omit, ): RunFinishedEvent => - buildEvent(EventType.RUN_FINISHED, RunFinishedEventSchema, { - ...props, - outcome: { type: "success" }, - }); + createRunFinishedEvent({ ...props, outcome: { type: "success" } }); /** * Creates a RUN_FINISHED event with `outcome: { type: "interrupt", interrupts }`. + * Throws if `interrupts` is empty. */ export const createRunFinishedInterruptEvent = ( props: Omit & { interrupts: Interrupt[] }, ): RunFinishedEvent => { const { interrupts, ...rest } = props; - return buildEvent(EventType.RUN_FINISHED, RunFinishedEventSchema, { + if (!interrupts || interrupts.length === 0) { + throw new AGUIError("interrupts array must contain at least one element"); + } + return createRunFinishedEvent({ ...rest, outcome: { type: "interrupt", interrupts }, }); }; -/** - * Creates a RUN_ERROR event. - */ +/** Creates a RUN_ERROR event. */ export const createRunErrorEvent = (props: RunErrorEventProps): RunErrorEvent => - buildEvent(EventType.RUN_ERROR, RunErrorEventSchema, props); - -/** - * Creates a STEP_STARTED event. - */ -export const createStepStartedEvent = (props: StepStartedEventProps): StepStartedEvent => - buildEvent(EventType.STEP_STARTED, StepStartedEventSchema, props); - -/** - * Creates a STEP_FINISHED event. - */ -export const createStepFinishedEvent = (props: StepFinishedEventProps): StepFinishedEvent => - buildEvent(EventType.STEP_FINISHED, StepFinishedEventSchema, props); - -/** - * Creates a REASONING_START event. - */ -export const createReasoningStartEvent = (props: ReasoningStartEventProps): ReasoningStartEvent => - buildEvent(EventType.REASONING_START, ReasoningStartEventSchema, props); - -/** - * Creates a REASONING_MESSAGE_START event. - */ + ({ + type: EventType.RUN_ERROR, + ...props, + }) as RunErrorEvent; + +/** Creates a STEP_STARTED event. */ +export const createStepStartedEvent = ( + props: StepStartedEventProps, +): StepStartedEvent => + ({ type: EventType.STEP_STARTED, ...props }) as StepStartedEvent; + +/** Creates a STEP_FINISHED event. */ +export const createStepFinishedEvent = ( + props: StepFinishedEventProps, +): StepFinishedEvent => + ({ type: EventType.STEP_FINISHED, ...props }) as StepFinishedEvent; + +/** Creates a REASONING_START event. */ +export const createReasoningStartEvent = ( + props: ReasoningStartEventProps, +): ReasoningStartEvent => + ({ type: EventType.REASONING_START, ...props }) as ReasoningStartEvent; + +/** Creates a REASONING_MESSAGE_START event. */ export const createReasoningMessageStartEvent = ( props: ReasoningMessageStartEventProps, ): ReasoningMessageStartEvent => - buildEvent(EventType.REASONING_MESSAGE_START, ReasoningMessageStartEventSchema, props); + ({ type: EventType.REASONING_MESSAGE_START, ...props }) as ReasoningMessageStartEvent; -/** - * Creates a REASONING_MESSAGE_CONTENT event. - */ +/** Creates a REASONING_MESSAGE_CONTENT event. */ export const createReasoningMessageContentEvent = ( props: ReasoningMessageContentEventProps, ): ReasoningMessageContentEvent => - buildEvent(EventType.REASONING_MESSAGE_CONTENT, ReasoningMessageContentEventSchema, props); + ({ type: EventType.REASONING_MESSAGE_CONTENT, ...props }) as ReasoningMessageContentEvent; -/** - * Creates a REASONING_MESSAGE_END event. - */ +/** Creates a REASONING_MESSAGE_END event. */ export const createReasoningMessageEndEvent = ( props: ReasoningMessageEndEventProps, ): ReasoningMessageEndEvent => - buildEvent(EventType.REASONING_MESSAGE_END, ReasoningMessageEndEventSchema, props); + ({ type: EventType.REASONING_MESSAGE_END, ...props }) as ReasoningMessageEndEvent; -/** - * Creates a REASONING_MESSAGE_CHUNK event. - */ +/** Creates a REASONING_MESSAGE_CHUNK event. */ export const createReasoningMessageChunkEvent = ( props: ReasoningMessageChunkEventProps, ): ReasoningMessageChunkEvent => - buildEvent(EventType.REASONING_MESSAGE_CHUNK, ReasoningMessageChunkEventSchema, props); + ({ type: EventType.REASONING_MESSAGE_CHUNK, ...props }) as ReasoningMessageChunkEvent; -/** - * Creates a REASONING_END event. - */ -export const createReasoningEndEvent = (props: ReasoningEndEventProps): ReasoningEndEvent => - buildEvent(EventType.REASONING_END, ReasoningEndEventSchema, props); +/** Creates a REASONING_END event. */ +export const createReasoningEndEvent = ( + props: ReasoningEndEventProps, +): ReasoningEndEvent => + ({ type: EventType.REASONING_END, ...props }) as ReasoningEndEvent; -/** - * Creates a REASONING_ENCRYPTED_VALUE event. - */ +/** Creates a REASONING_ENCRYPTED_VALUE event. */ export const createReasoningEncryptedValueEvent = ( props: ReasoningEncryptedValueEventProps, ): ReasoningEncryptedValueEvent => - buildEvent(EventType.REASONING_ENCRYPTED_VALUE, ReasoningEncryptedValueEventSchema, props); + ({ type: EventType.REASONING_ENCRYPTED_VALUE, ...props }) as ReasoningEncryptedValueEvent; diff --git a/sdks/typescript/packages/core/src/events.ts b/sdks/typescript/packages/core/src/events.ts index 101875c81c..674af43b6c 100644 --- a/sdks/typescript/packages/core/src/events.ts +++ b/sdks/typescript/packages/core/src/events.ts @@ -1,13 +1,7 @@ -import { z } from "zod"; -import { MessageSchema, StateSchema, RunAgentInputSchema, InterruptSchema } from "./types"; +// Canonical event types and the EventType enum for AG-UI. This is the +// authoritative event-surface module of @ag-ui/core. -// Text messages can have any role except "tool" -const TextMessageRoleSchema = z.union([ - z.literal("developer"), - z.literal("system"), - z.literal("assistant"), - z.literal("user"), -]); +import type { Message, RunAgentInput, Interrupt } from "./types"; export enum EventType { TEXT_MESSAGE_START = "TEXT_MESSAGE_START", @@ -19,25 +13,15 @@ export enum EventType { TOOL_CALL_END = "TOOL_CALL_END", TOOL_CALL_CHUNK = "TOOL_CALL_CHUNK", TOOL_CALL_RESULT = "TOOL_CALL_RESULT", - /** - * @deprecated Use REASONING_START instead. Will be removed in 1.0.0. - */ + /** @deprecated Use REASONING_START instead. Will be removed in 1.0.0. */ THINKING_START = "THINKING_START", - /** - * @deprecated Use REASONING_END instead. Will be removed in 1.0.0. - */ + /** @deprecated Use REASONING_END instead. Will be removed in 1.0.0. */ THINKING_END = "THINKING_END", - /** - * @deprecated Use REASONING_MESSAGE_START instead. Will be removed in 1.0.0. - */ + /** @deprecated Use REASONING_MESSAGE_START instead. Will be removed in 1.0.0. */ THINKING_TEXT_MESSAGE_START = "THINKING_TEXT_MESSAGE_START", - /** - * @deprecated Use REASONING_MESSAGE_CONTENT instead. Will be removed in 1.0.0. - */ + /** @deprecated Use REASONING_MESSAGE_CONTENT instead. Will be removed in 1.0.0. */ THINKING_TEXT_MESSAGE_CONTENT = "THINKING_TEXT_MESSAGE_CONTENT", - /** - * @deprecated Use REASONING_MESSAGE_END instead. Will be removed in 1.0.0. - */ + /** @deprecated Use REASONING_MESSAGE_END instead. Will be removed in 1.0.0. */ THINKING_TEXT_MESSAGE_END = "THINKING_TEXT_MESSAGE_END", STATE_SNAPSHOT = "STATE_SNAPSHOT", STATE_DELTA = "STATE_DELTA", @@ -60,293 +44,323 @@ export enum EventType { REASONING_ENCRYPTED_VALUE = "REASONING_ENCRYPTED_VALUE", } -export const BaseEventSchema = z - .object({ - type: z.nativeEnum(EventType), - timestamp: z.number().optional(), - rawEvent: z.any().optional(), - }) - .passthrough(); - -export const TextMessageStartEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.TEXT_MESSAGE_START), - messageId: z.string(), - role: TextMessageRoleSchema.default("assistant"), - name: z.string().optional(), -}); - -export const TextMessageContentEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.TEXT_MESSAGE_CONTENT), - messageId: z.string(), - delta: z.string(), -}); - -export const TextMessageEndEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.TEXT_MESSAGE_END), - messageId: z.string(), -}); - -export const TextMessageChunkEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.TEXT_MESSAGE_CHUNK), - messageId: z.string().optional(), - role: TextMessageRoleSchema.optional(), - delta: z.string().optional(), - name: z.string().optional(), -}); - -/** - * @deprecated Use ReasoningMessageStartEventSchema instead. Will be removed in 1.0.0. - */ -export const ThinkingTextMessageStartEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.THINKING_TEXT_MESSAGE_START), -}); - -/** - * @deprecated Use ReasoningMessageContentEventSchema instead. Will be removed in 1.0.0. - */ -export const ThinkingTextMessageContentEventSchema = TextMessageContentEventSchema.omit({ - messageId: true, - type: true, -}).extend({ - type: z.literal(EventType.THINKING_TEXT_MESSAGE_CONTENT), -}); - -/** - * @deprecated Use ReasoningMessageEndEventSchema instead. Will be removed in 1.0.0. - */ -export const ThinkingTextMessageEndEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.THINKING_TEXT_MESSAGE_END), -}); - -export const ToolCallStartEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.TOOL_CALL_START), - toolCallId: z.string(), - toolCallName: z.string(), - parentMessageId: z.string().optional(), -}); - -export const ToolCallArgsEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.TOOL_CALL_ARGS), - toolCallId: z.string(), - delta: z.string(), -}); - -export const ToolCallEndEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.TOOL_CALL_END), - toolCallId: z.string(), -}); - -export const ToolCallResultEventSchema = BaseEventSchema.extend({ - messageId: z.string(), - type: z.literal(EventType.TOOL_CALL_RESULT), - toolCallId: z.string(), - content: z.string(), - role: z.literal("tool").optional(), -}); - -export const ToolCallChunkEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.TOOL_CALL_CHUNK), - toolCallId: z.string().optional(), - toolCallName: z.string().optional(), - parentMessageId: z.string().optional(), - delta: z.string().optional(), -}); - -/** - * @deprecated Use ReasoningStartEventSchema instead. Will be removed in 1.0.0. - */ -export const ThinkingStartEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.THINKING_START), - title: z.string().optional(), -}); - -/** - * @deprecated Use ReasoningEndEventSchema instead. Will be removed in 1.0.0. - */ -export const ThinkingEndEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.THINKING_END), -}); - -export const StateSnapshotEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.STATE_SNAPSHOT), - snapshot: StateSchema, -}); - -export const StateDeltaEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.STATE_DELTA), - delta: z.array(z.any()), // JSON Patch (RFC 6902) -}); - -export const MessagesSnapshotEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.MESSAGES_SNAPSHOT), - messages: z.array(MessageSchema), -}); - -export const ActivitySnapshotEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.ACTIVITY_SNAPSHOT), - messageId: z.string(), - activityType: z.string(), - content: z.record(z.any()), - replace: z.boolean().optional().default(true), -}); - -export const ActivityDeltaEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.ACTIVITY_DELTA), - messageId: z.string(), - activityType: z.string(), - patch: z.array(z.any()), -}); - -export const RawEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.RAW), - event: z.any(), - source: z.string().optional(), -}); - -export const CustomEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.CUSTOM), - name: z.string(), - value: z.any(), -}); - -export const RunStartedEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.RUN_STARTED), - threadId: z.string(), - runId: z.string(), - parentRunId: z.string().optional(), - input: RunAgentInputSchema.optional(), -}); - -export const RunFinishedSuccessOutcomeSchema = z - .object({ - type: z.literal("success"), - }) - .strict(); - -export const RunFinishedInterruptOutcomeSchema = z - .object({ - type: z.literal("interrupt"), - interrupts: z.array(InterruptSchema).min(1), - }) - .strict(); - -export const RunFinishedOutcomeSchema = z.discriminatedUnion("type", [ - RunFinishedSuccessOutcomeSchema, - RunFinishedInterruptOutcomeSchema, -]); - -export const RunFinishedEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.RUN_FINISHED), - threadId: z.string(), - runId: z.string(), - result: z.any().optional(), - // Accept `null` and treat it as omitted, so producers like the Pydantic-based - // Python SDK that serialize via `model_dump()` (without `exclude_none=True`) - // and emit `"outcome": null` for the legacy no-outcome case still validate. - outcome: RunFinishedOutcomeSchema.nullable().optional().transform((v) => v ?? undefined), -}); - -export const RunErrorEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.RUN_ERROR), - message: z.string(), - code: z.string().optional(), -}); - -export const StepStartedEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.STEP_STARTED), - stepName: z.string(), -}); - -export const StepFinishedEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.STEP_FINISHED), - stepName: z.string(), -}); - -// Schema for the encrypted signature subtype -export const ReasoningEncryptedValueSubtypeSchema = z.union([ - z.literal("tool-call"), - z.literal("message"), -]); - -export const ReasoningStartEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.REASONING_START), - messageId: z.string(), -}); - -export const ReasoningMessageStartEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.REASONING_MESSAGE_START), - messageId: z.string(), - role: z.literal("reasoning"), -}); - -export const ReasoningMessageContentEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.REASONING_MESSAGE_CONTENT), - messageId: z.string(), - delta: z.string(), -}); - -export const ReasoningMessageEndEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.REASONING_MESSAGE_END), - messageId: z.string(), -}); - -export const ReasoningMessageChunkEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.REASONING_MESSAGE_CHUNK), - messageId: z.string().optional(), - delta: z.string().optional(), -}); - -export const ReasoningEndEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.REASONING_END), - messageId: z.string(), -}); - -export const ReasoningEncryptedValueEventSchema = BaseEventSchema.extend({ - type: z.literal(EventType.REASONING_ENCRYPTED_VALUE), - subtype: ReasoningEncryptedValueSubtypeSchema, - entityId: z.string(), - encryptedValue: z.string(), -}); - -export const EventSchemas = z.discriminatedUnion("type", [ - TextMessageStartEventSchema, - TextMessageContentEventSchema, - TextMessageEndEventSchema, - TextMessageChunkEventSchema, - ThinkingStartEventSchema, - ThinkingEndEventSchema, - ThinkingTextMessageStartEventSchema, - ThinkingTextMessageContentEventSchema, - ThinkingTextMessageEndEventSchema, - ToolCallStartEventSchema, - ToolCallArgsEventSchema, - ToolCallEndEventSchema, - ToolCallChunkEventSchema, - ToolCallResultEventSchema, - StateSnapshotEventSchema, - StateDeltaEventSchema, - MessagesSnapshotEventSchema, - ActivitySnapshotEventSchema, - ActivityDeltaEventSchema, - RawEventSchema, - CustomEventSchema, - RunStartedEventSchema, - RunFinishedEventSchema, - RunErrorEventSchema, - StepStartedEventSchema, - StepFinishedEventSchema, - ReasoningStartEventSchema, - ReasoningMessageStartEventSchema, - ReasoningMessageContentEventSchema, - ReasoningMessageEndEventSchema, - ReasoningMessageChunkEventSchema, - ReasoningEndEventSchema, - ReasoningEncryptedValueEventSchema, -]); - -export type BaseEvent = z.infer; -export type AGUIEvent = z.infer; -export type BaseEventFields = z.infer; +// --------------------------------------------------------------------------- +// Shared primitives +// --------------------------------------------------------------------------- + +export type TextMessageRole = "developer" | "system" | "assistant" | "user"; + +export type ReasoningEncryptedValueSubtype = "tool-call" | "message"; + +// --------------------------------------------------------------------------- +// BaseEvent +// --------------------------------------------------------------------------- + +export interface BaseEvent { + type: EventType; + timestamp?: number; + rawEvent?: any; + [k: string]: unknown; +} + +// Alias kept for parity with events.ts +export type BaseEventFields = BaseEvent; + +// --------------------------------------------------------------------------- +// Text-message events +// --------------------------------------------------------------------------- + +export interface TextMessageStartEvent extends BaseEvent { + type: EventType.TEXT_MESSAGE_START; + messageId: string; + // .default("assistant") means z.infer gives non-optional TextMessageRole + role: TextMessageRole; + name?: string; +} + +export interface TextMessageContentEvent extends BaseEvent { + type: EventType.TEXT_MESSAGE_CONTENT; + messageId: string; + delta: string; +} + +export interface TextMessageEndEvent extends BaseEvent { + type: EventType.TEXT_MESSAGE_END; + messageId: string; +} + +export interface TextMessageChunkEvent extends BaseEvent { + type: EventType.TEXT_MESSAGE_CHUNK; + messageId?: string; + role?: TextMessageRole; + delta?: string; + name?: string; +} + +// --------------------------------------------------------------------------- +// Deprecated Thinking events (aliased to Reasoning counterparts in 1.0.0) +// --------------------------------------------------------------------------- + +/** @deprecated Use ReasoningTextMessageStartEvent instead. Will be removed in 1.0.0. */ +export interface ThinkingTextMessageStartEvent extends BaseEvent { + type: EventType.THINKING_TEXT_MESSAGE_START; +} + +/** @deprecated Use ReasoningMessageContentEvent instead. Will be removed in 1.0.0. */ +export interface ThinkingTextMessageContentEvent extends BaseEvent { + type: EventType.THINKING_TEXT_MESSAGE_CONTENT; + // ThinkingTextMessageContentEventSchema omits messageId from TextMessageContentEventSchema + delta: string; +} + +/** @deprecated Use ReasoningMessageEndEvent instead. Will be removed in 1.0.0. */ +export interface ThinkingTextMessageEndEvent extends BaseEvent { + type: EventType.THINKING_TEXT_MESSAGE_END; +} + +// --------------------------------------------------------------------------- +// Tool-call events +// --------------------------------------------------------------------------- + +export interface ToolCallStartEvent extends BaseEvent { + type: EventType.TOOL_CALL_START; + toolCallId: string; + toolCallName: string; + parentMessageId?: string; +} + +export interface ToolCallArgsEvent extends BaseEvent { + type: EventType.TOOL_CALL_ARGS; + toolCallId: string; + delta: string; +} + +export interface ToolCallEndEvent extends BaseEvent { + type: EventType.TOOL_CALL_END; + toolCallId: string; +} + +export interface ToolCallResultEvent extends BaseEvent { + messageId: string; + type: EventType.TOOL_CALL_RESULT; + toolCallId: string; + content: string; + role?: "tool"; +} + +export interface ToolCallChunkEvent extends BaseEvent { + type: EventType.TOOL_CALL_CHUNK; + toolCallId?: string; + toolCallName?: string; + parentMessageId?: string; + delta?: string; +} + +// --------------------------------------------------------------------------- +// Deprecated Thinking start/end +// --------------------------------------------------------------------------- + +/** @deprecated Use ReasoningStartEvent instead. Will be removed in 1.0.0. */ +export interface ThinkingStartEvent extends BaseEvent { + type: EventType.THINKING_START; + title?: string; +} + +/** @deprecated Use ReasoningEndEvent instead. Will be removed in 1.0.0. */ +export interface ThinkingEndEvent extends BaseEvent { + type: EventType.THINKING_END; +} + +// --------------------------------------------------------------------------- +// State / message snapshot events +// --------------------------------------------------------------------------- + +export interface StateSnapshotEvent extends BaseEvent { + type: EventType.STATE_SNAPSHOT; + snapshot: any; +} + +export interface StateDeltaEvent extends BaseEvent { + type: EventType.STATE_DELTA; + delta: any[]; +} + +export interface MessagesSnapshotEvent extends BaseEvent { + type: EventType.MESSAGES_SNAPSHOT; + messages: Message[]; +} + +export interface ActivitySnapshotEvent extends BaseEvent { + type: EventType.ACTIVITY_SNAPSHOT; + messageId: string; + activityType: string; + content: Record; + // .optional().default(true) — z.infer OUTPUT is boolean (not optional) + replace: boolean; +} + +export interface ActivityDeltaEvent extends BaseEvent { + type: EventType.ACTIVITY_DELTA; + messageId: string; + activityType: string; + patch: any[]; +} + +// --------------------------------------------------------------------------- +// Raw / Custom events +// --------------------------------------------------------------------------- + +export interface RawEvent extends BaseEvent { + type: EventType.RAW; + event: any; + source?: string; +} + +export interface CustomEvent extends BaseEvent { + type: EventType.CUSTOM; + name: string; + value: any; +} + +// --------------------------------------------------------------------------- +// Run lifecycle events +// --------------------------------------------------------------------------- + +export interface RunStartedEvent extends BaseEvent { + type: EventType.RUN_STARTED; + threadId: string; + runId: string; + parentRunId?: string; + input?: RunAgentInput; +} + +export interface RunFinishedSuccessOutcome { + type: "success"; +} + +export interface RunFinishedInterruptOutcome { + type: "interrupt"; + interrupts: Interrupt[]; +} + +export type RunFinishedOutcome = RunFinishedSuccessOutcome | RunFinishedInterruptOutcome; + +export interface RunFinishedEvent extends BaseEvent { + type: EventType.RUN_FINISHED; + threadId: string; + runId: string; + result?: any; + // nullable().optional().transform(v => v ?? undefined) → output is RunFinishedOutcome | undefined + outcome?: RunFinishedOutcome; +} + +export interface RunErrorEvent extends BaseEvent { + type: EventType.RUN_ERROR; + message: string; + code?: string; +} + +// --------------------------------------------------------------------------- +// Step events +// --------------------------------------------------------------------------- + +export interface StepStartedEvent extends BaseEvent { + type: EventType.STEP_STARTED; + stepName: string; +} + +export interface StepFinishedEvent extends BaseEvent { + type: EventType.STEP_FINISHED; + stepName: string; +} + +// --------------------------------------------------------------------------- +// Reasoning events +// --------------------------------------------------------------------------- + +export interface ReasoningStartEvent extends BaseEvent { + type: EventType.REASONING_START; + messageId: string; +} + +export interface ReasoningMessageStartEvent extends BaseEvent { + type: EventType.REASONING_MESSAGE_START; + messageId: string; + role: "reasoning"; +} + +export interface ReasoningMessageContentEvent extends BaseEvent { + type: EventType.REASONING_MESSAGE_CONTENT; + messageId: string; + delta: string; +} + +export interface ReasoningMessageEndEvent extends BaseEvent { + type: EventType.REASONING_MESSAGE_END; + messageId: string; +} + +export interface ReasoningMessageChunkEvent extends BaseEvent { + type: EventType.REASONING_MESSAGE_CHUNK; + messageId?: string; + delta?: string; +} + +export interface ReasoningEndEvent extends BaseEvent { + type: EventType.REASONING_END; + messageId: string; +} + +export interface ReasoningEncryptedValueEvent extends BaseEvent { + type: EventType.REASONING_ENCRYPTED_VALUE; + subtype: ReasoningEncryptedValueSubtype; + entityId: string; + encryptedValue: string; +} + +// --------------------------------------------------------------------------- +// AGUIEvent discriminated union +// --------------------------------------------------------------------------- + +export type AGUIEvent = + | TextMessageStartEvent + | TextMessageContentEvent + | TextMessageEndEvent + | TextMessageChunkEvent + | ThinkingStartEvent + | ThinkingEndEvent + | ThinkingTextMessageStartEvent + | ThinkingTextMessageContentEvent + | ThinkingTextMessageEndEvent + | ToolCallStartEvent + | ToolCallArgsEvent + | ToolCallEndEvent + | ToolCallChunkEvent + | ToolCallResultEvent + | StateSnapshotEvent + | StateDeltaEvent + | MessagesSnapshotEvent + | ActivitySnapshotEvent + | ActivityDeltaEvent + | RawEvent + | CustomEvent + | RunStartedEvent + | RunFinishedEvent + | RunErrorEvent + | StepStartedEvent + | StepFinishedEvent + | ReasoningStartEvent + | ReasoningMessageStartEvent + | ReasoningMessageContentEvent + | ReasoningMessageEndEvent + | ReasoningMessageChunkEvent + | ReasoningEndEvent + | ReasoningEncryptedValueEvent; + +// --------------------------------------------------------------------------- +// AGUIEventByType mapped type +// --------------------------------------------------------------------------- + export type AGUIEventByType = { [EventType.TEXT_MESSAGE_START]: TextMessageStartEvent; [EventType.TEXT_MESSAGE_CONTENT]: TextMessageContentEvent; @@ -382,89 +396,62 @@ export type AGUIEventByType = { [EventType.REASONING_END]: ReasoningEndEvent; [EventType.REASONING_ENCRYPTED_VALUE]: ReasoningEncryptedValueEvent; }; + export type AGUIEventOf = AGUIEventByType[T]; export type EventPayloadOf = Omit, keyof BaseEventFields>; -type EventProps = Omit, "type">; - -export type BaseEventProps = EventProps; - -export type TextMessageStartEventProps = EventProps; -export type TextMessageContentEventProps = EventProps; -export type TextMessageEndEventProps = EventProps; -export type TextMessageChunkEventProps = EventProps; -export type ThinkingTextMessageStartEventProps = EventProps< - typeof ThinkingTextMessageStartEventSchema ->; -export type ThinkingTextMessageContentEventProps = EventProps< - typeof ThinkingTextMessageContentEventSchema ->; -export type ThinkingTextMessageEndEventProps = EventProps; -export type ToolCallStartEventProps = EventProps; -export type ToolCallArgsEventProps = EventProps; -export type ToolCallEndEventProps = EventProps; -export type ToolCallChunkEventProps = EventProps; -export type ToolCallResultEventProps = EventProps; -export type ThinkingStartEventProps = EventProps; -export type ThinkingEndEventProps = EventProps; -export type StateSnapshotEventProps = EventProps; -export type StateDeltaEventProps = EventProps; -export type MessagesSnapshotEventProps = EventProps; -export type ActivitySnapshotEventProps = EventProps; -export type ActivityDeltaEventProps = EventProps; -export type RawEventProps = EventProps; -export type CustomEventProps = EventProps; -export type RunStartedEventProps = EventProps; -export type RunFinishedEventProps = EventProps; -export type RunErrorEventProps = EventProps; -export type StepStartedEventProps = EventProps; -export type StepFinishedEventProps = EventProps; -export type ReasoningStartEventProps = EventProps; -export type ReasoningMessageStartEventProps = EventProps; -export type ReasoningMessageContentEventProps = EventProps< - typeof ReasoningMessageContentEventSchema ->; -export type ReasoningMessageEndEventProps = EventProps; -export type ReasoningMessageChunkEventProps = EventProps; -export type ReasoningEndEventProps = EventProps; -export type ReasoningEncryptedValueEventProps = EventProps< - typeof ReasoningEncryptedValueEventSchema ->; - -export type TextMessageStartEvent = z.infer; -export type TextMessageContentEvent = z.infer; -export type TextMessageEndEvent = z.infer; -export type TextMessageChunkEvent = z.infer; -export type ThinkingTextMessageStartEvent = z.infer; -export type ThinkingTextMessageContentEvent = z.infer; -export type ThinkingTextMessageEndEvent = z.infer; -export type ToolCallStartEvent = z.infer; -export type ToolCallArgsEvent = z.infer; -export type ToolCallEndEvent = z.infer; -export type ToolCallChunkEvent = z.infer; -export type ToolCallResultEvent = z.infer; -export type ThinkingStartEvent = z.infer; -export type ThinkingEndEvent = z.infer; -export type StateSnapshotEvent = z.infer; -export type StateDeltaEvent = z.infer; -export type MessagesSnapshotEvent = z.infer; -export type ActivitySnapshotEvent = z.infer; -export type ActivityDeltaEvent = z.infer; -export type RawEvent = z.infer; -export type CustomEvent = z.infer; -export type RunStartedEvent = z.infer; -export type RunFinishedEvent = z.infer; -export type RunFinishedOutcome = z.infer; -export type RunFinishedSuccessOutcome = z.infer; -export type RunFinishedInterruptOutcome = z.infer; -export type RunErrorEvent = z.infer; -export type StepStartedEvent = z.infer; -export type StepFinishedEvent = z.infer; -export type ReasoningStartEvent = z.infer; -export type ReasoningMessageStartEvent = z.infer; -export type ReasoningMessageContentEvent = z.infer; -export type ReasoningMessageEndEvent = z.infer; -export type ReasoningMessageChunkEvent = z.infer; -export type ReasoningEndEvent = z.infer; -export type ReasoningEncryptedValueEvent = z.infer; -export type ReasoningEncryptedValueSubtype = z.infer; +// --------------------------------------------------------------------------- +// Props types (mirror z.input with "type" omitted) +// Fields with .default() are optional in Props (callers can omit them) +// --------------------------------------------------------------------------- + +export type BaseEventProps = Omit; + +// TextMessageStartEvent: role has .default("assistant") → optional in Props +export type TextMessageStartEventProps = Omit & { + role?: TextMessageRole; +}; +export type TextMessageContentEventProps = Omit; +export type TextMessageEndEventProps = Omit; +export type TextMessageChunkEventProps = Omit; + +export type ThinkingTextMessageStartEventProps = Omit; +export type ThinkingTextMessageContentEventProps = Omit; +export type ThinkingTextMessageEndEventProps = Omit; + +export type ToolCallStartEventProps = Omit; +export type ToolCallArgsEventProps = Omit; +export type ToolCallEndEventProps = Omit; +export type ToolCallChunkEventProps = Omit; +export type ToolCallResultEventProps = Omit; + +export type ThinkingStartEventProps = Omit; +export type ThinkingEndEventProps = Omit; + +export type StateSnapshotEventProps = Omit; +export type StateDeltaEventProps = Omit; +export type MessagesSnapshotEventProps = Omit; + +// ActivitySnapshotEvent: replace has .optional().default(true) → optional in Props +export type ActivitySnapshotEventProps = Omit & { + replace?: boolean; +}; +export type ActivityDeltaEventProps = Omit; + +export type RawEventProps = Omit; +export type CustomEventProps = Omit; + +export type RunStartedEventProps = Omit; +export type RunFinishedEventProps = Omit; +export type RunErrorEventProps = Omit; + +export type StepStartedEventProps = Omit; +export type StepFinishedEventProps = Omit; + +export type ReasoningStartEventProps = Omit; +export type ReasoningMessageStartEventProps = Omit; +export type ReasoningMessageContentEventProps = Omit; +export type ReasoningMessageEndEventProps = Omit; +export type ReasoningMessageChunkEventProps = Omit; +export type ReasoningEndEventProps = Omit; +export type ReasoningEncryptedValueEventProps = Omit; diff --git a/sdks/typescript/packages/core/src/index.ts b/sdks/typescript/packages/core/src/index.ts index b77fb19dd6..c6e081187c 100644 --- a/sdks/typescript/packages/core/src/index.ts +++ b/sdks/typescript/packages/core/src/index.ts @@ -1,39 +1,11 @@ -// Export all base types and schemas +// Base types export * from "./types"; -// Export all capability-related types and schemas -export { - SubAgentInfoSchema, - IdentityCapabilitiesSchema, - TransportCapabilitiesSchema, - ToolsCapabilitiesSchema, - OutputCapabilitiesSchema, - StateCapabilitiesSchema, - MultiAgentCapabilitiesSchema, - ReasoningCapabilitiesSchema, - MultimodalInputCapabilitiesSchema, - MultimodalOutputCapabilitiesSchema, - MultimodalCapabilitiesSchema, - ExecutionCapabilitiesSchema, - HumanInTheLoopCapabilitiesSchema, - AgentCapabilitiesSchema, -} from "./capabilities"; -export type { - SubAgentInfo, - IdentityCapabilities, - TransportCapabilities, - ToolsCapabilities, - OutputCapabilities, - StateCapabilities, - MultiAgentCapabilities, - ReasoningCapabilities, - MultimodalInputCapabilities, - MultimodalOutputCapabilities, - MultimodalCapabilities, - ExecutionCapabilities, - HumanInTheLoopCapabilities, - AgentCapabilities, -} from "./capabilities"; +// Capability types +export * from "./capabilities"; -// Export all event-related types and schemas +// Event types and EventType enum export * from "./events"; + +// Event factories +export * from "./event-factories"; diff --git a/sdks/typescript/packages/core/src/schemas.ts b/sdks/typescript/packages/core/src/schemas.ts new file mode 100644 index 0000000000..97d8e710a6 --- /dev/null +++ b/sdks/typescript/packages/core/src/schemas.ts @@ -0,0 +1,724 @@ +// zod schemas for AG-UI types and events. This module is published at the +// `@ag-ui/core/schemas` subpath. zod is an optional peer dependency — install +// it explicitly if you import from this module. The schemas mirror the types +// exported from the main `@ag-ui/core` entry. +// +// Cross-version note: written to work on zod >= 3.24.0 AND zod 4.x. +// API used here is stable across both majors: +// - z.enum([...]) instead of z.nativeEnum() (removed in zod 4) +// - z.record(z.string(), X) instead of z.record(X) (two-arg form required in zod 4) + +import { z } from "zod"; +import { EventType } from "./events"; + +// --------------------------------------------------------------------------- +// EventType enum values as a z.enum tuple — works on zod 3.24+ and zod 4. +// z.nativeEnum() was removed in zod 4, so we enumerate values explicitly. +// --------------------------------------------------------------------------- + +export const EventTypeSchema = z.enum([ + "TEXT_MESSAGE_START", + "TEXT_MESSAGE_CONTENT", + "TEXT_MESSAGE_END", + "TEXT_MESSAGE_CHUNK", + "TOOL_CALL_START", + "TOOL_CALL_ARGS", + "TOOL_CALL_END", + "TOOL_CALL_CHUNK", + "TOOL_CALL_RESULT", + "THINKING_START", + "THINKING_END", + "THINKING_TEXT_MESSAGE_START", + "THINKING_TEXT_MESSAGE_CONTENT", + "THINKING_TEXT_MESSAGE_END", + "STATE_SNAPSHOT", + "STATE_DELTA", + "MESSAGES_SNAPSHOT", + "ACTIVITY_SNAPSHOT", + "ACTIVITY_DELTA", + "RAW", + "CUSTOM", + "RUN_STARTED", + "RUN_FINISHED", + "RUN_ERROR", + "STEP_STARTED", + "STEP_FINISHED", + "REASONING_START", + "REASONING_MESSAGE_START", + "REASONING_MESSAGE_CONTENT", + "REASONING_MESSAGE_END", + "REASONING_MESSAGE_CHUNK", + "REASONING_END", + "REASONING_ENCRYPTED_VALUE", +] as const); + +// --------------------------------------------------------------------------- +// Base types (from types.ts) +// --------------------------------------------------------------------------- + +export const FunctionCallSchema = z.object({ + name: z.string(), + arguments: z.string(), +}); + +export const ToolCallSchema = z.object({ + id: z.string(), + type: z.literal("function"), + function: FunctionCallSchema, + encryptedValue: z.string().optional(), +}); + +export const TextInputContentSchema = z.object({ + type: z.literal("text"), + text: z.string(), +}); + +export const InputContentDataSourceSchema = z.object({ + type: z.literal("data"), + value: z.string(), + mimeType: z.string(), +}); + +export const InputContentUrlSourceSchema = z.object({ + type: z.literal("url"), + value: z.string(), + mimeType: z.string().optional(), +}); + +export const InputContentSourceSchema = z.discriminatedUnion("type", [ + InputContentDataSourceSchema, + InputContentUrlSourceSchema, +]); + +export const ImageInputContentSchema = z.object({ + type: z.literal("image"), + source: InputContentSourceSchema, + metadata: z.unknown().optional(), +}); + +export const AudioInputContentSchema = z.object({ + type: z.literal("audio"), + source: InputContentSourceSchema, + metadata: z.unknown().optional(), +}); + +export const VideoInputContentSchema = z.object({ + type: z.literal("video"), + source: InputContentSourceSchema, + metadata: z.unknown().optional(), +}); + +export const DocumentInputContentSchema = z.object({ + type: z.literal("document"), + source: InputContentSourceSchema, + metadata: z.unknown().optional(), +}); + +export const ImageInputPartSchema = ImageInputContentSchema; +export const AudioInputPartSchema = AudioInputContentSchema; +export const VideoInputPartSchema = VideoInputContentSchema; +export const DocumentInputPartSchema = DocumentInputContentSchema; + +export const BinaryInputContentSchema = z + .object({ + type: z.literal("binary"), + mimeType: z.string(), + id: z.string().optional(), + url: z.string().optional(), + data: z.string().optional(), + filename: z.string().optional(), + }) + .refine((value) => Boolean(value.id || value.url || value.data), { + message: "BinaryInputContent requires at least one of id, url, or data.", + }); + +export const InputContentSchema = z + .discriminatedUnion("type", [ + TextInputContentSchema, + ImageInputContentSchema, + AudioInputContentSchema, + VideoInputContentSchema, + DocumentInputContentSchema, + z.object({ + type: z.literal("binary"), + mimeType: z.string(), + id: z.string().optional(), + url: z.string().optional(), + data: z.string().optional(), + filename: z.string().optional(), + }), + ]) + .refine( + (value) => { + if (value.type === "binary") { + return Boolean( + (value as { id?: string; url?: string; data?: string }).id || + (value as { id?: string; url?: string; data?: string }).url || + (value as { id?: string; url?: string; data?: string }).data, + ); + } + return true; + }, + { message: "BinaryInputContent requires at least one of id, url, or data." }, + ); + +export const InputContentPartSchema = InputContentSchema; + +const BaseMessageSchema = z.object({ + id: z.string(), + name: z.string().optional(), + encryptedValue: z.string().optional(), +}); + +export const DeveloperMessageSchema = BaseMessageSchema.extend({ + role: z.literal("developer"), + content: z.string(), +}); + +export const SystemMessageSchema = BaseMessageSchema.extend({ + role: z.literal("system"), + content: z.string(), +}); + +export const AssistantMessageSchema = BaseMessageSchema.extend({ + role: z.literal("assistant"), + content: z.string().optional(), + toolCalls: z.array(ToolCallSchema).optional(), +}); + +export const UserMessageSchema = BaseMessageSchema.extend({ + role: z.literal("user"), + content: z.union([z.string(), z.array(InputContentSchema)]), +}); + +export const ToolMessageSchema = z.object({ + id: z.string(), + content: z.string(), + role: z.literal("tool"), + toolCallId: z.string(), + error: z.string().optional(), + encryptedValue: z.string().optional(), +}); + +export const ActivityMessageSchema = z.object({ + id: z.string(), + role: z.literal("activity"), + activityType: z.string(), + content: z.record(z.string(), z.any()), +}); + +export const ReasoningMessageSchema = z.object({ + id: z.string(), + role: z.literal("reasoning"), + content: z.string(), + encryptedValue: z.string().optional(), +}); + +export const MessageSchema = z.discriminatedUnion("role", [ + DeveloperMessageSchema, + SystemMessageSchema, + AssistantMessageSchema, + UserMessageSchema, + ToolMessageSchema, + ActivityMessageSchema, + ReasoningMessageSchema, +]); + +export const RoleSchema = z.union([ + z.literal("developer"), + z.literal("system"), + z.literal("assistant"), + z.literal("user"), + z.literal("tool"), + z.literal("activity"), + z.literal("reasoning"), +]); + +export const ContextSchema = z.object({ + description: z.string(), + value: z.string(), +}); + +export const ToolSchema = z.object({ + name: z.string(), + description: z.string(), + parameters: z.any(), + metadata: z.record(z.string(), z.any()).optional(), +}); + +export const InterruptSchema = z.object({ + id: z.string(), + reason: z.string(), + message: z.string().optional(), + toolCallId: z.string().optional(), + responseSchema: z.record(z.string(), z.any()).optional(), + expiresAt: z.string().optional(), + metadata: z.record(z.string(), z.any()).optional(), +}); + +export const ResumeEntrySchema = z.object({ + interruptId: z.string(), + status: z.enum(["resolved", "cancelled"]), + payload: z.any().optional(), +}); + +export const RunAgentInputSchema = z.object({ + threadId: z.string(), + runId: z.string(), + parentRunId: z.string().optional(), + state: z.any(), + messages: z.array(MessageSchema), + tools: z.array(ToolSchema), + context: z.array(ContextSchema), + forwardedProps: z.any(), + resume: z.array(ResumeEntrySchema).optional(), +}); + +export const StateSchema = z.any(); + +// --------------------------------------------------------------------------- +// Event schemas (from events.ts) +// --------------------------------------------------------------------------- + +const TextMessageRoleSchema = z.union([ + z.literal("developer"), + z.literal("system"), + z.literal("assistant"), + z.literal("user"), +]); + +export const BaseEventSchema = z + .object({ + type: EventTypeSchema, + timestamp: z.number().optional(), + rawEvent: z.any().optional(), + }) + .passthrough(); + +export const TextMessageStartEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.TEXT_MESSAGE_START), + messageId: z.string(), + role: TextMessageRoleSchema.default("assistant"), + name: z.string().optional(), +}); + +export const TextMessageContentEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.TEXT_MESSAGE_CONTENT), + messageId: z.string(), + delta: z.string(), +}); + +export const TextMessageEndEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.TEXT_MESSAGE_END), + messageId: z.string(), +}); + +export const TextMessageChunkEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.TEXT_MESSAGE_CHUNK), + messageId: z.string().optional(), + role: TextMessageRoleSchema.optional(), + delta: z.string().optional(), + name: z.string().optional(), +}); + +/** + * @deprecated Use ReasoningTextMessageStartEventSchema instead. Will be removed in 1.0.0. + */ +export const ThinkingTextMessageStartEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.THINKING_TEXT_MESSAGE_START), +}); + +/** + * @deprecated Use ReasoningMessageContentEventSchema instead. Will be removed in 1.0.0. + */ +export const ThinkingTextMessageContentEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.THINKING_TEXT_MESSAGE_CONTENT), + delta: z.string(), +}); + +/** + * @deprecated Use ReasoningMessageEndEventSchema instead. Will be removed in 1.0.0. + */ +export const ThinkingTextMessageEndEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.THINKING_TEXT_MESSAGE_END), +}); + +export const ToolCallStartEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.TOOL_CALL_START), + toolCallId: z.string(), + toolCallName: z.string(), + parentMessageId: z.string().optional(), +}); + +export const ToolCallArgsEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.TOOL_CALL_ARGS), + toolCallId: z.string(), + delta: z.string(), +}); + +export const ToolCallEndEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.TOOL_CALL_END), + toolCallId: z.string(), +}); + +export const ToolCallResultEventSchema = BaseEventSchema.extend({ + messageId: z.string(), + type: z.literal(EventType.TOOL_CALL_RESULT), + toolCallId: z.string(), + content: z.string(), + role: z.literal("tool").optional(), +}); + +export const ToolCallChunkEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.TOOL_CALL_CHUNK), + toolCallId: z.string().optional(), + toolCallName: z.string().optional(), + parentMessageId: z.string().optional(), + delta: z.string().optional(), +}); + +/** + * @deprecated Use ReasoningStartEventSchema instead. Will be removed in 1.0.0. + */ +export const ThinkingStartEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.THINKING_START), + title: z.string().optional(), +}); + +/** + * @deprecated Use ReasoningEndEventSchema instead. Will be removed in 1.0.0. + */ +export const ThinkingEndEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.THINKING_END), +}); + +export const StateSnapshotEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.STATE_SNAPSHOT), + snapshot: StateSchema, +}); + +export const StateDeltaEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.STATE_DELTA), + delta: z.array(z.any()), +}); + +export const MessagesSnapshotEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.MESSAGES_SNAPSHOT), + messages: z.array(MessageSchema), +}); + +export const ActivitySnapshotEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.ACTIVITY_SNAPSHOT), + messageId: z.string(), + activityType: z.string(), + content: z.record(z.string(), z.any()), + replace: z.boolean().optional().default(true), +}); + +export const ActivityDeltaEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.ACTIVITY_DELTA), + messageId: z.string(), + activityType: z.string(), + patch: z.array(z.any()), +}); + +export const RawEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.RAW), + event: z.any(), + source: z.string().optional(), +}); + +export const CustomEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.CUSTOM), + name: z.string(), + value: z.any(), +}); + +export const RunStartedEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.RUN_STARTED), + threadId: z.string(), + runId: z.string(), + parentRunId: z.string().optional(), + input: RunAgentInputSchema.optional(), +}); + +export const RunFinishedSuccessOutcomeSchema = z + .object({ + type: z.literal("success"), + }) + .strict(); + +export const RunFinishedInterruptOutcomeSchema = z + .object({ + type: z.literal("interrupt"), + interrupts: z.array(InterruptSchema).min(1), + }) + .strict(); + +export const RunFinishedOutcomeSchema = z.discriminatedUnion("type", [ + RunFinishedSuccessOutcomeSchema, + RunFinishedInterruptOutcomeSchema, +]); + +export const RunFinishedEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.RUN_FINISHED), + threadId: z.string(), + runId: z.string(), + result: z.any().optional(), + // Accept `null` and treat it as omitted, so producers that emit `"outcome": null` + // for the legacy no-outcome case still validate. + outcome: RunFinishedOutcomeSchema.nullable().optional().transform((v) => v ?? undefined), +}); + +export const RunErrorEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.RUN_ERROR), + message: z.string(), + code: z.string().optional(), +}); + +export const StepStartedEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.STEP_STARTED), + stepName: z.string(), +}); + +export const StepFinishedEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.STEP_FINISHED), + stepName: z.string(), +}); + +export const ReasoningEncryptedValueSubtypeSchema = z.union([ + z.literal("tool-call"), + z.literal("message"), +]); + +export const ReasoningStartEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.REASONING_START), + messageId: z.string(), +}); + +export const ReasoningMessageStartEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.REASONING_MESSAGE_START), + messageId: z.string(), + role: z.literal("reasoning"), +}); + +export const ReasoningMessageContentEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.REASONING_MESSAGE_CONTENT), + messageId: z.string(), + delta: z.string(), +}); + +export const ReasoningMessageEndEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.REASONING_MESSAGE_END), + messageId: z.string(), +}); + +export const ReasoningMessageChunkEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.REASONING_MESSAGE_CHUNK), + messageId: z.string().optional(), + delta: z.string().optional(), +}); + +export const ReasoningEndEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.REASONING_END), + messageId: z.string(), +}); + +export const ReasoningEncryptedValueEventSchema = BaseEventSchema.extend({ + type: z.literal(EventType.REASONING_ENCRYPTED_VALUE), + subtype: ReasoningEncryptedValueSubtypeSchema, + entityId: z.string(), + encryptedValue: z.string(), +}); + +/** + * Discriminated union of all AG-UI event schemas. Suitable for validating + * untrusted event payloads from the wire. + */ +export const EventSchemas = z.discriminatedUnion("type", [ + TextMessageStartEventSchema, + TextMessageContentEventSchema, + TextMessageEndEventSchema, + TextMessageChunkEventSchema, + ThinkingStartEventSchema, + ThinkingEndEventSchema, + ThinkingTextMessageStartEventSchema, + ThinkingTextMessageContentEventSchema, + ThinkingTextMessageEndEventSchema, + ToolCallStartEventSchema, + ToolCallArgsEventSchema, + ToolCallEndEventSchema, + ToolCallChunkEventSchema, + ToolCallResultEventSchema, + StateSnapshotEventSchema, + StateDeltaEventSchema, + MessagesSnapshotEventSchema, + ActivitySnapshotEventSchema, + ActivityDeltaEventSchema, + RawEventSchema, + CustomEventSchema, + RunStartedEventSchema, + RunFinishedEventSchema, + RunErrorEventSchema, + StepStartedEventSchema, + StepFinishedEventSchema, + ReasoningStartEventSchema, + ReasoningMessageStartEventSchema, + ReasoningMessageContentEventSchema, + ReasoningMessageEndEventSchema, + ReasoningMessageChunkEventSchema, + ReasoningEndEventSchema, + ReasoningEncryptedValueEventSchema, +]); + +// --------------------------------------------------------------------------- +// Capability schemas (from capabilities.ts) +// --------------------------------------------------------------------------- + +/** Describes a sub-agent that can be invoked by a parent agent. */ +export const SubAgentInfoSchema = z.object({ + /** Unique name or identifier of the sub-agent. */ + name: z.string(), + /** What this sub-agent specializes in. Helps clients build agent selection UIs. */ + description: z.string().optional(), +}); + +/** + * Basic metadata about the agent. Useful for discovery UIs, agent marketplaces, + * and debugging. + */ +export const IdentityCapabilitiesSchema = z.object({ + name: z.string().optional(), + type: z.string().optional(), + description: z.string().optional(), + version: z.string().optional(), + provider: z.string().optional(), + documentationUrl: z.string().optional(), + metadata: z.record(z.string(), z.unknown()).optional(), +}); + +/** + * Declares which transport mechanisms the agent supports. + */ +export const TransportCapabilitiesSchema = z.object({ + streaming: z.boolean().optional(), + websocket: z.boolean().optional(), + httpBinary: z.boolean().optional(), + pushNotifications: z.boolean().optional(), + resumable: z.boolean().optional(), +}); + +/** + * Tool calling capabilities. + */ +export const ToolsCapabilitiesSchema = z.object({ + supported: z.boolean().optional(), + items: z.array(ToolSchema).optional(), + parallelCalls: z.boolean().optional(), + clientProvided: z.boolean().optional(), +}); + +/** + * Output format support. + */ +export const OutputCapabilitiesSchema = z.object({ + structuredOutput: z.boolean().optional(), + supportedMimeTypes: z.array(z.string()).optional(), +}); + +/** + * State and memory management capabilities. + */ +export const StateCapabilitiesSchema = z.object({ + snapshots: z.boolean().optional(), + deltas: z.boolean().optional(), + memory: z.boolean().optional(), + persistentState: z.boolean().optional(), +}); + +/** + * Multi-agent coordination capabilities. + */ +export const MultiAgentCapabilitiesSchema = z.object({ + supported: z.boolean().optional(), + delegation: z.boolean().optional(), + handoffs: z.boolean().optional(), + subAgents: z.array(SubAgentInfoSchema).optional(), +}); + +/** + * Reasoning and thinking capabilities. + */ +export const ReasoningCapabilitiesSchema = z.object({ + supported: z.boolean().optional(), + streaming: z.boolean().optional(), + encrypted: z.boolean().optional(), +}); + +/** + * Modalities the agent can accept as input. + */ +export const MultimodalInputCapabilitiesSchema = z.object({ + image: z.boolean().optional(), + audio: z.boolean().optional(), + video: z.boolean().optional(), + pdf: z.boolean().optional(), + file: z.boolean().optional(), +}); + +/** + * Modalities the agent can produce as output. + */ +export const MultimodalOutputCapabilitiesSchema = z.object({ + image: z.boolean().optional(), + audio: z.boolean().optional(), +}); + +/** + * Multimodal input and output support. + */ +export const MultimodalCapabilitiesSchema = z.object({ + input: MultimodalInputCapabilitiesSchema.optional(), + output: MultimodalOutputCapabilitiesSchema.optional(), +}); + +/** + * Execution control and limits. + */ +export const ExecutionCapabilitiesSchema = z.object({ + codeExecution: z.boolean().optional(), + sandboxed: z.boolean().optional(), + maxIterations: z.number().optional(), + maxExecutionTime: z.number().optional(), +}); + +/** + * Human-in-the-loop interaction support. + */ +export const HumanInTheLoopCapabilitiesSchema = z.object({ + supported: z.boolean().optional(), + approvals: z.boolean().optional(), + interventions: z.boolean().optional(), + feedback: z.boolean().optional(), + interrupts: z.boolean().optional(), + approveWithEdits: z.boolean().optional(), +}); + +/** + * A typed, categorized snapshot of an agent's current capabilities. + * Returned by `getCapabilities()` on `AbstractAgent`. + */ +export const AgentCapabilitiesSchema = z.object({ + identity: IdentityCapabilitiesSchema.optional(), + transport: TransportCapabilitiesSchema.optional(), + tools: ToolsCapabilitiesSchema.optional(), + output: OutputCapabilitiesSchema.optional(), + state: StateCapabilitiesSchema.optional(), + multiAgent: MultiAgentCapabilitiesSchema.optional(), + reasoning: ReasoningCapabilitiesSchema.optional(), + multimodal: MultimodalCapabilitiesSchema.optional(), + execution: ExecutionCapabilitiesSchema.optional(), + humanInTheLoop: HumanInTheLoopCapabilitiesSchema.optional(), + custom: z.record(z.string(), z.unknown()).optional(), +}); + diff --git a/sdks/typescript/packages/core/src/types.ts b/sdks/typescript/packages/core/src/types.ts index 0045f17cb1..e3745807ee 100644 --- a/sdks/typescript/packages/core/src/types.ts +++ b/sdks/typescript/packages/core/src/types.ts @@ -1,258 +1,196 @@ -import { z } from "zod"; - -export const FunctionCallSchema = z.object({ - name: z.string(), - arguments: z.string(), -}); - -export const ToolCallSchema = z.object({ - id: z.string(), - type: z.literal("function"), - function: FunctionCallSchema, - encryptedValue: z.string().optional(), -}); - -export const BaseMessageSchema = z.object({ - id: z.string(), - role: z.string(), - content: z.string().optional(), - name: z.string().optional(), - encryptedValue: z.string().optional(), -}); - -export const TextInputContentSchema = z.object({ - type: z.literal("text"), - text: z.string(), -}); - -export const InputContentDataSourceSchema = z.object({ - type: z.literal("data"), - value: z.string(), - mimeType: z.string(), -}); - -export const InputContentUrlSourceSchema = z.object({ - type: z.literal("url"), - value: z.string(), - mimeType: z.string().optional(), -}); - -export const InputContentSourceSchema = z.discriminatedUnion("type", [ - InputContentDataSourceSchema, - InputContentUrlSourceSchema, -]); - -export const ImageInputContentSchema = z.object({ - type: z.literal("image"), - source: InputContentSourceSchema, - metadata: z.unknown().optional(), -}); - -export const AudioInputContentSchema = z.object({ - type: z.literal("audio"), - source: InputContentSourceSchema, - metadata: z.unknown().optional(), -}); - -export const VideoInputContentSchema = z.object({ - type: z.literal("video"), - source: InputContentSourceSchema, - metadata: z.unknown().optional(), -}); - -export const DocumentInputContentSchema = z.object({ - type: z.literal("document"), - source: InputContentSourceSchema, - metadata: z.unknown().optional(), -}); - -export const ImageInputPartSchema = ImageInputContentSchema; -export const AudioInputPartSchema = AudioInputContentSchema; -export const VideoInputPartSchema = VideoInputContentSchema; -export const DocumentInputPartSchema = DocumentInputContentSchema; - -const LegacyBinaryInputContentObjectSchema = z.object({ - type: z.literal("binary"), - mimeType: z.string(), - id: z.string().optional(), - url: z.string().optional(), - data: z.string().optional(), - filename: z.string().optional(), -}); - -const ensureBinaryPayload = ( - value: { id?: string; url?: string; data?: string }, - ctx: z.RefinementCtx, -) => { - if (!value.id && !value.url && !value.data) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "BinaryInputContent requires at least one of id, url, or data.", - path: ["id"], - }); - } -}; - -export const BinaryInputContentSchema = LegacyBinaryInputContentObjectSchema.superRefine( - (value, ctx) => { - ensureBinaryPayload(value, ctx); - }, -); - -const InputContentBaseSchema = z.discriminatedUnion("type", [ - TextInputContentSchema, - ImageInputContentSchema, - AudioInputContentSchema, - VideoInputContentSchema, - DocumentInputContentSchema, - LegacyBinaryInputContentObjectSchema, -]); - -export const InputContentSchema = InputContentBaseSchema.superRefine((value, ctx) => { - if (value.type === "binary") { - ensureBinaryPayload(value, ctx); - } -}); - -export const DeveloperMessageSchema = BaseMessageSchema.extend({ - role: z.literal("developer"), - content: z.string(), -}); - -export const SystemMessageSchema = BaseMessageSchema.extend({ - role: z.literal("system"), - content: z.string(), -}); - -export const AssistantMessageSchema = BaseMessageSchema.extend({ - role: z.literal("assistant"), - content: z.string().optional(), - toolCalls: z.array(ToolCallSchema).optional(), -}); - -export const UserMessageSchema = BaseMessageSchema.extend({ - role: z.literal("user"), - content: z.union([z.string(), z.array(InputContentSchema)]), -}); - -export const ToolMessageSchema = z.object({ - id: z.string(), - content: z.string(), - role: z.literal("tool"), - toolCallId: z.string(), - error: z.string().optional(), - encryptedValue: z.string().optional(), -}); - -export const ActivityMessageSchema = z.object({ - id: z.string(), - role: z.literal("activity"), - activityType: z.string(), - content: z.record(z.any()), -}); - -export const ReasoningMessageSchema = z.object({ - id: z.string(), - role: z.literal("reasoning"), - content: z.string(), - encryptedValue: z.string().optional(), -}); - -export const MessageSchema = z.discriminatedUnion("role", [ - DeveloperMessageSchema, - SystemMessageSchema, - AssistantMessageSchema, - UserMessageSchema, - ToolMessageSchema, - ActivityMessageSchema, - ReasoningMessageSchema, -]); - -export const RoleSchema = z.union([ - z.literal("developer"), - z.literal("system"), - z.literal("assistant"), - z.literal("user"), - z.literal("tool"), - z.literal("activity"), - z.literal("reasoning"), -]); - -export const ContextSchema = z.object({ - description: z.string(), - value: z.string(), -}); - -export const ToolSchema = z.object({ - name: z.string(), - description: z.string(), - parameters: z.any(), // JSON Schema for the tool parameters - metadata: z.record(z.any()).optional(), // Arbitrary tool metadata (e.g. a2ui schema) -}); - -export const InterruptSchema = z.object({ - id: z.string(), - reason: z.string(), - message: z.string().optional(), - toolCallId: z.string().optional(), - responseSchema: z.record(z.any()).optional(), - expiresAt: z.string().optional(), - metadata: z.record(z.any()).optional(), -}); - -export const ResumeEntrySchema = z.object({ - interruptId: z.string(), - status: z.enum(["resolved", "cancelled"]), - payload: z.any().optional(), -}); - -export const RunAgentInputSchema = z.object({ - threadId: z.string(), - runId: z.string(), - parentRunId: z.string().optional(), - state: z.any(), - messages: z.array(MessageSchema), - tools: z.array(ToolSchema), - context: z.array(ContextSchema), - forwardedProps: z.any(), - resume: z.array(ResumeEntrySchema).optional(), -}); - -export const StateSchema = z.any(); - -export type ToolCall = z.infer; -export type FunctionCall = z.infer; -export type TextInputContent = z.infer; -export type InputContentDataSource = z.infer; -export type InputContentUrlSource = z.infer; -export type InputContentSource = z.infer; -export type ImageInputContent = z.infer; -export type AudioInputContent = z.infer; -export type VideoInputContent = z.infer; -export type DocumentInputContent = z.infer; +// Canonical TypeScript types for AG-UI messages, tools, run input, and core +// supporting types. This is the authoritative type surface of @ag-ui/core. + +export interface FunctionCall { + name: string; + arguments: string; +} + +export interface ToolCall { + id: string; + type: "function"; + function: FunctionCall; + encryptedValue?: string; +} + +export interface TextInputContent { + type: "text"; + text: string; +} + +export interface InputContentDataSource { + type: "data"; + value: string; + mimeType: string; +} + +export interface InputContentUrlSource { + type: "url"; + value: string; + mimeType?: string; +} + +export type InputContentSource = InputContentDataSource | InputContentUrlSource; + +export interface ImageInputContent { + type: "image"; + source: InputContentSource; + metadata?: unknown; +} + +export interface AudioInputContent { + type: "audio"; + source: InputContentSource; + metadata?: unknown; +} + +export interface VideoInputContent { + type: "video"; + source: InputContentSource; + metadata?: unknown; +} + +export interface DocumentInputContent { + type: "document"; + source: InputContentSource; + metadata?: unknown; +} + export type ImageInputPart = ImageInputContent; export type AudioInputPart = AudioInputContent; export type VideoInputPart = VideoInputContent; export type DocumentInputPart = DocumentInputContent; -export type BinaryInputContent = z.infer; -export type InputContent = z.infer; -export type InputContentPart = z.infer; -export type DeveloperMessage = z.infer; -export type SystemMessage = z.infer; -export type AssistantMessage = z.infer; -export type UserMessage = z.infer; -export type ToolMessage = z.infer; -export type ActivityMessage = z.infer; -export type ReasoningMessage = z.infer; -export type Message = z.infer; -export type Context = z.infer; -export type Tool = z.infer; -export type RunAgentInput = z.infer; -export type State = z.infer; -export type Role = z.infer; -export type Interrupt = z.infer; -export type ResumeEntry = z.infer; -export type ResumeStatus = z.infer["status"]; + +export interface BinaryInputContent { + type: "binary"; + mimeType: string; + id?: string; + url?: string; + data?: string; + filename?: string; +} + +export type InputContent = + | TextInputContent + | ImageInputContent + | AudioInputContent + | VideoInputContent + | DocumentInputContent + | BinaryInputContent; + +export type InputContentPart = InputContent; + +interface BaseMessageFields { + id: string; + name?: string; + encryptedValue?: string; +} + +export interface DeveloperMessage extends BaseMessageFields { + role: "developer"; + content: string; +} + +export interface SystemMessage extends BaseMessageFields { + role: "system"; + content: string; +} + +export interface AssistantMessage extends BaseMessageFields { + role: "assistant"; + content?: string; + toolCalls?: ToolCall[]; +} + +export interface UserMessage extends BaseMessageFields { + role: "user"; + content: string | InputContent[]; +} + +export interface ToolMessage { + id: string; + content: string; + role: "tool"; + toolCallId: string; + error?: string; + encryptedValue?: string; +} + +export interface ActivityMessage { + id: string; + role: "activity"; + activityType: string; + content: Record; +} + +export interface ReasoningMessage { + id: string; + role: "reasoning"; + content: string; + encryptedValue?: string; +} + +export type Message = + | DeveloperMessage + | SystemMessage + | AssistantMessage + | UserMessage + | ToolMessage + | ActivityMessage + | ReasoningMessage; + +export type Role = + | "developer" + | "system" + | "assistant" + | "user" + | "tool" + | "activity" + | "reasoning"; + +export interface Context { + description: string; + value: string; +} + +export interface Tool { + name: string; + description: string; + parameters?: any; + metadata?: Record; +} + +export interface Interrupt { + id: string; + reason: string; + message?: string; + toolCallId?: string; + responseSchema?: Record; + expiresAt?: string; + metadata?: Record; +} + +export type ResumeStatus = "resolved" | "cancelled"; + +export interface ResumeEntry { + interruptId: string; + status: ResumeStatus; + payload?: any; +} + +export interface RunAgentInput { + threadId: string; + runId: string; + parentRunId?: string; + state?: any; + messages: Message[]; + tools: Tool[]; + context: Context[]; + forwardedProps?: any; + resume?: ResumeEntry[]; +} + +export type State = any; export class AGUIError extends Error { constructor(message: string) { diff --git a/sdks/typescript/packages/core/tsdown.config.ts b/sdks/typescript/packages/core/tsdown.config.ts index 3b532acd9e..748272eb9c 100644 --- a/sdks/typescript/packages/core/tsdown.config.ts +++ b/sdks/typescript/packages/core/tsdown.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "tsdown"; export default defineConfig((inlineConfig) => ({ - entry: ["src/index.ts"], + entry: ["src/index.ts", "src/schemas.ts"], format: ["cjs", "esm"], dts: true, exports: true, diff --git a/sdks/typescript/packages/proto/package.json b/sdks/typescript/packages/proto/package.json index 16a6de60cb..82121a0032 100644 --- a/sdks/typescript/packages/proto/package.json +++ b/sdks/typescript/packages/proto/package.json @@ -25,7 +25,8 @@ "dependencies": { "@ag-ui/core": "workspace:*", "@bufbuild/protobuf": "^2.2.5", - "@protobuf-ts/protoc": "^2.11.1" + "@protobuf-ts/protoc": "^2.11.1", + "zod": "^3.24.0 || ^4.0.0" }, "devDependencies": { "@vitest/coverage-istanbul": "^4.0.18", @@ -38,6 +39,7 @@ }, "exports": { ".": { + "types": "./dist/index.d.ts", "require": "./dist/index.js", "import": "./dist/index.mjs" }, diff --git a/sdks/typescript/packages/proto/src/proto.ts b/sdks/typescript/packages/proto/src/proto.ts index 7d18200b4a..9fa349c366 100644 --- a/sdks/typescript/packages/proto/src/proto.ts +++ b/sdks/typescript/packages/proto/src/proto.ts @@ -1,11 +1,11 @@ import { BaseEvent, AGUIEvent, - EventSchemas, EventType, Message, RunFinishedOutcome, } from "@ag-ui/core"; +import { EventSchemas } from "@ag-ui/core/schemas"; import * as protoEvents from "./generated/events"; import * as protoPatch from "./generated/patch";