Skip to content

Agents API ​

List, retrieve, and invoke AI agents via the Console SDK. Agents are invoked through the OpenAI Responses API format.

What's new in 3.1.0

client.agents.sessions (start, list, read, update and delete sessions), responses.create({ session_id }), session files, approvals and questions, the session event log, and client.webhooks. See the Agent Sessions guide and the pages linked in Sessions, approvals, events, files and webhooks.

Quick Start ​

typescript
import { ConsoleClient } from '@cognipeer/console-sdk';

const client = new ConsoleClient({
  apiKey: process.env.COGNIPEER_API_KEY!,
});

// Invoke an agent
const response = await client.agents.responses.create({
  model: 'support-bot',
  input: 'How do I reset my password?',
});

console.log(response.output[0]?.content[0]?.text);

List Agents ​

Fetch all agents available to the current API token.

typescript
const agents = await client.agents.list();
console.log(agents);

// Filter by status
const activeAgents = await client.agents.list({ status: 'active' });

Parameters ​

NameTypeRequiredDescription
statusstringNoFilter: active, inactive, draft

Return Type ​

typescript
interface AgentListItem {
  key: string;
  name: string;
  description?: string;
  config: {
    modelKey: string;
    temperature?: number;
    topP?: number;
    maxTokens?: number;
  };
  status: 'active' | 'inactive' | 'draft';
  createdAt: string;
}

Get Agent ​

Retrieve details for a specific agent.

typescript
const agent = await client.agents.get('support-bot');
console.log(agent.name, agent.config.modelKey);

Parameters ​

NameTypeRequiredDescription
agentKeystringYesUnique agent key

Files: agent.attachments ​

The single-agent lookup (not list()) also says whether the agent takes files, so a client can decide whether to show an attach button before it uploads anything:

typescript
const agent = await client.agents.get('support-bot');

if (agent.attachments?.enabled) {
  console.log(agent.attachments.max_files, agent.attachments.max_file_size_mb, agent.attachments.allowed_mime_types);
  if (!agent.attachments.accepts_images) console.log('Images would be replaced by a note: the model is text only');
}
typescript
interface AgentAttachmentsInfo {
  enabled: boolean;              // files are on AND the agent's bucket can take a file now
  max_files: number;             // files one session holds (0 while off)
  max_file_size_mb: number;      // largest file (0 while off)
  allowed_mime_types: string[];  // `image/*` wildcards allowed; empty = any
  accepts_images: boolean;       // the agent's model reads images
}

Where the files live (bucket, folder, retention) is the agent's Configure → Files setting in the Console, not something a caller chooses. Files are off by default for an agent and need a bucket; with files off (or on with no bucket) an upload is refused with 403 attachments_disabled. attachments is absent on a Console that predates session files. See Session files.

Invoke Agent (Responses API) ​

Use client.agents.responses.create() to invoke an agent. The request format follows the OpenAI Responses API.

typescript
const response = await client.agents.responses.create({
  model: 'support-bot',
  input: 'What are your business hours?',
});

console.log(response.id);     // "resp_64a1b2c3d4e5f6"
console.log(response.output); // Assistant response
console.log(response.usage);  // Token usage

POST /api/client/v1/responses

Parameters ​

NameTypeRequiredDescription
modelstringYesAgent key
inputstring | ResponseInputItem[]YesUser message, or message items (see Input parts)
previous_response_idstringNoContinue a conversation (resp_... or run_...)
session_idstringNoContinue a session (for example one from sessions.create()); same session as previous_response_id
conversationstring | { id }NoOpenAI-style alias of session_id
versionnumberNoSpecific published version
instructionsstringNoSystem prompt override
temperaturenumberNoSampling temperature
top_pnumberNoTop-p sampling
max_output_tokensnumberNoMaximum output tokens
runtime_contextRuntimeContextNoHeaders / metadata forwarded to the agent's downstream tool, MCP and connected-agent calls
budget{ max_cost_usd, on_reach? }NoSpend cap for the conversation this request creates; ignored on a continued session. See Session budget
backgroundbooleanNoQueue the turn and return an AgentRun — see Background Runs
callback_url / callback_secretstringNoBackground only — see Background Parameters

Multi-Turn Conversations ​

Pass previous_response_id to continue a conversation:

typescript
const first = await client.agents.responses.create({
  model: 'support-bot',
  input: 'I have a billing question',
});

const followUp = await client.agents.responses.create({
  model: 'support-bot',
  input: 'Can you show me the pricing page?',
  previous_response_id: first.id,
});

session_id (alias conversation, a string or { id }) names the same conversation as previous_response_id (resp_<id>) in the session's own words. Give one, or several that agree: session_id or conversation naming a different session than previous_response_id is a 400 session_conflict (session_id and conversation disagreeing with each other is a plain 400 invalid_request), and a session of another agent or project is a 404 session_not_found. It is what you use to continue a session you started with sessions.create(), because that one has no response yet:

typescript
const session = await client.agents.sessions.create({ agent: 'support-bot' });

const reply = await client.agents.responses.create({
  model: 'support-bot',
  session_id: session.id,
  input: 'I have a billing question',
});

Input parts: files and images ​

input can be an array of message items whose content mixes text, files and images:

typescript
interface ResponseInputItem {
  role: 'user' | 'system' | 'assistant';
  content: string | Array<
    | { type: 'input_text'; text: string }
    | { type: 'input_file'; file_id?: string; file_data?: string; filename?: string }
    | { type: 'input_image'; file_id?: string; image_url?: string | { url: string } }
  >;
}
  • file_id names a file already uploaded to the conversation with sessions.files.upload(). The request must name that conversation (session_id, conversation or previous_response_id).
  • file_data is inline base64 or a data: URL, used when there is no file_id; the Console stores it as a session file (filename names it).
  • input_image.image_url is a data: URL or an http(s) URL the Console fetches (public hosts only).
  • Sending files to an agent that takes none is a 403 attachments_disabled.

Response Type ​

typescript
interface AgentResponse {
  id: string;                     // "resp_<conversationId>"
  object: 'response';
  model: string;                  // the agent key
  output: ResponseOutputMessage[];
  status: 'completed' | 'failed' | 'incomplete' | 'requires_action';
  incomplete_details?: { reason: string } | null;   // e.g. { reason: 'max_cost' }
  stop_reason?: string;           // 'completed' | 'limit' | 'cancelled' | 'requires_action' | ...
  stop_detail?: string;           // the runtime's own wording, e.g. which limit
  required_action?: {             // present when status is 'requires_action'
    type: 'agent_actions';
    actions: PendingAction[];
  };
  usage: { input_tokens: number; output_tokens: number; total_tokens: number };
  created_at: number;             // unix seconds
  previous_response_id: string | null;
  version: number | null;         // published version used
}

interface ResponseOutputMessage {
  id: string;
  type: 'message';
  role: 'assistant';
  content: Array<{ type: 'output_text'; text: string }>;
}

status is no longer limited to completed / failed (3.1.0). An exhaustive switch over it needs the incomplete and requires_action cases.

  • incomplete — a run limit or a cancellation ended the turn before a final answer (see stop_reason).
  • requires_action — the turn paused on a tool approval or a question. required_action.actions lists what to answer; answer with sessions.actions.resolve(). The conversation id is the response id without its resp_ prefix.

Background Runs ​

Long agent turns (many tool calls, slow downstream services) can outlive proxies and HTTP timeouts. Pass background: true to queue the turn and get an AgentRun back immediately (202), then poll it or receive a callback.

typescript
const run = await client.agents.responses.create(
  {
    model: 'research-agent',
    input: 'Compare these three vendors',
    background: true,
    callback_url: 'https://example.com/hooks/agent-run', // optional
    callback_secret: process.env.AGENT_CALLBACK_SECRET,   // optional, min 16 chars
  },
  { idempotencyKey: 'vendor-compare-42' },                // optional, sent as Idempotency-Key
);

run.id;     // "run_66f2..."
run.status; // "queued"

