{"_id":"@ai-sdk-tools/workflow","name":"@ai-sdk-tools/workflow","dist-tags":{"latest":"0.1.0-beta.1"},"versions":{"0.1.0-beta.1":{"name":"@ai-sdk-tools/workflow","private":false,"version":"0.1.0-beta.1","description":"Human-in-the-loop confirmation workflows that scale with your AI application - type-safe, performant, and beautifully integrated with AI SDK","main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./client":{"types":"./dist/client.d.ts","import":"./dist/client.mjs","require":"./dist/client.js"}},"scripts":{"build":"tsup","dev":"tsup --watch","clean":"rm -rf dist","prepublishOnly":"bun run clean && bun run build","type-check":"tsc --noEmit"},"keywords":["ai","workflow","human-in-the-loop","confirmation","streaming","react","ai-sdk","typescript"],"author":{"name":"Pontus Abrahamsson"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/midday-ai/ai-sdk-tools.git"},"bugs":{"url":"https://github.com/midday-ai/ai-sdk-tools/issues"},"homepage":"https://github.com/midday-ai/ai-sdk-tools#readme","publishConfig":{"access":"public"},"dependencies":{"zod":"^4.1.8"},"devDependencies":{"@types/react":"^19.1.13","typescript":"^5.9.2","tsup":"^8.5.0","@ai-sdk-tools/store":"workspace:*"},"peerDependencies":{"@ai-sdk-tools/store":"0.7.0-beta.4","ai":"^4.0.0","react":"^18.0.0 || ^19.0.0"},"_id":"@ai-sdk-tools/workflow@0.1.0-beta.1","gitHead":"e98addc05cf35eda1d9ef99f5012e5876029f9df","_nodeVersion":"22.14.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-sSQ6Q3wZ84xh+rdru075mDVYWGeZXqToAmRm7bOJnIQOlU6KV53LUTKUIPpTJ0dBWENiOFGL6yZY5+Sxtq/LeA==","shasum":"70785a4b7d341b3e5dac097d1a10506b611376b1","tarball":"https://registry.npmjs.org/@ai-sdk-tools/workflow/-/workflow-0.1.0-beta.1.tgz","fileCount":16,"unpackedSize":213240,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCkaZ3FHcfP7OiihQdld7KeUGGQdeYYEmVFbHJ4Q7p/zgIhAJZ8hqxS7N/2c5DxX/vQfg6RLus6ytL1RFVdM58i3jyZ"}]},"_npmUser":{"name":"pontus-midday","email":"pontus@midday.ai"},"directories":{},"maintainers":[{"name":"pontus-midday","email":"pontus@midday.ai"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/workflow_0.1.0-beta.1_1759052941162_0.050674188876688175"},"_hasShrinkwrap":false}},"time":{"created":"2025-09-28T09:49:01.054Z","0.1.0-beta.1":"2025-09-28T09:49:01.338Z","modified":"2025-09-28T09:49:01.681Z"},"maintainers":[{"name":"pontus-midday","email":"pontus@midday.ai"}],"description":"Human-in-the-loop confirmation workflows that scale with your AI application - type-safe, performant, and beautifully integrated with AI SDK","homepage":"https://github.com/midday-ai/ai-sdk-tools#readme","keywords":["ai","workflow","human-in-the-loop","confirmation","streaming","react","ai-sdk","typescript"],"repository":{"type":"git","url":"git+https://github.com/midday-ai/ai-sdk-tools.git"},"author":{"name":"Pontus Abrahamsson"},"bugs":{"url":"https://github.com/midday-ai/ai-sdk-tools/issues"},"license":"MIT","readme":"# @ai-sdk-tools/workflow\n\n> **Human-in-the-loop workflows that scale with your AI application**\n\nWorkflows that integrate seamlessly with AI SDK. Add smart confirmation workflows to your AI applications with a single hook - type-safe, performant, and production-ready.\n\n## Features\n\n- 🚀 **Zero-config**: Works out of the box with sensible defaults\n- ⚡ **Scales with Zustand**: Eliminates prop drilling, built for performance\n- 🔄 **Smart Flows**: Auto-approval, conditional confirmation, timeout handling\n- 🎯 **Priority-aware**: Critical flows get immediate attention\n- 📊 **Rich Events**: Comprehensive lifecycle event handlers\n- 🛡️ **Type-safe**: Full input/output validation with Zod schemas\n- 🎨 **UI Agnostic**: Backend handles logic, frontend owns presentation\n- 🔧 **AI SDK Native**: Follows AI SDK patterns and conventions\n\n## Installation\n\n```bash\nnpm install @ai-sdk-tools/workflow\n```\n\n## Quick Start\n\n### Backend Setup\n\n```typescript\n// app/api/chat/route.ts\nimport { workflow, createTypedWorkflowContext } from \"@ai-sdk-tools/workflow\";\nimport { streamText, tool } from \"ai\";\nimport { z } from \"zod\";\n\nconst { setContext } = createTypedFlowContext();\n\n // Define your flow - AI SDK style configuration\nconst deleteFileFlow = flow({\n  id: \"delete-file\",\n  inputSchema: z.object({\n    fileName: z.string(),\n    path: z.string(),\n    size: z.number(),\n  }),\n  outputSchema: z.object({\n    success: z.boolean(),\n    message: z.string(),\n    deletedFile: z.string().optional(),\n  }),\n  priority: \"high\",\n  timeout: 30000, // 30 seconds\n  autoApprove: (data) => data.size < 1024, // Auto-approve small files\n});\n\nexport async function POST(req: Request) {\n  const { messages } = await req.json();\n\n  const stream = createUIMessageStream({\n    execute: ({ writer }) => {\n      setContext({ writer });\n\n      const result = streamText({\n        model: openai(\"gpt-4o\"),\n        messages: convertToModelMessages(messages),\n        tools: {\n          deleteFile: tool({\n            description: \"Delete a file\",\n            inputSchema: deleteFileFlow.inputSchema,\n            outputSchema: deleteFileFlow.outputSchema,\n            execute: async (input) => {\n              // Backend only handles flow logic, no UI text\n              const flowStream = deleteFileFlow.stream(input);\n              const response = await flowStream.waitForResponse();\n              \n              if (response === \"approved\") {\n                return deleteFileFlow.validateOutput({\n                  success: true,\n                  message: `File ${input.fileName} deleted successfully.`,\n                  deletedFile: input.fileName,\n                });\n              } else {\n                return deleteFileFlow.validateOutput({\n                  success: false,\n                  message: \"File deletion cancelled.\",\n                });\n              }\n            },\n          }),\n        },\n      });\n\n      writer.merge(result.toUIMessageStream());\n    },\n  });\n\n  return createUIMessageStreamResponse({ stream });\n}\n```\n\n### Frontend Usage\n\n```typescript\n// components/FlowPanel.tsx\nimport { useFlow, usePendingFlows } from \"@ai-sdk-tools/flow/client\";\n\nfunction FlowPanel() {\n  const pendingFlows = usePendingFlows();\n  \n  return (\n    <div className=\"flow-panel\">\n      {pendingFlows.map((flow) => (\n        <FlowCard key={flow.id} flowId={flow.id} />\n      ))}\n    </div>\n  );\n}\n\nfunction FlowCard({ flowId }: { flowId: string }) {\n  const deleteFlow = useFlow(deleteFileFlow, {\n    onApproved: (data) => console.log('Approved!', data),\n    onRejected: (data) => console.log('Rejected!', data),\n  });\n\n  if (!deleteFlow.isPending) return null;\n\n  return (\n    <div className=\"flow-card\">\n      <h3>{deleteFlow.action}</h3>\n      <p>{deleteFlow.description}</p>\n      \n      {deleteFlow.data && (\n        <div className=\"flow-data\">\n          <p>File: {deleteFlow.data.fileName}</p>\n          <p>Size: {deleteFlow.data.size} bytes</p>\n        </div>\n      )}\n      \n      {deleteFlow.timeRemaining && (\n        <div className=\"timeout-indicator\">\n          {Math.ceil(deleteFlow.timeRemaining / 1000)}s remaining\n        </div>\n      )}\n      \n      <div className=\"flow-actions\">\n        <button \n          onClick={() => deleteFlow.approve(\"User confirmed\")}\n          disabled={!deleteFlow.canApprove}\n        >\n          Approve\n        </button>\n        <button \n          onClick={() => deleteFlow.reject(\"Too risky\")}\n          disabled={!deleteFlow.canReject}\n        </button>\n      </div>\n    </div>\n  );\n}\n```\n\n## API Reference\n\n### `flow(config)`\n\nCreates a flow definition using AI SDK-style configuration.\n\n```typescript\nconst myFlow = flow({\n  id: string;                           // Unique identifier\n  inputSchema: z.ZodSchema<T>;          // Input validation schema\n  outputSchema?: z.ZodSchema<O>;        // Output validation schema\n  priority?: \"low\" | \"medium\" | \"high\" | \"critical\";\n  timeout?: number;                     // Timeout in milliseconds\n  autoApprove?: (data: T) => boolean;   // Auto-approval function\n});\n```\n\n### `useFlow(flowDef, options?)`\n\nHook for managing a single flow.\n\nReturns:\n- `data`: The flow payload data\n- `status`: Current flow status\n- `isPending`: Whether flow is waiting for user action\n- `approve(reason?)`: Approve the flow\n- `reject(reason?)`: Reject the flow\n- `cancel()`: Cancel the flow\n- `timeRemaining`: Time left before timeout\n\n### `useFlows(options?)`\n\nHook for managing multiple flows.\n\nReturns:\n- `pending`: Array of pending flows\n- `hasPending`: Whether there are pending flows\n- `pendingCount`: Number of pending flows\n- `approveAll(reason?)`: Approve all pending flows\n- `rejectAll(reason?)`: Reject all pending flows\n\n## Examples\n\n### Flow with Output Schema\n\n```typescript\nconst sendEmailFlow = flow({\n  id: \"send-email\",\n  inputSchema: z.object({\n    to: z.array(z.string().email()),\n    subject: z.string(),\n    body: z.string(),\n  }),\n  outputSchema: z.object({\n    success: z.boolean(),\n    messageId: z.string().optional(),\n    recipients: z.array(z.string()),\n    error: z.string().optional(),\n  }),\n  priority: \"medium\",\n  autoApprove: (data) => {\n    // Auto-approve internal emails\n    return data.to.every(email => email.endsWith(\"@company.com\"));\n  },\n});\n\n// In your tool\nexecute: async (input) => {\n  const flowStream = sendEmailFlow.stream(input);\n  const response = await flowStream.waitForResponse();\n  \n  if (response === \"approved\") {\n    const result = {\n      success: true,\n      messageId: \"msg_123\",\n      recipients: input.to,\n    };\n    // Validates output against schema\n    return sendEmailFlow.validateOutput(result);\n  }\n}\n```\n\n### Multiple Flow Management\n\n```typescript\nfunction FlowDashboard() {\n  const flows = useFlows({\n    priorityFilter: [\"high\", \"critical\"],\n    onFlow: (type, flow) => {\n      if (flow.priority === \"critical\") {\n        showNotification(`Critical action required: ${flow.action}`);\n      }\n    },\n  });\n\n  return (\n    <div>\n      <div className=\"flow-summary\">\n        Pending: {flows.pendingCount}\n        {flows.hasPending && (\n          <button onClick={() => flows.approveAll()}>\n            Approve All\n          </button>\n        )}\n      </div>\n      \n      {flows.pending.map(flow => (\n        <FlowCard key={flow.id} flow={flow} />\n      ))}\n    </div>\n  );\n}\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-dbd5ffcd33d50cb12005e38fc0510df1"}