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

@AlemTuzlak AlemTuzlak 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. Rename transaction/verbplugin, strip server-side composition (ctx.call/sub-runs), drop clientTransaction, 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'
const drafting = chatVerb((req) => chat({ ... }))
const heroImage = verb({ input: z.object({ prompt: z.string() }), execute: async (req, ctx) => generateImage({ ... }) })
export const blogTransaction = defineTransaction({ drafting, heroImage, narration })
export const blogTxnDef = clientTransaction<typeof blogTransaction>({ drafting: 'chat', heroImage: 'one-shot', narration: 'one-shot' })

This PR

import { definePlugin, chatPlugin, imagePlugin, speechPlugin } from '@tanstack/ai-plugin-toolkit'
const drafting = chatPlugin((req) => chat({ ... }))
const heroImage = imagePlugin((req) => generateImage({ ... }))   // media factory over generationPlugin
const narration = speechPlugin((req) => generateSpeech({ ... }))
export const blogPlugin = 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:

const draft = await p.drafting.sendMessage(topic)
await Promise.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 PR
import { usePlugin } from '@tanstack/ai-react/plugin'
import { blogPlugin } from '../lib/blog-studio'      // the real definePlugin value
const p = usePlugin(blogPlugin, {
  connection,
  drafting:  { forwardedProps: { tone: 'punchy' } }, // flat, keyed by plugin name
  heroImage: { 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 place
import { definePlugin, chatPlugin, imagePlugin } from '@tanstack/ai-plugin-toolkit'
export const heroImage = imagePlugin((req) => generateImage({ ... }))
export const blogPlugin = 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.

const heroImage = imagePlugin((req) => generateImage({ ... }))

const img = await heroImage.run({ prompt: 'a cat' })   // raw params  → ImageGenerationResult
await heroImage.run(request)                             // HTTP Request → parse + validate → result
await heroImage.run(body)                                // parsed body  → validate → result

const { text, structured } = await drafting.run(messages) // chat → collected result

// serve one plugin yourself:
export const GET = async ({ request }) => Response.json(await heroImage.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

#942 This PR
defineTransaction / useTransaction / createTransaction definePlugin / usePlugin / createPlugin
chatVerb / verb chatPlugin / generationPlugin (+ imagePlugin/videoPlugin/audioPlugin/speechPlugin/transcriptionPlugin/summarizePlugin)
clientTransaction removed — bind off the definePlugin value
TransactionClient PluginClient
execute(req, ctx) + ctx.call execute(req) + client-side orchestration
TRANSACTION_EVENTS, sub-runs, subRuns removed (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

coderabbitai Bot 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-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

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

🟥 Major bumps

Package Version Reason
@tanstack/ai-angular 0.2.3 → 1.0.0 Changeset
@tanstack/ai-anthropic 0.16.1 → 1.0.0 Changeset
@tanstack/ai-bedrock 0.1.2 → 1.0.0 Changeset
@tanstack/ai-fal 0.9.10 → 1.0.0 Changeset
@tanstack/ai-gemini 0.19.1 → 1.0.0 Changeset
@tanstack/ai-grok 0.14.7 → 1.0.0 Changeset
@tanstack/ai-groq 0.5.1 → 1.0.0 Changeset
@tanstack/ai-mistral 0.2.1 → 1.0.0 Changeset
@tanstack/ai-ollama 0.8.14 → 1.0.0 Changeset
@tanstack/ai-openai 0.16.0 → 1.0.0 Changeset
@tanstack/ai-openrouter 0.15.8 → 1.0.0 Changeset
@tanstack/ai-preact 0.10.3 → 1.0.0 Changeset
@tanstack/ai-react 0.16.4 → 1.0.0 Changeset
@tanstack/ai-sandbox 0.2.2 → 1.0.0 Changeset
@tanstack/ai-solid 0.14.3 → 1.0.0 Changeset
@tanstack/ai-svelte 0.14.3 → 1.0.0 Changeset
@tanstack/ai-vue 0.14.3 → 1.0.0 Changeset
@tanstack/ai-acp 0.2.1 → 1.0.0 Dependent
@tanstack/ai-claude-code 0.2.1 → 1.0.0 Dependent
@tanstack/ai-code-mode 0.3.6 → 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.3.9 → 1.0.0 Dependent
@tanstack/ai-codex 0.2.1 → 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.32 → 1.0.0 Dependent
@tanstack/ai-grok-build 0.2.1 → 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.45 → 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.45 → 1.0.0 Dependent
@tanstack/ai-opencode 0.2.1 → 1.0.0 Dependent
@tanstack/ai-react-ui 0.8.13 → 1.0.0 Dependent
@tanstack/ai-sandbox-cloudflare 0.2.2 → 1.0.0 Dependent
@tanstack/ai-sandbox-daytona 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-docker 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-local-process 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-sprites 0.2.1 → 1.0.0 Dependent
@tanstack/ai-sandbox-vercel 0.2.0 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.7.12 → 1.0.0 Dependent
@tanstack/openai-base 0.9.7 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.40.0 → 0.41.0 Changeset
@tanstack/ai-client 0.20.0 → 0.21.0 Changeset
@tanstack/ai-plugin-toolkit 0.1.0 → 0.2.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-devtools-core 0.4.22 → 0.4.23 Dependent
@tanstack/ai-isolate-cloudflare 0.2.36 → 0.2.37 Dependent
@tanstack/ai-mcp 0.2.3 → 0.2.4 Dependent
@tanstack/ai-vue-ui 0.2.31 → 0.2.32 Dependent
@tanstack/preact-ai-devtools 0.1.65 → 0.1.66 Dependent
@tanstack/react-ai-devtools 0.2.65 → 0.2.66 Dependent
@tanstack/solid-ai-devtools 0.2.65 → 0.2.66 Dependent

@nx-cloud

nx-cloud Bot commented Jul 15, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 83d1a97

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 1m 58s View ↗

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

@nx-cloud

nx-cloud Bot 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

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ❌ Failed 12m 39s View ↗
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 2m 15s View ↗

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

@pkg-pr-new

pkg-pr-new Bot 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

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants