@artinet/sdk
Version:
A TypeScript SDK for building collaborative AI agents.
396 lines (395 loc) • 15.1 kB
TypeScript
/**
* 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>>>;
}