UNPKG

@artinet/sdk

Version:

A TypeScript SDK for building collaborative AI agents.

396 lines (395 loc) 15.1 kB
/** * Copyright 2025 The Artinet Project * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Agent Factory * * This module provides a fluent builder API for constructing A2A agents and * execution engines. It enables declarative definition of multi-step agent * workflows with type-safe step composition and automatic execution orchestration. * * **Key Features:** * - Fluent API with method chaining (`.text()`, `.data()`, `.file()`, etc.) * - Type-safe argument passing between steps via `args` carry pattern * - Multiple output types: text, file, data, message, artifact, status, task * - Agent-to-agent orchestration via `.sendMessage()` * - Static value shortcuts for simple steps * - Step skipping via `skip()` function * * @module AgentFactory * @version 0.6 * @since 0.5.6 * @author The Artinet Project */ import { A2A } from "../types/index.js"; import * as A from './agent-builder.js'; import { Service } from "../services/a2a/service.js"; import { TaskParams } from './task-builder.js'; import { ServerParams as BaseServerParams } from "../server/params.js"; import { FactoryParams, textStep, fileStep, dataStep, messageStep, artifactStep, statusStep, taskStep, MessageSender } from './create.js'; /** * Fluent builder for constructing A2A agent execution engines. * * AgentFactory provides a type-safe, fluent API for composing multi-step * agent workflows. It supports method chaining to build complex agent behaviors * from individual processing steps, with automatic type inference for carried * arguments between steps. * * @template I - The arguments type received from previous steps (inferred automatically) * * @example * ```typescript * // Basic agent with text steps * const agent = cr8("MyAgent") * .text(({ content }) => `You said: ${content}`) * .agent; * * // Agent with carried args between steps * const agent = cr8("AnalysisAgent") * .text(({ content }) => ({ * reply: `Analyzing: ${content}`, * args: { originalContent: content } * })) * .data(({ args }) => ({ * wordCount: args?.originalContent?.split(' ').length, * timestamp: Date.now() * })) * .text(({ args }) => `Analysis complete: ${args?.wordCount} words`) * .agent; * * // Agent-to-agent orchestration * const orchestrator = cr8("Orchestrator") * .text("Starting multi-agent workflow...") * .sendMessage({ agent: otherAgent, message: "Process this" }) * .text(({ args }) => `Got result: ${args?.task?.status.state}`) * .agent; * ``` * * @public * @since 0.5.6 */ export declare class AgentFactory<ServerParams extends BaseServerParams, ServerType, I extends A.bargs = A.empty> implements A.AgentBuilder<I> { private readonly _agentCard; private readonly _params?; private readonly _steps; private readonly _serve; /** * Protected constructor to enforce factory method usage. * @param agentCard - The agent card to use * @param params - The parameters to use * @param steps - Initial steps array */ protected constructor(_agentCard: A2A.AgentCard, _params?: FactoryParams<ServerParams> | undefined, _steps?: Array<A.Resolved<any, any, any, any, any>>, _serve?: (params: { agent: Service; serverParams?: FactoryParams<ServerParams>; }) => ServerType); /** * Builds the step list for the workflow. * * @returns Array of workflow steps * @throws Error if no steps have been added * * @example * ```typescript * const steps = cr8.steps; * ``` */ get steps(): Array<A.Resolved<any, any, any, any, any>>; /** * The {@link A2A.AgentCard} to use * @returns The {@link A2A.AgentCard} */ get agentCard(): A2A.AgentCard; /** * The {@link FactoryParams} to use * @returns The {@link FactoryParams} */ get params(): FactoryParams<ServerParams> | undefined; /** * Creates an agent execution engine from the built workflow. * * @returns The {@link A2A.Engine} * * @example * ```typescript * const engine = builder.engine; * // Use engine with service execution * ``` */ get engine(): A2A.Engine; /** * Creates a complete A2A agent using the built workflow. * * @param params - The {@link ServiceParams} to use * @returns The {@link Service} * * @example * ```typescript * const agent = cr8({ * id: 'my-agent', * name: 'Assistant Agent', * capabilities: ['text-processing'] * }).agent; * ``` */ get agent(): Service; get server(): ServerType; from(engine?: A2A.Engine): Service; serve(engine?: A2A.Engine): ServerType; addStep<Ret extends A.AcceptedReturnValues = A.text, C extends A.bargs = A.empty, R extends A.rep<Ret, C> = A.rep<Ret, C>, Kind extends A.AcceptedKinds = 'text'>(step: A.Resolved<Ret, I, C, R, Kind>): AgentFactory<ServerParams, ServerType, A.inferCarry<R>>; /** * Adds a text processing step to the workflow. * * Text steps are the most common step type, producing text content that * becomes a TextPart in the agent's response message. * * @param step - A text step function or static string value * @returns New builder instance with updated type parameters * * @example * ```typescript * // Static text * builder.text("Hello, world!") * * // Dynamic text from content * builder.text(({ content }) => `You said: ${content}`) * * // With carried args * builder.text(({ args }) => ({ * reply: `Processing ${args?.itemCount} items`, * args: { processedAt: Date.now() } * })) * ``` */ text<C extends A.bargs = A.empty>(text: A.text): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.text, C>>>; text<C extends A.bargs = A.empty>(step: textStep<I, C>): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.text, C>>>; /** * Adds a file processing step to the workflow. * * File steps produce file content that becomes a FilePart in the agent's * response. Files can be specified by URI or inline bytes/base64 content. * * @param step - A file step function or static file object * @returns New builder instance with updated type parameters * * @example * ```typescript * // Static file by URI * builder.file({ uri: "https://example.com/doc.pdf" }) * * // Dynamic file generation * builder.file(async ({ args }) => ({ * name: 'report.pdf', * mimeType: 'application/pdf', * bytes: await generatePDF(args?.data) * })) * * // Multiple files * builder.file(() => [ * { uri: "https://example.com/file1.pdf" }, * { uri: "https://example.com/file2.pdf" } * ]) * ``` */ file<C extends A.bargs = A.empty, R extends A.rep<A.file, C> = A.rep<A.file, C>>(file: A.file): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.file, C>>>; file<C extends A.bargs = A.empty, R extends A.rep<A.file, C> = A.rep<A.file, C>>(step: fileStep<I, C>): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.file, C>>>; /** * Adds a data processing step to the workflow. * * Data steps produce structured JSON data that becomes a DataPart in the * agent's response. Useful for returning complex objects, API responses, * or any structured data. * * @param step - A data step function or static data object * @returns New builder instance with updated type parameters * * @example * ```typescript * // Static data * builder.data({ status: "ok", version: "1.0.0" }) * * // Dynamic data * builder.data(async ({ content }) => ({ * analysis: await analyzeText(content), * timestamp: Date.now() * })) * * // With carried args * builder.data(({ args }) => ({ * reply: { result: args?.computedValue * 2 }, * args: { doubled: true } * })) * ``` */ data<C extends A.bargs = A.empty>(data: A.data): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.data, C>>>; data<C extends A.bargs = A.empty>(step: dataStep<I, C>): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.data, C>>>; /** * Adds a message step to the workflow. * * Message steps yield complete A2A messages with full control over role, * parts, and metadata. Use when you need to construct complex multi-part * messages or control the message structure directly. * * @param step - A message step function or static message/string * @returns New builder instance with updated type parameters * * @example * ```typescript * // Simple string message * builder.message("Hello from the agent!") * * // Full message with parts * builder.message(({ context }) => ({ * role: "agent", * parts: [ * { kind: "text", text: "Here are your files:" }, * { kind: "file", file: { uri: "https://example.com/doc.pdf" } } * ] * })) * * // Using describe helper * builder.message(({ args }) => describe.message({ * role: "agent", * parts: [{ kind: "text", text: args?.greeting }] * })) * ``` */ message<C extends A.bargs = A.empty>(message: A.sMessage): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.sMessage, C>>>; message<C extends A.bargs = A.empty>(step: messageStep<I, C>): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.sMessage, C>>>; /** * Adds an artifact step to the workflow. * * Artifact steps create persistent, versioned outputs that can be referenced * across task sessions. Use for documents, generated files, or content that * clients may need to retrieve later. * * @param step - An artifact step function or static artifact object * @returns New builder instance with updated type parameters * * @example * ```typescript * // Static artifact * builder.artifact(describe.artifact({ * artifactId: "report-001", * parts: [{ kind: "text", text: "Report content" }] * })) * * // Dynamic artifact * builder.artifact(async ({ context, args }) => ({ * artifactId: `analysis-${context.taskId}`, * name: "Analysis Results", * parts: [{ kind: "data", data: args?.analysisData }] * })) * ``` */ artifact<C extends A.bargs = A.empty>(step: artifactStep<I, C>): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.sArtifact, C>>>; artifact<C extends A.bargs = A.empty>(artifact: A.sArtifact): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.sArtifact, C>>>; /** * Adds a status update step to the workflow. * * Status steps emit task state updates during execution. Use to communicate * progress, intermediate states, or completion to clients. Supports simple * state strings or full status objects with messages. * * @param step - A status step function, status object, or state string * @returns New builder instance with updated type parameters * * @example * ```typescript * // Simple state string * builder.status("working") * * // Status with message * builder.status(({ args }) => ({ * status: { * state: A2A.TaskState.working, * message: describe.message(`Step ${args?.step} of 5 complete`) * } * })) * * // Mark completion * builder.status(() => ({ * status: { state: A2A.TaskState.completed } * })) * ``` */ status<C extends A.bargs = A.empty>(status: A.sUpdate): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.sUpdate, C>>>; status<C extends A.bargs = A.empty>(step: statusStep<I, C>): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.sUpdate, C>>>; /** * Adds a task step to the workflow. * * Task steps yield complete A2A task objects. Use when you need full control * over the task representation, including status, artifacts, and history. * Particularly useful for orchestration scenarios or final task construction. * * @param step - A task step function, task object, or string * @returns New builder instance with updated type parameters * * @example * ```typescript * // Simple string (auto-converted to task) * builder.task("Operation completed") * * // Full task object * builder.task(({ context }) => describe.task({ * id: context.taskId, * contextId: context.contextId, * status: { state: A2A.TaskState.completed } * })) * * // With carried args * builder.task(({ context }) => ({ * reply: describe.task({ id: context.taskId }), * args: { completedAt: Date.now() } * })) * ``` */ task<C extends A.bargs = A.empty>(task: A.sTask): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.sTask, C>>>; task<C extends A.bargs = A.empty>(step: taskStep<I, C>): AgentFactory<ServerParams, ServerType, A.inC<A.rep<A.sTask, C>>>; /** * Adds an agent-to-agent orchestration step to the workflow. * * This step sends a message to another agent (local Service or remote A2A Server) * and yields the response as a task. Enables multi-agent workflows where one * agent delegates work to others. * * **Note:** This is currently a blocking call. Streaming responses are not * yet supported in orchestration steps. * @note Args passed from the previous step are inserted, by default, * (`unshift`) as `DataPart`s onto the forwarded `Message`.`Parts`. * * @param agent - The target agent (Agent or AgentMessenger) * @param message - Message to send (defaults to context.userMessage) * @returns New builder instance with task carry args (args.task) * * @example * ```typescript * // Delegate to another agent * const orchestrator = cr8("Orchestrator") * .text("Starting workflow...") * .sendMessage({ agent: analysisAgent, message: "Analyze this data" }) * .text(({ args }) => `Analysis result: ${args?.task?.status.state}`) * .agent; * * // Chain multiple agents * const pipeline = cr8("Pipeline") * .sendMessage({ agent: preprocessor }) * .sendMessage({ agent: analyzer }) * .sendMessage({ agent: postprocessor }) * .text(({ args }) => `Final result: ${args?.task?.status.message}`) * .agent; * * // Forward user's message to another agent * const proxy = cr8("Proxy") * .sendMessage({ agent: targetAgent }) // uses context.userMessage * .agent; * ``` */ sendMessage<Carry extends A.BaseArgs = { task?: A2A.Task; }>(agent_and_message: { agent: MessageSender; message?: A.sMessage | string; }): AgentFactory<ServerParams, ServerType, A.inferCarry<A.Reply<A.Stateless<TaskParams>, Carry>>>; }