From b1f85108e44a6b97ccd9ae4ec50c02deb62819e0 Mon Sep 17 00:00:00 2001 From: Gagan Singh Date: Mon, 16 Mar 2026 09:02:21 -0700 Subject: [PATCH 01/10] added mastra support --- src/mastra.ts | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/mastra.ts diff --git a/src/mastra.ts b/src/mastra.ts new file mode 100644 index 0000000..dd1056f --- /dev/null +++ b/src/mastra.ts @@ -0,0 +1,117 @@ +/** + * Mastra integration for the LLP SDK. + * + * Import via the sub-path export: + * import { wrapWithLLPAnnotation, type LLPMastraContext } from 'llpsdk/mastra'; + * + * Does not require @mastra/core as a dependency — uses duck typing for RuntimeContext. + * + * Usage: + * + * ```ts + * import { wrapWithLLPAnnotation, type LLPMastraContext } from 'llpsdk/mastra'; + * import { RuntimeContext } from '@mastra/core/runtime-context'; + * import { createTool } from '@mastra/core/tools'; + * + * const myTool = createTool({ + * id: 'my_tool', + * inputSchema: z.object({ query: z.string() }), + * execute: wrapWithLLPAnnotation('my_tool', async ({ context }) => { + * return await doSomething(context.query); + * }), + * }); + * + * // In handleMessage: + * const runtimeContext = new RuntimeContext(); + * runtimeContext.set('llpMessage', message); + * runtimeContext.set('llpAnnotater', annotater); + * const result = await agent.generate(prompt, { runtimeContext }); + * ``` + */ + +import type { Annotater } from './annotate.js'; +import type { TextMessage } from './message.js'; + +/** + * The runtime context type for LLP-connected Mastra agents. + * + * Use as the type parameter for RuntimeContext: + * new RuntimeContext() + */ +export type LLPMastraContext = { + llpMessage: TextMessage; + llpAnnotater: Annotater; +}; + +/** + * Minimal interface that RuntimeContext satisfies. + * Used internally so this module does not depend on @mastra/core. + */ +interface LLPMastraRuntimeContextLike { + get(key: 'llpMessage'): TextMessage; + get(key: 'llpAnnotater'): Annotater; +} + +export interface LLPAnnotationOptions { + /** Override how tool input is serialized for the annotation. Default: JSON.stringify. */ + serializeInput?: (value: unknown) => string; + /** Override how tool output is serialized for the annotation. Default: JSON.stringify. */ + serializeOutput?: (value: unknown) => string; + /** Called when annotation fails. Default: console.warn. Never throws. */ + onAnnotationError?: (error: unknown) => void; +} + +function serialize(value: unknown): string { + try { + return typeof value === 'string' ? value : JSON.stringify(value); + } catch { + return String(value); + } +} + +/** + * Wraps a Mastra tool's execute function with LLP tool-call annotation. + * + * Handles timing, success annotation, exception annotation, and annotation + * error swallowing — the Mastra equivalent of createLLPToolMiddleware() for + * LangChain. + * + * The returned function is compatible with Mastra's createTool execute signature. + * The tool's runtimeContext must be a RuntimeContext. + */ +export function wrapWithLLPAnnotation( + toolId: string, + execute: (args: { context: TInput }) => Promise, + options: LLPAnnotationOptions = {}, +): (args: { context: TInput; runtimeContext: LLPMastraRuntimeContextLike }) => Promise { + const serializeInput = options.serializeInput ?? serialize; + const serializeOutput = options.serializeOutput ?? serialize; + const onAnnotationError = + options.onAnnotationError ?? + ((error: unknown) => console.warn('[LLP] Failed to annotate tool call:', error)); + + return async ({ context, runtimeContext }) => { + const startMs = Date.now(); + const llpMessage = runtimeContext.get('llpMessage'); + const llpAnnotater = runtimeContext.get('llpAnnotater'); + const params = serializeInput(context); + + try { + const result = await execute({ context }); + llpAnnotater + .annotateToolCall( + llpMessage.toolCall(toolId, params, serializeOutput(result), Date.now() - startMs), + ) + .catch(onAnnotationError); + return result; + } catch (err) { + const e = err instanceof Error ? err : new Error(String(err)); + llpAnnotater + .annotateToolCall( + llpMessage.toolCallException(toolId, params, e, Date.now() - startMs), + ) + .catch(onAnnotationError); + throw err; + } + }; +} From 7240b2f29b129a0835510f0913b455182d2de2f8 Mon Sep 17 00:00:00 2001 From: Gagan Singh Date: Mon, 16 Mar 2026 09:03:29 -0700 Subject: [PATCH 02/10] updated package.json for mastra --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index e5c0ef3..e4ba8a3 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,10 @@ "./langchain": { "import": "./dist/langchain.js", "types": "./dist/langchain.d.ts" + }, + "./mastra": { + "import": "./dist/mastra.js", + "types": "./dist/mastra.d.ts" } }, "peerDependencies": { From 98e951ae2377b9c5977c327ed8c268d19fdc41e7 Mon Sep 17 00:00:00 2001 From: Gagan Singh Date: Mon, 16 Mar 2026 09:40:38 -0700 Subject: [PATCH 03/10] fixed tests and formatting issue --- src/mastra.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mastra.ts b/src/mastra.ts index dd1056f..2e66c5a 100644 --- a/src/mastra.ts +++ b/src/mastra.ts @@ -107,9 +107,7 @@ export function wrapWithLLPAnnotation( } catch (err) { const e = err instanceof Error ? err : new Error(String(err)); llpAnnotater - .annotateToolCall( - llpMessage.toolCallException(toolId, params, e, Date.now() - startMs), - ) + .annotateToolCall(llpMessage.toolCallException(toolId, params, e, Date.now() - startMs)) .catch(onAnnotationError); throw err; } From b874323f9c529bbcdaa2e958ffb30d64c1a652ff Mon Sep 17 00:00:00 2001 From: Gagan Singh Date: Tue, 24 Mar 2026 01:09:43 -0700 Subject: [PATCH 04/10] llp javascript sdk update for mastra 1.15 support --- src/mastra.ts | 75 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/src/mastra.ts b/src/mastra.ts index 2e66c5a..4ef456e 100644 --- a/src/mastra.ts +++ b/src/mastra.ts @@ -4,28 +4,28 @@ * Import via the sub-path export: * import { wrapWithLLPAnnotation, type LLPMastraContext } from 'llpsdk/mastra'; * - * Does not require @mastra/core as a dependency — uses duck typing for RuntimeContext. + * Does not require @mastra/core as a dependency — uses duck typing for RequestContext. * - * Usage: + * Usage (Mastra v1.x): * * ```ts * import { wrapWithLLPAnnotation, type LLPMastraContext } from 'llpsdk/mastra'; - * import { RuntimeContext } from '@mastra/core/runtime-context'; + * import { RequestContext } from '@mastra/core/request-context'; * import { createTool } from '@mastra/core/tools'; * * const myTool = createTool({ * id: 'my_tool', * inputSchema: z.object({ query: z.string() }), - * execute: wrapWithLLPAnnotation('my_tool', async ({ context }) => { - * return await doSomething(context.query); + * execute: wrapWithLLPAnnotation('my_tool', async (inputData) => { + * return await doSomething(inputData.query); * }), * }); * * // In handleMessage: - * const runtimeContext = new RuntimeContext(); - * runtimeContext.set('llpMessage', message); - * runtimeContext.set('llpAnnotater', annotater); - * const result = await agent.generate(prompt, { runtimeContext }); + * const requestContext = new RequestContext(); + * requestContext.set('llpMessage', message); + * requestContext.set('llpAnnotater', annotater); + * const result = await agent.generate(prompt, { requestContext }); * ``` */ @@ -35,8 +35,8 @@ import type { TextMessage } from './message.js'; /** * The runtime context type for LLP-connected Mastra agents. * - * Use as the type parameter for RuntimeContext: - * new RuntimeContext() + * Use as the type parameter for RequestContext: + * new RequestContext() */ export type LLPMastraContext = { llpMessage: TextMessage; @@ -44,14 +44,22 @@ export type LLPMastraContext = { }; /** - * Minimal interface that RuntimeContext satisfies. + * Minimal interface that RequestContext satisfies. * Used internally so this module does not depend on @mastra/core. */ -interface LLPMastraRuntimeContextLike { +interface LLPMastraRequestContextLike { get(key: 'llpMessage'): TextMessage; get(key: 'llpAnnotater'): Annotater; } +/** + * Minimal duck-type for Mastra v1.x ToolExecutionContext. + * Only requires the requestContext field we actually use. + */ +interface MastraToolExecutionContextLike { + requestContext?: LLPMastraRequestContextLike; +} + export interface LLPAnnotationOptions { /** Override how tool input is serialized for the annotation. Default: JSON.stringify. */ serializeInput?: (value: unknown) => string; @@ -76,39 +84,44 @@ function serialize(value: unknown): string { * error swallowing — the Mastra equivalent of createLLPToolMiddleware() for * LangChain. * - * The returned function is compatible with Mastra's createTool execute signature. - * The tool's runtimeContext must be a RuntimeContext. + * Compatible with Mastra v1.x createTool execute signature: (inputData, context) => Promise + * The tool's requestContext must be a RequestContext. */ export function wrapWithLLPAnnotation( toolId: string, - execute: (args: { context: TInput }) => Promise, + execute: (inputData: TInput) => Promise, options: LLPAnnotationOptions = {}, -): (args: { context: TInput; runtimeContext: LLPMastraRuntimeContextLike }) => Promise { +): (inputData: TInput, context: MastraToolExecutionContextLike) => Promise { const serializeInput = options.serializeInput ?? serialize; const serializeOutput = options.serializeOutput ?? serialize; const onAnnotationError = options.onAnnotationError ?? ((error: unknown) => console.warn('[LLP] Failed to annotate tool call:', error)); - return async ({ context, runtimeContext }) => { + return async (inputData, context) => { const startMs = Date.now(); - const llpMessage = runtimeContext.get('llpMessage'); - const llpAnnotater = runtimeContext.get('llpAnnotater'); - const params = serializeInput(context); + const requestContext = context.requestContext; + const llpMessage = requestContext?.get('llpMessage'); + const llpAnnotater = requestContext?.get('llpAnnotater'); + const params = serializeInput(inputData); try { - const result = await execute({ context }); - llpAnnotater - .annotateToolCall( - llpMessage.toolCall(toolId, params, serializeOutput(result), Date.now() - startMs), - ) - .catch(onAnnotationError); + const result = await execute(inputData); + if (llpMessage && llpAnnotater) { + llpAnnotater + .annotateToolCall( + llpMessage.toolCall(toolId, params, serializeOutput(result), Date.now() - startMs), + ) + .catch(onAnnotationError); + } return result; } catch (err) { - const e = err instanceof Error ? err : new Error(String(err)); - llpAnnotater - .annotateToolCall(llpMessage.toolCallException(toolId, params, e, Date.now() - startMs)) - .catch(onAnnotationError); + if (llpMessage && llpAnnotater) { + const e = err instanceof Error ? err : new Error(String(err)); + llpAnnotater + .annotateToolCall(llpMessage.toolCallException(toolId, params, e, Date.now() - startMs)) + .catch(onAnnotationError); + } throw err; } }; From c2ddd7e3d88437e15a69c6100f48e2a731a13547 Mon Sep 17 00:00:00 2001 From: Gagan Singh Date: Tue, 24 Mar 2026 11:51:03 -0700 Subject: [PATCH 05/10] example agents similar to llpsdk-python --- README.md | 5 + examples/mastra-weather-agent/README.md | 34 ++ examples/mastra-weather-agent/package.json | 20 ++ examples/mastra-weather-agent/tsconfig.json | 12 + .../mastra-weather-agent/weather-agent.ts | 290 ++++++++++++++++++ examples/simple-agent.ts | 27 -- examples/simple-agent/README.md | 17 + examples/simple-agent/simple-agent.ts | 40 +++ package.json | 3 +- 9 files changed, 419 insertions(+), 29 deletions(-) create mode 100644 examples/mastra-weather-agent/README.md create mode 100644 examples/mastra-weather-agent/package.json create mode 100644 examples/mastra-weather-agent/tsconfig.json create mode 100644 examples/mastra-weather-agent/weather-agent.ts delete mode 100644 examples/simple-agent.ts create mode 100644 examples/simple-agent/README.md create mode 100644 examples/simple-agent/simple-agent.ts diff --git a/README.md b/README.md index 0692c47..49959cb 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,11 @@ async function main() { main(); ``` +## Examples + +- `examples/simple-agent` is a standalone example package containing the literal Python `simple_agent.py` port +- `examples/mastra-weather-agent` is a standalone Mastra example package with its own `package.json` + ## LangChain Tool Call Capture If your LangChain agent uses tools, the SDK can automatically annotate those diff --git a/examples/mastra-weather-agent/README.md b/examples/mastra-weather-agent/README.md new file mode 100644 index 0000000..2c4f27f --- /dev/null +++ b/examples/mastra-weather-agent/README.md @@ -0,0 +1,34 @@ +# Weather Agent Example + +Mastra-based LLP weather agent example. + +This example: + +- uses `@mastra/core` for the agent runtime +- uses `llpsdk/mastra` to annotate tool calls back to LLP +- answers weather questions for a fixed set of cities +- declines unsupported cities and non-weather questions + +## Setup + +```bash +cd /Users/gagansingh/code/llpsdk/llp-javascript/examples/mastra-weather-agent +npm install +``` + +## Environment + +Create a `.env` file in this directory: + +```bash +LLP_URL=wss://llphq.com/agent/websocket +LLP_API_KEY=your-api-key +MODEL_NAME=ollama/llama3.1 +AGENT_NAME=weather-agent-mastra +``` + +## Run + +```bash +npm start +``` diff --git a/examples/mastra-weather-agent/package.json b/examples/mastra-weather-agent/package.json new file mode 100644 index 0000000..d9ee4cc --- /dev/null +++ b/examples/mastra-weather-agent/package.json @@ -0,0 +1,20 @@ +{ + "name": "mastra-weather-agent", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "start": "tsx weather-agent.ts" + }, + "dependencies": { + "@mastra/core": "^1.15.0", + "dotenv": "^17.2.3", + "llpsdk": "file:../..", + "zod": "^3.25.76" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "tsx": "^4.19.0", + "typescript": "^5.6.0" + } +} diff --git a/examples/mastra-weather-agent/tsconfig.json b/examples/mastra-weather-agent/tsconfig.json new file mode 100644 index 0000000..f73119b --- /dev/null +++ b/examples/mastra-weather-agent/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["weather-agent.ts"] +} diff --git a/examples/mastra-weather-agent/weather-agent.ts b/examples/mastra-weather-agent/weather-agent.ts new file mode 100644 index 0000000..9c7c7f1 --- /dev/null +++ b/examples/mastra-weather-agent/weather-agent.ts @@ -0,0 +1,290 @@ +import { dirname, join } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { Agent } from '@mastra/core/agent'; +import { RequestContext } from '@mastra/core/request-context'; +import { createTool } from '@mastra/core/tools'; +import { config } from 'dotenv'; +import { type Annotater, LLPClient, type LLPClientConfig, type TextMessage } from 'llpsdk'; +import { type LLPMastraContext, wrapWithLLPAnnotation } from 'llpsdk/mastra'; +import * as z from 'zod'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +config({ path: join(__dirname, '.env') }); + +const SYSTEM_PROMPT = ` +You are a weather assistant that helps with current weather questions. +You MUST return ONLY valid JSON. + +Return JSON matching ONE of these shapes: + +For weather questions: +{ + "type": "forecast", + "city": "supported city name", + "summary": "short answer to the user's question", + "conditions": "brief conditions", + "temperature_f": 72, + "advisory": "short practical advice" +} + +For capability questions: +{ + "type": "capabilities" +} + +For unsupported or out-of-domain questions: +{ + "type": "decline", + "reason": "brief explanation" +} + +Important rules: +1. Use the get_weather tool for every weather question about a supported city. +2. Supported cities are New York, Los Angeles, Chicago, Miami, Seattle, Denver, San Francisco, Austin, Boston, and London. +3. If the city is unsupported, return type "decline" and mention the supported cities. +4. If the question is not about weather, return type "decline". +5. Keep summaries concise and practical. +`; + +const supportedCities = [ + 'New York', + 'Los Angeles', + 'Chicago', + 'Miami', + 'Seattle', + 'Denver', + 'San Francisco', + 'Austin', + 'Boston', + 'London', +] as const; + +const weatherData: Record< + (typeof supportedCities)[number], + { conditions: string; temperature_f: number; advisory: string } +> = { + 'New York': { + conditions: 'Partly cloudy with a steady northwest breeze', + temperature_f: 45, + advisory: 'A light jacket is a good idea.', + }, + 'Los Angeles': { + conditions: 'Sunny and mild', + temperature_f: 72, + advisory: 'Comfortable weather for being outside.', + }, + Chicago: { + conditions: 'Overcast, windy, with a chance of snow', + temperature_f: 30, + advisory: 'Dress warmly and plan for wind.', + }, + Miami: { + conditions: 'Humid and warm', + temperature_f: 82, + advisory: 'Stay hydrated if you are outdoors.', + }, + Seattle: { + conditions: 'Rainy with a cool west wind', + temperature_f: 48, + advisory: 'Bring a rain jacket or umbrella.', + }, + Denver: { + conditions: 'Clear skies with a crisp breeze', + temperature_f: 38, + advisory: 'Layers will help with the cooler air.', + }, + 'San Francisco': { + conditions: 'Foggy and breezy', + temperature_f: 58, + advisory: 'A light outer layer will be useful.', + }, + Austin: { + conditions: 'Sunny and pleasant', + temperature_f: 68, + advisory: 'Good conditions for outdoor plans.', + }, + Boston: { + conditions: 'Cold and windy', + temperature_f: 35, + advisory: 'Bundle up before heading out.', + }, + London: { + conditions: 'Light drizzle with cool air', + temperature_f: 50, + advisory: 'Carry an umbrella.', + }, +}; + +const weatherToolResponseSchema = z.object({ + city: z.enum(supportedCities), + conditions: z.string(), + temperature_f: z.number(), + advisory: z.string(), +}); + +const weatherResponseSchema = z.union([ + z.object({ + type: z.literal('forecast'), + city: z.enum(supportedCities), + summary: z.string(), + conditions: z.string(), + temperature_f: z.number(), + advisory: z.string(), + }), + z.object({ + type: z.literal('capabilities'), + }), + z.object({ + type: z.literal('decline'), + reason: z.string(), + }), +]); + +type WeatherToolResponse = z.infer; +type WeatherResponse = z.infer; +type WeatherAgent = ReturnType; + +const getWeatherTool = createTool({ + id: 'get_weather', + description: 'Get the current weather for a supported city.', + inputSchema: z.object({ + city: z.enum(supportedCities), + }), + execute: wrapWithLLPAnnotation<{ city: (typeof supportedCities)[number] }, WeatherToolResponse>( + 'get_weather', + async (inputData) => { + const weather = weatherData[inputData.city]; + return { + city: inputData.city, + conditions: weather.conditions, + temperature_f: weather.temperature_f, + advisory: weather.advisory, + }; + }, + ), +}); + +function createWeatherAgent(model: string) { + return new Agent({ + id: 'mastra-weather-agent', + name: 'mastra-weather-agent', + instructions: SYSTEM_PROMPT, + model, + tools: { get_weather: getWeatherTool }, + }); +} + +function buildPrompt(message: TextMessage): string { + return `${message.prompt} + +Return ONLY valid JSON matching the required schema.`; +} + +function extractJson(text: string): string { + const codeBlock = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/); + if (codeBlock) { + return codeBlock[1].trim(); + } + return text.trim(); +} + +function parseResponse(text: string): WeatherResponse { + return weatherResponseSchema.parse(JSON.parse(extractJson(text)) as unknown); +} + +function formatCapabilities(): string { + return `I can help with current weather questions for these cities: ${supportedCities.join(', ')}.`; +} + +function formatForecast(response: Extract): string { + return `${response.city}: ${response.summary} + +Conditions: ${response.conditions} +Temperature: ${response.temperature_f}°F +Advice: ${response.advisory}`; +} + +async function handleMessage( + agent: WeatherAgent, + message: TextMessage, + annotater: Annotater, +): Promise { + const requestContext = new RequestContext(); + requestContext.set('llpMessage', message); + requestContext.set('llpAnnotater', annotater); + + try { + const result = await agent.generate(buildPrompt(message), { requestContext }); + const response = parseResponse(result.text); + + if (response.type === 'capabilities') { + return formatCapabilities(); + } + + if (response.type === 'decline') { + return response.reason; + } + + return formatForecast(response); + } catch (error) { + console.error('Agent execution failed:', error); + return "I'm sorry, I hit an error while handling that weather request."; + } +} + +async function main(): Promise { + const agentName = process.env.AGENT_NAME ?? 'mastra-weather-agent'; + const apiKey = process.env.LLP_API_KEY ?? ''; + const model = process.env.MODEL_NAME ?? 'ollama/llama3.1'; + + if (!process.env.LLP_URL) { + throw new Error('LLP_URL env var is not defined'); + } + + if (!apiKey) { + throw new Error('LLP_API_KEY env var is not defined'); + } + + const llpConfig: LLPClientConfig = { + url: process.env.LLP_URL, + responseTimeout: 600_000, + }; + + const client = new LLPClient(agentName, apiKey, llpConfig) + .onStart(() => createWeatherAgent(model)) + .onMessage(async (agent, msg, annotater) => { + const response = await handleMessage(agent, msg, annotater); + return msg.reply(response); + }) + .onStop(() => { + console.log('session ended'); + }); + + const shutdown = async () => { + console.log('\nShutting down...'); + await client.close(); + console.log('Disconnected'); + process.exit(0); + }; + + process.on('SIGINT', shutdown); + process.on('SIGTERM', shutdown); + + try { + console.log(`Mastra weather agent initialized model=${model}`); + await client.connect(); + console.log('Connected to platform'); + await new Promise(() => {}); + } catch (error) { + console.error('Fatal error:', error); + process.exit(1); + } +} + +const isMainModule = + process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href; + +if (isMainModule) { + void main(); +} diff --git a/examples/simple-agent.ts b/examples/simple-agent.ts deleted file mode 100644 index d9830a4..0000000 --- a/examples/simple-agent.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { config } from 'dotenv'; -import { LLPClient } from '../src/index.js'; - -async function main() { - config(); - const client = new LLPClient('simple-agent', process.env.LLP_API_KEY || ''); - - // Register handlers - client.onMessage(async (_session, msg) => { - // process msg.prompt with your agent - return msg.reply('hello from TypeScript!'); - }); - - try { - await client.connect(); - console.log(`Connected! Session: ${client.getSessionId()}`); - - // Keep running - await new Promise(() => {}); // Wait forever - } catch (err) { - console.error('Error:', err); - } finally { - await client.close(); - } -} - -main(); diff --git a/examples/simple-agent/README.md b/examples/simple-agent/README.md new file mode 100644 index 0000000..8145254 --- /dev/null +++ b/examples/simple-agent/README.md @@ -0,0 +1,17 @@ +# Simple Agent Example + +This is a literal TypeScript port of the Python `simple_agent.py` example. + +It: + +- connects to LLP +- annotates a hardcoded `get_weather` tool call for Seattle +- replies with the fixed string `this is my response` + +## Run + +```bash +LLP_URL=wss://llphq.com/agent/websocket \ +LLP_API_KEY=your-api-key \ +npm run example +``` diff --git a/examples/simple-agent/simple-agent.ts b/examples/simple-agent/simple-agent.ts new file mode 100644 index 0000000..fda55ec --- /dev/null +++ b/examples/simple-agent/simple-agent.ts @@ -0,0 +1,40 @@ +import { config } from 'dotenv'; +import { LLPClient, type LLPSession, type TextMessage } from '../../src/index.js'; + +async function main() { + config(); + + const platformUrl = process.env.LLP_URL; + const apiKey = process.env.LLP_API_KEY; + + if (!platformUrl) { + throw new Error('LLP_URL env var is not defined'); + } + + if (!apiKey) { + throw new Error('LLP_API_KEY env var is not defined'); + } + + const client = new LLPClient('simple-agent', apiKey, { url: platformUrl }); + + client.onMessage(async (session: LLPSession, msg: TextMessage) => { + const toolCall = msg.toolCall('get_weather', '{"city":"Seattle"}', 'rainy', 1_000); + await session.annotateToolCall(toolCall); + return msg.reply('this is my response'); + }); + + try { + console.log('Connecting to server...'); + await client.connect(); + console.log(`Connected! Session: ${client.getSessionId()}`); + console.log('Agent running. Press Ctrl+C to exit...'); + await new Promise(() => {}); + } catch (error) { + console.error('Error:', error); + } finally { + await client.close(); + console.log('Disconnected.'); + } +} + +main(); diff --git a/package.json b/package.json index e4ba8a3..6311dee 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,7 @@ "lint": "biome check .", "format": "biome format --write .", "formatcheck": "biome format .", - "typecheck": "tsc --noEmit", - "example": "tsx examples/simple-agent.ts" + "typecheck": "tsc --noEmit" }, "dependencies": { "ws": "^8.18.0", From 12d735dd7571cc2528b81cb09e765aaa4d8d8725 Mon Sep 17 00:00:00 2001 From: Gagan Singh Date: Tue, 24 Mar 2026 22:51:37 -0700 Subject: [PATCH 06/10] clean up and update to mastra agent --- .../mastra-weather-agent/weather-agent.ts | 83 +++---------------- 1 file changed, 13 insertions(+), 70 deletions(-) diff --git a/examples/mastra-weather-agent/weather-agent.ts b/examples/mastra-weather-agent/weather-agent.ts index 9c7c7f1..b6470e8 100644 --- a/examples/mastra-weather-agent/weather-agent.ts +++ b/examples/mastra-weather-agent/weather-agent.ts @@ -1,5 +1,5 @@ import { dirname, join } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { fileURLToPath } from 'node:url'; import { Agent } from '@mastra/core/agent'; import { RequestContext } from '@mastra/core/request-context'; import { createTool } from '@mastra/core/tools'; @@ -13,37 +13,13 @@ const __dirname = dirname(__filename); config({ path: join(__dirname, '.env') }); const SYSTEM_PROMPT = ` -You are a weather assistant that helps with current weather questions. -You MUST return ONLY valid JSON. - -Return JSON matching ONE of these shapes: - -For weather questions: -{ - "type": "forecast", - "city": "supported city name", - "summary": "short answer to the user's question", - "conditions": "brief conditions", - "temperature_f": 72, - "advisory": "short practical advice" -} - -For capability questions: -{ - "type": "capabilities" -} - -For unsupported or out-of-domain questions: -{ - "type": "decline", - "reason": "brief explanation" -} +You are a helpful meteorologist that gives succinct responses regarding the weather for various American cities. Important rules: 1. Use the get_weather tool for every weather question about a supported city. 2. Supported cities are New York, Los Angeles, Chicago, Miami, Seattle, Denver, San Francisco, Austin, Boston, and London. -3. If the city is unsupported, return type "decline" and mention the supported cities. -4. If the question is not about weather, return type "decline". +3. If the city is unsupported, decline and mention the supported cities. +4. If the question is not about weather, decline. 5. Keep summaries concise and practical. `; @@ -116,13 +92,6 @@ const weatherData: Record< }, }; -const weatherToolResponseSchema = z.object({ - city: z.enum(supportedCities), - conditions: z.string(), - temperature_f: z.number(), - advisory: z.string(), -}); - const weatherResponseSchema = z.union([ z.object({ type: z.literal('forecast'), @@ -141,8 +110,8 @@ const weatherResponseSchema = z.union([ }), ]); -type WeatherToolResponse = z.infer; type WeatherResponse = z.infer; +type WeatherToolResult = { city: string; conditions: string; temperature_f: number; advisory: string }; type WeatherAgent = ReturnType; const getWeatherTool = createTool({ @@ -151,16 +120,10 @@ const getWeatherTool = createTool({ inputSchema: z.object({ city: z.enum(supportedCities), }), - execute: wrapWithLLPAnnotation<{ city: (typeof supportedCities)[number] }, WeatherToolResponse>( + execute: wrapWithLLPAnnotation<{ city: (typeof supportedCities)[number] }, WeatherToolResult>( 'get_weather', async (inputData) => { - const weather = weatherData[inputData.city]; - return { - city: inputData.city, - conditions: weather.conditions, - temperature_f: weather.temperature_f, - advisory: weather.advisory, - }; + return { city: inputData.city, ...weatherData[inputData.city] }; }, ), }); @@ -175,24 +138,6 @@ function createWeatherAgent(model: string) { }); } -function buildPrompt(message: TextMessage): string { - return `${message.prompt} - -Return ONLY valid JSON matching the required schema.`; -} - -function extractJson(text: string): string { - const codeBlock = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/); - if (codeBlock) { - return codeBlock[1].trim(); - } - return text.trim(); -} - -function parseResponse(text: string): WeatherResponse { - return weatherResponseSchema.parse(JSON.parse(extractJson(text)) as unknown); -} - function formatCapabilities(): string { return `I can help with current weather questions for these cities: ${supportedCities.join(', ')}.`; } @@ -215,8 +160,11 @@ async function handleMessage( requestContext.set('llpAnnotater', annotater); try { - const result = await agent.generate(buildPrompt(message), { requestContext }); - const response = parseResponse(result.text); + const result = await agent.generate(message.prompt, { + requestContext, + output: weatherResponseSchema, + }); + const response = result.object as WeatherResponse; if (response.type === 'capabilities') { return formatCapabilities(); @@ -282,9 +230,4 @@ async function main(): Promise { } } -const isMainModule = - process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href; - -if (isMainModule) { - void main(); -} +void main(); From 6463cf1a6985550ca21f1d1fad818e8243ccbb80 Mon Sep 17 00:00:00 2001 From: Gagan Singh Date: Tue, 24 Mar 2026 22:53:53 -0700 Subject: [PATCH 07/10] make format for mastra agent --- examples/mastra-weather-agent/weather-agent.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/mastra-weather-agent/weather-agent.ts b/examples/mastra-weather-agent/weather-agent.ts index b6470e8..ded3b78 100644 --- a/examples/mastra-weather-agent/weather-agent.ts +++ b/examples/mastra-weather-agent/weather-agent.ts @@ -111,7 +111,12 @@ const weatherResponseSchema = z.union([ ]); type WeatherResponse = z.infer; -type WeatherToolResult = { city: string; conditions: string; temperature_f: number; advisory: string }; +type WeatherToolResult = { + city: string; + conditions: string; + temperature_f: number; + advisory: string; +}; type WeatherAgent = ReturnType; const getWeatherTool = createTool({ From 36d1e8908b2be182cf2e7376ba1b8c5b149e63a2 Mon Sep 17 00:00:00 2001 From: Michael Slezak Date: Wed, 25 Mar 2026 00:05:06 -0600 Subject: [PATCH 08/10] make the mastra agent example more lean --- .../mastra-weather-agent/weather-agent.ts | 57 +------------------ 1 file changed, 1 insertion(+), 56 deletions(-) diff --git a/examples/mastra-weather-agent/weather-agent.ts b/examples/mastra-weather-agent/weather-agent.ts index ded3b78..f88b3a3 100644 --- a/examples/mastra-weather-agent/weather-agent.ts +++ b/examples/mastra-weather-agent/weather-agent.ts @@ -92,25 +92,6 @@ const weatherData: Record< }, }; -const weatherResponseSchema = z.union([ - z.object({ - type: z.literal('forecast'), - city: z.enum(supportedCities), - summary: z.string(), - conditions: z.string(), - temperature_f: z.number(), - advisory: z.string(), - }), - z.object({ - type: z.literal('capabilities'), - }), - z.object({ - type: z.literal('decline'), - reason: z.string(), - }), -]); - -type WeatherResponse = z.infer; type WeatherToolResult = { city: string; conditions: string; @@ -143,18 +124,6 @@ function createWeatherAgent(model: string) { }); } -function formatCapabilities(): string { - return `I can help with current weather questions for these cities: ${supportedCities.join(', ')}.`; -} - -function formatForecast(response: Extract): string { - return `${response.city}: ${response.summary} - -Conditions: ${response.conditions} -Temperature: ${response.temperature_f}°F -Advice: ${response.advisory}`; -} - async function handleMessage( agent: WeatherAgent, message: TextMessage, @@ -165,21 +134,7 @@ async function handleMessage( requestContext.set('llpAnnotater', annotater); try { - const result = await agent.generate(message.prompt, { - requestContext, - output: weatherResponseSchema, - }); - const response = result.object as WeatherResponse; - - if (response.type === 'capabilities') { - return formatCapabilities(); - } - - if (response.type === 'decline') { - return response.reason; - } - - return formatForecast(response); + return await agent.generate(message.prompt, { requestContext }); } catch (error) { console.error('Agent execution failed:', error); return "I'm sorry, I hit an error while handling that weather request."; @@ -214,16 +169,6 @@ async function main(): Promise { console.log('session ended'); }); - const shutdown = async () => { - console.log('\nShutting down...'); - await client.close(); - console.log('Disconnected'); - process.exit(0); - }; - - process.on('SIGINT', shutdown); - process.on('SIGTERM', shutdown); - try { console.log(`Mastra weather agent initialized model=${model}`); await client.connect(); From 692679c87462b619589573f00fd7c5a86ef847e0 Mon Sep 17 00:00:00 2001 From: Gagan Singh Date: Wed, 25 Mar 2026 01:21:23 -0700 Subject: [PATCH 09/10] mastra agent working --- examples/mastra-weather-agent/weather-agent.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/examples/mastra-weather-agent/weather-agent.ts b/examples/mastra-weather-agent/weather-agent.ts index f88b3a3..c561bcb 100644 --- a/examples/mastra-weather-agent/weather-agent.ts +++ b/examples/mastra-weather-agent/weather-agent.ts @@ -4,7 +4,7 @@ import { Agent } from '@mastra/core/agent'; import { RequestContext } from '@mastra/core/request-context'; import { createTool } from '@mastra/core/tools'; import { config } from 'dotenv'; -import { type Annotater, LLPClient, type LLPClientConfig, type TextMessage } from 'llpsdk'; +import { type Annotater, LLPClient, type TextMessage } from 'llpsdk'; import { type LLPMastraContext, wrapWithLLPAnnotation } from 'llpsdk/mastra'; import * as z from 'zod'; @@ -134,7 +134,8 @@ async function handleMessage( requestContext.set('llpAnnotater', annotater); try { - return await agent.generate(message.prompt, { requestContext }); + const result = await agent.generate(message.prompt, { requestContext }); + return result.text; } catch (error) { console.error('Agent execution failed:', error); return "I'm sorry, I hit an error while handling that weather request."; @@ -144,22 +145,13 @@ async function handleMessage( async function main(): Promise { const agentName = process.env.AGENT_NAME ?? 'mastra-weather-agent'; const apiKey = process.env.LLP_API_KEY ?? ''; - const model = process.env.MODEL_NAME ?? 'ollama/llama3.1'; - - if (!process.env.LLP_URL) { - throw new Error('LLP_URL env var is not defined'); - } + const model = process.env.MODEL_NAME ?? 'ollama-cloud/gpt-oss:120b' if (!apiKey) { throw new Error('LLP_API_KEY env var is not defined'); } - const llpConfig: LLPClientConfig = { - url: process.env.LLP_URL, - responseTimeout: 600_000, - }; - - const client = new LLPClient(agentName, apiKey, llpConfig) + const client = new LLPClient(agentName, apiKey) .onStart(() => createWeatherAgent(model)) .onMessage(async (agent, msg, annotater) => { const response = await handleMessage(agent, msg, annotater); From 16696b0e34c5acda705c8550c08f12dc7e1d9812 Mon Sep 17 00:00:00 2001 From: Michael Slezak Date: Wed, 25 Mar 2026 09:12:56 -0600 Subject: [PATCH 10/10] fix formatting error --- examples/mastra-weather-agent/weather-agent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mastra-weather-agent/weather-agent.ts b/examples/mastra-weather-agent/weather-agent.ts index c561bcb..7a70576 100644 --- a/examples/mastra-weather-agent/weather-agent.ts +++ b/examples/mastra-weather-agent/weather-agent.ts @@ -145,7 +145,7 @@ async function handleMessage( async function main(): Promise { const agentName = process.env.AGENT_NAME ?? 'mastra-weather-agent'; const apiKey = process.env.LLP_API_KEY ?? ''; - const model = process.env.MODEL_NAME ?? 'ollama-cloud/gpt-oss:120b' + const model = process.env.MODEL_NAME ?? 'ollama-cloud/gpt-oss:120b'; if (!apiKey) { throw new Error('LLP_API_KEY env var is not defined');