# Calljmp Full Documentation > Complete reference for building AI agents with Calljmp. This document contains all code examples, type definitions, API signatures, and patterns needed to generate correct Calljmp code. ## Table of Contents 1. [Overview](#overview) 2. [Installation & Setup](#installation--setup) 3. [Agent Structure](#agent-structure) 4. [LLM Module](#llm-module) 5. [Workflow Module](#workflow-module) 6. [Web Module](#web-module) 7. [Memory Module](#memory-module) 8. [Datasets Module](#datasets-module) 9. [Vault Module](#vault-module) 10. [Integrations Module](#integrations-module) 11. [Live Module](#live-module) 12. [Prompts](#prompts) 13. [Schema Module](#schema-module) 14. [Web SDK](#web-sdk) 15. [CLI Commands](#cli-commands) 16. [REST API](#rest-api) 17. [Type Definitions](#type-definitions) 18. [Complete Examples](#complete-examples) --- ## Overview Calljmp is an agentic AI platform for building TypeScript AI agents. Agents are serverless functions that: - Process input and generate AI responses - Use LLMs (Workers AI open-source models or premium models like OpenAI, Anthropic, xAI/Grok) - Execute multi-step workflows with retries and parallel execution - Query documents via RAG (semantic search) - Scrape websites for real-time data - Send messages to Slack and call webhooks - Store and retrieve state via memory **Package**: `@calljmp/agent` --- ## Installation & Setup ### Install CLI ```bash npm install -g @calljmp/cli ``` ### Create Agent Project ```bash calljmp init ``` This creates: - `index.ts` - Main agent code - `package.json` - Dependencies including `@calljmp/agent` - `.calljmp/` - Configuration files ### Deploy Agent ```bash calljmp agent run # Build, deploy, and run locally calljmp agent deploy # Deploy only (no local run) calljmp agent run --input '{}' # Test with specific input calljmp agent run -n sentiment # Run a specific agent file (sentiment.ts) ``` ### Generate Types for Prompts and Vault ```bash calljmp typegen # Generates .calljmp/types/agent.d.ts with prompt and vault type definitions ``` --- ## Agent Structure Every agent exports an async `run` function that receives input and returns output. Optionally export a `config` object for metadata. ### Basic Agent ```typescript import { llm } from '@calljmp/agent'; export async function run(input: { text: string }) { const response = await llm.generate({ input: [ { role: 'system', content: 'You are helpful.' }, { role: 'user', content: input.text }, ], }); return response.response; } ``` ### Agent with Config ```typescript import { llm, AgentConfig } from '@calljmp/agent'; export const config: AgentConfig = { name: 'My Agent', description: 'A helpful AI assistant', }; export async function run(input: { text: string }) { const response = await llm.generate({ input: [ { role: 'system', content: 'You are helpful.' }, { role: 'user', content: input.text }, ], }); return response.response; } ``` ### Agent with Input Schema (for Forms/Portals) ```typescript import { llm, schema, AgentConfig } from '@calljmp/agent'; const inputSchema = { properties: { text: { title: 'Input Text', type: 'string', description: 'Text to analyze', minLength: 1, maxLength: 1000, }, }, required: ['text'], } as const satisfies schema.Schema; export const config: AgentConfig = { name: 'Text Analyzer', description: 'Analyzes input text', forms: { inputSchema }, }; export async function run(input: schema.infer) { // input.text is typed as string const response = await llm.generate({ input: [ { role: 'user', content: input.text }, ], }); return response.response; } ``` ### Agent with Multiple Tools ```typescript import { llm, workflow, memory, datasets, web, vault, integrations } from '@calljmp/agent'; export async function run(input: { query: string; userId: string }) { // Phase 1: Get context from memory and datasets const context = await workflow.phase({ name: 'Gather context' }, async () => { const memoryContext = memory.short.context(`user:${input.userId}`); const { segments } = await datasets.query(input.query); return { memoryContext, segments }; }); // Phase 2: Generate response with LLM const response = await workflow.phase({ name: 'Generate response' }, async () => { return llm.generate({ input: [ { role: 'system', content: `Context: ${context.segments.map(s => s.content).join('\n\n')}`, }, { role: 'user', content: input.query }, ], memory: context.memoryContext, }); }); // Phase 3: Notify via Slack await workflow.phase({ name: 'Notify' }, async () => { await integrations.slack.postMessage({ channel: '#alerts', text: `Processed query for user ${input.userId}`, }); }); return response.response; } ``` ### Agent with Finalization ```typescript import { llm, AgentContext } from '@calljmp/agent'; export async function run(input: { text: string }, context: AgentContext) { context.onFinally(async outcome => { if (outcome.status === 'completed') { console.log('Done:', outcome.result); } else if (outcome.status === 'failed') { console.error('Error:', outcome.error.message); } else if (outcome.status === 'canceled') { console.log('Canceled:', outcome.reason); } }); const response = await llm.generate({ input: [{ role: 'user', content: input.text }], }); return response.response; } ``` ### AgentContext / Finalization Types ```typescript interface AgentContext { onFinally: ( finalizer: AgentFinalizer ) => void; } type AgentFinalizer = ( outcome: AgentOutcome ) => void | Promise; type AgentOutcome = | AgentCompletedOutcome | AgentFailedOutcome | AgentCanceledOutcome; interface AgentCompletedOutcome { status: 'completed'; runId: string; method?: string; input: Input; result: Output; } interface AgentFailedOutcome { status: 'failed'; runId: string; method?: string; input: Input; error: { name: string; message: string; stack?: string }; } interface AgentCanceledOutcome { status: 'canceled'; runId: string; method?: string; input: Input; reason: string; } ``` > Finalizer is **not** called on suspension — only on `completed`, `failed`, or `canceled` outcomes. Errors thrown inside a finalizer are logged but do not affect the agent outcome. --- ## LLM Module The `llm` module provides text generation, structured outputs, and tool calling. ### Import ```typescript import { llm } from '@calljmp/agent'; ``` ### Basic Text Generation ```typescript const response = await llm.generate({ input: [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'What is TypeScript?' }, ], }); // response.response is a string ``` ### Available Models **System Models (no API key required):** - `@cf/meta/llama-3.1-8b-instruct-fp8-fast` - Fast inference, no tools/JSON schema support - `@cf/qwen/qwen3-30b-a3b-fp8` - Supports tools and JSON schema - `@cf/zai-org/glm-4.7-flash` - High-performance model with tools and JSON schema support - `@cf/google/gemma-4-26b-a4b-it` - Google Gemma 4 26B with tools and JSON schema support - `@cf/moonshotai/kimi-k2.6` - MoonshotAI Kimi K2.6 with tools and JSON schema support **OpenAI Models (require `openaiApiKey` in Vault):** - `openai/gpt-5` - `openai/gpt-5-mini` - `openai/gpt-5-nano` - `openai/gpt-5-codex` - `openai/gpt-4.1` - `openai/gpt-4.1-mini` - `openai/gpt-4.1-nano` - `openai/gpt-4o` - `openai/gpt-4o-mini` ### Specifying a Model ```typescript const response = await llm.generate({ model: 'openai/gpt-4o', input: [{ role: 'user', content: 'Hello' }], }); ``` ### Structured Output with Zod Schema ```typescript import { llm } from '@calljmp/agent'; import { z } from 'zod'; const response = await llm.generate({ input: [{ role: 'user', content: 'Extract: John Doe, john@example.com' }], responseSchema: z.object({ name: z.string().describe('Full name'), email: z.string().email().describe('Email address'), }), }); // response.response is { name: string; email: string } console.log(response.response.name); // "John Doe" console.log(response.response.email); // "john@example.com" ``` ### Tool Calling ```typescript import { llm } from '@calljmp/agent'; import { z } from 'zod'; const response = await llm.generate({ input: [{ role: 'user', content: 'What is the weather in Paris?' }], tools: [ llm.tool({ name: 'getWeather', description: 'Get current weather for a city', parameters: z.object({ city: z.string().describe('City name'), unit: z.enum(['celsius', 'fahrenheit']).optional(), }), execute: async ({ city, unit }) => { // Call weather API here return { temperature: 22, condition: 'sunny', unit: unit || 'celsius' }; }, }), ], toolChoice: 'auto', // 'auto' | 'required' | 'none' }); ``` ### With Memory Context ```typescript import { llm, memory } from '@calljmp/agent'; export async function run(input: { message: string; userId: string }) { const context = memory.short.context(`chat:${input.userId}`); const response = await llm.generate({ input: [ { role: 'system', content: 'You are helpful.' }, { role: 'user', content: input.message }, ], memory: context, // Automatically loads/saves conversation history }); return response.response; } ``` ### Loading Memory Context Explicitly ```typescript import { llm, memory } from '@calljmp/agent'; export async function run(input: { message: string }) { const chatContext = memory.short.context('chat:data'); const { history } = await llm.context(chatContext); const response = await llm.generate({ input: [ ...history, // Include conversation history { role: 'user', content: input.message }, ], }); return response.response; } ``` ### Generation Parameters ```typescript const response = await llm.generate({ model: 'openai/gpt-4o', input: [{ role: 'user', content: 'Write a poem' }], maxTokens: 500, temperature: 0.7, // 0.0-2.0, higher = more creative topP: 0.9, // Nucleus sampling topK: 40, // Top-k sampling seed: 42, // For reproducibility frequencyPenalty: 0.5, // Reduce repetition presencePenalty: 0.5, // Encourage new topics maxIterations: 5, // Max tool call iterations trim: { strategy: 'removeOldest', // or 'summarizeOldest' maxTokens: 4000, }, }); ``` ### Input Message Types ```typescript // System message { role: 'system', content: 'You are an expert.' } // User message { role: 'user', content: 'Hello' } // Assistant message (for conversation history) { role: 'assistant', content: 'Hi there!' } // With timestamp { role: 'user', content: 'Hello', timestamp: Date.now() } ``` ### llm.generate() Full Signature ```typescript function generate< Schema extends z.ZodObject | undefined = undefined, Tools extends Array> | undefined = undefined, >(args: { model?: Model; input: (Input | Promise)[]; maxTokens?: number; temperature?: number; topP?: number; topK?: number; seed?: number; repetitionPenalty?: number; frequencyPenalty?: number; presencePenalty?: number; toolChoice?: 'auto' | 'required' | 'none'; tools?: Tools; responseSchema?: Schema; maxIterations?: number; memory?: MemoryContext; trim?: { strategy?: 'removeOldest' | 'summarizeOldest'; maxTokens?: number; } | 'removeOldest' | 'summarizeOldest'; }): Promise<{ response: Schema extends z.ZodObject ? z.infer : string; }>; ``` ### llm.tool() Signature ```typescript function tool, Result = any>(config: { name: string; description: string; parameters: Parameters; execute: (params: z.infer) => Promise | Result; }): Tool; ``` ### llm.context() Signature ```typescript function context(memory: MemoryContext): Promise<{ history: Input[] }>; ``` --- ## Workflow Module The `workflow` module provides multi-step orchestration with phases, retries, and parallel execution. ### Import ```typescript import { workflow } from '@calljmp/agent'; ``` ### Phases Phases are tracked execution steps. Use them to organize agent logic: ```typescript import { workflow, llm } from '@calljmp/agent'; export async function run(input: { text: string }) { // Phase 1: Analyze const analysis = await workflow.phase( { name: 'Analyze sentiment', description: 'Classify text sentiment' }, async () => { return llm.generate({ input: [ { role: 'system', content: 'Classify as positive/negative/neutral.' }, { role: 'user', content: input.text }, ], }); } ); // Phase 2: Generate response based on analysis const response = await workflow.phase( { name: 'Generate response' }, async () => { const tone = analysis.response.includes('positive') ? 'enthusiastic' : 'professional'; return llm.generate({ input: [ { role: 'system', content: `Respond in a ${tone} tone.` }, { role: 'user', content: input.text }, ], }); } ); return response.response; } ``` ### Phase with Simple String Name ```typescript const result = await workflow.phase('Process data', async () => { // Your logic here return processedData; }); ``` ### Phases with Scopes (for loops) ```typescript export async function run(input: { items: { id: string }[] }) { for (const item of input.items) { await workflow.phase( { name: 'Processing item', description: 'Process each item in the collection.', scope: { itemId: item.id }, // Phase scoping for unique identification }, async () => { // Phase logic here } ); } } ``` ### Retries Wrap phases with retry logic for fault tolerance: ```typescript const result = await workflow.retry( { retries: 3, delay: 1000, // ms backoff: 'exponential', // or 'linear' onError: (error, attempt) => { console.log(`Attempt ${attempt} failed: ${error.message}`); }, }, workflow.phase({ name: 'Call external API' }, async () => { const response = await fetch('https://api.example.com/data'); if (!response.ok) throw new Error('API failed'); return response.json(); }) ); ``` ### Parallel Execution Run multiple tasks concurrently: ```typescript const [result1, result2, result3] = await workflow.parallel( { concurrency: 2 }, // Max concurrent tasks [ workflow.phase({ name: 'Task 1' }, () => fetch('/api/1').then(r => r.json())), workflow.phase({ name: 'Task 2' }, () => fetch('/api/2').then(r => r.json())), workflow.phase({ name: 'Task 3' }, () => fetch('/api/3').then(r => r.json())), ] ); ``` ### Suspend and Resume Pause agent execution waiting for external input: ```typescript import { workflow } from '@calljmp/agent'; export async function run(input: { amount: number }) { await workflow.phase('1st phase', async () => { // Perform some operations }); if (input.amount > 1000) { // Suspend for human approval await workflow.suspend({ reason: 'Amount exceeds $1000, requires approval', timeout: '1 hour', // or number (ms) or 'indefinite' scope: { amount: input.amount }, }); } // Continue after resume await workflow.phase('2nd phase', async () => { // Continue operations after resuming }); return { approved: true, amount: input.amount }; } ``` **Resume via CLI:** ```bash calljmp agent resume --target {RUN_ID} --resumption {TOKEN} ``` **Resume via REST API:** ```bash curl -X POST https://api.calljmp.com/target/v1/agent/{RUN_ID}/resume \ -H "Authorization: Bearer inv_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"resumption":"{TOKEN}","input":{"approved":true}}' ``` ### workflow.phase() Signature ```typescript function phase( nameOrConfig: string | { name: string; description?: string; scope?: Record; }, block: () => Promise ): PhaseResult; ``` ### workflow.retry() Signature ```typescript function retry( options: { retries?: number; delay?: number; backoff?: 'exponential' | 'linear'; onError?: (error: Error, attempt: number) => void; }, result: PhaseResult ): RetryResult; // Or without options: function retry(result: PhaseResult): RetryResult; ``` ### workflow.parallel() Signature ```typescript function parallel>>( options: { concurrency?: number }, tasks: [...Tasks] ): ParallelResult<{ [K in keyof Tasks]: Awaited> }>; ``` ### workflow.suspend() Signature ```typescript function suspend(options?: string | { reason?: string; timeout?: number | 'indefinite' | DurationLiteral; // e.g., '1 hour', '30 minutes' scope?: Record; }): Promise; ``` --- ## Web Module The `web` module provides web scraping capabilities. ### Import ```typescript import { web } from '@calljmp/agent'; ``` ### Scrape as Text ```typescript const result = await web.scrape({ url: 'https://example.com', format: 'text', }); // result.content contains the page text content ``` ### Scrape as HTML ```typescript const result = await web.scrape({ url: 'https://example.com', format: 'html', }); // result.content contains the HTML ``` ### Simple Scrape (defaults to text) ```typescript const result = await web.scrape({ url: 'https://calljmp.com' }); // Automatically performs user-like browsing actions ``` ### With Consent Handling ```typescript const result = await web.scrape({ url: 'https://example.com', format: 'text', consent: { selectors: ['#accept-cookies', '.consent-button'], textPatterns: ['Accept', 'I agree'], }, }); ``` ### With Activity Simulation ```typescript const result = await web.scrape({ url: 'https://example.com', format: 'text', activity: { enabled: true, maxScrolls: 5, }, }); ``` ### Extract Structured Content ```typescript const result = await web.scrape({ url: 'https://example.com', extract: [ { selector: '.item', fields: ['text', 'html', 'attributes', 'url'], where: { attributes: { 'data-type': { $eq: 'product' }, 'data-price': { $regex: '^\d+\.\d{2}$' }, }, text: { $startsWith: 'Limited', }, }, }, ], }); // result.content is an array of matching elements ``` ### web.scrape() Signature ```typescript function scrape(args: { url: string | URL; format?: F; consent?: { selectors?: string[]; textPatterns?: string[]; }; activity?: { enabled?: boolean; maxScrolls?: number; }; extract?: E; }): Promise< E extends WebExtractOptions[] ? ScrapeExtractedResult : F extends 'text' ? ScrapeTextResult : F extends 'html' ? ScrapeHtmlResult : never >; interface WebExtractOptions { selector: string; where?: { attributes?: WebAttributeFilters; text?: WebTextFilters; }; fields?: ('html' | 'text' | 'attributes' | 'url' | 'index')[]; } interface WebAttributeFilters { [attribute: string]: { $eq?: string | number | boolean; $ne?: string | number | boolean; $contains?: string; $startsWith?: string; $endsWith?: string; $regex?: string; $in?: (string | number)[]; $nin?: (string | number)[]; }; } interface WebTextFilters { $contains?: string; $startsWith?: string; $endsWith?: string; $regex?: string; } interface WebExtractedElement { html?: string; text?: string; attributes?: Record; url?: string; index?: number; } interface ScrapeTextResult { format: 'text'; content: string; /** @deprecated Use `content` instead */ text: string; } interface ScrapeHtmlResult { format: 'html'; content: string; } interface ScrapeExtractedResult { format: 'html'; content: Array; } ``` --- ## Memory Module The `memory` module provides state persistence across agent invocations. ### Import ```typescript import { memory } from '@calljmp/agent'; ``` ### Short-term Memory Context Use `memory.short.context()` for conversation history and session state: ```typescript import { llm, memory } from '@calljmp/agent'; export async function run(input: { message: string; sessionId: string }) { // Create a context for this session const context = memory.short.context(`session:${input.sessionId}`); // Use with LLM - automatically loads/saves history const response = await llm.generate({ input: [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: input.message }, ], memory: context, }); return response.response; } ``` ### Context with Default Values ```typescript const context = memory.short.context({ key: 'chat:data', defaultValue: { messages: [] }, }); ``` ### Manual Memory Operations ```typescript // Get a value const value = await memory.short.retrieve('my-key'); const valueWithDefault = await memory.short.retrieve('my-key', 'default'); // Store a value await memory.short.store('my-key', { data: 'value' }); // Delete a value await memory.short.delete('my-key'); ``` ### Context Methods ```typescript const ctx = memory.short.context({ key: 'my-state' }); // Get current state const state = await ctx.get(); const stateWithDefault = await ctx.get({ count: 0 }); // Set state await ctx.set({ count: 1 }); // Clear state (set to null or call delete) await ctx.set(null); await ctx.delete(); ``` ### memory.short.context() Signature ```typescript function context( options?: { key?: string; defaultValue?: T } | string ): MemoryContext; interface MemoryContext { get(): Promise; get(defaultValue: T): Promise; set(value: T | null): Promise; delete(): Promise; } ``` ### MemoryProvider Interface ```typescript interface MemoryProvider { retrieve(key: string): Promise; retrieve(key: string, defaultValue: T): Promise; store(key: string, value: T): Promise; delete(key: string): Promise; } ``` --- ## Datasets Module The `datasets` module provides RAG (Retrieval-Augmented Generation) via semantic search over uploaded documents. ### Import ```typescript import { datasets } from '@calljmp/agent'; ``` ### Query Datasets ```typescript const { segments } = await datasets.query('What are the main features?'); // Each segment contains: // - type: 'page' // - index: number // - source?: string // - content: string // - score: number (0-1 relevance score) // - metadata: { document: { title?, language? }, pageIndex: number } ``` ### Query with Options ```typescript const { segments } = await datasets.query({ prompt: 'What are the pricing options?', topK: 10, // Max results to return minScore: 0.7, // Minimum relevance score (0-1) }); ``` ### Query with Optimized Prompt ```typescript const { segments } = await datasets.query({ prompt: { text: 'pricing information', optimize: true, // Optimize query for better retrieval (default: true) }, }); ``` ### Disable Query Optimization ```typescript const { segments } = await datasets.query({ prompt: { text: 'What are the key features?', optimize: false, }, }); ``` ### Using with LLM (RAG Pattern) ```typescript import { llm, datasets, workflow } from '@calljmp/agent'; export async function run(input: { question: string }) { // Step 1: Retrieve relevant documents const docs = await workflow.phase({ name: 'Retrieve documents' }, async () => { const { segments } = await datasets.query({ prompt: input.question, topK: 5, minScore: 0.6, }); return segments; }); // Step 2: Generate answer using retrieved context const answer = await workflow.phase({ name: 'Generate answer' }, async () => { if (docs.length === 0) { return { answer: "I couldn't find relevant information.", sources: [] }; } const context = docs .map(s => `[${s.metadata.document.title || 'Doc'}]: ${s.content}`) .join('\n\n'); const response = await llm.generate({ input: [ { role: 'system', content: `Answer based on the context. If unsure, say so. Context: ${context}`, }, { role: 'user', content: input.question }, ], }); return { answer: response.response, sources: docs.map(s => ({ title: s.metadata.document.title, page: s.metadata.pageIndex, score: s.score, })), }; }); return answer; } ``` ### datasets.query() Signature ```typescript function query( args: string | { prompt: string | { text: string; optimize?: boolean }; topK?: number; minScore?: number; } ): Promise<{ segments: DatasetSegment[]; }>; interface DatasetSegment { type: 'page'; index: number; source?: string; content: string; score: number; metadata: { document: { title?: string; language?: string; }; pageIndex: number; }; } ``` **Note:** Agents must be granted access to datasets via Dashboard > Agents > Settings > Datasets. --- ## Vault Module The `vault` module provides access to securely stored credentials and configuration. ### Import ```typescript import { vault } from '@calljmp/agent'; ``` ### Access Vault Values ```typescript const apiKey = vault.values.openaiApiKey; const customValue = vault.values.myCustomKey; ``` ### Add Vault Values via CLI ```bash # Add a sensitive secret calljmp vault add --sensitive --name openaiApiKey # Add a non-sensitive variable calljmp vault add --name myVariable --value "some value" # List all vault values calljmp vault list ``` ### Common Vault Keys | Key Name | Purpose | Auto-detect | | -------------- | -------------------------- | ----------- | | `openaiApiKey` | OpenAI API key | Yes | | Any name | Custom secrets/config | Manual | Auto-detected keys are automatically injected when you use the corresponding model. ### vault.values Type ```typescript interface KeyValues { [keyName: string]: string | number | Record | null; } const values: KeyValues; ``` **Setup:** Dashboard > Agents > Vault > Add key-value pair --- ## Integrations Module The `integrations` module connects agents to external services. ### Import ```typescript import { integrations } from '@calljmp/agent'; ``` ### Slack Integration #### Post Plain Text ```typescript await integrations.slack.postMessage({ channel: '#general', text: 'Hello from my agent!', }); ``` #### Post with Markdown ```typescript await integrations.slack.postMessage({ channel: '#alerts', text: '*Alert:* Something happened', mrkdwn: true, }); ``` #### Post Rich Message with Block Kit ```typescript await integrations.slack.postMessage({ channel: '#results', blocks: [ { type: 'header', text: { type: 'plain_text', text: 'Analysis Complete', emoji: true }, }, { type: 'section', fields: [ { type: 'mrkdwn', text: '*Status:*\nSuccess' }, { type: 'mrkdwn', text: '*Duration:*\n2.5s' }, ], }, { type: 'section', text: { type: 'mrkdwn', text: 'Click below for details.' }, }, { type: 'context', elements: [ { type: 'mrkdwn', text: `Analyzed at ${new Date().toLocaleString()}` }, ], }, ], }); ``` ### integrations.slack.postMessage() Signature ```typescript function postMessage(params: { channel: string; text?: string; markdown_text?: string; mrkdwn?: boolean; blocks?: Array<{ type: string; [key: string]: any; text?: { type: string; text: string; emoji?: boolean }; fields?: Array<{ type: string; [key: string]: any }>; }>; }): Promise; ``` **Setup:** Dashboard > Configuration > Slack > Authorize --- ## Live Module The `live` module enables real-time event publishing during agent execution. ### Import ```typescript import { live } from '@calljmp/agent'; ``` ### Publish Events ```typescript await live.publish({ type: 'tool.call', data: { summary: 'Searching knowledge base' }, }); ``` ### Event Patterns ```typescript // Progress event await live.publish({ type: 'progress', data: { step: 1, total: 5, message: 'Processing data...' }, }); // Tool call event await live.publish({ type: 'tool.call', data: { tool: 'queryKnowledgeBase', summary: 'Searching for pricing information', }, }); // Tool result event await live.publish({ type: 'tool.result', data: { resultsCount: 5 }, }); // Custom event await live.publish({ type: 'analysis.complete', data: { result: 'positive', confidence: 0.95, timestamp: Date.now(), }, }); ``` ### Publishing in Workflow Phases ```typescript import { workflow, live, datasets } from '@calljmp/agent'; export async function run(input: { query: string }) { await workflow.phase({ name: 'Query knowledge base' }, async () => { await live.publish({ type: 'tool.call', data: { summary: 'Searching knowledge base' }, }); const results = await datasets.query(input.query); await live.publish({ type: 'tool.result', data: { resultsCount: results.segments.length }, }); return results; }); } ``` ### live.publish() Signature ```typescript interface Message< T extends number = number, K extends Record = Record, > { type: T; payload: K; } function publish( message: Message | Record, options?: { throwOnError?: boolean } ): Promise; ``` --- ## Prompts Prompts are centralized LLM instructions managed in the Dashboard. ### Create Prompts Dashboard > Prompts > Click + > Write content > Auto-saved ### Generate Types ```bash calljmp typegen ``` This generates `.calljmp/types/agent.d.ts` with typed prompt names. ### Use in Code ```typescript import { llm, prompts } from '@calljmp/agent'; export async function run(input: { text: string }) { const response = await llm.generate({ input: [ { role: 'system', content: await prompts.customerService.content(), }, { role: 'user', content: input.text }, ], }); return response.response; } ``` ### Prompt Interface ```typescript interface Prompt { readonly name: string; content: () => Promise; } interface Prompts { [keyName: string]: Prompt; } ``` **Benefits:** - Update prompts without redeploying agents - Non-technical team members can iterate on AI behavior - Version history tracked automatically - Type-safe access in code --- ## Schema Module The `schema` module provides JSON Schema types for defining input schemas. ### Import ```typescript import { schema } from '@calljmp/agent'; ``` ### Define Input Schema ```typescript const inputSchema = { properties: { query: { title: 'Query', type: 'string', description: 'The query to search for', minLength: 1, maxLength: 1000, }, count: { title: 'Result Count', type: 'integer', description: 'Number of results to return', minimum: 1, maximum: 100, }, }, required: ['query'], } as const satisfies schema.Schema; ``` ### Infer TypeScript Type ```typescript type Input = schema.infer; // Input is { query: string; count?: number } ``` ### Use in Agent ```typescript import { schema, AgentConfig } from '@calljmp/agent'; const inputSchema = { properties: { text: { title: 'Text', type: 'string', minLength: 1, }, }, required: ['text'], } as const satisfies schema.Schema; export const config: AgentConfig = { name: 'My Agent', description: 'Processes text input', forms: { inputSchema }, }; export async function run(input: schema.infer) { return { result: input.text.toUpperCase() }; } ``` ### Schema Property Types ```typescript type SchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null'; type SchemaFormat = | 'date-time' | 'date' | 'time' | 'email' | 'hostname' | 'ipv4' | 'ipv6' | 'uri' | 'uri-reference' | 'uuid' | 'json-pointer' | 'relative-json-pointer' | 'regex'; interface SchemaProperty { type?: SchemaType | SchemaType[]; title?: string; description?: string; default?: any; examples?: any[]; // String validation minLength?: number; maxLength?: number; pattern?: string; format?: SchemaFormat; // Number validation minimum?: number; maximum?: number; // Array validation items?: SchemaProperty; minItems?: number; maxItems?: number; } ``` --- ## Web SDK The Web SDK (`@calljmp/sdk-web`) enables React applications to invoke agents. ### Installation ```bash npm install @calljmp/sdk-web ``` ### Initialize Client ```typescript import { Calljmp } from '@calljmp/sdk-web'; const client = new Calljmp({ projectId: 'your-project-id', }); // For development const devClient = new Calljmp({ projectId: 'your-project-id', development: { enabled: true, baseUrl: 'http://localhost:8787', }, }); ``` ### Get Agent Instance ```typescript // Using string shorthand const agent = client.agents.agent('your-agent-lookup-key'); // Using object const agent = client.agents.agent({ lookupKey: 'your-agent-lookup-key' }); ``` ### Connect to Agent (WebSocket) ```typescript // Connect await agent.connect(); // Check connection status console.log(agent.connected); // true if connected console.log(agent.connecting); // true if connecting console.log(agent.reconnecting); // true if reconnecting // Disconnect await agent.disconnect(); ``` ### Send Messages ```typescript // Send a message await agent.send({ type: 1, payload: { message: 'Hello' }, }); ``` ### Handle Messages ```typescript // Set handler via constructor options const agent = client.agents.agent({ lookupKey: 'my-agent', onMessage: async (message) => { console.log('Received:', message); }, autoConnect: true, // Connect automatically (default: true) }); // Or set handler later agent.onMessage = async (message) => { // Handle incoming message }; ``` ### Calljmp Client Signature ```typescript class Calljmp { readonly agents: Agents; constructor(config: { projectId: string; development?: { enabled?: boolean; baseUrl?: string; }; }); } ``` ### Agent Class Signature ```typescript class Agent { readonly autoConnect: boolean; onMessage: MessageHandler | null; connect(): Promise; disconnect(): Promise; send(message: { type?: number; payload: Record }): Promise; get connected(): boolean; get connecting(): boolean; get reconnecting(): boolean; } type MessageHandler = (message: Message) => Promise | void; ``` --- ## CLI Commands ### Authentication & Setup ```bash calljmp init # Initialize agent project calljmp typegen # Generate TypeScript types for prompts and vault ``` ### Agent Management ```bash calljmp agent run # Build, deploy, and run locally calljmp agent deploy # Deploy without running calljmp agent run --input '{}' # Test with specific input calljmp agent run -n myagent # Run specific agent (myagent.ts) calljmp agent run --force-deploy # Force redeployment calljmp agent deploy --force # Force deploy even if code unchanged ``` ### Resume Suspended Agents ```bash calljmp agent resume --target {RUN_ID} --resumption {TOKEN} calljmp agent resume --target {RUN_ID} --resumption {TOKEN} --input '{"approved":true}' ``` ### Vault Management ```bash calljmp vault list # List all vault values calljmp vault add --name key # Add a variable calljmp vault add --name key --sensitive # Add a secret calljmp vault delete --name key # Delete a value ``` --- ## REST API ### Base URL ``` https://api.calljmp.com/target/v1 ``` ### Authentication ``` Authorization: Bearer inv_YOUR_INVOCATION_KEY ``` Find your invocation key in Dashboard > Agents > [Agent] > Invocation tab. ### Run Agent ```bash curl -X POST https://api.calljmp.com/target/v1/agent/run \ -H "Authorization: Bearer inv_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"input":{"text":"Your message"}}' ``` **Response:** ```json { "runId": "run_abc123", "status": "pending" } ``` ### Check Status ```bash curl -X GET https://api.calljmp.com/target/v1/agent/{RUN_ID}/status \ -H "Authorization: Bearer inv_YOUR_KEY" ``` **Response:** ```json { "status": "completed", "result": { "response": "..." } } ``` ### Resume Suspended Agent ```bash curl -X POST https://api.calljmp.com/target/v1/agent/{RUN_ID}/resume \ -H "Authorization: Bearer inv_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"resumption":"{TOKEN}","input":{"approved":true}}' ``` ### Cancel Agent ```bash curl -X DELETE https://api.calljmp.com/target/v1/agent/{RUN_ID} \ -H "Authorization: Bearer inv_YOUR_KEY" ``` ### Status Values | Status | Description | | ----------- | ------------------------------------- | | `pending` | Agent is queued for execution | | `running` | Agent is currently executing | | `completed` | Agent finished successfully | | `failed` | Agent encountered an error | | `suspended` | Agent is waiting for external input | | `canceled` | Agent was canceled | --- ## Type Definitions ### Agent Types ```typescript interface AgentConfig { name: string; description: string; forms?: { inputSchema?: Schema; }; } interface AgentPhaseConfig { name: string; description?: string; scope?: Record; } interface AgentRetryOptions { retries?: number; delay?: number; backoff?: 'exponential' | 'linear'; onError?: (error: Error, attempt: number) => void; } interface AgentParallelOptions { concurrency?: number; } interface AgentSuspendOptions { reason?: string; timeout?: number | 'indefinite' | DurationLiteral; scope?: Record; } enum AgentStatus { Pending = 'pending', Running = 'running', Completed = 'completed', Failed = 'failed', Suspended = 'suspended', Canceled = 'canceled', } enum AgentType { Permanent = 'permanent', Ephemeral = 'ephemeral', } ``` ### LLM Types ```typescript type Model = | '@cf/meta/llama-3.1-8b-instruct-fp8-fast' | '@cf/qwen/qwen3-30b-a3b-fp8' | '@cf/zai-org/glm-4.7-flash' | '@cf/google/gemma-4-26b-a4b-it' | '@cf/moonshotai/kimi-k2.6' | 'openai/gpt-5' | 'openai/gpt-5-mini' | 'openai/gpt-5-nano' | 'openai/gpt-5-codex' | 'openai/gpt-4.1' | 'openai/gpt-4.1-mini' | 'openai/gpt-4.1-nano' | 'openai/gpt-4o' | 'openai/gpt-4o-mini' | `openai/${string}`; type InputRole = 'system' | 'user' | 'assistant'; interface SystemInput { role: 'system'; content: string; timestamp?: number; } interface UserInput { role: 'user'; content: string; timestamp?: number; } interface AssistantInput { role: 'assistant'; content: string; timestamp?: number; } type Input = SystemInput | UserInput | AssistantInput; interface Tool, Result = any> { type: 'function'; function: { name: string; description: string; parameters: Parameters; execute: (params: z.infer) => Promise | Result; }; } interface Prompt { readonly name: string; content: () => Promise; } ``` ### Workflow Types ```typescript interface Result extends Promise {} interface PhaseResult extends Result {} interface RetryResult extends Result {} interface ParallelResult extends Result {} ``` ### Memory Types ```typescript interface MemoryProvider { retrieve(key: string): Promise; retrieve(key: string, defaultValue: T): Promise; store(key: string, value: T): Promise; delete(key: string): Promise; } interface MemoryContext { get(): Promise; get(defaultValue: T): Promise; set(value: T | null): Promise; delete(): Promise; } ``` ### Dataset Types ```typescript type DatasetSegmentType = 'page'; interface DatasetPageSegment { type: 'page'; index: number; source?: string; content: string; score: number; metadata: { document: { title?: string; language?: string; }; pageIndex: number; }; } type DatasetSegment = DatasetPageSegment; ``` ### Web Types ```typescript type ScrapeFormat = 'text' | 'html'; interface ScrapeTextResult { format: 'text'; content: string; /** @deprecated Use `content` instead */ text: string; } interface ScrapeHtmlResult { format: 'html'; content: string; } type ScrapeResult = ScrapeTextResult | ScrapeHtmlResult; ``` ### Vault Types ```typescript interface KeyValues { [keyName: string]: string | number | Record | null; } ``` --- ## Complete Examples ### Example 1: Basic Chat Agent with Memory ```typescript import { llm, memory, AgentConfig } from '@calljmp/agent'; export const config: AgentConfig = { name: 'Chat Agent', description: 'A simple chat agent with memory', }; export async function run(input: { message: string; sessionId: string }) { const context = memory.short.context(`chat:${input.sessionId}`); const response = await llm.generate({ input: [ { role: 'system', content: 'You are a helpful AI assistant.' }, { role: 'user', content: input.message }, ], memory: context, }); return { reply: response.response }; } ``` ### Example 2: Sentiment Analyzer with Structured Output and Slack ```typescript import { workflow, llm, integrations, schema, AgentConfig } from '@calljmp/agent'; import { z } from 'zod'; const inputSchema = { properties: { text: { title: 'Input text', type: 'string', description: 'Text to analyze sentiment for', minLength: 1, maxLength: 1000, }, }, required: ['text'], } as const satisfies schema.Schema; export const config: AgentConfig = { name: 'Sentiment Analyzer', description: 'A simple sentiment analysis agent.', forms: { inputSchema }, }; export async function run(input: schema.infer) { const analysis = await workflow.phase( { name: 'Analyze Sentiment', description: 'Analyze the sentiment of the provided text.' }, async () => { const { response } = await llm.generate({ input: [ { role: 'system', content: 'Analyze the sentiment of the text.' }, { role: 'user', content: input.text }, ], responseSchema: z.object({ sentiment: z.enum(['positive', 'negative', 'neutral']), explanation: z.string(), }), }); return response; } ); await workflow.phase('Post to Slack', () => integrations.slack.postMessage({ channel: '#agents', blocks: [ { type: 'header', text: { type: 'plain_text', text: '📊 Sentiment Analysis', emoji: true }, }, { type: 'section', fields: [ { type: 'mrkdwn', text: `*Input Text:*\n${input.text}` }, { type: 'mrkdwn', text: `*Sentiment:*\n${analysis.sentiment}` }, ], }, { type: 'section', text: { type: 'mrkdwn', text: `*Explanation:*\n${analysis.explanation}` }, }, ], }) ); return analysis; } ``` ### Example 3: Document Q&A with RAG and Tool Calling ```typescript import { llm, workflow, datasets, schema, AgentConfig } from '@calljmp/agent'; import { z } from 'zod'; const inputSchema = { properties: { query: { title: 'Query', type: 'string', description: 'The query to search for in the documents.', minLength: 1, maxLength: 1000, }, }, required: ['query'], } as const satisfies schema.Schema; export const config: AgentConfig = { name: 'Document retrieval', description: 'A simple document retrieval agent.', forms: { inputSchema }, }; export async function run(input: schema.infer) { const { response } = await workflow.phase('Chat with documents', () => llm.generate({ model: 'openai/gpt-4o', input: [ { role: 'system', content: `You are a document assistant. Answer questions using ONLY the retrieved context. If information is not in the documents, say so clearly.`, }, { role: 'user', content: input.query }, ], tools: [ llm.tool({ name: 'retrieve_documents', description: 'Retrieve relevant documents based on the query.', parameters: z.object({ query: z.string().min(1).max(1000).describe('The search query'), }), execute: ({ query }) => workflow.phase( { name: 'Retrieve documents', scope: { query } }, async () => { const { segments } = await datasets.query(query); return segments .map(seg => `[${seg.source ?? 'Unknown'}]\nRelevance: ${seg.score.toFixed(2)}\n${seg.content}`) .join('\n\n---\n\n'); } ), }), ], temperature: 0.2, }) ); return response; } ``` ### Example 4: Chat Agent with Knowledge Base and Live Events ```typescript import { workflow, llm, datasets, live, memory, AgentConfig } from '@calljmp/agent'; import { z } from 'zod'; export const config: AgentConfig = { name: 'Knowledge Chat', description: 'A chat agent with knowledge base integration.', }; export async function run(input: { message: string }) { return workflow.phase({ name: 'Chat' }, async () => { const { response } = await llm.generate({ input: [ { role: 'system', content: `You are a helpful assistant. Use the knowledge base tool to answer questions.`, }, { role: 'user', content: input.message }, ], memory: memory.short.context('chat:data'), trim: { maxTokens: 5000 }, tools: [ llm.tool({ name: 'queryKnowledgeBase', description: 'Search the knowledge base for information.', parameters: z.object({ query: z.string().describe('The search query'), summary: z.string().describe('Brief description of the action'), }), execute: ({ query, summary }) => workflow.phase({ name: 'Query knowledge base', scope: { query } }, async () => { await live.publish({ type: 'tool.call', data: { summary } }); const { segments } = await datasets.query(query); await live.publish({ type: 'tool.result', data: { count: segments.length } }); return segments.map(s => s.content).join('\n---\n'); }), }), ], }); return response; }); } ``` ### Example 5: Multi-Step Workflow with Retry and Web Scraping ```typescript import { llm, workflow, web, integrations, AgentConfig } from '@calljmp/agent'; import { z } from 'zod'; export const config: AgentConfig = { name: 'Web Analyzer', description: 'Scrapes and analyzes web content', }; export async function run(input: { url: string }) { // Phase 1: Scrape website with retry const content = await workflow.retry( { retries: 3, delay: 2000, backoff: 'exponential' }, workflow.phase({ name: 'Scrape website' }, async () => { const result = await web.scrape({ url: input.url, format: 'text' }); return result.text; }) ); // Phase 2: Analyze content const analysis = await workflow.phase({ name: 'Analyze content' }, async () => { return llm.generate({ input: [ { role: 'system', content: 'Summarize the main points of this content.' }, { role: 'user', content: content }, ], responseSchema: z.object({ summary: z.string(), keyPoints: z.array(z.string()), sentiment: z.enum(['positive', 'negative', 'neutral']), }), }); }); // Phase 3: Notify via Slack await workflow.phase({ name: 'Send notification' }, async () => { await integrations.slack.postMessage({ channel: '#analysis-results', blocks: [ { type: 'header', text: { type: 'plain_text', text: 'Website Analysis Complete' } }, { type: 'section', text: { type: 'mrkdwn', text: `*URL:* ${input.url}` } }, { type: 'section', text: { type: 'mrkdwn', text: `*Summary:*\n${analysis.response.summary}` } }, ], }); }); return analysis.response; } ``` ### Example 6: Human-in-the-Loop with Suspend/Resume ```typescript import { llm, workflow, AgentConfig } from '@calljmp/agent'; import { z } from 'zod'; export const config: AgentConfig = { name: 'Approval Workflow', description: 'Requires human approval for high-risk actions', }; export async function run(input: { action: string; amount: number }) { // Phase 1: Validate request const validation = await workflow.phase({ name: 'Validate' }, async () => { return llm.generate({ input: [ { role: 'system', content: 'Classify if this action is high-risk (amount > 1000 or action contains "delete").' }, { role: 'user', content: JSON.stringify(input) }, ], responseSchema: z.object({ isHighRisk: z.boolean(), reason: z.string(), }), }); }); // Phase 2: Require approval for high-risk actions if (validation.response.isHighRisk) { await workflow.suspend({ reason: `High-risk action detected: ${validation.response.reason}`, timeout: '24 hours', scope: { action: input.action, amount: input.amount }, }); } // Phase 3: Execute action (runs after resume for high-risk) const result = await workflow.phase({ name: 'Execute' }, async () => { return { success: true, action: input.action, amount: input.amount, timestamp: new Date().toISOString(), }; }); return result; } ``` --- ## Glossary | Term | Definition | | ---------------- | ------------------------------------------------------------------------ | | Agent | A serverless AI workflow that processes requests and performs tasks | | Dataset | A collection of documents your agents can search for information | | RAG | Retrieval Augmented Generation - using your data to enhance AI responses | | Phase | A tracked step within an agent's workflow | | Segment | A searchable chunk of text extracted from a document | | LLM | Large Language Model - AI that processes and generates text | | Vault | Secure storage for API keys and configuration variables | | Suspended | Agent paused, waiting for external input before continuing | | Resumption Token | Token required to resume a suspended agent | | Portal | A standalone web application hosting AI agents | --- ## Support - Documentation: https://docs.calljmp.com - GitHub Issues: https://github.com/Calljmp/calljmp-agent/issues - GitHub Discussions: https://github.com/Calljmp/calljmp-agent/discussions - X: @calljmpdev