Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c1d0941
refactor: simplify chatRestore middleware by removing unnecessary typ…
VISHWAJ33T Dec 28, 2025
503a21a
refactor: update middleware usage in chat route to use 'before' inste…
VISHWAJ33T Dec 28, 2025
5a94b30
feat: add workflow runtime adapter for ai-router integration with dyn…
VISHWAJ33T Dec 28, 2025
e02338b
feat: implement workflow management in AiRouter with dynamic endpoint…
VISHWAJ33T Dec 28, 2025
041b2c2
feat: add workflow module with createWorkflow function and export wor…
VISHWAJ33T Dec 28, 2025
fe97329
feat: update package.json and tsup.config.ts to include workflow modu…
VISHWAJ33T Dec 28, 2025
d397df9
feat: implement onboarding workflow with user creation and email veri…
VISHWAJ33T Dec 28, 2025
6c29334
feat: add research workflow with input validation, web search, summar…
VISHWAJ33T Dec 28, 2025
41dbd7c
feat: add shared router for AI workflows with research and onboarding…
VISHWAJ33T Dec 28, 2025
d05023c
feat: create onboarding workflow page with user input, email verifica…
VISHWAJ33T Dec 28, 2025
a764f55
feat: add research workflow page with user input, error handling, and…
VISHWAJ33T Dec 28, 2025
3c34589
feat: add workflows page with examples for research and onboarding wo…
VISHWAJ33T Dec 28, 2025
55459d2
feat: integrate workflow module into Next.js configuration and update…
VISHWAJ33T Dec 28, 2025
20b1bd8
fix: update AiRouter instantiation to remove undefined parameters for…
VISHWAJ33T Dec 28, 2025
5f161b8
Merge branch 'main' into feat/workflow-integration-and-example
VISHWAJ33T Jan 2, 2026
9e24403
chore: update dependencies and improve workflow integration in Next.j…
VISHWAJ33T Jan 2, 2026
f9fd512
refactor: comment out workflow adapter implementation for future revi…
VISHWAJ33T Jan 18, 2026
fc5a6ab
refactor: update AiRouter to handle workflows via API routes and comm…
VISHWAJ33T Jan 18, 2026
e44c6fe
refactor: comment out workflow type definitions and implementation fo…
VISHWAJ33T Jan 18, 2026
0249d4e
refactor: update workflow exports to include orchestrate and comment …
VISHWAJ33T Jan 18, 2026
4ad0fa6
refactor: add orchestrate workflow export to package.json for improve…
VISHWAJ33T Jan 18, 2026
dbbe342
feat: implement orchestration workflow with step definitions and buil…
VISHWAJ33T Jan 18, 2026
6fc8609
fix: update entry point for workflow to use orchestrate module for im…
VISHWAJ33T Jan 18, 2026
6b64496
fix: enable UI visibility for current time agent and enhance response…
VISHWAJ33T Jan 18, 2026
8ac65f9
refactor: comment out workflow imports and router setup for future re…
VISHWAJ33T Jan 18, 2026
53bf0f8
refactor: comment out workflow router integration for future evaluation
VISHWAJ33T Jan 18, 2026
62c22e4
refactor: comment out workflow implementations and schemas for future…
VISHWAJ33T Jan 18, 2026
74dc681
feat: add agent workflow and step functions for HTTP integration
VISHWAJ33T Jan 18, 2026
fd586dc
feat: implement POST endpoint for orchestration workflow initiation w…
VISHWAJ33T Jan 18, 2026
eda1d3c
feat: add orchestrateWorkflow function with step execution logic for …
VISHWAJ33T Jan 18, 2026
d8cd85b
feat: add step execution functions for orchestration workflow, includ…
VISHWAJ33T Jan 18, 2026
36ebc5a
chore: update workflow dependency to version 4.0.1-beta.41 in package…
VISHWAJ33T Jan 18, 2026
8a804d5
feat: add POST endpoint for signaling/resuming workflows and GET endp…
VISHWAJ33T Jan 18, 2026
b11965d
feat: add Orchestration Workflow page and UI components for initiatin…
VISHWAJ33T Jan 18, 2026
3ad65e6
chore: update workflow dependencies and package-lock.json for consist…
VISHWAJ33T Jan 18, 2026
d5b857e
feat: add ScheduleEventConfig interface and schedule configuration to…
VISHWAJ33T Jan 18, 2026
7229e34
feat: implement schedule processing for worker events in serverless c…
VISHWAJ33T Jan 18, 2026
9c24996
chore: comment out schedule configuration in pdf.worker.ts for future…
VISHWAJ33T Jan 18, 2026
66bc958
Merge pull request #44 from microfox-ai/feat/workflow-orchestration-u…
VISHWAJ33T Jan 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ type Output = z.infer<typeof OutputSchema>;
export const workerConfig: WorkerConfig = {
timeout: 300, // 5 minutes
memorySize: 1024, // 1GB
// schedule: [{
// method: 'scheduler',
// rate: ['cron(0 0/4 ? * MON-FRI *)'],
// timezone: 'America/New_York',
// input: { key1: 'value1' }
// },
// {
// rate: 'rate(10 minutes)',
// enabled: true,
// input: { key1: 'value1', key2: 'value2' }
// },
// 'rate(2 hours)',
// { rate: 'cron(0 12 * * ? *)', enabled: false }
// ]
};

