Skip to main content

JavaScript / TypeScript SDK

@kapaai/agent-core is the framework-agnostic foundation. It handles session tokens, streaming, tool execution, and the agent loop. No React, no DOM. Works in browsers, Node.js, Deno, and edge runtimes.

Use this package when you're not using React, or when you need full control over the UI. React users should use @kapaai/agent-react instead (it includes @kapaai/agent-core as a dependency).

Installation

npm install @kapaai/agent-core

The Agent class

Agent is the main entry point. It combines session management, streaming, tool execution, and approval flow in a single class.

import { Agent } from '@kapaai/agent-core';

const agent = new Agent({
projectId: 'your-project-id',
integrationId: 'your-integration-id',
model: 'kapa-agent-1.0',
tools: [],
context: {},
getSessionToken: async () => {
const res = await fetch('/api/session', { method: 'POST' });
return res.json(); // SDK accepts the raw API response directly
},
onMessagesChange: (messages) => {
// Called whenever messages update (new text, tool calls, etc.)
renderMessages(messages);
},
onStreamingChange: (isStreaming) => {
// Called when streaming starts or stops
toggleLoadingIndicator(isStreaming);
},
});

// Send a message — triggers the agent loop
await agent.sendMessage('How do I get started?');

Methods

MethodDescription
sendMessage(text)Send a user message and run the agent loop.
resetConversation()Clear all messages and abort any in-progress request.
stopGeneration()Abort the current streaming response.
approveToolCall(id)Approve a tool that's waiting for user confirmation.
rejectToolCall(id)Reject a tool that's waiting for user confirmation.
updateOptions(partial)Update tools, context, or other options without resetting.
getMessages()Get the current messages array.
getIsStreaming()Whether the agent is currently streaming.
getThreadId()The current conversation thread ID.
clearSession()Force clear the cached session token.
getFaviconUrl(sourceUrl)Fetch a favicon for a source URL. Returns a blob URL.
listThreads(options?)List past conversations. Requires external_owner_id on the session. See Conversation history.
resumeThread(threadId)Load a past conversation and replace the current chat state. Throws ThreadNotFoundError on 404.
deleteThread(threadId)Soft-delete a thread. Resets the chat if the deleted thread is active. Throws ThreadNotFoundError on 404.

Options

type AgentOptions<TContext> = {
projectId: string;
integrationId: string;
model: string;
getSessionToken: () => Promise<{ token: string; expiresAt: number }>;
tools: ToolDefinition<TContext>[];
context: TContext;
builtinToolMeta?: Record<string, ToolDisplayMeta>;
customInstructions?: string;
user?: EndUserInfo;
sourceGroupIdsInclude?: string[];
onEvent?: OnAgentEvent;
onMessagesChange: (messages: AgentMessage[]) => void;
onStreamingChange: (isStreaming: boolean) => void;
onThreadIdChange?: (threadId: string | null) => void;
enableHistory?: boolean;
};

Set enableHistory: true to enable conversation history. By default it is false. When false, calling listThreads, resumeThread, or deleteThread throws HistoryDisabledError synchronously without making a network call.

Models

The model option specifies which version of the Kapa agent to use. Each model version produces a specific answer style, tool-calling behavior, and response quality. Pinning a model version ensures your integration behaves consistently in production. When a new version is released (e.g. kapa-agent-2.0), your existing integration is unaffected. You can test the new version and upgrade when you are satisfied with the results.

ModelDescription
kapa-agent-1.0First stable agent model. Optimized for knowledge base Q&A and multi-step tool use with streaming.

Built-in tools

Every agent can search your knowledge base out of the box, with no configuration and no tool of your own to define. The agent includes a built-in search_knowledge_base tool that retrieves from your project's knowledge sources server-side, the same sources you've configured in the Kapa dashboard, using Kapa's production retrieval pipeline. Results come back with their source URLs, which the SDK surfaces as source tiles in the chat so answers stay grounded and verifiable. This tool is executed by Kapa's backend and cannot be customized, but you can provide display metadata via builtinToolMeta so it shows a friendly name in your UI instead of the raw tool name.

Search results are pruned: a small LLM drops chunks it deems irrelevant to the query, keeping the retrieval result lean. This matters in an agent, where many tool results compete for context. Read about the research behind this in How we prune RAG context.

Source groups

The sourceGroupIdsInclude option allows you to limit the agent to specific source groups:

const agent = new Agent({
// ...
sourceGroupIdsInclude: ['product-a-group', 'version-2-group'],
});