const done = await client.agents.runs.wait(run.id, {
  pollIntervalMs: 2000, // default 1000
  timeoutMs: 15 * 60_000, // default: wait until terminal
});

if (done.status === 'succeeded') {
  console.log(done.result?.output[0]?.content[0]?.text);
  console.log(done.result?.id); // "resp_..."
} else {
  console.log(done.status, done.error); // failed | canceled, { type, message }
}

Background Parameters ​

NameWhereDescription
backgroundbodytrue queues the turn and returns an AgentRun
callback_urlbodyWebhook the Console POSTs the terminal event to
callback_secretbodyMin 16 chars; signs callbacks with HMAC-SHA256
idempotencyKey2nd argumentIdempotency-Key header. Background only — sending it on a synchronous call is a 400

Retrying an identical background request with the same idempotencyKey returns the original run instead of starting a second one. Reusing the key with a different request is a 409 idempotency_key_conflict.

Run Methods ​

typescript
await client.agents.runs.get('run_66f2...');    // current status
await client.agents.runs.cancel('run_66f2...'); // request cancellation
await client.agents.runs.wait('run_66f2...', { signal: controller.signal });
  • wait() resolves with the run for succeeded, failed and canceled — and for requires_action, where the run is paused on a tool approval or a question and polling would never end (run.result.required_action.actions lists what to answer with client.agents.sessions.actions.resolve()) — check status. It throws AgentRunWaitTimeoutError (with lastRun) when timeoutMs elapses and the signal's reason when signal aborts. Neither stops the run; call cancel() for that.
  • cancel() is cooperative: the run it returns may still say running for about a second before it lands on canceled. Canceling a finished run is a 409 with errorCode === 'agent_run_already_terminal'.

runs.wait() changed in 3.1.0

It now also returns when a run is requires_action. Code that assumed "returned means finished" must check run.status. isAgentRunTerminal() is unchanged (requires_action is not terminal); the new isAgentRunSettled() is true for terminal statuses and requires_action.

Continuing the Conversation ​

Either the run id (run_...) or its result id (resp_...) works as previous_response_id:

typescript
const next = await client.agents.responses.create({
  model: 'research-agent',
  input: 'Now summarize it in three bullets',
  previous_response_id: done.id,
});

AgentRun Type ​

typescript
// `requires_action` is not terminal: the run continues once the actions are resolved.
type AgentRunStatus = 'queued' | 'running' | 'requires_action' | 'succeeded' | 'failed' | 'canceled';

interface AgentRun {
  id: string;                 // "run_..."
  object: 'agent.run';
  status: AgentRunStatus;
  stop_reason?: string | null;  // 'end_turn' | 'limit' | 'requires_action' | 'cancelled' | 'error' | ...
  agent?: string;
  conversation_id?: string;
  result?: AgentResponse | null;                  // when succeeded, or the requires_action response
  error?: { type: string; message: string | null } | null; // when failed/canceled
  created_at: number;
  started_at?: number | null;
  completed_at?: number | null;
  cancel_requested_at?: number | null;
  max_duration_ms?: number | null;
  callback?: { url: string; status: string | null; attempts: number; signed: boolean } | null;
}

The 202 from create({ background: true }) is already the full run (stop_reason, agent, conversation_id, callback... with result: null and error: null until it finishes); runs.get() returns the same shape.

Error codes ​

