psadk
v1.5.0
Published
ps adk adapter
Downloads
1,829
Readme
PSADK
ps adk adapter
- samples : sample agents using PSADK
Installation
$ npm install psadkFeatures
- Multi-Provider LLM Support: OpenAI, Anthropic, Google Gemini, AWS Bedrock, Azure OpenAI, and more
- A2A Protocol Compatible: Build agents that work with the Agent-to-Agent protocol
- Local Agent Mode: Run agents fully in-process via
agent-core— no PS API dependency required - Prompt Caching: Reduce costs and latency with built-in prompt caching support (90% cost reduction!)
- Custom Tools: Create and integrate custom tools with your agents
- Type-Safe: Full TypeScript support with comprehensive type definitions
- Authentication: Built-in JWT authentication and token management
Documentation
The classes are documented here Documentation
New: Prompt Caching Guide - Learn how to reduce costs and latency by 90% with prompt caching
New: Local Agent Mode - Run agents in-process without the PS API
Local Agent Mode
PSAgent now supports a local execution mode that runs the agent fully in-process using agent-core, bypassing the PS API entirely.
LLM calls are routed through the PS LLM API — only the agent orchestration loop runs locally.
Enabling Local Mode
Set local: true in your PSAgentConfig and add an llm entry to ServiceUrls pointing at your PS API base URL:
import { PSAgent, PSAgentLogger } from 'psadk';
import type { LocalAgentEvent } from 'psadk';
const agent = new PSAgent(
{
name: 'MyAgent',
description: 'A local agent',
instruction: 'You are a helpful assistant.',
model: 'gpt-4o-mini',
token: process.env.PS_API_TOKEN,
local: true, // ← enable local mode
onEvent: (event: LocalAgentEvent) => {
console.log(event.agentStatus, event.output?.response ?? event.message ?? '');
},
},
new PSAgentLogger({ prefix: 'MyAgent' }),
{
agents: 'https://dev.lionis.ai',
events: 'https://dev.lionis.ai',
llm: 'https://dev.lionis.ai', // ← LLM API base URL for local mode
},
);
const result = await agent.start({ query: 'Hello!' });
console.log(result);Local Agent Configuration
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| local | boolean | false | Run the agent in-process via agent-core instead of the PS API |
| onEvent | (event: LocalAgentEvent) => void | — | Callback invoked for each streamed event during local execution |
| ServiceUrls.llm | string | agents URL | Base URL for the PS LLM API (e.g. https://dev.lionis.ai) |
| llm_config.reasoning_effort | 'minimal' \| 'low' \| 'medium' \| 'high' | — | Controls model reasoning: medium/high → reasoning: true; low/minimal → reasoning: false |
| llm_config.context_window | number | 128000 | Context window size in tokens passed to the local agent runtime |
| llm_config.max_completion_tokens | number | 4096 | Max output tokens for the model |
LocalAgentEvent Shape
interface LocalAgentEvent {
agentStatus: 'pending' | 'in-progress' | 'waiting' | 'completed' | 'failed';
timestamp: number; // Unix timestamp (ms)
tool?: string; // Present when agentStatus === 'waiting'
toolInput?: { request_body: Record<string, any> }; // Tool input arguments
output?: { response: string; [key: string]: any }; // Present on 'completed'
message?: string; // Present on 'failed' or 'in-progress'
}Behaviour Differences in Local Mode
| Operation | Remote mode | Local mode |
|-----------|-------------|------------|
| start() | Submits task to PS API | Runs agent-core loop in-process |
| resume() | Sends tool output to PS API | No-op (tools execute in-process) |
| cancel() | Calls PS API cancel endpoint | Calls agent.abort() on the in-process agent |
| getEvents() | Polls PS events API | Returns in-memory event log snapshot |
Running the Local Agent Example
# Build first
npm run build
# Run the example
PS_API_TOKEN=<token> PS_API_URL=https://dev.lionis.ai npm run local-agent-testSee examples/local-agent-test.ts for a full working example with tools and event streaming.
Agent Core Module
The agent-core module provides a low-level, event-driven agent runtime built on @earendil-works/pi-ai. This module is designed for advanced use cases where you need fine-grained control over agent execution, tool calling, and message handling.
Note: The
agent-coremodule adds additional dependencies including LLM provider SDKs. It's imported separately viapsadk/agent-coreto keep the main package lightweight.
Installation
import { Agent } from 'psadk/agent-core';Key Features
- Stateful Agent Execution: Lifecycle management with states (idle, running, stopped, error)
- Event-Driven Architecture: Hooks for messages, thinking, tool calls, and errors
- Tool Execution: Sequential and parallel tool calling modes
- Message History: Automatic tracking and state management
- LLM Proxy: Server-side routing for LLM calls
- Type Safety: Comprehensive TypeScript types with runtime validation using TypeBox
Basic Example
import { Agent } from 'psadk/agent-core';
const agent = new Agent({
name: 'MyAgent',
model: 'gpt-4',
systemPrompt: 'You are a helpful assistant.',
tools: [
{
name: 'get_weather',
description: 'Get the current weather for a location',
inputSchema: {
type: 'object',
properties: {
location: { type: 'string' },
},
required: ['location'],
},
execute: async ({ location }) => {
// Implementation
return { temperature: 72, conditions: 'sunny' };
},
},
],
onMessage: (msg) => console.log('Agent:', msg.content),
onThinking: (thinking) => console.log('Thinking:', thinking),
onToolCall: (toolCall) => console.log('Tool:', toolCall.name),
onError: (error) => console.error('Error:', error),
});
// Start the agent and send a message
await agent.run("What's the weather in San Francisco?");Agent Configuration
interface AgentConfig {
name: string;
model: string;
systemPrompt?: string;
tools?: Tool[];
parallel?: boolean; // Enable parallel tool execution
maxSteps?: number; // Maximum number of execution steps
onMessage?: (message: Message) => void;
onThinking?: (thinking: string) => void;
onToolCall?: (toolCall: ToolCall) => void;
onToolResult?: (result: ToolResult) => void;
onError?: (error: Error) => void;
onStateChange?: (state: AgentState) => void;
}Advanced: Using the Proxy
The proxy module allows you to route LLM calls through a server:
import { ProxyStream } from 'psadk/agent-core';
const proxy = new ProxyStream({
endpoint: 'https://your-server.com/llm',
apiKey: 'your-api-key',
});
const stream = await proxy.createCompletion({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Hello!' }],
});
for await (const chunk of stream) {
console.log(chunk);
}Agent Lifecycle
The agent follows a state machine:
- idle: Initial state, ready to accept tasks
- running: Actively processing a task
- stopped: Gracefully stopped
- error: Encountered an error
agent.onStateChange((state) => {
console.log(`Agent state changed to: ${state}`);
});
// Stop the agent
await agent.stop();
// Reset to idle state
await agent.reset();Error Handling
const agent = new Agent({
name: 'SafeAgent',
model: 'gpt-4',
onError: (error) => {
console.error('Agent error:', error.message);
// Handle error (e.g., retry, alert, log)
},
});
try {
await agent.run('Process this task');
} catch (error) {
// Errors are also thrown, so you can handle them here
console.error('Task failed:', error);
}Developing with PSADK
A2A Server
hello agent - simple chat agent
clone of https://github.com/a2aproject/a2a-samples/blob/main/samples/js/src/agents/coder/index.ts
import { getServiceToken, PSAgent, PSAgentLogger } from 'psadk';
import dotenv from 'dotenv';
dotenv.config();
async function main() {
const token = await getServiceToken({});
const HelloAgent = new PSAgent({
name: 'HelloAgent',
description: 'A simple hello world agent',
instruction: 'You are a friendly assistant that greets users.',
token,
port: 41241,
log: new PSAgentLogger({ prefix: 'HelloAgent' }),
});
const { app } = HelloAgent.getA2AServer();
app.listen(41241, () => {
console.log('HelloAgent is running on http://localhost:41241');
});
}
main().catch((error) => {
console.error(error);
process.exit(1);
});create a .env file with the appropriate keys
PS_API_URL=https://dev.lionis.ai
PS_APP_KEY=sk_0abb4....
PS_CLIENT_ID=b5cc89b7-....
PS_PROJECT_ID=15a2e....
PS_WORKSPACE_ID=4ffba209-xxx-xxx-xxx-....
PS_SVC_CLIENT_ID=0483....
PS_SVC_CLIENT_SECRET=gfjt678...
PS_SVC_APP_KEY=sk_5f2967....
DEBUG=*
movie info agent - example with custom tools
clone of https://github.com/a2aproject/a2a-samples/blob/main/samples/js/src/agents/movie-agent/index.ts
import { PSAI, PSAgent, PSAgentLogger } from 'psadk';
import dotenv from 'dotenv';
import { movie_search_tool, movie_searchpeople_tool } from './tmdb_tools.js';
import { prompt } from './movie_agent_prompts.js';
dotenv.config();
async function main() {
// const token = await PSAI.getServiceToken({});
const token = (await PSAI.getAuthToken()).accessToken;
const instruction = prompt.replace('{{now}}', new Date().toLocaleString());
const MovieAgent = new PSAgent({
name: 'MovieAgent',
description: 'An agent that can answer questions about movies using TMDB.',
instruction,
model: 'gpt-4.1-mini',
token,
port: 41241,
log: new PSAgentLogger({ prefix: 'MovieAgent' }),
tools: [movie_search_tool, movie_searchpeople_tool],
});
const { app, port } = MovieAgent.getA2AServer();
app.listen(port, () => {
console.log(`MovieAgent is running on http://localhost:${port}`);
});
}
main().catch((error) => {
console.error(error);
process.exit(1);
});Use can use a2a inspector ui just enter the agent card url http://localhost:41241/.well-known/agent-card.json to interact with the agent
Custom tool
import { PSAgentTool } from 'psadk';
import { z } from 'zod/v4';
export const movie_search_tool = new PSAgentTool({
name: 'search_movies',
description: 'search TMDB for movies by title',
type: 'API',
local_execution: true,
hitl: true,
inputschema: {
title: `search_movies_inputs.`,
description: 'Inputs for search_movies tool.',
url: 'http://localhost/search_movies',
method: 'POST',
requestBody: z.toJSONSchema(
z
.object({
query: z.string().meta({
title: 'query',
description: 'The search query string for movies.',
}),
})
.meta({
title: 'search_movies_request_body',
description:
'The request body parameters for the search_movies tool.',
}),
{ io: 'input' },
),
},
runCallback: async ({ query }) => {
console.log('[tmdb:searchMovies]', JSON.stringify(query));
try {
const data = await callTmdbApi('movie', query);
// Only modify image paths to be full URLs
const results = data.results.map((movie: any) => {
if (movie.poster_path) {
movie.poster_path = `https://image.tmdb.org/t/p/w500${movie.poster_path}`;
}
if (movie.backdrop_path) {
movie.backdrop_path = `https://image.tmdb.org/t/p/w500${movie.backdrop_path}`;
}
return movie;
});
return {
status: 'success',
output: {
...data,
results,
},
};
} catch (error) {
console.error('Error searching movies:', error);
// Re-throwing allows the caller to handle it appropriately
throw error;
}
},
});local agent - run agent in-process with event streaming
import { PSAgent, PSAgentLogger } from 'psadk';
import type { LocalAgentEvent } from 'psadk';
import dotenv from 'dotenv';
dotenv.config();
async function main() {
const token = process.env.PS_API_TOKEN!;
const baseUrl = process.env.PS_API_URL || 'https://dev.lionis.ai';
const agent = new PSAgent(
{
name: 'LocalHelloAgent',
description: 'A simple agent running in local mode',
instruction: 'You are a friendly assistant that greets users.',
model: 'gpt-4o-mini',
token,
local: true, // run agent-core in-process
llm_config: {
reasoning_effort: 'high', // 'medium'|'high' → reasoning: true; 'low'|'minimal' → reasoning: false
context_window: 128000, // token context window (default: 128000)
max_completion_tokens: 4096, // max output tokens (default: 4096)
},
onEvent: (event: LocalAgentEvent) => {
switch (event.agentStatus) {
case 'pending':
console.log('⏳ Agent starting...');
break;
case 'in-progress':
console.log('🔄 Agent thinking...');
break;
case 'waiting':
console.log(`🔧 Calling tool: ${event.tool}`);
console.log(' Input:', JSON.stringify(event.toolInput?.request_body));
break;
case 'completed':
console.log('✅ Done:', event.output?.response);
break;
case 'failed':
console.error('❌ Failed:', event.message);
break;
}
},
},
new PSAgentLogger({ prefix: 'LocalHelloAgent' }),
{
agents: baseUrl,
events: baseUrl,
llm: baseUrl, // LLM calls route through PS LLM API
},
);
const result = await agent.start({ query: 'Say hello!' });
console.log('Result:', result);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});Client: Sending a Message
The A2AClient makes it easy to communicate with any A2A-compliant agent.
// client.ts
import { A2AClient, SendMessageSuccessResponse } from '@a2a-js/sdk/client';
import { Message, MessageSendParams } from '@a2a-js/sdk';
import { v4 as uuidv4 } from 'uuid';
async function run() {
// Create a client pointing to the agent's Agent Card URL.
const client = await A2AClient.fromCardUrl(
'http://localhost:41241/.well-known/agent-card.json',
);
const sendParams: MessageSendParams = {
message: {
messageId: uuidv4(),
role: 'user',
parts: [{ kind: 'text', text: 'Hi there!' }],
kind: 'message',
},
};
const response = await client.sendMessage(sendParams);
if ('error' in response) {
console.error('Error:', response.error.message);
} else {
const result = (response as SendMessageSuccessResponse).result as Message;
console.log('Agent response:', result.parts[0].text); // "Hello, world!"
}
}
await run();