When set, the agent retrieves from sources in the specified groups and any sources not assigned to a group (global sources). You can get the group IDs from the "Manage groups" page in your Kapa dashboard. See source groups documentation for details.

Message format

Messages are either user or assistant messages:

type AgentMessage =
| { role: 'user'; content: string }
| {
role: 'assistant';
content: string;
blocks: ContentBlock[];
isError?: boolean;
};

Assistant messages contain blocks, an array of text and tool call blocks in the order they appeared during streaming:

type ContentBlock =
| { type: 'text'; content: string }
| { type: 'tool_calls'; toolCalls: ToolCallDisplay[] };

Each ToolCallDisplay tracks the lifecycle of a tool call:

type ToolCallDisplay = {
id: string;
name: string;
arguments: Record<string, unknown>;
status: ToolCallStatus;
result?: unknown;
error?: string;
durationMs?: number;
sources?: AgentSource[];
displayName?: string;
needsApproval?: boolean;
toolType?: 'builtin' | 'external';
};

The toolType field is populated on tool calls loaded from a resumed thread. It is 'builtin' for server-side tools (e.g. search_knowledge_base) and 'external' for client-side tools. It is undefined on live tool calls.

ToolCallStatus

StatusDescription
pendingTool call received, waiting to start.
approval_requestedWaiting for user to approve or deny.
executingTool's execute function is running.
completedFinished successfully.
errorexecute threw an error.
deniedUser clicked Deny.
stoppedGeneration was stopped while tool was in progress.

Session management

The Agent class handles session tokens automatically:

  • Tokens are fetched lazily (only when sendMessage is first called)
  • Tokens are cached and refreshed 30 seconds before expiry
  • On 401 errors, the token is cleared and retried once
  • Concurrent refresh calls are deduplicated

You never need to manage tokens yourself. Just provide the getSessionToken function.

Events

The onEvent callback fires at key moments. Use it for analytics, logging, or monitoring.

const agent = new Agent({
// ...
onEvent: (event) => {
console.log(event.type, event.data);
},
});
EventDataWhen
message_sent{ messageLength }User sends a message.
response_completed{ threadId, toolCallCount }Agent finishes responding.
response_error{ error, threadId }Agent response failed.
generation_stopped{ threadId }User stopped generation.
tool_executed{ toolName, status, durationMs, error? }A tool finished (success or error).
tool_approved{ toolName, toolCallId }User approved a tool.
tool_denied{ toolName, toolCallId }User denied a tool.
conversation_reset{}Conversation was reset.
thread_resumed{ threadId: string }A thread was successfully resumed.
thread_deleted{ threadId: string }A thread was deleted.

Conversation history

The Agent class provides three methods to list, resume, and delete past conversations. These methods require the session to have been created with external_owner_id. See Authentication for how to set this up.

listThreads(options?)

Returns a paginated list of threads for the current session owner.

const { threads, nextCursor } = await agent.listThreads();

// Fetch the next page
const nextPage = await agent.listThreads({ cursor: nextCursor });

Each item in threads is a ThreadSummary:

type ThreadSummary = {
id: string;
title: string | null;
lastActivityAt: string | null;
messageCount: number;
};

nextCursor is null when there are no more pages.

Throws SessionWithoutOwnerError if the session was created without external_owner_id.

resumeThread(threadId)

Loads a past conversation and replaces the current chat state.

await agent.resumeThread('thread-abc123');

The onMessagesChange callback fires immediately with the restored messages. The onEvent callback fires a thread_resumed event.

Throws ThreadNotFoundError if the thread does not exist or belongs to a different owner.

deleteThread(threadId)

Soft-deletes a thread.

await agent.deleteThread('thread-abc123');

If the deleted thread is the currently active one, the chat resets (equivalent to calling resetConversation()). The onEvent callback fires a thread_deleted event.

Throws ThreadNotFoundError if the thread does not exist or belongs to a different owner.

Error classes

All three history methods can throw errors imported from @kapaai/agent-core:

ClassThrown when
SessionWithoutOwnerErrorlistThreads called but the session has no external_owner_id.
ThreadNotFoundErrorresumeThread or deleteThread called with an ID that does not exist or belongs to a different owner.
HistoryDisabledErrorAny history method called when enableHistory is false or not set.
import {
ThreadNotFoundError,
SessionWithoutOwnerError,
HistoryDisabledError,
} from '@kapaai/agent-core';

try {
await agent.resumeThread(threadId);
} catch (err) {
if (err instanceof ThreadNotFoundError) {
// Thread no longer exists
}
}