export const pdfWorker = createWorker<typeof InputSchema, Output>({
Expand Down
11 changes: 7 additions & 4 deletions examples/root/app/ai/agents/system/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ export const systemAgent = aiRouter
inputSchema: z.object({}),
execute: async () => ({ result: new Date().toLocaleDateString() }),
metadata: {
hideUI: true,
hideUI: false, // Set to false so return value is written to stream for workflow steps
},
})
.agent('/current_time', async (ctx) => {
const { format } = ctx.request.params;
ctx.response.write({ type: 'data-start', data: 'Getting current time...' });
const currentTime = new Date().toLocaleTimeString('en-US', {
hour12: format === '12h',
})
ctx.response.write({ type: 'data-end', data: `${currentTime}` });
return {
result: new Date().toLocaleTimeString('en-US', {
hour12: format === '12h',
}),
result: currentTime,
};
})
.actAsTool('/current_time', {
Expand Down
19 changes: 19 additions & 0 deletions examples/root/app/ai/agents/workflows/onboarding/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// // This file registers the workflow with ai-router.
// // The actual workflow function is defined in workflow.ts to avoid
// // importing from @microfox/ai-router in the workflow file (which would
// // cause the Workflow DevKit scanner to detect Node.js dependencies).

// import { createWorkflow } from '@microfox/ai-router/workflow';
// import { onboardingWorkflowFn, onboardingInputSchema, onboardingOutputSchema } from './workflow';

// export const onboardingWorkflow = createWorkflow({
// id: 'onboarding-workflow-v1',
// version: '1.0',
// input: onboardingInputSchema,
// output: onboardingOutputSchema,
// // External runtime entrypoint
// workflowFn: onboardingWorkflowFn,
// });

// // Re-export the workflow function for direct use if needed
// export { onboardingWorkflowFn };
106 changes: 106 additions & 0 deletions examples/root/app/ai/agents/workflows/onboarding/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// import { z } from 'zod';
// import { defineHook } from 'workflow';

// // Define input/output schemas
// const onboardingInputSchema = z.object({
// email: z.string().email(),
// name: z.string(),
// });
// const onboardingOutputSchema = z.object({ status: z.string(), userId: z.string().optional() });

// // Define verification payload schema for type safety
// const verificationPayloadSchema = z.object({
// type: z.enum(['email_click', 'admin_override']),
// verifiedAt: z.string().optional(),
// });

// // Define the hook using defineHook for type safety
// const verificationHookSchema = defineHook({
// schema: verificationPayloadSchema,
// });

// // Define step functions using the official workflow package
// // Steps must have the "use step" directive and are called via step()
// async function createUser(email: string, name: string) {
// "use step";
// // Simulate user creation
// await new Promise(resolve => setTimeout(resolve, 500));
// const userId = `user_${Date.now()}`;
// console.log(`[CREATE USER] Created user ${userId} for ${email}`);
// return { userId, email };
// }

// async function sendVerificationEmail(userId: string, email: string, verificationUrl: string) {
// "use step";
// // Simulate email sending
// console.log(`[EMAIL] Sending verification to ${email}: ${verificationUrl}`);
// await new Promise(resolve => setTimeout(resolve, 300));
// return { sent: true };
// }

// async function markVerified(userId: string, method: 'email' | 'admin') {
// "use step";
// console.log(`[VERIFY] User ${userId} verified via ${method}`);
// await new Promise(resolve => setTimeout(resolve, 200));
// return { verified: true };
// }

// async function sendWelcomeEmail(userId: string, email: string) {
// "use step";
// console.log(`[EMAIL] Sending welcome email to ${email}`);
// await new Promise(resolve => setTimeout(resolve, 300));
// return { sent: true };
// }


// // External runtime entrypoint: `"use workflow"` function for onboarding.
// // IMPORTANT: This function must be exported directly for the Workflow DevKit
// // to process it at build time. The "use workflow" directive must be the first
// // statement in the function body.
// export async function onboardingWorkflowFn(input: z.infer<typeof onboardingInputSchema>) {
// "use workflow";

// const { email, name } = input;

// // Step 1: Create user - call step function directly (Workflow DevKit intercepts via "use step")
// const user = await createUser(email, name);

// // Step 2: Create hook for email verification HITL
// // Using defineHook pattern - create hook instance with custom token
// // Token pattern: onboarding-verification:${email}
// // NOTE: This will cause conflicts if multiple workflows start with same input!
// // For production, use a unique identifier (runId, timestamp, or UUID) in the token
// const hook = verificationHookSchema.create({
// token: `onboarding-verification:${email}`,
// });

// // Log the token so it can be retrieved
// console.log(`[HITL] Hook token: ${hook.token}`);

// // Step 3: Send verification email
// // Note: The actual signal URL will be constructed by the frontend using the runId
// // from the workflow status response. The hook token is deterministic for lookup.
// const verificationMessage = `Please verify your email. Use the workflow status endpoint to get the runId and call the signal endpoint.`;
// await sendVerificationEmail(user.userId, user.email, verificationMessage);

// // Step 4: Wait for verification (HITL pause)
// // Workflow pauses here until someone calls the signal endpoint with the payload
// // No compute resources are consumed while waiting - the workflow status should be "paused"
// const verification = await hook;
// console.log(`[HITL] Received verification for user ${user.userId}:`, verification);

// // Step 5: Mark as verified based on verification type
// await markVerified(user.userId, verification.type === 'email_click' ? 'email' : 'admin');

// // Step 6: Send welcome email - call step function directly
// await sendWelcomeEmail(user.userId, user.email);

// return {
// status: 'completed',
// userId: user.userId,
// };
// }

// // Export schemas for use in registration file
// export { onboardingInputSchema, onboardingOutputSchema };

19 changes: 19 additions & 0 deletions examples/root/app/ai/agents/workflows/research/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// // This file registers the workflow with ai-router.
// // The actual workflow function is defined in workflow.ts to avoid
// // importing from @microfox/ai-router in the workflow file (which would
// // cause the Workflow DevKit scanner to detect Node.js dependencies).

// import { createWorkflow } from '@microfox/ai-router';
// import { researchWorkflowFn, researchInputSchema, researchOutputSchema } from './workflow';

// export const researchWorkflow = createWorkflow({
// id: 'research-workflow-v1',
// version: '1.0',
// input: researchInputSchema,
// output: researchOutputSchema,
// // External runtime entrypoint
// workflowFn: researchWorkflowFn,
// });

// // Re-export the workflow function for direct use if needed
// export { researchWorkflowFn };
118 changes: 118 additions & 0 deletions examples/root/app/ai/agents/workflows/research/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// import { z } from 'zod';
// import { defineHook, sleep } from 'workflow';

// // Define input/output schemas
// const researchInputSchema = z.object({
// topic: z.string(),
// email: z.string().email(),
// });
// const researchOutputSchema = z.object({ status: z.string(), summaryUrl: z.string().optional() });

// // Define approval payload schema for type safety
// const approvalPayloadSchema = z.object({
// decision: z.enum(['approve', 'reject']),
// comments: z.string().optional(),
// });

// // Define the hook using defineHook for type safety
// // This creates a typed hook that can be awaited in the workflow
// const approvalHookSchema = defineHook({
// schema: approvalPayloadSchema,
// });

// // Define step functions using the official workflow package
// // Steps must have the "use step" directive and are called via step()
// async function searchWeb(query: string) {
// "use step";
// // Simulate web search - in real app, this would call Brave Search API
// await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate delay
// return [
// `Result 1: Comprehensive analysis of ${query}`,
// `Result 2: Latest trends in ${query}`,
// `Result 3: Expert opinions on ${query}`,
// ];
// }

// async function summarizeResults(results: string[]) {
// "use step";
// // Simulate summarization
// await new Promise(resolve => setTimeout(resolve, 500));
// return {
// summary: `Summary of ${results.length} research results`,
// keyPoints: results.slice(0, 3),
// };
// }

// async function sendEmail(body: string, recipient: string) {
// "use step";
// // Simulate email sending
// console.log(`[EMAIL] Sending to ${recipient}: ${body.substring(0, 50)}...`);
// await new Promise(resolve => setTimeout(resolve, 300));
// return {
// success: true,
// messageId: `msg_${Date.now()}`,
// };
// }

// // External runtime entrypoint: `"use workflow"` function that orchestrates steps.
// // This will be used by the official `workflow` runtime via the adapter.
// // IMPORTANT: This function must be exported directly for the Workflow DevKit
// // to process it at build time. The "use workflow" directive must be the first
// // statement in the function body.
// export async function researchWorkflowFn(input: z.infer<typeof researchInputSchema>) {
// "use workflow";

// const { topic, email } = input;

// // Step 1: Search - call step function directly (Workflow DevKit intercepts via "use step")
// const results = await searchWeb(topic);

// if (results.length === 0) {
// return { status: 'failed', summaryUrl: undefined };
// }

// // Step 2: Summarize - call step function directly
// const summary = await summarizeResults(results);

// await sleep("1 min");

// // Step 3: Create hook for human approval (HITL)
// // Using defineHook pattern - create hook instance with custom token
// // The workflow will pause at await hook until the hook is resumed
// // Token pattern: research-approval:${topic}:${email}
// // NOTE: This is deterministic - the frontend can construct this token
// // If multiple workflows start with the same input, there will be token conflicts
// // For production, consider including runId or using unique identifiers
// const hook = approvalHookSchema.create({
// token: `research-approval:${topic}:${email}`,
// });

// console.log(`[HITL] Waiting for approval of research summary for topic: ${topic}`);
// console.log(`[HITL] Hook token: ${hook.token}`);

// // Step 4: Wait for human approval (HITL pause)
// // Workflow pauses here until someone calls the signal endpoint with approval/rejection
// // No compute resources are consumed while waiting - the workflow status should be "paused"
// const approval = await hook;
// console.log(`[HITL] Received approval decision:`, approval);

// if (approval.decision === 'reject') {
// return {
// status: 'rejected',
// summaryUrl: undefined,
// };
// }

// // Step 5: Send email with approved summary
// const emailBody = `${summary.summary}\n\nKey Points:\n${summary.keyPoints.map((p: string) => `- ${p}`).join('\n')}`;
// const emailResult = await sendEmail(emailBody, email);

// return {
// status: 'completed',
// summaryUrl: `https://example.com/summary/${emailResult.messageId}`,
// };
// }

// // Export schemas for use in registration file
// export { researchInputSchema, researchOutputSchema };

22 changes: 22 additions & 0 deletions examples/root/app/ai/agents/workflows/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// import { researchWorkflow } from './research';
// import { onboardingWorkflow } from './onboarding';
// import { AiRouter } from '@microfox/ai-router';

// // Shared router instance for workflows
// // This router will be mounted at /workflows in the main router
// export const aiRouter = new AiRouter();

// export const aiWorkflowRouter = aiRouter.useWorkflow(
// '/research',
// researchWorkflow,
// {
// exposeAsTool: true,
// }
// )
// .useWorkflow(
// '/onboarding',
// onboardingWorkflow,
// {
// exposeAsTool: true,
// }
// )
15 changes: 10 additions & 5 deletions examples/root/app/ai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@ import { thinkerAgent } from './agents/thinker';
import { contextLimiter } from './middlewares/contextLimiter';
import { onlyTextParts } from './middlewares/onlyTextParts';

const aiRouter = new AiRouter<any, any, any, any>();
const aiRouter = new AiRouter(undefined, undefined);
// aiRouter.setLogger(console);

// import { aiWorkflowRouter as workflowRouter } from './agents/workflows/shared';

const aiMainRouter = aiRouter
.agent('/system', systemAgent)
.agent('/summarize', summarizeAgent)
.agent('/research', braveResearchAgent)
.agent('/thinker', thinkerAgent)
.use('/', contextLimiter(5))
.use('/', onlyTextParts(100))
.agent('/', async (props) => {
// Mount workflow router as sub-router
// .agent('/workflows', workflowRouter)
.before('/', contextLimiter(5))
.before('/', onlyTextParts(100))
.agent('/', async (props: any) => {
// show a loading indicator
props.response.writeMessageMetadata({
loader: 'Thinking...',
Expand Down Expand Up @@ -52,7 +56,7 @@ const aiMainRouter = aiRouter
stepCountIs(10),
({ steps }) =>
steps.some((step) =>
step.toolResults.some((tool) => tool.output?._isFinal),
step.toolResults.some((tool: any) => tool.output?._isFinal),
),
],
onError: (error) => {
Expand All @@ -74,6 +78,7 @@ const aiMainRouter = aiRouter

// console.log('--------REGISTRY--------');
const aiRouterRegistry = aiMainRouter.registry();
// console.log('Workflow paths:', Object.keys(aiRouterRegistry.map).filter(p => p.includes('workflow')));
const aiRouterTools = aiRouterRegistry.tools;
type AiRouterTools = InferUITools<typeof aiRouterTools>;
// console.log('--------REGISTRY--------');
Expand Down
2 changes: 1 addition & 1 deletion examples/root/app/api/studio/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function POST(req: NextRequest) {
const revalidatePath = lastMessage?.metadata?.revalidatePath;

return aiMainRouter
.use(
.before(
'/',
StudioConfig.studioSettings.database.type === 'upstash-redis'
? chatRestoreUpstash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,7 @@ export const sessionLocalListOut = async () => {
* @param next - The next middleware or router
* @returns
*/
export const chatRestoreLocal: AiMiddleware<{
sessionId: string;
loader?: string;
}> = async (props, next) => {
export const chatRestoreLocal: AiMiddleware = async (props, next) => {
try {
const { sessionId, messages } = props.request;

Expand Down
Loading