@cxpa/sdk
v0.7.0
Published
Official SDK for the CXP Agent Platform
Readme
@cxpa/sdk
Official SDK for the CXP Agent Platform.
Two consumer roles, two subpath imports:
| Subpath | For | What it gives you |
|---|---|---|
| @cxpa/sdk/agent | Agent runtime code (Trigger.dev tasks, n8n workflows, standalone Node) | createAgentClient, triggerRun, registerRun, ingestRun |
| @cxpa/sdk/zone | Next.js zone apps embedded in a platform agent page | createZoneClient, createZoneMiddleware, getSession |
| @cxpa/sdk/types | Shared types (errors, payload shapes) | Run, RunStatus, CallbackPayload, ResolvedCredential, ApiError |
Install
npm install @cxpa/sdknext is an optional peer dependency — install it only if you use @cxpa/sdk/zone.
Agent runtime — quickstart
import { createAgentClient } from '@cxpa/sdk/agent'
const cxpa = createAgentClient({
baseUrl: process.env.CXPA_API_URL!,
apiKey: process.env.CXPA_API_KEY!,
runId: payload.runId,
credentialsToken: payload.credentialsToken,
})
await cxpa.started()
const cred = await cxpa.resolveCredential(payload.connectionIds.openai.connectionId)
await cxpa.complete({ output: { result: 'done' } })Human-in-the-loop
Pause a run for review with cxpa.waiting({ tokenId, description, url? }) — pass an optional absolute http(s) url to override the {{run_url}} link in waiting-state notifications so reviewers land on your own HITL UI — then resume it — from anywhere holding the agent_api_key (an approval service, a Slack backend, the task itself) — with cxpa.completeWaitpoint(payload?). The waitpoint token is resolved server-side from the run, so only the bound runId is needed. Zone apps resume via createZoneClient().runs.completeWaitpoint(runId, payload?) instead.
Ingest-key operations
For agent-scoped operations using ingest_api_key (not agent_api_key):
import {
triggerRun,
triggerRunAndWait,
registerRun,
ingestRun,
} from '@cxpa/sdk/agent'
// Start a platform-managed run (fire-and-forget — returns immediately)
await triggerRun({ baseUrl, ingestApiKey, agentId, input: {...} })
// Synchronous trigger — block until the run finishes, then return its output.
// Designed for agents that produce a payload the caller consumes inline,
// e.g. PDF reports, generated documents, AI-rendered responses.
const result = await triggerRunAndWait({
baseUrl, ingestApiKey, agentId,
input: { topic: 'Q3 financial summary' },
timeoutMs: 120_000, // server further caps at agents.running_timeout_ms
})
if (result.timedOut) {
// Wait window elapsed; the run is still in flight. Poll for completion.
console.log('still running:', result.runId)
} else if (result.status === 'completed') {
// result.output_data has the agent payload.
return new Response(result.output_data?.pdf as string, {
headers: { 'content-type': 'application/pdf' },
})
} else {
throw new Error(`Run ${result.status}: ${result.error}`)
}
// Register an externally-triggered run, get a credentialsToken
const { runId, credentialsToken } = await registerRun({
baseUrl, ingestApiKey, agentId, externalRunId: '...', input: {...}
})
// Monitoring-only status update
await ingestRun({
baseUrl, ingestApiKey, agentId,
externalRunId: '...', status: 'completed',
startedAt: '...', completedAt: '...', durationMs: 1234,
})When to use triggerRunAndWait vs triggerRun
| Use triggerRun when | Use triggerRunAndWait when |
|---|---|
| The caller does not need the agent output in the response | The caller hands the output straight to the end user (download, render, forward) |
| The run takes longer than ~5 minutes | The run completes in seconds or low minutes |
| You'll poll, subscribe, or wait for a callback yourself | You want a single HTTP request that returns the result |
Both endpoints take the same input. triggerRunAndWait is supported by every platform-managed runtime — n8n workflows that terminate in a "Respond to Webhook" node return the output inline; Trigger.dev tasks run normally and the platform polls until they reach a terminal state.
Zone app — quickstart
// middleware.ts
import { createZoneMiddleware } from '@cxpa/sdk/zone'
export const middleware = createZoneMiddleware({
secret: process.env.ZONE_JWT_SECRET!,
audience: process.env.AUDIENCE_ID!,
})
export const config = {
matcher: ['/:orgSlug/:agentSlug/app/:path*', '/:orgSlug/:agentSlug/app'],
}// app/page.tsx
import { getSession, createZoneClient } from '@cxpa/sdk/zone'
export default async function Page() {
const session = await getSession()
const cxpa = createZoneClient({
baseUrl: process.env.PLATFORM_API_BASE_URL!,
token: session.token,
agentId: session.claims.agentId,
})
const { runId } = await cxpa.runs.create({ payload: { url: 'https://example.com' } })
const run = await cxpa.runs.get(runId)
return <pre>{JSON.stringify(run, null, 2)}</pre>
}For zone apps that download or render the agent output inline (e.g. a "Generate Report" button that hands the user a PDF), use the synchronous variant — it waits for the run to finish and returns the output in the same response:
const result = await cxpa.runs.createAndWait({
payload: { topic: 'Q3 financial summary' },
timeoutMs: 120_000,
})
if (result.timedOut) {
// Run is still in flight — fall back to polling cxpa.runs.get(result.runId).
} else if (result.status === 'completed') {
// result.output_data has whatever the agent returned.
}Errors
All non-2xx responses throw ApiError:
import { ApiError } from '@cxpa/sdk'
try {
await cxpa.complete({ output: {} })
} catch (err) {
if (err instanceof ApiError && err.status === 401) {
// re-auth
}
throw err
}License
MIT