Errors are CognipeerAPIErrors; branch on errorCode (the body's error.code) with isAgentRunErrorCode:

typescript
import { AgentRunErrorCodes, isAgentRunErrorCode } from '@cognipeer/console-sdk';

try {
  await client.agents.responses.create({ model: 'research-agent', input: '...', previous_response_id: prev });
} catch (error) {
  if (isAgentRunErrorCode(error, AgentRunErrorCodes.Conflict)) {
    // 409: a run is already queued/running on this conversation
  } else if (isAgentRunErrorCode(error, AgentRunErrorCodes.SyncTimeout)) {
    // 504: synchronous turn hit the server ceiling — retry with background: true
  } else if (isAgentRunErrorCode(error, AgentRunErrorCodes.ConcurrencyLimit)) {
    // 429: too many background runs in flight for this tenant
  }
}
errorCodeStatusMeaning
agent_run_conflict409A run is already active on this conversation (sync or background)
timeout504Synchronous turn exceeded the server ceiling. Tool side effects may have happened, so the SDK does not retry it
agent_run_concurrency_limit429Tenant's concurrent background-run limit reached
idempotency_key_conflict409Idempotency-Key reused with a different request
idempotency_key_sync_not_supported400Idempotency-Key sent without background: true
agent_run_already_terminal409cancel() on a finished run

Session, approval, file and budget codes (session_not_found, action_not_pending, file_too_large, budget_reached, …) are listed on Agent error codes.

Verifying Callbacks (Node.js) ​

When the run finishes, the Console POSTs JSON to callback_url with:

  • X-Cognipeer-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(callback_secret, "<t>.<raw body>")>
  • X-Cognipeer-Event: agent_run.succeeded | agent_run.failed | agent_run.canceled | agent_run.requires_action
  • X-Cognipeer-Event-Id — stable per run + event; deliveries are retried, so dedupe on it

verifyWebhook (alias: verifyAgentRunCallback) lives in the Node-only @cognipeer/console-sdk/webhooks entry point (it uses node:crypto, which the browser-safe main entry avoids). Webhook subscriptions (client.webhooks) are signed with the same scheme. Always verify the raw request body:

typescript
import express from 'express';
import { verifyWebhook } from '@cognipeer/console-sdk/webhooks';
import type { AgentRunCallbackEvent } from '@cognipeer/console-sdk';

app.post('/hooks/agent-run', express.raw({ type: 'application/json' }), (req, res) => {
  const ok = verifyWebhook({
    rawBody: req.body, // Buffer
    signatureHeader: req.get('X-Cognipeer-Signature'),
    secret: process.env.AGENT_CALLBACK_SECRET!,
    toleranceSeconds: 300, // default
  });
  if (!ok) return res.sendStatus(400);

  const event = JSON.parse(req.body.toString('utf8')) as AgentRunCallbackEvent;
  // event.event === 'agent_run.succeeded' → event.data.result
  res.sendStatus(204);
});

The callback body is an AgentRunCallbackEvent: { id, event, createdAt, runId, tenantId?, projectId?, conversationId?, data }, where data is { result } on success and on requires_action (the result carries required_action), { errorReason, message? } on failure, and {} on cancel. For the full signature scheme, retries and troubleshooting see Webhooks.

Sessions, approvals, events, files and webhooks ​

A turn can pause for a tool approval or a question (status: 'requires_action', or a run in requires_action). Everything around a conversation's runtime lives under client.agents.sessions, and event subscriptions under client.webhooks:

You want to...CallPage
Start a session without sending a message, list, read, update the budget of, or delete sessionssessions.create / list / get / update / deleteAgent sessions
Give the agent a file before or between messages, read it backsessions.files.upload / list / content / contentStream / deleteSession files
Answer a tool approval or a questionsessions.actions.list / resolveApprovals & questions
Page or follow the append-only event logsessions.events.list / streamSession events
Get called when a session idles, needs an answer or hits its budgetwebhooks.* and verifyWebhookWebhooks
Branch on an errorerrorCode, AgentActionErrorCodesAgent error codes

Answering an approval is the Console's agent.approve permission (the person who started the session always may); a question follows session ownership. Managing webhooks is the Console's webhooks permission: webhooks.read lists endpoints and deliveries (with each URL's userinfo and query string removed, https://host/path?…), webhooks.admin changes anything and sees the full URL. A call without the permission answers 403 (forbidden, with a decisionId).

Legacy Chat Method ​

Deprecated

client.agents.chat() is deprecated. Use client.agents.responses.create() instead.

typescript
// Deprecated — avoid in new code
const result = await client.agents.chat('support-bot', {
  message: 'Hello',
});

An agent that pauses for an approval or a question answers with an empty content; the result then also carries status: 'requires_action' and pendingActions (resolve them with client.agents.sessions.actions.resolve()). A completed turn has neither.

See Also ​

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub