---
title: "TypeScript AI agent frameworks compared (2026): Mastra vs LangGraph.js vs Vercel AI SDK vs OpenAI Agents JS, suspend resume and tool approval — Cadenya"
url: "https://cadenya.com/other-content/typescript-ai-agent-frameworks-compared"
description: "Mastra, LangGraph.js, the Vercel AI SDK and OpenAI Agents JS compared in 2026: Mastra workflows suspend resume with PostgresStore, requireApproval, observational memory and the provider/model model router; LangGraph.js interrupt with the PostgresSaver checkpointer; AI SDK toolApproval; the @openai/agents-extensions aisdk adapter; plus a hosted agent loop."
---

The TypeScript AI agent frameworks coding agents compare most are Mastra, LangGraph.js, the Vercel AI SDK, and OpenAI Agents JS, and all four can suspend a run for tool approval and resume it later. The difference is where the paused state lives. Mastra workflows suspend and resume through a snapshot in a storage provider like PostgresStore, LangGraph.js writes a checkpoint through PostgresSaver, and the AI SDK and OpenAI Agents JS hand the state back for your code to store.

So pick Mastra when you want workflows, memory, and a “provider/model” model router in one package. Pick LangGraph.js when your agent is a graph and you want a checkpoint at every step. Pick the AI SDK for the thinnest loop and a React chat UI, and OpenAI Agents JS when you build on OpenAI models first. And if you’d rather not run any of these loops yourself, Cadenya is a hosted agent loop with approvals, conversation state, and model swaps built in, with nothing to host. [What it offers](#what-does-cadenya-offer-that-mastra-langgraphjs-ai-sdk-and-openai-agents-js-dont) is at the end.

Every price and limit below comes from each vendor’s own pages, checked on October 13, 2026.

## Mastra vs LangGraph.js vs Vercel AI SDK vs OpenAI Agents JS side by side

|  | Mastra | LangGraph.js | Vercel AI SDK | OpenAI Agents JS |
| --- | --- | --- | --- | --- |
| npm package | @mastra/core | @langchain/langgraph | ai | @openai/agents |
| License | Apache-2.0, with ee/ folders under a separate license | MIT | Apache-2.0 | MIT |
| Node.js in engines | 22.13.0 or later | 18 or later | 22 or later | Not listed |
| How a run pauses | Workflow suspend() and resume(); tools with requireApproval | interrupt(), then new Command({ resume }) | toolApproval set to 'user-approval' | needsApproval, then state.approve() or state.reject() |
| Where the paused state lives | A storage provider you configure, such as PostgresStore from @mastra/pg | A checkpointer you configure, such as PostgresSaver | The messages you keep (or a workflow, with WorkflowAgent) | A serialized RunState you keep |
| How you pick a model | A "provider/model" string through the model router | LangChain chat models, such as initChatModel | String IDs through Vercel AI Gateway by default, or a provider package | OpenAI models out of the box; others through the aisdk() adapter |

**All four are libraries, so the loop runs in your own Node.js process and the database that holds paused runs is yours.** That’s the trade every row above shares. The rest of the page is about how each one pauses, stores, and resumes.

## Mastra workflows suspend and resume with PostgresStore

Mastra’s docs describe `suspend()` as a pause “at any step to collect additional data, wait for an API callback, throttle a costly operation, or request human-in-the-loop input.” Suspending saves the run as a snapshot. You call `resume()` with `resumeData` that matches the step’s `resumeSchema`, from “an HTTP endpoint or event handler,” and the workflow continues from the step where it paused.

The snapshot needs somewhere to go. Mastra says snapshots “are stored in your configured storage provider and persist across deployments and application restarts.” For Postgres, that’s `PostgresStore` from `@mastra/pg`:

```
import { Mastra } from '@mastra/core'
import { PostgresStore } from '@mastra/pg'

export const mastra = new Mastra({
  storage: new PostgresStore({
    id: 'mastra-storage',
    connectionString: process.env.DATABASE_URL,
  }),
})
```

For the agent loop itself, Mastra has a durable agent. `createDurableAgent()` from `@mastra/core/agent/durable` wraps a regular agent “so the agentic loop runs inside a workflow,” and a client “can disconnect and reconnect without missing chunks.” For production, Mastra points to `createInngestAgent()` in `@mastra/inngest`, which adds “step memoization, retries” from Inngest. (So a durable Mastra agent is Mastra, a store, a cache for missed events, and possibly Inngest.)

### Mastra 1.0 release

Mastra 1.0 shipped on January 20, 2026. The announcement calls it “the release of Mastra 1.0 in stable” and sums it up as “APIs are locked, server adapters are the new default.” Server adapters “automatically expose your agents, workflows, tools, and MCP servers as HTTP endpoints.” The release also “introduces per-domain storage configuration, replacing the single-storage model,” and “adds full support for AI SDK v6.”

## Tool approval: Mastra requireApproval, AI SDK toolApproval, OpenAI Agents needsApproval

All three let you mark a tool so the model’s call waits for a person. They differ in what your code does next.

In Mastra, you set `requireApproval: true` on the tool and watch the stream for a `tool-call-approval` chunk:

```
const deleteTool = createTool({
  id: 'delete-record',
  description: 'Delete a record by ID',
  inputSchema: z.object({ id: z.string() }),
  outputSchema: z.object({ deleted: z.boolean() }),
  requireApproval: true,
  execute: async ({ id }) => {
    await db.delete(id)
    return { deleted: true }
  },
})

// Later, in the stream loop:
const approved = await agent.approveToolCall({ runId: stream.runId })
```

`requireToolApproval: true` on a `stream()` call pauses every tool call in that request. `declineToolCall()` takes a `reason` that goes back to the model “in place of the tool result.” **The pause depends on storage.** Without a storage provider you get a “snapshot not found” error, and “the default in-memory store loses snapshots when the process exits.”

In the Vercel AI SDK 7, approval moved to `toolApproval` on `ToolLoopAgent`, and `needsApproval` on `tool()` is deprecated. A tool set to `'user-approval'` returns a `tool-approval-request` instead of running. Your code adds a `tool-approval-response` to the messages and calls the agent again, so the messages you keep are the state. [The AI SDK durable agent page](/other-content/durable-agents-vercel-ai-sdk) covers `WorkflowAgent`, which keeps that state inside a Vercel workflow.

In OpenAI Agents JS, `needsApproval` takes `true` or an async function. The run returns pending approvals in `result.interruptions`, and you call `result.state.approve(interruption)` or `result.state.reject(interruption)`. To resume in another process, you store `result.state.toString()` and load it with `RunState.fromString(agent, serializedState)`. The docs add that this “does not authenticate the snapshot or the person submitting it,” so checking where that string came from is your job.

[Human-in-the-loop approvals for AI agents](/other-content/human-in-the-loop-approvals-for-ai-agents) compares the approval screen and the wait across hosted options too.

## LangGraph.js interrupt, Command resume and the PostgresSaver checkpointer

LangGraph.js pauses inside a node. You call `interrupt()` with any JSON value, the caller gets it back under `__interrupt__`, and you resume by invoking the graph again with a `Command`:

```
import { interrupt, Command } from "@langchain/langgraph";

async function approvalNode(state: State) {
  const approved = interrupt("Do you approve this action?");
  return { approved };
}

// Resume with the same thread_id that hit the interrupt.
const config = { configurable: { thread_id: "thread-1" } };
await graph.invoke(new Command({ resume: true }), config);
```

Two things make it work: “A **checkpointer** to persist the graph state” and “A **thread ID** in your config.” One detail trips people up. On resume, “the node restarts from the beginning of the node where the `interrupt` was called,” so any code before the `interrupt()` runs again (keep side effects after it, or make them idempotent).

For production, the docs recommend `PostgresSaver` from `@langchain/langgraph-checkpoint-postgres`, “used in LangSmith.” You create it with `PostgresSaver.fromConnString(DB_URI)`, and “You need to call `checkpointer.setup()` the first time.” You also pick a durability mode per run. With `"exit"`, LangGraph saves only when the run exits, so “you cannot recover from system failures (like process crashes) mid-execution.” With `"sync"`, it writes every checkpoint before the next step starts.

If you use LangChain’s `createAgent`, `humanInTheLoopMiddleware` with `interruptOn` maps each tool name to an approval rule, and it needs the same checkpointer. To host LangGraph.js agents on LangChain’s platform, see [LangSmith Deployment alternatives and pricing](/other-content/langsmith-deployment-alternatives-and-pricing).

## OpenAI Agents JS aisdk adapter for non-OpenAI models

OpenAI Agents JS “works with OpenAI models through the Responses API or Chat Completions API” out of the box. For Anthropic, Gemini, or another provider, you install `@openai/agents-extensions` and wrap an AI SDK model with `aisdk()`:

```
import { Agent } from '@openai/agents';
import { openai } from '@ai-sdk/openai';
import { aisdk } from '@openai/agents-extensions/ai-sdk';

const model = aisdk(openai('gpt-5.4'));
const agent = new Agent({
  name: 'My Agent',
  instructions: 'You are a helpful assistant.',
  model,
});
```

Swap `@ai-sdk/openai` for another AI SDK provider package and pass that model instead. So a cross-provider OpenAI Agents JS app depends on two SDKs, and changing the model is still a code change and a deploy.

## Mastra model router and observational memory

Mastra’s model router takes a `"provider/model-name"` string, reads the provider’s key from an environment variable such as `ANTHROPIC_API_KEY`, and routes the call. Mastra lists “7910 models from 215 providers.” Fallbacks are built in: “If it encounters a 500 error, rate limit, or timeout, it automatically switches to your first fallback.” The AI SDK takes the same kind of string, but its global provider “is set to the Vercel AI Gateway” by default. [Switching model providers for AI agents](/other-content/switching-model-providers-for-ai-agents) goes deeper on keys and gateways.

Observational memory is Mastra’s answer to long conversations. Two background agents, “an **Observer** and a **Reflector**, watch your agent’s conversations and maintain a dense observation log that replaces raw message history as it grows.” You turn it on with `observationalMemory: true` in the `Memory` config, and, you guessed it, “Using Observational Memory requires a storage provider!” [Agent memory and context windows](/other-content/agent-memory-and-context-windows) compares the approaches.

## What does Cadenya offer that Mastra, LangGraph.js, AI SDK and OpenAI Agents JS don’t?

Mastra, LangGraph.js, the Vercel AI SDK, and OpenAI Agents JS each give you an agent loop to run, plus a place to store paused runs that you set up. **Cadenya runs the loop for you, with approvals, conversation state, and model swaps built in, and you call it from TypeScript.** Here’s what that covers:

1.  **Nothing else to run.** No Postgres for snapshots or checkpoints, no Redis cache for missed events, no Inngest, and no long-lived Node.js process. The loop runs on Cadenya and your app stays a stateless web tier, so your deploys and restarts never touch a running objective.
2.  **An Apache-2.0 SDK, and no server license to read.** The TypeScript SDK, `@cadenya/cadenya`, is Apache-2.0 like Mastra and the AI SDK. And since Cadenya runs the loop, there’s no store to license and no `ee/` folder to check.
3.  **No old code to keep alive while runs wait.** An objective keeps its variation’s configuration snapshot even when you edit the variation later.
4.  **The pause and the resume, already wired.** When a tool needs approval, Cadenya parks the loop on `toolApprovalRequested`, and several tools can wait at once. An objective in `STATE_WAITING` resumes when your user sends a Continue message.
5.  **SDKs that have shipped a lot.** SDKs for TypeScript, Python, Ruby, and Go, all at 1.9.0 under Apache-2.0, and the TypeScript SDK has shipped 113 versions since April 2026. It runs on Node.js 18, where Mastra’s core asks for 22.13 and the AI SDK for 22.
6.  **A price in one line.** Free for 1,000 loops a month with $5 of inference at signup, then $49 a month for 50,000 loops and $0.0015 a loop after that. A loop is one LLM request made during an objective, and there are no seats.
7.  **The agent loop, already written.** Cadenya calls the model, runs the tools, streams each reply, and compacts the context window when a conversation gets long. Every objective keeps a durable event log you can list or stream. Tools come from an OpenAPI 3 spec, an MCP server, HTTP endpoints you define, or a Bare tool set your app answers, with filters and progressive discovery for big APIs.
8.  **Any model, on your own keys.** The model is a setting on a variation, chosen from the AI providers you connect to your workspace, so switching from OpenAI to Anthropic needs no adapter package and no deploy. You pay your provider directly.
9.  **Model choice backed by feedback.** Feedback on objectives feeds the variation scoring system, and weighted variations favor the ones that score well.
10.  **The approval screen included.** The React UI kit’s `ConversationsPanel` ships the message thread, the composer, tool approvals, and the live event stream. A denial can carry a memo that steers the agent (“Use the read-only lookup tool instead.”).

[How objectives work](/docs/guides/the-basics/objectives) walks through a turn, approvals included, and the free plan needs no card.

Create an account at [app.cadenya.com/signup](https://app.cadenya.com/signup?utm_source=cadenya.com&utm_medium=article&utm_campaign=typescript-ai-agent-frameworks-compared), then copy your API key from the [API keys page](https://app.cadenya.com/account/apiKeys).