@artinet/sdk
Version:
A TypeScript SDK for building collaborative AI agents.
124 lines (123 loc) • 4.5 kB
JavaScript
/**
* Copyright 2025 The Artinet Project
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview A2A Agent Builder and Execution Engine 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
*
* **Basic Usage:**
* ```typescript
* import { cr8 } from "@artinet/sdk";
*
* const agent = cr8("MyAgent")
* .text(({ content }) => `You said: ${content}`)
* .data(({ content }) => ({ length: content?.length }))
* .agent;
* ```
*
* @module A2ABuilder
* @version 0.6
* @since 0.5.6
* @author The Artinet Project
*/
import { A2A } from "../types/index.js";
import * as transform from './transform.js';
import { describe } from './index.js';
import { extractTextContent } from '../services/a2a/helpers/content.js';
import { logger } from "../config/index.js";
import { formatJson } from "../utils/utils.js";
import { isProcessing } from "../utils/constants.js";
/**
* Creates an agent execution engine from a list of workflow steps.
*
* This function transforms a list of resolved step definitions into an executable
* A2A engine that processes contexts through the defined workflow. The engine
* is an async generator that yields updates as each step completes.
*
* **Execution Flow:**
* 1. Yields "submitted" status update
* 2. Executes each step in order, yielding transformed results
* 3. Passes carried args from one step to the next
* 4. Yields final task on completion
*
* @param stepsList - Array of resolved workflow steps (from AgentFactory.steps)
* @returns A2A.Engine async generator function
* @throws Error if stepsList is empty
*
* @example
* ```typescript
* // Typically accessed via AgentFactory
* const engine = cr8("MyAgent")
* .text("Hello")
* .data({ timestamp: Date.now() })
* .engine;
*
* // Or create manually from steps
* const engine = createStepEngine(factory.steps);
*
* // Execute the engine
* for await (const update of engine(context)) {
* console.log(update.kind, update);
* }
* ```
*
* @public
* @since 0.5.6
*/
export function createStepEngine(stepsList) {
if (stepsList.length === 0) {
throw new Error('No steps provided');
}
return async function* (context) {
logger.info(`engine[context:${context.contextId}]: starting`);
logger.debug(`engine[context:${context.contextId}]: taskId: ${context.taskId}`);
const content = extractTextContent(context.userMessage);
let _skipStep = false;
const input = {
message: context.messages,
context: context,
content: content,
skip: () => {
_skipStep = true;
return;
},
};
const submitted = describe.update.submitted({
contextId: context.contextId,
taskId: context.taskId,
});
logger.debug(`engine[context:${context.contextId}]: submitted`);
yield submitted;
for (const step of stepsList) {
if (await context.isCancelled()) {
break;
}
logger.debug(`engine[context:${context.contextId}]: executing step[${step.id}]: ${step.kind}`);
const ret = await step.step({ ...input });
if (_skipStep) {
_skipStep = false;
logger.debug(`engine[context:${context.contextId}]: skipping step[${step.id}]`);
continue;
}
logger.debug(`engine[context:${context.contextId}]: transforming step[${step.id}]`);
const carried = yield* transform.Reply(ret, context, step.handler);
input.args = carried;
}
const task = await context.getTask();
logger.debug(`engine[context:${context.contextId}]: completed task[${task.id}]: ${formatJson(task)}`);
task.status.state = isProcessing(task.status.state) ? A2A.TaskState.completed : task.status.state;
yield task;
};
}