From 49b7f67dba44b1a042b169a809d04618869b9068 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 5 Feb 2026 17:12:28 -0500 Subject: [PATCH 01/11] feat(backend): Add test session tokens endpoints --- .changeset/eighty-bobcats-unite.md | 5 ++++ .../src/api/endpoints/TestSessionTokenApi.ts | 19 ++++++++++++++ packages/backend/src/api/endpoints/index.ts | 1 + packages/backend/src/api/factory.ts | 2 ++ .../backend/src/api/resources/Deserializer.ts | 3 +++ packages/backend/src/api/resources/JSON.ts | 11 ++++++++ .../src/api/resources/TestSessionToken.ts | 25 +++++++++++++++++++ packages/backend/src/api/resources/index.ts | 1 + 8 files changed, 67 insertions(+) create mode 100644 .changeset/eighty-bobcats-unite.md create mode 100644 packages/backend/src/api/endpoints/TestSessionTokenApi.ts create mode 100644 packages/backend/src/api/resources/TestSessionToken.ts diff --git a/.changeset/eighty-bobcats-unite.md b/.changeset/eighty-bobcats-unite.md new file mode 100644 index 00000000000..45e0df390c0 --- /dev/null +++ b/.changeset/eighty-bobcats-unite.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': minor +--- + +Add support for Test Session Tokens API endpoint which allows developers to create test session tokens that can be used to impersonate users during automated flows. diff --git a/packages/backend/src/api/endpoints/TestSessionTokenApi.ts b/packages/backend/src/api/endpoints/TestSessionTokenApi.ts new file mode 100644 index 00000000000..59501f0b7a1 --- /dev/null +++ b/packages/backend/src/api/endpoints/TestSessionTokenApi.ts @@ -0,0 +1,19 @@ +import type { TestSessionToken } from '../resources/TestSessionToken'; +import { AbstractAPI } from './AbstractApi'; + +type CreateTestSessionTokenParams = { + userId: string; + sessionMaxDurationInSeconds?: number; +}; + +const basePath = '/test_session_tokens'; + +export class TestSessionTokenAPI extends AbstractAPI { + public async create(params: CreateTestSessionTokenParams) { + return this.request({ + method: 'POST', + path: basePath, + bodyParams: params, + }); + } +} diff --git a/packages/backend/src/api/endpoints/index.ts b/packages/backend/src/api/endpoints/index.ts index c03875a427d..c0c7211ac3f 100644 --- a/packages/backend/src/api/endpoints/index.ts +++ b/packages/backend/src/api/endpoints/index.ts @@ -24,6 +24,7 @@ export * from './SamlConnectionApi'; export * from './SessionApi'; export * from './SignInTokenApi'; export * from './SignUpApi'; +export * from './TestSessionTokenApi'; export * from './TestingTokenApi'; export * from './UserApi'; export * from './WaitlistEntryApi'; diff --git a/packages/backend/src/api/factory.ts b/packages/backend/src/api/factory.ts index 6c82869a46e..d928cacdefe 100644 --- a/packages/backend/src/api/factory.ts +++ b/packages/backend/src/api/factory.ts @@ -25,6 +25,7 @@ import { SignInTokenAPI, SignUpAPI, TestingTokenAPI, + TestSessionTokenAPI, UserAPI, WaitlistEntryAPI, WebhookAPI, @@ -88,6 +89,7 @@ export function createBackendApiClient(options: CreateBackendApiOptions) { sessions: new SessionAPI(request), signInTokens: new SignInTokenAPI(request), signUps: new SignUpAPI(request), + testSessionTokens: new TestSessionTokenAPI(request), testingTokens: new TestingTokenAPI(request), users: new UserAPI(request), waitlistEntries: new WaitlistEntryAPI(request), diff --git a/packages/backend/src/api/resources/Deserializer.ts b/packages/backend/src/api/resources/Deserializer.ts index 79fbe119836..27ef0db7671 100644 --- a/packages/backend/src/api/resources/Deserializer.ts +++ b/packages/backend/src/api/resources/Deserializer.ts @@ -33,6 +33,7 @@ import { SignInToken, SignUpAttempt, SMSMessage, + TestSessionToken, Token, User, } from '.'; @@ -169,6 +170,8 @@ function jsonToObject(item: any): any { return SamlConnection.fromJSON(item); case ObjectType.SignInToken: return SignInToken.fromJSON(item); + case ObjectType.TestSessionToken: + return TestSessionToken.fromJSON(item); case ObjectType.SignUpAttempt: return SignUpAttempt.fromJSON(item); case ObjectType.Session: diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index 075e0f6fa95..8c3bff33185 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -62,6 +62,7 @@ export const ObjectType = { Token: 'token', TotalCount: 'total_count', TestingToken: 'testing_token', + TestSessionToken: 'test_session_token', Role: 'role', Permission: 'permission', BillingPayer: 'commerce_payer', @@ -512,6 +513,16 @@ export interface SignInTokenJSON extends ClerkResourceJSON { updated_at: number; } +export interface TestSessionTokenJSON extends ClerkResourceJSON { + object: typeof ObjectType.TestSessionToken; + user_id: string; + token: string; + status: 'pending' | 'accepted' | 'revoked'; + url: string; + created_at: number; + updated_at: number; +} + export interface SignUpJSON extends ClerkResourceJSON { object: typeof ObjectType.SignUpAttempt; id: string; diff --git a/packages/backend/src/api/resources/TestSessionToken.ts b/packages/backend/src/api/resources/TestSessionToken.ts new file mode 100644 index 00000000000..a255d8702fd --- /dev/null +++ b/packages/backend/src/api/resources/TestSessionToken.ts @@ -0,0 +1,25 @@ +import type { TestSessionTokenJSON } from './JSON'; + +export class TestSessionToken { + constructor( + readonly id: string, + readonly userId: string, + readonly token: string, + readonly status: 'pending' | 'accepted' | 'revoked', + readonly url: string, + readonly createdAt: number, + readonly updatedAt: number, + ) {} + + static fromJSON(data: TestSessionTokenJSON): TestSessionToken { + return new TestSessionToken( + data.id, + data.user_id, + data.token, + data.status, + data.url, + data.created_at, + data.updated_at, + ); + } +} diff --git a/packages/backend/src/api/resources/index.ts b/packages/backend/src/api/resources/index.ts index 00e119dbb21..9d1e8fcda86 100644 --- a/packages/backend/src/api/resources/index.ts +++ b/packages/backend/src/api/resources/index.ts @@ -55,6 +55,7 @@ export * from './SignInTokens'; export * from './SignUpAttempt'; export * from './SMSMessage'; export * from './TestingToken'; +export * from './TestSessionToken'; export * from './Token'; export * from './User'; export * from './Verification'; From e72278fbad788701f3bce9ce64236759b32506c5 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 5 Feb 2026 17:33:08 -0500 Subject: [PATCH 02/11] chore: Add JSDoc --- .../src/api/resources/TestSessionToken.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/backend/src/api/resources/TestSessionToken.ts b/packages/backend/src/api/resources/TestSessionToken.ts index a255d8702fd..27bcde94834 100644 --- a/packages/backend/src/api/resources/TestSessionToken.ts +++ b/packages/backend/src/api/resources/TestSessionToken.ts @@ -1,6 +1,23 @@ import type { TestSessionTokenJSON } from './JSON'; +/** + * Represents a test session token resource. + * + * Test session tokens are used for testing purposes and allow creating sessions + * for users without requiring full authentication flows. + */ export class TestSessionToken { + /** + * Creates a new TestSessionToken instance. + * + * @param id - The unique identifier for the test session token + * @param userId - The unique identifier of the user associated with this token + * @param token - The test session token string value + * @param status - The current status of the token: 'pending', 'accepted', or 'revoked' + * @param url - The URL associated with the test session token + * @param createdAt - Unix timestamp (in milliseconds) indicating when the token was created + * @param updatedAt - Unix timestamp (in milliseconds) indicating when the token was last updated + */ constructor( readonly id: string, readonly userId: string, @@ -11,6 +28,12 @@ export class TestSessionToken { readonly updatedAt: number, ) {} + /** + * Creates a TestSessionToken instance from a JSON object. + * + * @param data - The JSON object containing test session token data + * @returns A new TestSessionToken instance + */ static fromJSON(data: TestSessionTokenJSON): TestSessionToken { return new TestSessionToken( data.id, From 1d5a9761268889fe41613335afbe4d1fd56fd9f5 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 5 Feb 2026 17:37:00 -0500 Subject: [PATCH 03/11] chore: Add redirectUrl --- packages/backend/src/api/endpoints/TestSessionTokenApi.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend/src/api/endpoints/TestSessionTokenApi.ts b/packages/backend/src/api/endpoints/TestSessionTokenApi.ts index 59501f0b7a1..becd607a6fb 100644 --- a/packages/backend/src/api/endpoints/TestSessionTokenApi.ts +++ b/packages/backend/src/api/endpoints/TestSessionTokenApi.ts @@ -4,6 +4,7 @@ import { AbstractAPI } from './AbstractApi'; type CreateTestSessionTokenParams = { userId: string; sessionMaxDurationInSeconds?: number; + redirectUrl?: string; }; const basePath = '/test_session_tokens'; From bbe33f9073c697490ff5803c224c79ef2ec55b19 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Fri, 6 Feb 2026 17:14:32 -0500 Subject: [PATCH 04/11] chore: Rename Test Session -> Agent --- .changeset/eighty-bobcats-unite.md | 2 +- .../src/api/endpoints/AgentTokenApi.ts | 33 ++++++++++++ .../src/api/endpoints/TestSessionTokenApi.ts | 20 -------- packages/backend/src/api/endpoints/index.ts | 2 +- packages/backend/src/api/factory.ts | 7 ++- .../backend/src/api/resources/AgentToken.ts | 50 +++++++++++++++++++ .../backend/src/api/resources/Deserializer.ts | 6 +-- packages/backend/src/api/resources/JSON.ts | 6 +-- .../src/api/resources/TestSessionToken.ts | 48 ------------------ packages/backend/src/api/resources/index.ts | 3 +- 10 files changed, 97 insertions(+), 80 deletions(-) create mode 100644 packages/backend/src/api/endpoints/AgentTokenApi.ts delete mode 100644 packages/backend/src/api/endpoints/TestSessionTokenApi.ts create mode 100644 packages/backend/src/api/resources/AgentToken.ts delete mode 100644 packages/backend/src/api/resources/TestSessionToken.ts diff --git a/.changeset/eighty-bobcats-unite.md b/.changeset/eighty-bobcats-unite.md index 45e0df390c0..4e455785b43 100644 --- a/.changeset/eighty-bobcats-unite.md +++ b/.changeset/eighty-bobcats-unite.md @@ -2,4 +2,4 @@ '@clerk/backend': minor --- -Add support for Test Session Tokens API endpoint which allows developers to create test session tokens that can be used to impersonate users during automated flows. +Add support for Agent Tokens API endpoint which allows developers to create agent tokens that can be used to impersonate users through automated flows. diff --git a/packages/backend/src/api/endpoints/AgentTokenApi.ts b/packages/backend/src/api/endpoints/AgentTokenApi.ts new file mode 100644 index 00000000000..da6ea851390 --- /dev/null +++ b/packages/backend/src/api/endpoints/AgentTokenApi.ts @@ -0,0 +1,33 @@ +import type { AgentToken } from '../resources/AgentToken'; +import { AbstractAPI } from './AbstractApi'; + +type CreateAgentTokenParams = { + /** + * The ID of the user to create an agent token for. + */ + userId: string; + /** + * The maximum duration that the session which will be created by the generated agent token should last. + * By default, the duration is 30 minutes. + */ + sessionMaxDurationInSeconds?: number; + /** + * The URL to redirect to after the agent token is consumed. + */ + redirectUrl?: string; +}; + +const basePath = '/agent_tokens'; + +export class AgentTokenAPI extends AbstractAPI { + /** + * @experimental This is an experimental API for the Agent Tokens feature that is available under a private beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. + */ + public async create(params: CreateAgentTokenParams) { + return this.request({ + method: 'POST', + path: basePath, + bodyParams: params, + }); + } +} diff --git a/packages/backend/src/api/endpoints/TestSessionTokenApi.ts b/packages/backend/src/api/endpoints/TestSessionTokenApi.ts deleted file mode 100644 index becd607a6fb..00000000000 --- a/packages/backend/src/api/endpoints/TestSessionTokenApi.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { TestSessionToken } from '../resources/TestSessionToken'; -import { AbstractAPI } from './AbstractApi'; - -type CreateTestSessionTokenParams = { - userId: string; - sessionMaxDurationInSeconds?: number; - redirectUrl?: string; -}; - -const basePath = '/test_session_tokens'; - -export class TestSessionTokenAPI extends AbstractAPI { - public async create(params: CreateTestSessionTokenParams) { - return this.request({ - method: 'POST', - path: basePath, - bodyParams: params, - }); - } -} diff --git a/packages/backend/src/api/endpoints/index.ts b/packages/backend/src/api/endpoints/index.ts index c0c7211ac3f..d5bd5e7880a 100644 --- a/packages/backend/src/api/endpoints/index.ts +++ b/packages/backend/src/api/endpoints/index.ts @@ -1,4 +1,5 @@ export * from './ActorTokenApi'; +export * from './AgentTokenApi'; export * from './AccountlessApplicationsAPI'; export * from './AbstractApi'; export * from './AllowlistIdentifierApi'; @@ -24,7 +25,6 @@ export * from './SamlConnectionApi'; export * from './SessionApi'; export * from './SignInTokenApi'; export * from './SignUpApi'; -export * from './TestSessionTokenApi'; export * from './TestingTokenApi'; export * from './UserApi'; export * from './WaitlistEntryApi'; diff --git a/packages/backend/src/api/factory.ts b/packages/backend/src/api/factory.ts index d928cacdefe..d7d90e7fca4 100644 --- a/packages/backend/src/api/factory.ts +++ b/packages/backend/src/api/factory.ts @@ -1,6 +1,7 @@ import { AccountlessApplicationAPI, ActorTokenAPI, + AgentTokenAPI, AllowlistIdentifierAPI, APIKeysAPI, BetaFeaturesAPI, @@ -25,7 +26,6 @@ import { SignInTokenAPI, SignUpAPI, TestingTokenAPI, - TestSessionTokenAPI, UserAPI, WaitlistEntryAPI, WebhookAPI, @@ -45,6 +45,10 @@ export function createBackendApiClient(options: CreateBackendApiOptions) { buildRequest({ ...options, requireSecretKey: false }), ), actorTokens: new ActorTokenAPI(request), + /** + * @experimental This is an experimental API for the Agent Tokens feature that is available under a private beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. + */ + agentTokens: new AgentTokenAPI(request), allowlistIdentifiers: new AllowlistIdentifierAPI(request), apiKeys: new APIKeysAPI( buildRequest({ @@ -89,7 +93,6 @@ export function createBackendApiClient(options: CreateBackendApiOptions) { sessions: new SessionAPI(request), signInTokens: new SignInTokenAPI(request), signUps: new SignUpAPI(request), - testSessionTokens: new TestSessionTokenAPI(request), testingTokens: new TestingTokenAPI(request), users: new UserAPI(request), waitlistEntries: new WaitlistEntryAPI(request), diff --git a/packages/backend/src/api/resources/AgentToken.ts b/packages/backend/src/api/resources/AgentToken.ts new file mode 100644 index 00000000000..519e03e20d7 --- /dev/null +++ b/packages/backend/src/api/resources/AgentToken.ts @@ -0,0 +1,50 @@ +import type { AgentTokenJSON } from './JSON'; + +/** + * Represents a agent token resource. + * + * Agent tokens are used for testing purposes and allow creating sessions + * for users without requiring full authentication flows. + */ +export class AgentToken { + constructor( + /** + * The unique identifier for the agent token. + */ + readonly id: string, + /** + * The unique identifier for the user associated with this token. + */ + readonly userId: string, + /** + * The agent token string value. + */ + readonly token: string, + /** + * The current status of the token: 'pending', 'accepted', or 'revoked'. + */ + readonly status: 'pending' | 'accepted' | 'revoked', + /** + * The URL associated with the agent token. + */ + readonly url: string, + /** + * Unix timestamp (in milliseconds) indicating when the token was created. + */ + readonly createdAt: number, + /** + * Unix timestamp (in milliseconds) indicating when the token was last updated. + */ + readonly updatedAt: number, + ) {} + + /** + * Creates a AgentToken instance from a JSON object. + * + * @param data - The JSON object containing agent token data + * @returns A new AgentToken instance + */ + static fromJSON(data: AgentTokenJSON): AgentToken { + return new AgentToken(data.id, data.user_id, data.token, data.status, data.url, data.created_at, data.updated_at); + } +} diff --git a/packages/backend/src/api/resources/Deserializer.ts b/packages/backend/src/api/resources/Deserializer.ts index 27ef0db7671..d8b0a938133 100644 --- a/packages/backend/src/api/resources/Deserializer.ts +++ b/packages/backend/src/api/resources/Deserializer.ts @@ -1,5 +1,6 @@ import { ActorToken, + AgentToken, AllowlistIdentifier, APIKey, BlocklistIdentifier, @@ -33,7 +34,6 @@ import { SignInToken, SignUpAttempt, SMSMessage, - TestSessionToken, Token, User, } from '.'; @@ -170,8 +170,8 @@ function jsonToObject(item: any): any { return SamlConnection.fromJSON(item); case ObjectType.SignInToken: return SignInToken.fromJSON(item); - case ObjectType.TestSessionToken: - return TestSessionToken.fromJSON(item); + case ObjectType.AgentToken: + return AgentToken.fromJSON(item); case ObjectType.SignUpAttempt: return SignUpAttempt.fromJSON(item); case ObjectType.Session: diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index 8c3bff33185..feea7ce531a 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -62,7 +62,7 @@ export const ObjectType = { Token: 'token', TotalCount: 'total_count', TestingToken: 'testing_token', - TestSessionToken: 'test_session_token', + AgentToken: 'agent_token', Role: 'role', Permission: 'permission', BillingPayer: 'commerce_payer', @@ -513,8 +513,8 @@ export interface SignInTokenJSON extends ClerkResourceJSON { updated_at: number; } -export interface TestSessionTokenJSON extends ClerkResourceJSON { - object: typeof ObjectType.TestSessionToken; +export interface AgentTokenJSON extends ClerkResourceJSON { + object: typeof ObjectType.AgentToken; user_id: string; token: string; status: 'pending' | 'accepted' | 'revoked'; diff --git a/packages/backend/src/api/resources/TestSessionToken.ts b/packages/backend/src/api/resources/TestSessionToken.ts deleted file mode 100644 index 27bcde94834..00000000000 --- a/packages/backend/src/api/resources/TestSessionToken.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { TestSessionTokenJSON } from './JSON'; - -/** - * Represents a test session token resource. - * - * Test session tokens are used for testing purposes and allow creating sessions - * for users without requiring full authentication flows. - */ -export class TestSessionToken { - /** - * Creates a new TestSessionToken instance. - * - * @param id - The unique identifier for the test session token - * @param userId - The unique identifier of the user associated with this token - * @param token - The test session token string value - * @param status - The current status of the token: 'pending', 'accepted', or 'revoked' - * @param url - The URL associated with the test session token - * @param createdAt - Unix timestamp (in milliseconds) indicating when the token was created - * @param updatedAt - Unix timestamp (in milliseconds) indicating when the token was last updated - */ - constructor( - readonly id: string, - readonly userId: string, - readonly token: string, - readonly status: 'pending' | 'accepted' | 'revoked', - readonly url: string, - readonly createdAt: number, - readonly updatedAt: number, - ) {} - - /** - * Creates a TestSessionToken instance from a JSON object. - * - * @param data - The JSON object containing test session token data - * @returns A new TestSessionToken instance - */ - static fromJSON(data: TestSessionTokenJSON): TestSessionToken { - return new TestSessionToken( - data.id, - data.user_id, - data.token, - data.status, - data.url, - data.created_at, - data.updated_at, - ); - } -} diff --git a/packages/backend/src/api/resources/index.ts b/packages/backend/src/api/resources/index.ts index 9d1e8fcda86..fb2bc91dff4 100644 --- a/packages/backend/src/api/resources/index.ts +++ b/packages/backend/src/api/resources/index.ts @@ -1,5 +1,6 @@ export * from './AccountlessApplication'; export * from './ActorToken'; +export * from './AgentToken'; export * from './AllowlistIdentifier'; export * from './APIKey'; export * from './BlocklistIdentifier'; @@ -54,8 +55,6 @@ export * from './Session'; export * from './SignInTokens'; export * from './SignUpAttempt'; export * from './SMSMessage'; -export * from './TestingToken'; -export * from './TestSessionToken'; export * from './Token'; export * from './User'; export * from './Verification'; From b4e5759fbfbaa2b720779f98a8dd2e7713825c14 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Fri, 6 Feb 2026 17:25:26 -0500 Subject: [PATCH 05/11] fix: Re-add testing-token export --- packages/backend/src/api/resources/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend/src/api/resources/index.ts b/packages/backend/src/api/resources/index.ts index fb2bc91dff4..e1c28b19df1 100644 --- a/packages/backend/src/api/resources/index.ts +++ b/packages/backend/src/api/resources/index.ts @@ -55,6 +55,7 @@ export * from './Session'; export * from './SignInTokens'; export * from './SignUpAttempt'; export * from './SMSMessage'; +export * from './TestingToken'; export * from './Token'; export * from './User'; export * from './Verification'; From a72b3fcdc4fafaece11c8e26fa2a3c86e8878b5d Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Wed, 18 Feb 2026 21:41:14 -0500 Subject: [PATCH 06/11] chore: Update to use latest paths/params --- .changeset/eighty-bobcats-unite.md | 2 +- .../backend/src/api/endpoints/AgentTaskApi.ts | 62 +++++++++++++++++++ .../src/api/endpoints/AgentTokenApi.ts | 33 ---------- packages/backend/src/api/endpoints/index.ts | 2 +- packages/backend/src/api/factory.ts | 6 +- .../resources/{AgentToken.ts => AgentTask.ts} | 14 ++--- .../backend/src/api/resources/Deserializer.ts | 6 +- packages/backend/src/api/resources/JSON.ts | 6 +- packages/backend/src/api/resources/index.ts | 2 +- 9 files changed, 81 insertions(+), 52 deletions(-) create mode 100644 packages/backend/src/api/endpoints/AgentTaskApi.ts delete mode 100644 packages/backend/src/api/endpoints/AgentTokenApi.ts rename packages/backend/src/api/resources/{AgentToken.ts => AgentTask.ts} (71%) diff --git a/.changeset/eighty-bobcats-unite.md b/.changeset/eighty-bobcats-unite.md index 4e455785b43..1947cdd0687 100644 --- a/.changeset/eighty-bobcats-unite.md +++ b/.changeset/eighty-bobcats-unite.md @@ -2,4 +2,4 @@ '@clerk/backend': minor --- -Add support for Agent Tokens API endpoint which allows developers to create agent tokens that can be used to impersonate users through automated flows. +Add support for Agent Tasks API endpoint which allows developers to create agent tasks that can be used to act on behalf of users through automated flows. diff --git a/packages/backend/src/api/endpoints/AgentTaskApi.ts b/packages/backend/src/api/endpoints/AgentTaskApi.ts new file mode 100644 index 00000000000..d4695ab711b --- /dev/null +++ b/packages/backend/src/api/endpoints/AgentTaskApi.ts @@ -0,0 +1,62 @@ +import type { AgentTask } from '../resources/AgentTask'; +import { AbstractAPI } from './AbstractApi'; + +// type AgentTaskOnBehalfOfParams = ; + +type CreateAgentTaskParams = { + /** + * The user to create an agent task for. + */ + onBehalfOf: + | { + /** + * The identifier of the user to create an agent task for. + */ + identifier: string; + userId?: never; + } + | { + /** + * The ID of the user to create an agent task for. + */ + userId: string; + identifier?: never; + }; + /** + * The permissions the agent task will have. + */ + permissions: string; + /** + * The name of the agent to create an agent task for. + */ + agentName: string; + /** + * The description of the agent task to create. + */ + taskDescription: string; + /** + * The URL to redirect to after the agent task is consumed. + */ + redirectUrl: string; + + /** + * The maximum duration that the session which will be created by the generated agent task should last. + * By default, the duration is 30 minutes. + */ + sessionMaxDurationInSeconds?: number; +}; + +const basePath = '/agents/tasks'; + +export class AgentTaskAPI extends AbstractAPI { + /** + * @experimental This is an experimental API for the Agent Tokens feature that is available under a private beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. + */ + public async create(params: CreateAgentTaskParams) { + return this.request({ + method: 'POST', + path: basePath, + bodyParams: params, + }); + } +} diff --git a/packages/backend/src/api/endpoints/AgentTokenApi.ts b/packages/backend/src/api/endpoints/AgentTokenApi.ts deleted file mode 100644 index da6ea851390..00000000000 --- a/packages/backend/src/api/endpoints/AgentTokenApi.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { AgentToken } from '../resources/AgentToken'; -import { AbstractAPI } from './AbstractApi'; - -type CreateAgentTokenParams = { - /** - * The ID of the user to create an agent token for. - */ - userId: string; - /** - * The maximum duration that the session which will be created by the generated agent token should last. - * By default, the duration is 30 minutes. - */ - sessionMaxDurationInSeconds?: number; - /** - * The URL to redirect to after the agent token is consumed. - */ - redirectUrl?: string; -}; - -const basePath = '/agent_tokens'; - -export class AgentTokenAPI extends AbstractAPI { - /** - * @experimental This is an experimental API for the Agent Tokens feature that is available under a private beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. - */ - public async create(params: CreateAgentTokenParams) { - return this.request({ - method: 'POST', - path: basePath, - bodyParams: params, - }); - } -} diff --git a/packages/backend/src/api/endpoints/index.ts b/packages/backend/src/api/endpoints/index.ts index d5bd5e7880a..1f5d7bf7f51 100644 --- a/packages/backend/src/api/endpoints/index.ts +++ b/packages/backend/src/api/endpoints/index.ts @@ -1,5 +1,5 @@ export * from './ActorTokenApi'; -export * from './AgentTokenApi'; +export * from './AgentTaskApi'; export * from './AccountlessApplicationsAPI'; export * from './AbstractApi'; export * from './AllowlistIdentifierApi'; diff --git a/packages/backend/src/api/factory.ts b/packages/backend/src/api/factory.ts index d7d90e7fca4..22f1aa89c21 100644 --- a/packages/backend/src/api/factory.ts +++ b/packages/backend/src/api/factory.ts @@ -1,7 +1,7 @@ import { AccountlessApplicationAPI, ActorTokenAPI, - AgentTokenAPI, + AgentTaskAPI, AllowlistIdentifierAPI, APIKeysAPI, BetaFeaturesAPI, @@ -46,9 +46,9 @@ export function createBackendApiClient(options: CreateBackendApiOptions) { ), actorTokens: new ActorTokenAPI(request), /** - * @experimental This is an experimental API for the Agent Tokens feature that is available under a private beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. + * @experimental This is an experimental API for the Agent Tasks feature that is available under a private beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. */ - agentTokens: new AgentTokenAPI(request), + agentTasks: new AgentTaskAPI(request), allowlistIdentifiers: new AllowlistIdentifierAPI(request), apiKeys: new APIKeysAPI( buildRequest({ diff --git a/packages/backend/src/api/resources/AgentToken.ts b/packages/backend/src/api/resources/AgentTask.ts similarity index 71% rename from packages/backend/src/api/resources/AgentToken.ts rename to packages/backend/src/api/resources/AgentTask.ts index 519e03e20d7..484e953739f 100644 --- a/packages/backend/src/api/resources/AgentToken.ts +++ b/packages/backend/src/api/resources/AgentTask.ts @@ -1,4 +1,4 @@ -import type { AgentTokenJSON } from './JSON'; +import type { AgentTaskJSON } from './JSON'; /** * Represents a agent token resource. @@ -6,7 +6,7 @@ import type { AgentTokenJSON } from './JSON'; * Agent tokens are used for testing purposes and allow creating sessions * for users without requiring full authentication flows. */ -export class AgentToken { +export class AgentTask { constructor( /** * The unique identifier for the agent token. @@ -39,12 +39,12 @@ export class AgentToken { ) {} /** - * Creates a AgentToken instance from a JSON object. + * Creates a AgentTask instance from a JSON object. * - * @param data - The JSON object containing agent token data - * @returns A new AgentToken instance + * @param data - The JSON object containing agent task data + * @returns A new AgentTask instance */ - static fromJSON(data: AgentTokenJSON): AgentToken { - return new AgentToken(data.id, data.user_id, data.token, data.status, data.url, data.created_at, data.updated_at); + static fromJSON(data: AgentTaskJSON): AgentTask { + return new AgentTask(data.id, data.user_id, data.token, data.status, data.url, data.created_at, data.updated_at); } } diff --git a/packages/backend/src/api/resources/Deserializer.ts b/packages/backend/src/api/resources/Deserializer.ts index d8b0a938133..626d5e20019 100644 --- a/packages/backend/src/api/resources/Deserializer.ts +++ b/packages/backend/src/api/resources/Deserializer.ts @@ -1,6 +1,6 @@ import { ActorToken, - AgentToken, + AgentTask, AllowlistIdentifier, APIKey, BlocklistIdentifier, @@ -170,8 +170,8 @@ function jsonToObject(item: any): any { return SamlConnection.fromJSON(item); case ObjectType.SignInToken: return SignInToken.fromJSON(item); - case ObjectType.AgentToken: - return AgentToken.fromJSON(item); + case ObjectType.AgentTask: + return AgentTask.fromJSON(item); case ObjectType.SignUpAttempt: return SignUpAttempt.fromJSON(item); case ObjectType.Session: diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index feea7ce531a..ed0ba9e6723 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -19,6 +19,7 @@ import type { export const ObjectType = { AccountlessApplication: 'accountless_application', ActorToken: 'actor_token', + AgentTask: 'agent_task', AllowlistIdentifier: 'allowlist_identifier', ApiKey: 'api_key', BlocklistIdentifier: 'blocklist_identifier', @@ -62,7 +63,6 @@ export const ObjectType = { Token: 'token', TotalCount: 'total_count', TestingToken: 'testing_token', - AgentToken: 'agent_token', Role: 'role', Permission: 'permission', BillingPayer: 'commerce_payer', @@ -513,8 +513,8 @@ export interface SignInTokenJSON extends ClerkResourceJSON { updated_at: number; } -export interface AgentTokenJSON extends ClerkResourceJSON { - object: typeof ObjectType.AgentToken; +export interface AgentTaskJSON extends ClerkResourceJSON { + object: typeof ObjectType.AgentTask; user_id: string; token: string; status: 'pending' | 'accepted' | 'revoked'; diff --git a/packages/backend/src/api/resources/index.ts b/packages/backend/src/api/resources/index.ts index e1c28b19df1..a6d1691f96f 100644 --- a/packages/backend/src/api/resources/index.ts +++ b/packages/backend/src/api/resources/index.ts @@ -1,6 +1,6 @@ export * from './AccountlessApplication'; +export * from './AgentTask'; export * from './ActorToken'; -export * from './AgentToken'; export * from './AllowlistIdentifier'; export * from './APIKey'; export * from './BlocklistIdentifier'; From cd47a4f46ef5d5a52c1d3ae3410631c684a2ad03 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Wed, 18 Feb 2026 22:14:09 -0500 Subject: [PATCH 07/11] feat: Add Agent Task testing helpers --- .changeset/gentle-falcons-arrive.md | 5 +++ .../backend/src/api/endpoints/AgentTaskApi.ts | 2 - .../backend/src/api/resources/AgentTask.ts | 28 +++---------- packages/backend/src/api/resources/JSON.ts | 7 +--- packages/testing/src/common/agent-task.ts | 42 +++++++++++++++++++ packages/testing/src/common/index.ts | 1 + packages/testing/src/cypress/agent-task.ts | 18 ++++++++ packages/testing/src/cypress/index.ts | 1 + packages/testing/src/playwright/agent-task.ts | 17 ++++++++ packages/testing/src/playwright/index.ts | 1 + 10 files changed, 93 insertions(+), 29 deletions(-) create mode 100644 .changeset/gentle-falcons-arrive.md create mode 100644 packages/testing/src/common/agent-task.ts create mode 100644 packages/testing/src/cypress/agent-task.ts create mode 100644 packages/testing/src/playwright/agent-task.ts diff --git a/.changeset/gentle-falcons-arrive.md b/.changeset/gentle-falcons-arrive.md new file mode 100644 index 00000000000..43c8af40e79 --- /dev/null +++ b/.changeset/gentle-falcons-arrive.md @@ -0,0 +1,5 @@ +--- +'@clerk/testing': minor +--- + +Export `createAgentTaskUrl` helper for creating agent tasks via the Clerk Backend API from both `@clerk/testing/playwright` and `@clerk/testing/cypress` subpaths. diff --git a/packages/backend/src/api/endpoints/AgentTaskApi.ts b/packages/backend/src/api/endpoints/AgentTaskApi.ts index d4695ab711b..4e0b636797c 100644 --- a/packages/backend/src/api/endpoints/AgentTaskApi.ts +++ b/packages/backend/src/api/endpoints/AgentTaskApi.ts @@ -1,8 +1,6 @@ import type { AgentTask } from '../resources/AgentTask'; import { AbstractAPI } from './AbstractApi'; -// type AgentTaskOnBehalfOfParams = ; - type CreateAgentTaskParams = { /** * The user to create an agent task for. diff --git a/packages/backend/src/api/resources/AgentTask.ts b/packages/backend/src/api/resources/AgentTask.ts index 484e953739f..e5e821320f6 100644 --- a/packages/backend/src/api/resources/AgentTask.ts +++ b/packages/backend/src/api/resources/AgentTask.ts @@ -9,33 +9,17 @@ import type { AgentTaskJSON } from './JSON'; export class AgentTask { constructor( /** - * The unique identifier for the agent token. + * A stable identifier for the agent, unique per agent_name within an instance. */ - readonly id: string, + readonly agentId: string, /** - * The unique identifier for the user associated with this token. + * A unique identifier for this agent task. */ - readonly userId: string, + readonly taskId: string, /** - * The agent token string value. - */ - readonly token: string, - /** - * The current status of the token: 'pending', 'accepted', or 'revoked'. - */ - readonly status: 'pending' | 'accepted' | 'revoked', - /** - * The URL associated with the agent token. + * The FAPI URL that, when visited, creates a session for the user. */ readonly url: string, - /** - * Unix timestamp (in milliseconds) indicating when the token was created. - */ - readonly createdAt: number, - /** - * Unix timestamp (in milliseconds) indicating when the token was last updated. - */ - readonly updatedAt: number, ) {} /** @@ -45,6 +29,6 @@ export class AgentTask { * @returns A new AgentTask instance */ static fromJSON(data: AgentTaskJSON): AgentTask { - return new AgentTask(data.id, data.user_id, data.token, data.status, data.url, data.created_at, data.updated_at); + return new AgentTask(data.agent_id, data.task_id, data.url); } } diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index ed0ba9e6723..2da34e59185 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -515,12 +515,9 @@ export interface SignInTokenJSON extends ClerkResourceJSON { export interface AgentTaskJSON extends ClerkResourceJSON { object: typeof ObjectType.AgentTask; - user_id: string; - token: string; - status: 'pending' | 'accepted' | 'revoked'; + agent_id: string; + task_id: string; url: string; - created_at: number; - updated_at: number; } export interface SignUpJSON extends ClerkResourceJSON { diff --git a/packages/testing/src/common/agent-task.ts b/packages/testing/src/common/agent-task.ts new file mode 100644 index 00000000000..38792f8ebfd --- /dev/null +++ b/packages/testing/src/common/agent-task.ts @@ -0,0 +1,42 @@ +import type { ClerkClient } from '@clerk/backend'; +import { createClerkClient } from '@clerk/backend'; + +export type CreateAgentTaskParams = Parameters[0] & { + /** + * The secret key for your Clerk instance. + * If not provided, falls back to the `CLERK_SECRET_KEY` environment variable. + */ + secretKey?: string; +}; + +export const ERROR_MISSING_SECRET_KEY = + 'A secretKey is required to create agent tasks. ' + + 'Pass it directly or set the CLERK_SECRET_KEY environment variable.'; + +export const ERROR_AGENT_TASK_FAILED = 'Failed to create agent task: '; + +/** + * Creates an agent task using the Clerk Backend API and returns its URL. + * + * @internal Framework-specific wrappers should call this after resolving the secret key. + * + * @experimental This is an experimental API for the Agent Tasks feature that is available under a private beta, + * and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version + * and the clerk-js version to avoid breaking changes. + */ +export async function createAgentTaskUrl(params: CreateAgentTaskParams) { + const { secretKey, ...taskParams } = params; + + if (!secretKey) { + throw new Error(ERROR_MISSING_SECRET_KEY); + } + + const clerkClient = createClerkClient({ secretKey }); + + try { + const agentTask = await clerkClient.agentTasks.create(taskParams); + return agentTask.url; + } catch (error) { + throw new Error(ERROR_AGENT_TASK_FAILED + (error instanceof Error ? error.message : String(error))); + } +} diff --git a/packages/testing/src/common/index.ts b/packages/testing/src/common/index.ts index b5ddaed5011..bbf12afe13e 100644 --- a/packages/testing/src/common/index.ts +++ b/packages/testing/src/common/index.ts @@ -1,3 +1,4 @@ +export * from './agent-task'; export * from './constants'; export * from './types'; export * from './setup'; diff --git a/packages/testing/src/cypress/agent-task.ts b/packages/testing/src/cypress/agent-task.ts new file mode 100644 index 00000000000..9eb0a68296e --- /dev/null +++ b/packages/testing/src/cypress/agent-task.ts @@ -0,0 +1,18 @@ +/// +import { type CreateAgentTaskParams, createAgentTaskUrl as _createAgentTaskUrl } from '../common'; + +/** + * Creates an agent task using the Clerk Backend API and returns its URL. + * + * If `secretKey` is not provided, falls back to the `CLERK_SECRET_KEY` Cypress environment variable. + * + * @experimental This is an experimental API for the Agent Tasks feature that is available under a private beta, + * and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version + * and the clerk-js version to avoid breaking changes. + */ +export function createAgentTaskUrl(params: CreateAgentTaskParams) { + return _createAgentTaskUrl({ + ...params, + secretKey: params.secretKey || Cypress.env('CLERK_SECRET_KEY') || process.env.CLERK_SECRET_KEY, + }); +} diff --git a/packages/testing/src/cypress/index.ts b/packages/testing/src/cypress/index.ts index 95febb24a8e..d24666820e5 100644 --- a/packages/testing/src/cypress/index.ts +++ b/packages/testing/src/cypress/index.ts @@ -1,3 +1,4 @@ export { clerkSetup } from './setup'; +export { createAgentTaskUrl } from './agent-task'; export { setupClerkTestingToken } from './setupClerkTestingToken'; export { addClerkCommands } from './custom-commands'; diff --git a/packages/testing/src/playwright/agent-task.ts b/packages/testing/src/playwright/agent-task.ts new file mode 100644 index 00000000000..798e7091e66 --- /dev/null +++ b/packages/testing/src/playwright/agent-task.ts @@ -0,0 +1,17 @@ +import { type CreateAgentTaskParams, createAgentTaskUrl as _createAgentTaskUrl } from '../common'; + +/** + * Creates an agent task using the Clerk Backend API and returns its URL. + * + * If `secretKey` is not provided, falls back to the `CLERK_SECRET_KEY` environment variable. + * + * @experimental This is an experimental API for the Agent Tasks feature that is available under a private beta, + * and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version + * and the clerk-js version to avoid breaking changes. + */ +export function createAgentTaskUrl(params: CreateAgentTaskParams) { + return _createAgentTaskUrl({ + ...params, + secretKey: params.secretKey || process.env.CLERK_SECRET_KEY, + }); +} diff --git a/packages/testing/src/playwright/index.ts b/packages/testing/src/playwright/index.ts index da08ed20634..f2ce4057183 100644 --- a/packages/testing/src/playwright/index.ts +++ b/packages/testing/src/playwright/index.ts @@ -1,3 +1,4 @@ export { clerkSetup } from './setup'; +export { createAgentTaskUrl } from './agent-task'; export { setupClerkTestingToken } from './setupClerkTestingToken'; export { clerk } from './helpers'; From 95ee1fea378a967ce10ae28c43aaca82e24131bb Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Wed, 18 Feb 2026 23:12:31 -0500 Subject: [PATCH 08/11] chore: Pull apiUrl --- packages/testing/src/common/agent-task.ts | 16 ++++++++++++++-- packages/testing/src/cypress/agent-task.ts | 1 + packages/testing/src/playwright/agent-task.ts | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/testing/src/common/agent-task.ts b/packages/testing/src/common/agent-task.ts index 38792f8ebfd..d2c58f4a413 100644 --- a/packages/testing/src/common/agent-task.ts +++ b/packages/testing/src/common/agent-task.ts @@ -2,6 +2,11 @@ import type { ClerkClient } from '@clerk/backend'; import { createClerkClient } from '@clerk/backend'; export type CreateAgentTaskParams = Parameters[0] & { + /** + * The API URL for your Clerk instance. + * If not provided, falls back to the `CLERK_API_URL` environment variable. + */ + apiUrl?: string; /** * The secret key for your Clerk instance. * If not provided, falls back to the `CLERK_SECRET_KEY` environment variable. @@ -13,6 +18,9 @@ export const ERROR_MISSING_SECRET_KEY = 'A secretKey is required to create agent tasks. ' + 'Pass it directly or set the CLERK_SECRET_KEY environment variable.'; +export const ERROR_MISSING_API_URL = + 'An apiUrl is required to create agent tasks. ' + 'Pass it directly or set the CLERK_API_URL environment variable.'; + export const ERROR_AGENT_TASK_FAILED = 'Failed to create agent task: '; /** @@ -25,13 +33,17 @@ export const ERROR_AGENT_TASK_FAILED = 'Failed to create agent task: '; * and the clerk-js version to avoid breaking changes. */ export async function createAgentTaskUrl(params: CreateAgentTaskParams) { - const { secretKey, ...taskParams } = params; + const { apiUrl, secretKey, ...taskParams } = params; + + if (!apiUrl) { + throw new Error(ERROR_MISSING_API_URL); + } if (!secretKey) { throw new Error(ERROR_MISSING_SECRET_KEY); } - const clerkClient = createClerkClient({ secretKey }); + const clerkClient = createClerkClient({ apiUrl, secretKey }); try { const agentTask = await clerkClient.agentTasks.create(taskParams); diff --git a/packages/testing/src/cypress/agent-task.ts b/packages/testing/src/cypress/agent-task.ts index 9eb0a68296e..d4ac9f86ee4 100644 --- a/packages/testing/src/cypress/agent-task.ts +++ b/packages/testing/src/cypress/agent-task.ts @@ -13,6 +13,7 @@ import { type CreateAgentTaskParams, createAgentTaskUrl as _createAgentTaskUrl } export function createAgentTaskUrl(params: CreateAgentTaskParams) { return _createAgentTaskUrl({ ...params, + apiUrl: params.apiUrl || Cypress.env('CLERK_API_URL') || process.env.CLERK_API_URL, secretKey: params.secretKey || Cypress.env('CLERK_SECRET_KEY') || process.env.CLERK_SECRET_KEY, }); } diff --git a/packages/testing/src/playwright/agent-task.ts b/packages/testing/src/playwright/agent-task.ts index 798e7091e66..20247829887 100644 --- a/packages/testing/src/playwright/agent-task.ts +++ b/packages/testing/src/playwright/agent-task.ts @@ -12,6 +12,7 @@ import { type CreateAgentTaskParams, createAgentTaskUrl as _createAgentTaskUrl } export function createAgentTaskUrl(params: CreateAgentTaskParams) { return _createAgentTaskUrl({ ...params, + apiUrl: params.apiUrl || process.env.CLERK_API_URL, secretKey: params.secretKey || process.env.CLERK_SECRET_KEY, }); } From 2826253f2a60ac1413fe6a2f6f5575c2da19ac7c Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 19 Feb 2026 15:05:51 -0500 Subject: [PATCH 09/11] chore: Convert nested keys --- .../src/api/__tests__/AgentTaskApi.test.ts | 96 +++++++++++++++++++ .../backend/src/api/endpoints/AgentTaskApi.ts | 3 + 2 files changed, 99 insertions(+) create mode 100644 packages/backend/src/api/__tests__/AgentTaskApi.test.ts diff --git a/packages/backend/src/api/__tests__/AgentTaskApi.test.ts b/packages/backend/src/api/__tests__/AgentTaskApi.test.ts new file mode 100644 index 00000000000..8f77842f5f1 --- /dev/null +++ b/packages/backend/src/api/__tests__/AgentTaskApi.test.ts @@ -0,0 +1,96 @@ +import { http, HttpResponse } from 'msw'; +import { describe, expect, it } from 'vitest'; + +import { server, validateHeaders } from '../../mock-server'; +import { createBackendApiClient } from '../factory'; + +describe('AgentTaskAPI', () => { + const apiClient = createBackendApiClient({ + apiUrl: 'https://api.clerk.test', + secretKey: 'deadbeef', + }); + + const mockAgentTaskResponse = { + object: 'agent_task', + agent_id: 'agent_123', + task_id: 'task_456', + url: 'https://example.com/agent-task', + }; + + describe('create', () => { + it('converts nested onBehalfOf.userId to snake_case', async () => { + server.use( + http.post( + 'https://api.clerk.test/v1/agents/tasks', + validateHeaders(async ({ request }) => { + const body = await request.json(); + + expect(body).toEqual({ + on_behalf_of: { + user_id: 'user_123', + }, + permissions: 'read,write', + agent_name: 'test-agent', + task_description: 'Test task', + redirect_url: 'https://example.com/callback', + session_max_duration_in_seconds: 1800, + }); + + return HttpResponse.json(mockAgentTaskResponse); + }), + ), + ); + + const response = await apiClient.agentTasks.create({ + onBehalfOf: { + userId: 'user_123', + }, + permissions: 'read,write', + agentName: 'test-agent', + taskDescription: 'Test task', + redirectUrl: 'https://example.com/callback', + sessionMaxDurationInSeconds: 1800, + }); + + expect(response.agentId).toBe('agent_123'); + expect(response.taskId).toBe('task_456'); + expect(response.url).toBe('https://example.com/agent-task'); + }); + + it('converts nested onBehalfOf.identifier to snake_case', async () => { + server.use( + http.post( + 'https://api.clerk.test/v1/agents/tasks', + validateHeaders(async ({ request }) => { + const body = await request.json(); + + expect(body).toEqual({ + on_behalf_of: { + identifier: 'user@example.com', + }, + permissions: 'read', + agent_name: 'test-agent', + task_description: 'Test task', + redirect_url: 'https://example.com/callback', + }); + + return HttpResponse.json(mockAgentTaskResponse); + }), + ), + ); + + const response = await apiClient.agentTasks.create({ + onBehalfOf: { + identifier: 'user@example.com', + }, + permissions: 'read', + agentName: 'test-agent', + taskDescription: 'Test task', + redirectUrl: 'https://example.com/callback', + }); + + expect(response.agentId).toBe('agent_123'); + expect(response.taskId).toBe('task_456'); + }); + }); +}); diff --git a/packages/backend/src/api/endpoints/AgentTaskApi.ts b/packages/backend/src/api/endpoints/AgentTaskApi.ts index 4e0b636797c..e38397890aa 100644 --- a/packages/backend/src/api/endpoints/AgentTaskApi.ts +++ b/packages/backend/src/api/endpoints/AgentTaskApi.ts @@ -55,6 +55,9 @@ export class AgentTaskAPI extends AbstractAPI { method: 'POST', path: basePath, bodyParams: params, + options: { + deepSnakecaseBodyParamKeys: true, + }, }); } } From 219da80e5c979cfb9e763a0a6f7420ea2de81769 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 19 Feb 2026 16:16:10 -0500 Subject: [PATCH 10/11] feat: Add revoke endpoint --- packages/backend/src/api/endpoints/AgentTaskApi.ts | 9 +++++++++ packages/testing/src/common/agent-task.ts | 10 ++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/api/endpoints/AgentTaskApi.ts b/packages/backend/src/api/endpoints/AgentTaskApi.ts index e38397890aa..ecc55c8bf4d 100644 --- a/packages/backend/src/api/endpoints/AgentTaskApi.ts +++ b/packages/backend/src/api/endpoints/AgentTaskApi.ts @@ -1,3 +1,4 @@ +import { joinPaths } from '../../util/path'; import type { AgentTask } from '../resources/AgentTask'; import { AbstractAPI } from './AbstractApi'; @@ -60,4 +61,12 @@ export class AgentTaskAPI extends AbstractAPI { }, }); } + + public async revoke(agentTaskId: string) { + this.requireId(agentTaskId); + return this.request>({ + method: 'POST', + path: joinPaths(basePath, agentTaskId, 'revoke'), + }); + } } diff --git a/packages/testing/src/common/agent-task.ts b/packages/testing/src/common/agent-task.ts index d2c58f4a413..0444786209b 100644 --- a/packages/testing/src/common/agent-task.ts +++ b/packages/testing/src/common/agent-task.ts @@ -12,6 +12,12 @@ export type CreateAgentTaskParams = Parameters Date: Thu, 19 Feb 2026 16:34:05 -0500 Subject: [PATCH 11/11] chore: Allow passing of ClerkClient --- .changeset/gentle-falcons-arrive.md | 2 +- packages/backend/src/index.ts | 2 + packages/testing/src/common/agent-task.ts | 57 +++++++++++-------- packages/testing/src/cypress/agent-task.ts | 6 +- packages/testing/src/cypress/index.ts | 2 +- packages/testing/src/playwright/agent-task.ts | 6 +- packages/testing/src/playwright/index.ts | 2 +- 7 files changed, 43 insertions(+), 34 deletions(-) diff --git a/.changeset/gentle-falcons-arrive.md b/.changeset/gentle-falcons-arrive.md index 43c8af40e79..f829e6c4ff2 100644 --- a/.changeset/gentle-falcons-arrive.md +++ b/.changeset/gentle-falcons-arrive.md @@ -2,4 +2,4 @@ '@clerk/testing': minor --- -Export `createAgentTaskUrl` helper for creating agent tasks via the Clerk Backend API from both `@clerk/testing/playwright` and `@clerk/testing/cypress` subpaths. +Export `createAgentTestingTask` helper for creating agent tasks via the Clerk Backend API from both `@clerk/testing/playwright` and `@clerk/testing/cypress` subpaths. diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 1559d61f7fb..8905dc45cf1 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -55,6 +55,7 @@ export type { VerifyTokenOptions } from './tokens/verify'; */ export type { ActorTokenJSON, + AgentTaskJSON, AccountlessApplicationJSON, ClerkResourceJSON, TokenJSON, @@ -110,6 +111,7 @@ export type { * Resources */ export type { + AgentTask, APIKey, ActorToken, AccountlessApplication, diff --git a/packages/testing/src/common/agent-task.ts b/packages/testing/src/common/agent-task.ts index 0444786209b..747ee1ff1b0 100644 --- a/packages/testing/src/common/agent-task.ts +++ b/packages/testing/src/common/agent-task.ts @@ -1,24 +1,32 @@ -import type { ClerkClient } from '@clerk/backend'; +import type { AgentTask, ClerkClient } from '@clerk/backend'; import { createClerkClient } from '@clerk/backend'; -export type CreateAgentTaskParams = Parameters[0] & { - /** - * The API URL for your Clerk instance. - * If not provided, falls back to the `CLERK_API_URL` environment variable. - */ - apiUrl?: string; - /** - * The secret key for your Clerk instance. - * If not provided, falls back to the `CLERK_SECRET_KEY` environment variable. - */ - secretKey?: string; - - /** - * The Clerk client to use to create the agent task. - * If not provided, a new Clerk client will be created. - */ - clerkClient?: ClerkClient; -}; +export type CreateAgentTaskParams = Parameters[0] & + ( + | { + /** + * The API URL for your Clerk instance. + * If not provided, falls back to the `CLERK_API_URL` environment variable. + */ + apiUrl?: string; + /** + * The secret key for your Clerk instance. + * If not provided, falls back to the `CLERK_SECRET_KEY` environment variable. + */ + secretKey?: string; + + clerkClient?: never; + } + | { + /** + * The Clerk client to use to create the agent task. + * If not provided, a new Clerk client will be created. + */ + clerkClient?: ClerkClient; + apiUrl?: string; + secretKey?: string; + } + ); export const ERROR_MISSING_SECRET_KEY = 'A secretKey is required to create agent tasks. ' + @@ -38,18 +46,17 @@ export const ERROR_AGENT_TASK_FAILED = 'Failed to create agent task: '; * and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version * and the clerk-js version to avoid breaking changes. */ -export async function createAgentTaskUrl(params: CreateAgentTaskParams) { - const { apiUrl, secretKey, ...taskParams } = params; +export async function createAgentTestingTask(params: CreateAgentTaskParams): Promise { + const { apiUrl, secretKey, clerkClient, ...taskParams } = params; - if (!secretKey) { + if (!clerkClient && !secretKey) { throw new Error(ERROR_MISSING_SECRET_KEY); } - const clerkClient = createClerkClient({ apiUrl, secretKey }); + const client = clerkClient ?? createClerkClient({ apiUrl, secretKey }); try { - const agentTask = await clerkClient.agentTasks.create(taskParams); - return agentTask.url; + return await client.agentTasks.create(taskParams); } catch (error) { throw new Error(ERROR_AGENT_TASK_FAILED + (error instanceof Error ? error.message : String(error))); } diff --git a/packages/testing/src/cypress/agent-task.ts b/packages/testing/src/cypress/agent-task.ts index d4ac9f86ee4..37c8df7bab6 100644 --- a/packages/testing/src/cypress/agent-task.ts +++ b/packages/testing/src/cypress/agent-task.ts @@ -1,5 +1,5 @@ /// -import { type CreateAgentTaskParams, createAgentTaskUrl as _createAgentTaskUrl } from '../common'; +import { type CreateAgentTaskParams, createAgentTestingTask as _createAgentTestingTask } from '../common'; /** * Creates an agent task using the Clerk Backend API and returns its URL. @@ -10,8 +10,8 @@ import { type CreateAgentTaskParams, createAgentTaskUrl as _createAgentTaskUrl } * and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version * and the clerk-js version to avoid breaking changes. */ -export function createAgentTaskUrl(params: CreateAgentTaskParams) { - return _createAgentTaskUrl({ +export function createAgentTestingTask(params: CreateAgentTaskParams) { + return _createAgentTestingTask({ ...params, apiUrl: params.apiUrl || Cypress.env('CLERK_API_URL') || process.env.CLERK_API_URL, secretKey: params.secretKey || Cypress.env('CLERK_SECRET_KEY') || process.env.CLERK_SECRET_KEY, diff --git a/packages/testing/src/cypress/index.ts b/packages/testing/src/cypress/index.ts index d24666820e5..a06ab01c226 100644 --- a/packages/testing/src/cypress/index.ts +++ b/packages/testing/src/cypress/index.ts @@ -1,4 +1,4 @@ export { clerkSetup } from './setup'; -export { createAgentTaskUrl } from './agent-task'; +export { createAgentTestingTask } from './agent-task'; export { setupClerkTestingToken } from './setupClerkTestingToken'; export { addClerkCommands } from './custom-commands'; diff --git a/packages/testing/src/playwright/agent-task.ts b/packages/testing/src/playwright/agent-task.ts index 20247829887..164584a73be 100644 --- a/packages/testing/src/playwright/agent-task.ts +++ b/packages/testing/src/playwright/agent-task.ts @@ -1,4 +1,4 @@ -import { type CreateAgentTaskParams, createAgentTaskUrl as _createAgentTaskUrl } from '../common'; +import { type CreateAgentTaskParams, createAgentTestingTask as _createAgentTestingTask } from '../common'; /** * Creates an agent task using the Clerk Backend API and returns its URL. @@ -9,8 +9,8 @@ import { type CreateAgentTaskParams, createAgentTaskUrl as _createAgentTaskUrl } * and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version * and the clerk-js version to avoid breaking changes. */ -export function createAgentTaskUrl(params: CreateAgentTaskParams) { - return _createAgentTaskUrl({ +export function createAgentTestingTask(params: CreateAgentTaskParams) { + return _createAgentTestingTask({ ...params, apiUrl: params.apiUrl || process.env.CLERK_API_URL, secretKey: params.secretKey || process.env.CLERK_SECRET_KEY, diff --git a/packages/testing/src/playwright/index.ts b/packages/testing/src/playwright/index.ts index f2ce4057183..59a95a4b8d2 100644 --- a/packages/testing/src/playwright/index.ts +++ b/packages/testing/src/playwright/index.ts @@ -1,4 +1,4 @@ export { clerkSetup } from './setup'; -export { createAgentTaskUrl } from './agent-task'; +export { createAgentTestingTask } from './agent-task'; export { setupClerkTestingToken } from './setupClerkTestingToken'; export { clerk } from './helpers';