agents
Version:
A home for your AI agents
256 lines (254 loc) • 8.41 kB
JavaScript
import "./email-XHsSYsTO.js";
import "./internal_context-D9eKFth1.js";
import "./client-CtC9E06G.js";
import "./do-oauth-client-provider-DDg8QrEA.js";
import { o as getAgentByName } from "./src-i_UcyBYf.js";
import { WorkflowRejectedError } from "./workflow-types.js";
import { WorkflowEntrypoint } from "cloudflare:workers";
//#region src/workflows.ts
/**
* AgentWorkflow - Base class for Workflows that integrate with Agents
*
* Extends Cloudflare's WorkflowEntrypoint to provide seamless access to
* the Agent that started the workflow, enabling bidirectional communication.
*
* @example
* ```typescript
* import { AgentWorkflow } from 'agents/workflows';
* import type { MyAgent } from './agent';
*
* type TaskParams = { taskId: string; data: string };
*
* export class ProcessingWorkflow extends AgentWorkflow<MyAgent, TaskParams> {
* async run(event: AgentWorkflowEvent<TaskParams>, step: WorkflowStep) {
* // Access the originating Agent via typed RPC
* await this.agent.updateTaskStatus(event.payload.taskId, 'processing');
*
* const result = await step.do('process', async () => {
* // ... processing logic
* return { processed: true };
* });
*
* // Report progress to Agent (typed)
* await this.reportProgress({ step: 'process', status: 'complete', percent: 0.5 });
*
* // Broadcast to connected clients
* await this.broadcastToClients({ type: 'progress', data: result });
*
* return result;
* }
* }
* ```
*/
/**
* WeakSet to track which prototypes have been wrapped.
* This prevents re-wrapping on subsequent instantiations of the same class.
*/
const wrappedPrototypes = /* @__PURE__ */ new WeakSet();
/**
* Base class for Workflows that need access to their originating Agent.
*
* @template AgentType - The Agent class type (for typed RPC access)
* @template Params - User-defined params passed to the workflow (optional)
* @template ProgressType - Type for progress reporting (defaults to DefaultProgress)
* @template Env - Environment type (defaults to Cloudflare.Env)
*/
var AgentWorkflow = class extends WorkflowEntrypoint {
constructor(ctx, env) {
super(ctx, env);
this.__agentInitCalled = false;
const proto = Object.getPrototypeOf(this);
if (Object.hasOwn(proto, "run") && !wrappedPrototypes.has(proto)) {
const originalRun = proto.run;
proto.run = async function(event, step) {
if (!this.__agentInitCalled) {
const { __agentName, __agentBinding, __workflowName, ...userParams } = event.payload;
await this._initAgent(__agentName, __agentBinding, __workflowName, event.instanceId);
this.__agentInitCalled = true;
const cleanedEvent = {
...event,
payload: userParams
};
const wrappedStep = this._wrapStep(step);
return originalRun.call(this, cleanedEvent, wrappedStep);
}
return originalRun.call(this, event, step);
};
wrappedPrototypes.add(proto);
}
}
/**
* Initialize the Agent stub from workflow params.
* Called automatically before run() executes.
*/
async _initAgent(agentName, agentBinding, workflowName, instanceId) {
if (!agentName || !agentBinding || !workflowName) throw new Error("AgentWorkflow requires __agentName, __agentBinding, and __workflowName in params. Use agent.runWorkflow() to start workflows with proper agent context.");
this._workflowId = instanceId;
this._workflowName = workflowName;
const namespace = this.env[agentBinding];
if (!namespace) throw new Error(`Agent binding '${agentBinding}' not found in environment`);
this._agent = await getAgentByName(namespace, agentName);
}
/**
* Wrap WorkflowStep with durable Agent communication methods.
* Methods added to the wrapped step are idempotent and won't repeat on retry.
*
* Note: We add methods directly to the step object to preserve instanceof checks
* that Cloudflare's runtime may perform on the WorkflowStep class.
*/
_wrapStep(step) {
let stepCounter = 0;
const wrappedStep = step;
wrappedStep.reportComplete = async (result) => {
await step.do(`__agent_reportComplete_${stepCounter++}`, async () => {
await this.notifyAgent({
workflowName: this._workflowName,
workflowId: this._workflowId,
type: "complete",
result,
timestamp: Date.now()
});
});
};
wrappedStep.reportError = async (error) => {
const errorMessage = error instanceof Error ? error.message : error;
await step.do(`__agent_reportError_${stepCounter++}`, async () => {
await this.notifyAgent({
workflowName: this._workflowName,
workflowId: this._workflowId,
type: "error",
error: errorMessage,
timestamp: Date.now()
});
});
};
wrappedStep.sendEvent = async (event) => {
await step.do(`__agent_sendEvent_${stepCounter++}`, async () => {
await this.notifyAgent({
workflowName: this._workflowName,
workflowId: this._workflowId,
type: "event",
event,
timestamp: Date.now()
});
});
};
wrappedStep.updateAgentState = async (state) => {
await step.do(`__agent_updateState_${stepCounter++}`, async () => {
this.agent._workflow_updateState("set", state);
});
};
wrappedStep.mergeAgentState = async (partialState) => {
await step.do(`__agent_mergeState_${stepCounter++}`, async () => {
this.agent._workflow_updateState("merge", partialState);
});
};
wrappedStep.resetAgentState = async () => {
await step.do(`__agent_resetState_${stepCounter++}`, async () => {
this.agent._workflow_updateState("reset");
});
};
return wrappedStep;
}
/**
* Get the Agent stub for RPC calls.
* Provides typed access to the Agent's methods.
*
* @example
* ```typescript
* // Call any public method on the Agent
* await this.agent.updateStatus('processing');
* const data = await this.agent.getData();
* ```
*/
get agent() {
if (!this._agent) throw new Error("Agent not initialized. Ensure you're accessing this.agent inside run().");
return this._agent;
}
/**
* Get the workflow instance ID
*/
get workflowId() {
return this._workflowId;
}
/**
* Get the workflow binding name
*/
get workflowName() {
return this._workflowName;
}
/**
* Send a notification to the Agent via RPC.
*
* @param callback - Callback payload to send
*/
async notifyAgent(callback) {
await this.agent._workflow_handleCallback(callback);
}
/**
* Report progress to the Agent with typed progress data.
* Triggers onWorkflowProgress() on the Agent.
*
* @param progress - Typed progress data
*
* @example
* ```typescript
* // Using default progress type
* await this.reportProgress({ step: 'fetch', status: 'running' });
* await this.reportProgress({ step: 'fetch', status: 'complete', percent: 0.5 });
*
* // With custom progress type
* await this.reportProgress({ stage: 'extract', recordsProcessed: 100 });
* ```
*/
async reportProgress(progress) {
await this.notifyAgent({
workflowName: this._workflowName,
workflowId: this._workflowId,
type: "progress",
progress,
timestamp: Date.now()
});
}
/**
* Broadcast a message to all connected WebSocket clients via the Agent.
* This is non-durable and may repeat on workflow retry.
*
* @param message - Message to broadcast (will be JSON-stringified)
*/
broadcastToClients(message) {
this.agent._workflow_broadcast(message);
}
/**
* Wait for approval from the Agent.
* Handles rejection by reporting error (durably) and throwing WorkflowRejectedError.
*
* @param step - AgentWorkflowStep object
* @param options - Wait options (timeout, eventType, stepName)
* @returns Approval payload (throws WorkflowRejectedError if rejected)
*
* @example
* ```typescript
* const approval = await this.waitForApproval(step, { timeout: '7 days' });
* // approval contains the payload from approveWorkflow()
* ```
*/
async waitForApproval(step, options) {
const stepName = options?.stepName ?? "wait-for-approval";
const eventType = options?.eventType ?? "approval";
const timeout = options?.timeout;
const payload = (await step.waitForEvent(stepName, {
type: eventType,
timeout
})).payload;
if (!payload.approved) {
const reason = payload.reason;
await step.reportError(reason ?? "Workflow rejected");
throw new WorkflowRejectedError(reason, this._workflowId);
}
return payload.metadata;
}
};
//#endregion
export { AgentWorkflow, WorkflowRejectedError };
//# sourceMappingURL=workflows.js.map