diff --git a/apps/kitchensink-react/src/AppRoutes.tsx b/apps/kitchensink-react/src/AppRoutes.tsx
index 2c98a823a..85e265b25 100644
--- a/apps/kitchensink-react/src/AppRoutes.tsx
+++ b/apps/kitchensink-react/src/AppRoutes.tsx
@@ -1,3 +1,4 @@
+import {ResourceProvider} from '@sanity/sdk-react'
import {type JSX} from 'react'
import {Route, Routes} from 'react-router'
@@ -14,6 +15,7 @@ import {SearchRoute} from './DocumentCollection/SearchRoute'
import {PresenceRoute} from './Presence/PresenceRoute'
import {ProjectAuthHome} from './ProjectAuthentication/ProjectAuthHome'
import {ProtectedRoute} from './ProtectedRoute'
+import {AgentActionsRoute} from './routes/AgentActionsRoute'
import {DashboardContextRoute} from './routes/DashboardContextRoute'
import {DashboardWorkspacesRoute} from './routes/DashboardWorkspacesRoute'
import ExperimentalResourceClientRoute from './routes/ExperimentalResourceClientRoute'
@@ -121,6 +123,14 @@ export function AppRoutes(): JSX.Element {
{documentCollectionRoutes.map((route) => (
))}
+
+
+
+ }
+ />
} />
} />
} />
diff --git a/apps/kitchensink-react/src/routes/AgentActionsRoute.tsx b/apps/kitchensink-react/src/routes/AgentActionsRoute.tsx
new file mode 100644
index 000000000..495d8b10e
--- /dev/null
+++ b/apps/kitchensink-react/src/routes/AgentActionsRoute.tsx
@@ -0,0 +1,136 @@
+import {
+ type AgentGenerateOptions,
+ type AgentPromptOptions,
+ useAgentGenerate,
+ useAgentPrompt,
+} from '@sanity/sdk-react'
+import {Box, Button, Card, Code, Label, Stack, Text} from '@sanity/ui'
+import {type JSX, useMemo, useState} from 'react'
+
+import {PageLayout} from '../components/PageLayout'
+
+export function AgentActionsRoute(): JSX.Element {
+ const generate = useAgentGenerate()
+ const prompt = useAgentPrompt()
+ const [text, setText] = useState('Write a short poem about typescript and cats')
+ const [promptResult, setPromptResult] = useState('')
+ const [generateResult, setGenerateResult] = useState('')
+ const [isLoadingPrompt, setIsLoadingPrompt] = useState(false)
+ const [isLoadingGenerate, setIsLoadingGenerate] = useState(false)
+ const generateOptions = useMemo(() => {
+ return {
+ // Use the schema collection id (workspace name), not the type name
+ schemaId: '_.schemas.default',
+ targetDocument: {
+ operation: 'create',
+ _id: crypto.randomUUID(),
+ _type: 'movie',
+ },
+ instruction:
+ 'Generate a title and overview for a movie about $topic based on a famous movie. Try to not pick the same movie as someone else would pick.',
+ instructionParams: {topic: 'Sanity SDK'},
+ target: {include: ['title', 'overview']},
+ noWrite: true,
+ }
+ }, [])
+
+ const promptOptions = useMemo(
+ () => ({instruction: text, format: 'string'}),
+ [text],
+ )
+
+ return (
+
+
+
+
+
+
+ Sends an instruction to the LLM and returns plain text (or JSON if requested). Does
+ not reference a schema or write any data.
+
+
+
+
+
+
+
+
+ Generates title and overview for a movie; does not persist changes.
+
+
+ Schema‑aware content generation targeting the current project/dataset. Use schemaId of
+ your document type (e.g. "movie") and target to specify fields. Set noWrite
+ to preview without saving.
+
+
+
+ {generateResult && (
+
+
+ {generateResult}
+
+
+ )}
+
+
+
+
+ )
+}
diff --git a/packages/core/src/_exports/index.ts b/packages/core/src/_exports/index.ts
index e3df1f009..eaaadc053 100644
--- a/packages/core/src/_exports/index.ts
+++ b/packages/core/src/_exports/index.ts
@@ -5,6 +5,25 @@ import {type SanityProject as _SanityProject} from '@sanity/client'
*/
export type SanityProject = _SanityProject
+export type {
+ AgentGenerateOptions,
+ AgentGenerateResult,
+ AgentPatchOptions,
+ AgentPatchResult,
+ AgentPromptOptions,
+ AgentPromptResult,
+ AgentTransformOptions,
+ AgentTransformResult,
+ AgentTranslateOptions,
+ AgentTranslateResult,
+} from '../agent/agentActions'
+export {
+ agentGenerate,
+ agentPatch,
+ agentPrompt,
+ agentTransform,
+ agentTranslate,
+} from '../agent/agentActions'
export {AuthStateType} from '../auth/authStateType'
export {
type AuthState,
diff --git a/packages/core/src/agent/agentActions.test.ts b/packages/core/src/agent/agentActions.test.ts
new file mode 100644
index 000000000..e5108bb97
--- /dev/null
+++ b/packages/core/src/agent/agentActions.test.ts
@@ -0,0 +1,81 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import {firstValueFrom, of} from 'rxjs'
+import {beforeEach, describe, expect, it, vi} from 'vitest'
+
+import {
+ agentGenerate,
+ agentPatch,
+ agentPrompt,
+ agentTransform,
+ agentTranslate,
+} from './agentActions'
+
+let mockClient: any
+
+vi.mock('../client/clientStore', () => {
+ return {
+ getClientState: () => ({observable: of(mockClient)}),
+ }
+})
+
+describe('agent actions', () => {
+ beforeEach(() => {
+ mockClient = {
+ observable: {
+ agent: {
+ action: {
+ generate: vi.fn(),
+ transform: vi.fn(),
+ translate: vi.fn(),
+ },
+ },
+ },
+ agent: {
+ action: {
+ prompt: vi.fn(),
+ patch: vi.fn(),
+ },
+ },
+ }
+ })
+
+ it('agentGenerate returns observable from client', async () => {
+ mockClient.observable.agent.action.generate.mockReturnValue(of('gen'))
+ const instance = {config: {projectId: 'p', dataset: 'd'}} as any
+ const value = await firstValueFrom(agentGenerate(instance, {foo: 'bar'} as any))
+ expect(value).toBe('gen')
+ expect(mockClient.observable.agent.action.generate).toHaveBeenCalledWith({foo: 'bar'})
+ })
+
+ it('agentTransform returns observable from client', async () => {
+ mockClient.observable.agent.action.transform.mockReturnValue(of('xform'))
+ const instance = {config: {projectId: 'p', dataset: 'd'}} as any
+ const value = await firstValueFrom(agentTransform(instance, {a: 1} as any))
+ expect(value).toBe('xform')
+ expect(mockClient.observable.agent.action.transform).toHaveBeenCalledWith({a: 1})
+ })
+
+ it('agentTranslate returns observable from client', async () => {
+ mockClient.observable.agent.action.translate.mockReturnValue(of('xlate'))
+ const instance = {config: {projectId: 'p', dataset: 'd'}} as any
+ const value = await firstValueFrom(agentTranslate(instance, {b: 2} as any))
+ expect(value).toBe('xlate')
+ expect(mockClient.observable.agent.action.translate).toHaveBeenCalledWith({b: 2})
+ })
+
+ it('agentPrompt wraps promise into observable', async () => {
+ mockClient.agent.action.prompt.mockResolvedValue('prompted')
+ const instance = {config: {projectId: 'p', dataset: 'd'}} as any
+ const value = await firstValueFrom(agentPrompt(instance, {p: true} as any))
+ expect(value).toBe('prompted')
+ expect(mockClient.agent.action.prompt).toHaveBeenCalledWith({p: true})
+ })
+
+ it('agentPatch wraps promise into observable', async () => {
+ mockClient.agent.action.patch.mockResolvedValue('patched')
+ const instance = {config: {projectId: 'p', dataset: 'd'}} as any
+ const value = await firstValueFrom(agentPatch(instance, {q: false} as any))
+ expect(value).toBe('patched')
+ expect(mockClient.agent.action.patch).toHaveBeenCalledWith({q: false})
+ })
+})
diff --git a/packages/core/src/agent/agentActions.ts b/packages/core/src/agent/agentActions.ts
new file mode 100644
index 000000000..893f27aaf
--- /dev/null
+++ b/packages/core/src/agent/agentActions.ts
@@ -0,0 +1,139 @@
+import {type SanityClient} from '@sanity/client'
+import {from, Observable, switchMap} from 'rxjs'
+
+import {getClientState} from '../client/clientStore'
+import {type SanityInstance} from '../store/createSanityInstance'
+
+const API_VERSION = 'vX'
+
+/** @alpha */
+export type AgentGenerateOptions = Parameters<
+ SanityClient['observable']['agent']['action']['generate']
+>[0]
+
+/** @alpha */
+export type AgentTransformOptions = Parameters<
+ SanityClient['observable']['agent']['action']['transform']
+>[0]
+
+/** @alpha */
+export type AgentTranslateOptions = Parameters<
+ SanityClient['observable']['agent']['action']['translate']
+>[0]
+
+/** @alpha */
+export type AgentPromptOptions = Parameters[0]
+
+/** @alpha */
+export type AgentPatchOptions = Parameters[0]
+
+/** @alpha */
+export type AgentGenerateResult = Awaited<
+ ReturnType
+>
+
+/** @alpha */
+export type AgentTransformResult = Awaited<
+ ReturnType
+>
+
+/** @alpha */
+export type AgentTranslateResult = Awaited<
+ ReturnType
+>
+
+/** @alpha */
+export type AgentPromptResult = Awaited>
+
+/** @alpha */
+export type AgentPatchResult = Awaited>
+
+/**
+ * Generates a new document using the agent.
+ * @param instance - The Sanity instance.
+ * @param options - The options for the agent generate action. See the [Agent Actions API](https://www.sanity.io/docs/agent-actions/introduction) for more details.
+ * @returns An Observable emitting the result of the agent generate action.
+ * @alpha
+ */
+export function agentGenerate(
+ instance: SanityInstance,
+ options: AgentGenerateOptions,
+): AgentGenerateResult {
+ return getClientState(instance, {
+ apiVersion: API_VERSION,
+ projectId: instance.config.projectId,
+ dataset: instance.config.dataset,
+ }).observable.pipe(switchMap((client) => client.observable.agent.action.generate(options)))
+}
+
+/**
+ * Transforms a document using the agent.
+ * @param instance - The Sanity instance.
+ * @param options - The options for the agent transform action. See the [Agent Actions API](https://www.sanity.io/docs/agent-actions/introduction) for more details.
+ * @returns An Observable emitting the result of the agent transform action.
+ * @alpha
+ */
+export function agentTransform(
+ instance: SanityInstance,
+ options: AgentTransformOptions,
+): AgentTransformResult {
+ return getClientState(instance, {
+ apiVersion: API_VERSION,
+ projectId: instance.config.projectId,
+ dataset: instance.config.dataset,
+ }).observable.pipe(switchMap((client) => client.observable.agent.action.transform(options)))
+}
+
+/**
+ * Translates a document using the agent.
+ * @param instance - The Sanity instance.
+ * @param options - The options for the agent translate action. See the [Agent Actions API](https://www.sanity.io/docs/agent-actions/introduction) for more details.
+ * @returns An Observable emitting the result of the agent translate action.
+ * @alpha
+ */
+export function agentTranslate(
+ instance: SanityInstance,
+ options: AgentTranslateOptions,
+): AgentTranslateResult {
+ return getClientState(instance, {
+ apiVersion: API_VERSION,
+ projectId: instance.config.projectId,
+ dataset: instance.config.dataset,
+ }).observable.pipe(switchMap((client) => client.observable.agent.action.translate(options)))
+}
+
+/**
+ * Prompts the agent using the same instruction template format as the other actions, but returns text or json instead of acting on a document.
+ * @param instance - The Sanity instance.
+ * @param options - The options for the agent prompt action. See the [Agent Actions API](https://www.sanity.io/docs/agent-actions/introduction) for more details.
+ * @returns An Observable emitting the result of the agent prompt action.
+ * @alpha
+ */
+export function agentPrompt(
+ instance: SanityInstance,
+ options: AgentPromptOptions,
+): Observable {
+ return getClientState(instance, {
+ apiVersion: API_VERSION,
+ projectId: instance.config.projectId,
+ dataset: instance.config.dataset,
+ }).observable.pipe(switchMap((client) => from(client.agent.action.prompt(options))))
+}
+
+/**
+ * Patches a document using the agent.
+ * @param instance - The Sanity instance.
+ * @param options - The options for the agent patch action. See the [Agent Actions API](https://www.sanity.io/docs/agent-actions/introduction) for more details.
+ * @returns An Observable emitting the result of the agent patch action.
+ * @alpha
+ */
+export function agentPatch(
+ instance: SanityInstance,
+ options: AgentPatchOptions,
+): Observable {
+ return getClientState(instance, {
+ apiVersion: API_VERSION,
+ projectId: instance.config.projectId,
+ dataset: instance.config.dataset,
+ }).observable.pipe(switchMap((client) => from(client.agent.action.patch(options))))
+}
diff --git a/packages/react/src/_exports/sdk-react.ts b/packages/react/src/_exports/sdk-react.ts
index 88c38a770..c7d65074c 100644
--- a/packages/react/src/_exports/sdk-react.ts
+++ b/packages/react/src/_exports/sdk-react.ts
@@ -6,6 +6,13 @@ export {SanityApp, type SanityAppProps} from '../components/SanityApp'
export {SDKProvider, type SDKProviderProps} from '../components/SDKProvider'
export {ComlinkTokenRefreshProvider} from '../context/ComlinkTokenRefresh'
export {ResourceProvider, type ResourceProviderProps} from '../context/ResourceProvider'
+export {
+ useAgentGenerate,
+ useAgentPatch,
+ useAgentPrompt,
+ useAgentTransform,
+ useAgentTranslate,
+} from '../hooks/agent/agentActions'
export {useAuthState} from '../hooks/auth/useAuthState'
export {useAuthToken} from '../hooks/auth/useAuthToken'
export {useCurrentUser} from '../hooks/auth/useCurrentUser'
diff --git a/packages/react/src/hooks/agent/agentActions.test.tsx b/packages/react/src/hooks/agent/agentActions.test.tsx
new file mode 100644
index 000000000..f3bae2c19
--- /dev/null
+++ b/packages/react/src/hooks/agent/agentActions.test.tsx
@@ -0,0 +1,78 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import {renderHook} from '@testing-library/react'
+import {of} from 'rxjs'
+import {describe, expect, it, vi} from 'vitest'
+
+import {ResourceProvider} from '../../context/ResourceProvider'
+import {
+ useAgentGenerate,
+ useAgentPatch,
+ useAgentPrompt,
+ useAgentTransform,
+ useAgentTranslate,
+} from './agentActions'
+
+vi.mock('@sanity/sdk', async (orig) => {
+ const actual = await orig()
+ return {
+ ...(actual as Record),
+ agentGenerate: vi.fn(() => of('gen')),
+ agentTransform: vi.fn(() => of('xform')),
+ agentTranslate: vi.fn(() => of('xlate')),
+ agentPrompt: vi.fn(() => of('prompted')),
+ agentPatch: vi.fn(() => of('patched')),
+ }
+})
+
+describe('agent action hooks', () => {
+ const wrapper = ({children}: {children: React.ReactNode}) => (
+
+ {children}
+
+ )
+
+ it('useAgentGenerate returns a callable that delegates to core', async () => {
+ const {result} = renderHook(() => useAgentGenerate(), {wrapper})
+ const value = await new Promise((resolve, reject) => {
+ result.current({} as any).subscribe({
+ next: (v) => resolve(v),
+ error: reject,
+ })
+ })
+ expect(value).toBe('gen')
+ })
+
+ it('useAgentTransform returns a callable that delegates to core', async () => {
+ const {result} = renderHook(() => useAgentTransform(), {wrapper})
+ const value = await new Promise((resolve, reject) => {
+ result.current({} as any).subscribe({
+ next: (v) => resolve(v),
+ error: reject,
+ })
+ })
+ expect(value).toBe('xform')
+ })
+
+ it('useAgentTranslate returns a callable that delegates to core', async () => {
+ const {result} = renderHook(() => useAgentTranslate(), {wrapper})
+ const value = await new Promise((resolve, reject) => {
+ result.current({} as any).subscribe({
+ next: (v) => resolve(v),
+ error: reject,
+ })
+ })
+ expect(value).toBe('xlate')
+ })
+
+ it('useAgentPrompt returns a callable that delegates to core', async () => {
+ const {result} = renderHook(() => useAgentPrompt(), {wrapper})
+ const value = await result.current({} as any)
+ expect(value).toBe('prompted')
+ })
+
+ it('useAgentPatch returns a callable that delegates to core', async () => {
+ const {result} = renderHook(() => useAgentPatch(), {wrapper})
+ const value = await result.current({} as any)
+ expect(value).toBe('patched')
+ })
+})
diff --git a/packages/react/src/hooks/agent/agentActions.ts b/packages/react/src/hooks/agent/agentActions.ts
new file mode 100644
index 000000000..55a0d4fcb
--- /dev/null
+++ b/packages/react/src/hooks/agent/agentActions.ts
@@ -0,0 +1,136 @@
+import {
+ agentGenerate,
+ type AgentGenerateOptions,
+ agentPatch,
+ type AgentPatchOptions,
+ type AgentPatchResult,
+ agentPrompt,
+ type AgentPromptOptions,
+ type AgentPromptResult,
+ agentTransform,
+ type AgentTransformOptions,
+ agentTranslate,
+ type AgentTranslateOptions,
+ type SanityInstance,
+} from '@sanity/sdk'
+import {firstValueFrom} from 'rxjs'
+
+import {createCallbackHook} from '../helpers/createCallbackHook'
+
+interface Subscription {
+ unsubscribe(): void
+}
+
+interface Observer {
+ next?: (value: T) => void
+ error?: (err: unknown) => void
+ complete?: () => void
+}
+
+interface Subscribable {
+ subscribe(observer: Observer): Subscription
+ subscribe(
+ next: (value: T) => void,
+ error?: (err: unknown) => void,
+ complete?: () => void,
+ ): Subscription
+}
+
+/**
+ * @alpha
+ * Generates content for a document (or specific fields) via Sanity Agent Actions.
+ * - Uses instruction templates with `$variables` and supports `instructionParams` (constants, fields, documents, GROQ queries).
+ * - Can target specific paths/fields; supports image generation when targeting image fields.
+ * - Supports optional `temperature`, `async`, `noWrite`, and `conditionalPaths`.
+ *
+ * Returns a stable callback that triggers the action and yields a Subscribable stream.
+ */
+export const useAgentGenerate: () => (options: AgentGenerateOptions) => Subscribable =
+ createCallbackHook(agentGenerate) as unknown as () => (
+ options: AgentGenerateOptions,
+ ) => Subscribable
+
+/**
+ * @alpha
+ * Transforms an existing document or selected fields using Sanity Agent Actions.
+ * - Accepts `instruction` and `instructionParams` (constants, fields, documents, GROQ queries).
+ * - Can write to the same or a different `targetDocument` (create/edit), and target specific paths.
+ * - Supports per-path image transform instructions and image description operations.
+ * - Optional `temperature`, `async`, `noWrite`, `conditionalPaths`.
+ *
+ * Returns a stable callback that triggers the action and yields a Subscribable stream.
+ */
+export const useAgentTransform: () => (options: AgentTransformOptions) => Subscribable =
+ createCallbackHook(agentTransform) as unknown as () => (
+ options: AgentTransformOptions,
+ ) => Subscribable
+
+/**
+ * @alpha
+ * Translates documents or fields using Sanity Agent Actions.
+ * - Configure `fromLanguage`/`toLanguage`, optional `styleGuide`, and `protectedPhrases`.
+ * - Can write into a different `targetDocument`, and/or store language in a field.
+ * - Optional `temperature`, `async`, `noWrite`, `conditionalPaths`.
+ *
+ * Returns a stable callback that triggers the action and yields a Subscribable stream.
+ */
+export const useAgentTranslate: () => (options: AgentTranslateOptions) => Subscribable =
+ createCallbackHook(agentTranslate) as unknown as () => (
+ options: AgentTranslateOptions,
+ ) => Subscribable
+
+/**
+ * @alpha
+ * Prompts the LLM using the same instruction template format as other actions.
+ * - `format`: 'string' or 'json' (instruction must contain the word "json" for JSON responses).
+ * - Optional `temperature`.
+ *
+ * Returns a stable callback that triggers the action and resolves a Promise with the prompt result.
+ */
+function promptAdapter(
+ instance: SanityInstance,
+ options: AgentPromptOptions,
+): Promise {
+ return firstValueFrom(agentPrompt(instance, options))
+}
+
+/**
+ * @alpha
+ * Prompts the LLM using the same instruction template format as other actions.
+ * - `format`: 'string' or 'json' (instruction must contain the word "json" for JSON responses).
+ * - Optional `temperature`.
+ *
+ * Returns a stable callback that triggers the action and resolves a Promise with the prompt result.
+ */
+export const useAgentPrompt: () => (options: AgentPromptOptions) => Promise =
+ createCallbackHook(promptAdapter)
+
+/**
+ * @alpha
+ * Schema-aware patching with Sanity Agent Actions.
+ * - Validates provided paths/values against the document schema and merges object values safely.
+ * - Prevents duplicate keys and supports array appends (including after a specific keyed item).
+ * - Accepts `documentId` or `targetDocument` (mutually exclusive).
+ * - Optional `async`, `noWrite`, `conditionalPaths`.
+ *
+ * Returns a stable callback that triggers the action and resolves a Promise with the patch result.
+ */
+function patchAdapter(
+ instance: SanityInstance,
+ options: AgentPatchOptions,
+): Promise {
+ return firstValueFrom(agentPatch(instance, options))
+}
+
+/**
+ * @alpha
+ * Schema-aware patching with Sanity Agent Actions.
+ * - Validates provided paths/values against the document schema and merges object values safely.
+ * - Prevents duplicate keys and supports array appends (including after a specific keyed item).
+ * - Accepts `documentId` or `targetDocument` (mutually exclusive).
+ * - Optional `async`, `noWrite`, `conditionalPaths`.
+ *
+ * Returns a stable callback that triggers the action and resolves a Promise with the patch result.
+ */
+export const useAgentPatch: () => (options: AgentPatchOptions) => Promise =
+ createCallbackHook(patchAdapter)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 572bef6ac..be74619b0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12440,7 +12440,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@8.26.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.25.1(jiti@2.4.2)):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@8.26.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -12462,7 +12462,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.25.1(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.26.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.25.1(jiti@2.4.2))
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.26.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3