Skip to content

feat(plugin): rename transaction/verb to plugin, drop composition + clientTransaction, add media factories - #945

Closed
AlemTuzlak wants to merge 20 commits into
feat/transaction-client-stubfrom
feat/plugin-api
Closed

feat(plugin): rename transaction/verb to plugin, drop composition + clientTransaction, add media factories#945
AlemTuzlak wants to merge 20 commits into
feat/transaction-client-stubfrom
feat/plugin-api

Conversation

@AlemTuzlak

@AlemTuzlakAlemTuzlak commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on #942 (feat/transaction-client-stub). Reshapes that PR's app-defined "transaction/verb" registry into a plugin API, then extracts the authoring API into its own package and adds direct in-process execution:

  1. Renametransaction/verbplugin, strip server-side composition (ctx.call/sub-runs), dropclientTransaction, add media factories.
  2. @tanstack/ai-plugin-toolkit — a new package that is the single home for the plugin authoring API.
  3. .run() — every plugin can be executed directly, in-process.

definePlugin / usePlugin / createPlugin / .handler behave exactly as before — everything below is either a rename, an import-path move, or purely additive. Server-side composition returns later as a dedicated workflowPlugin.


1. Server API vs #942

#942

import{defineTransaction,chatVerb,verb,clientTransaction}from'@tanstack/ai/transaction'constdrafting=chatVerb((req)=>chat({ ... }))constheroImage=verb({input: z.object({prompt: z.string()}),execute: async(req,ctx)=>generateImage({ ... })})exportconstblogTransaction=defineTransaction({ drafting, heroImage, narration })exportconstblogTxnDef=clientTransaction<typeofblogTransaction>({drafting: 'chat',heroImage: 'one-shot',narration: 'one-shot'})

This PR

import{definePlugin,chatPlugin,imagePlugin,speechPlugin}from'@tanstack/ai-plugin-toolkit'constdrafting=chatPlugin((req)=>chat({ ... }))constheroImage=imagePlugin((req)=>generateImage({ ... }))// media factory over generationPluginconstnarration=speechPlugin((req)=>generateSpeech({ ... }))exportconstblogPlugin=definePlugin({ drafting, heroImage, narration })// no client stub — import this value directly

Media factories (all thin wrappers over the generic generationPlugin, input/result contract pre-bound): imagePlugin, videoPlugin, audioPlugin, speechPlugin, transcriptionPlugin, summarizePlugin.

2. Composition removed → client-side orchestration

#942 used a "composing verb" (execute(req, ctx) + ctx.call sub-runs). This PRgenerationPlugin.execute takes only req (no ctx); orchestration lives in the component:

constdraft=awaitp.drafting.sendMessage(topic)awaitPromise.all([p.heroImage.run({prompt: heroPromptFor(draft)}),p.narration.run({text: forNarration(draft.body)}),])

Gone: TransactionRunContext/ctx.call, sub-run streaming, TRANSACTION_EVENTS, the client sub-run demux, subRuns.

3. Client binding — clientTransaction stub + nested verbs map → import the def value + flat options

// This PRimport{usePlugin}from'@tanstack/ai-react/plugin'import{blogPlugin}from'../lib/blog-studio'// the real definePlugin valueconstp=usePlugin(blogPlugin,{
connection,drafting: {forwardedProps: {tone: 'punchy'}},// flat, keyed by plugin nameheroImage: {onResult: (img)=>save(img)},})

The definePlugin value carries names + kinds at runtime, so usePlugin binds off it directly (adapters are inert until handler runs server-side — no credential leak). Reserved keys (connection/id/threadId) are excluded from the per-plugin map.


4. New package: @tanstack/ai-plugin-toolkit

The plugin authoring API (definePlugin, chatPlugin, generationPlugin, the six media factories, .run, all plugin types) now lives in one package, so you define your plugins in a single shared module and import from one place:

// my-plugins.ts — the single placeimport{definePlugin,chatPlugin,imagePlugin}from'@tanstack/ai-plugin-toolkit'exportconstheroImage=imagePlugin((req)=>generateImage({ ... }))exportconstblogPlugin=definePlugin({drafting: chatPlugin(...), heroImage })// api route: blogPlugin.handler(request) | client: usePlugin(blogPlugin, { ... })

The @tanstack/ai/plugin subpath is removed (moved here). @tanstack/ai-client and the four framework hooks import plugin types from the toolkit. The toolkit depends only on @tanstack/ai and stays schema-library-agnostic (media input schemas are hand-rolled Standard Schemas — no runtime zod).

5. Direct execution: plugin.run()

Every plugin gets a .run() — a sibling to .handler — that executes it in-process and resolves with the typed result (no HTTP, no streaming Response; you wrap it in a Response yourself to serve). It accepts three input forms: raw params, an HTTP Request, or an already-parsed request body.

constheroImage=imagePlugin((req)=>generateImage({ ... }))constimg=awaitheroImage.run({prompt: 'a cat'})// raw params → ImageGenerationResultawaitheroImage.run(request)// HTTP Request → parse + validate → resultawaitheroImage.run(body)// parsed body → validate → resultconst{ text, structured }=awaitdrafting.run(messages)// chat → collected result// serve one plugin yourself:exportconstGET=async({ request })=>Response.json(awaitheroImage.run(request))

generationPluginPromise<TResult>; chatPluginPromise<{ text, structured }>. PluginRunOptions lets you pass a threadId/runId/signal/forwardedProps for the raw-params form.


Rename at a glance

#942This PR
defineTransaction / useTransaction / createTransactiondefinePlugin / usePlugin / createPlugin
chatVerb / verbchatPlugin / generationPlugin (+ imagePlugin/videoPlugin/audioPlugin/speechPlugin/transcriptionPlugin/summarizePlugin)
clientTransactionremoved — bind off the definePlugin value
TransactionClientPluginClient
execute(req, ctx) + ctx.callexecute(req) + client-side orchestration
TRANSACTION_EVENTS, sub-runs, subRunsremoved (returning via workflowPlugin)
import @tanstack/ai*/transaction@tanstack/ai-plugin-toolkit (authoring) / @tanstack/ai-*/plugin (hooks)
new:plugin.run() direct execution

Test plan

  • pnpm test:pr (CI canonical gate)
  • pnpm --filter @tanstack/ai-e2e test:e2e

Local: every changed package passed test:types + test:lib (toolkit 19, ai-client 427, react 160, solid 129, vue 117, svelte 79); e2e plugin suite 4/4 (chat, one-shot, media, direct .run()); test:docs, test:kiira (797/797), test:knip, test:sherif green. The one-shot full test:pr + full e2e are left to CI (nx-daemon/memory limits on the dev machine — infra, not code).

Follow-up (non-blocking)

runGenerationPluginStream's non-streaming branch could delegate to the existing streamGenerationResult helper (byte-identical today) — small DRY cleanup.

🤖 Generated with Claude Code

The media factories used z.object(...) at runtime, the first entry-reachable
runtime zod import in the package. Since zod is only a devDependency of this
deliberately schema-library-agnostic package, that bundled zod into the ESM
output (dist/esm/node_modules/zod) and shifted Rollup's preserveModules root,
nesting all emitted JS under dist/esm/packages/ai/src. Replace the six z.object
schemas with a hand-rolled Standard Schema helper (object + required-key check),
keeping the same public input types and typed req.input.
…ePlugin, flatten options, drop subRuns (solid/vue/svelte)
@coderabbitai

coderabbitaiBot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ec759251-98f0-4f3e-a199-0a57d99acdec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/plugin-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actionsBot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

20 package(s) bumped directly, 26 bumped as dependents.

🟥 Major bumps

PackageVersionReason
@tanstack/ai-angular0.2.3 → 1.0.0Changeset
@tanstack/ai-anthropic0.16.1 → 1.0.0Changeset
@tanstack/ai-bedrock0.1.2 → 1.0.0Changeset
@tanstack/ai-fal0.9.10 → 1.0.0Changeset
@tanstack/ai-gemini0.19.1 → 1.0.0Changeset
@tanstack/ai-grok0.14.7 → 1.0.0Changeset
@tanstack/ai-groq0.5.1 → 1.0.0Changeset
@tanstack/ai-mistral0.2.1 → 1.0.0Changeset
@tanstack/ai-ollama0.8.14 → 1.0.0Changeset
@tanstack/ai-openai0.16.0 → 1.0.0Changeset
@tanstack/ai-openrouter0.15.8 → 1.0.0Changeset
@tanstack/ai-preact0.10.3 → 1.0.0Changeset
@tanstack/ai-react0.16.4 → 1.0.0Changeset
@tanstack/ai-sandbox0.2.2 → 1.0.0Changeset
@tanstack/ai-solid0.14.3 → 1.0.0Changeset
@tanstack/ai-svelte0.14.3 → 1.0.0Changeset
@tanstack/ai-vue0.14.3 → 1.0.0Changeset
@tanstack/ai-acp0.2.1 → 1.0.0Dependent
@tanstack/ai-claude-code0.2.1 → 1.0.0Dependent
@tanstack/ai-code-mode0.3.6 → 1.0.0Dependent
@tanstack/ai-code-mode-skills0.3.9 → 1.0.0Dependent
@tanstack/ai-codex0.2.1 → 1.0.0Dependent
@tanstack/ai-elevenlabs0.2.32 → 1.0.0Dependent
@tanstack/ai-grok-build0.2.1 → 1.0.0Dependent
@tanstack/ai-isolate-node0.1.45 → 1.0.0Dependent
@tanstack/ai-isolate-quickjs0.1.45 → 1.0.0Dependent
@tanstack/ai-opencode0.2.1 → 1.0.0Dependent
@tanstack/ai-react-ui0.8.13 → 1.0.0Dependent
@tanstack/ai-sandbox-cloudflare0.2.2 → 1.0.0Dependent
@tanstack/ai-sandbox-daytona0.2.0 → 1.0.0Dependent
@tanstack/ai-sandbox-docker0.2.0 → 1.0.0Dependent
@tanstack/ai-sandbox-local-process0.2.0 → 1.0.0Dependent
@tanstack/ai-sandbox-sprites0.2.1 → 1.0.0Dependent
@tanstack/ai-sandbox-vercel0.2.0 → 1.0.0Dependent
@tanstack/ai-solid-ui0.7.12 → 1.0.0Dependent
@tanstack/openai-base0.9.7 → 1.0.0Dependent

🟨 Minor bumps

PackageVersionReason
@tanstack/ai0.40.0 → 0.41.0Changeset
@tanstack/ai-client0.20.0 → 0.21.0Changeset
@tanstack/ai-plugin-toolkit0.1.0 → 0.2.0Changeset

🟩 Patch bumps

PackageVersionReason
@tanstack/ai-devtools-core0.4.22 → 0.4.23Dependent
@tanstack/ai-isolate-cloudflare0.2.36 → 0.2.37Dependent
@tanstack/ai-mcp0.2.3 → 0.2.4Dependent
@tanstack/ai-vue-ui0.2.31 → 0.2.32Dependent
@tanstack/preact-ai-devtools0.1.65 → 0.1.66Dependent
@tanstack/react-ai-devtools0.2.65 → 0.2.66Dependent
@tanstack/solid-ai-devtools0.2.65 → 0.2.66Dependent

@nx-cloud

nx-cloudBot commented Jul 15, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 83d1a97

CommandStatusDurationResult
nx run-many --targets=build --exclude=examples/...✅ Succeeded1m 58sView ↗

☁️ Nx Cloud last updated this comment at 2026-07-16 11:30:53 UTC

@nx-cloud

nx-cloudBot commented Jul 15, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit 250d506

CommandStatusDurationResult
nx affected --targets=test:sherif,test:knip,tes...❌ Failed12m 39sView ↗
nx run-many --targets=build --exclude=examples/...✅ Succeeded2m 15sView ↗

☁️ Nx Cloud last updated this comment at 2026-07-15 12:48:12 UTC

@pkg-pr-new

pkg-pr-newBot commented Jul 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai@945

@tanstack/ai-acp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-acp@945

@tanstack/ai-angular

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-angular@945

@tanstack/ai-anthropic

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-anthropic@945

@tanstack/ai-bedrock

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-bedrock@945

@tanstack/ai-claude-code

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-claude-code@945

@tanstack/ai-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-client@945

@tanstack/ai-code-mode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode@945

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode-skills@945

@tanstack/ai-codex

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-codex@945

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-devtools-core@945

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-elevenlabs@945

@tanstack/ai-event-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-event-client@945

@tanstack/ai-fal

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-fal@945

@tanstack/ai-gemini

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-gemini@945

@tanstack/ai-grok

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok@945

@tanstack/ai-grok-build

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok-build@945

@tanstack/ai-groq

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-groq@945

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-cloudflare@945

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-node@945

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-quickjs@945

@tanstack/ai-mcp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mcp@945

@tanstack/ai-mistral

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mistral@945

@tanstack/ai-ollama

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-ollama@945

@tanstack/ai-openai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openai@945

@tanstack/ai-opencode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-opencode@945

@tanstack/ai-openrouter

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openrouter@945

@tanstack/ai-plugin-toolkit

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-plugin-toolkit@945

@tanstack/ai-preact

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-preact@945

@tanstack/ai-react

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react@945

@tanstack/ai-react-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react-ui@945

@tanstack/ai-sandbox

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox@945

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-cloudflare@945

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-daytona@945

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-docker@945

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-local-process@945

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-sprites@945

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-vercel@945

@tanstack/ai-solid

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid@945

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid-ui@945

@tanstack/ai-svelte

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-svelte@945

@tanstack/ai-utils

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-utils@945

@tanstack/ai-vue

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue@945

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue-ui@945

@tanstack/openai-base

npm i https://pkg.pr.new/TanStack/ai/@tanstack/openai-base@945

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/preact-ai-devtools@945

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/react-ai-devtools@945

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/solid-ai-devtools@945

commit: 83d1a97

@tombeckenhamtombeckenham added the waiting-on: author Waiting for the author to respond or update label Jul 23, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on: authorWaiting for the author to respond or update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@AlemTuzlak@jherr@tombeckenham