Initial release of Letta Code SDK
Programmatic control of Letta Code CLI with persistent agent memory. Features: - createSession() / resumeSession() / prompt() API - resumeConversation() for multi-threaded conversations - Multi-turn conversations with memory - Tool execution (Bash, Read, Edit, etc.) - System prompt and memory configuration - Permission callbacks (canUseTool) - Message streaming with typed events 👾 Generated with [Letta Code](https://letta.com)
This commit is contained in:
822
examples/v2-examples.ts
Normal file
822
examples/v2-examples.ts
Normal file
@@ -0,0 +1,822 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
/**
|
||||
* Letta Code SDK V2 Examples
|
||||
*
|
||||
* Comprehensive tests for all SDK features.
|
||||
* Parallel to Claude Agent SDK V2 examples.
|
||||
*
|
||||
* Run with: LETTA_CLI_PATH=path/to/letta.js bun v2-examples.ts [example]
|
||||
*/
|
||||
|
||||
import { createSession, resumeSession, resumeConversation, prompt } from '../src/index.js';
|
||||
|
||||
const CLI_PATH = process.env.LETTA_CLI_PATH;
|
||||
if (!CLI_PATH) {
|
||||
console.error('Set LETTA_CLI_PATH environment variable');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const example = process.argv[2] || 'basic';
|
||||
|
||||
switch (example) {
|
||||
case 'basic':
|
||||
await basicSession();
|
||||
break;
|
||||
case 'multi-turn':
|
||||
await multiTurn();
|
||||
break;
|
||||
case 'one-shot':
|
||||
await oneShot();
|
||||
break;
|
||||
case 'resume':
|
||||
await sessionResume();
|
||||
break;
|
||||
case 'options':
|
||||
await testOptions();
|
||||
break;
|
||||
case 'message-types':
|
||||
await testMessageTypes();
|
||||
break;
|
||||
case 'session-properties':
|
||||
await testSessionProperties();
|
||||
break;
|
||||
case 'tool-execution':
|
||||
await testToolExecution();
|
||||
break;
|
||||
case 'permission-callback':
|
||||
await testPermissionCallback();
|
||||
break;
|
||||
case 'system-prompt':
|
||||
await testSystemPrompt();
|
||||
break;
|
||||
case 'memory-config':
|
||||
await testMemoryConfig();
|
||||
break;
|
||||
case 'convenience-props':
|
||||
await testConvenienceProps();
|
||||
break;
|
||||
case 'conversations':
|
||||
await testConversations();
|
||||
break;
|
||||
case 'all':
|
||||
await basicSession();
|
||||
await multiTurn();
|
||||
await oneShot();
|
||||
await sessionResume();
|
||||
await testOptions();
|
||||
await testMessageTypes();
|
||||
await testSessionProperties();
|
||||
await testToolExecution();
|
||||
await testPermissionCallback();
|
||||
await testSystemPrompt();
|
||||
await testMemoryConfig();
|
||||
await testConvenienceProps();
|
||||
await testConversations();
|
||||
console.log('\n✅ All examples passed');
|
||||
break;
|
||||
default:
|
||||
console.log('Usage: bun v2-examples.ts [basic|multi-turn|one-shot|resume|options|message-types|session-properties|tool-execution|permission-callback|system-prompt|memory-config|convenience-props|conversations|all]');
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// BASIC EXAMPLES
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
// Basic session with send/receive pattern
|
||||
async function basicSession() {
|
||||
console.log('=== Basic Session ===\n');
|
||||
|
||||
await using session = createSession({
|
||||
model: 'haiku',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
await session.send('Hello! Introduce yourself in one sentence.');
|
||||
|
||||
let response = '';
|
||||
for await (const msg of session.stream()) {
|
||||
if (msg.type === 'assistant') {
|
||||
response += msg.content;
|
||||
}
|
||||
if (msg.type === 'result') {
|
||||
console.log(`Letta: ${response.trim()}`);
|
||||
console.log(`[Result: ${msg.success ? 'success' : 'failed'}, ${msg.durationMs}ms]`);
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
// Multi-turn conversation
|
||||
async function multiTurn() {
|
||||
console.log('=== Multi-Turn Conversation ===\n');
|
||||
|
||||
await using session = createSession({
|
||||
model: 'haiku',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
// Turn 1
|
||||
await session.send('What is 5 + 3? Just the number.');
|
||||
let turn1Result = '';
|
||||
for await (const msg of session.stream()) {
|
||||
if (msg.type === 'assistant') turn1Result += msg.content;
|
||||
}
|
||||
console.log(`Turn 1: ${turn1Result.trim()}`);
|
||||
|
||||
// Turn 2 - agent remembers context
|
||||
await session.send('Multiply that by 2. Just the number.');
|
||||
let turn2Result = '';
|
||||
for await (const msg of session.stream()) {
|
||||
if (msg.type === 'assistant') turn2Result += msg.content;
|
||||
}
|
||||
console.log(`Turn 2: ${turn2Result.trim()}`);
|
||||
console.log();
|
||||
}
|
||||
|
||||
// One-shot convenience function
|
||||
async function oneShot() {
|
||||
console.log('=== One-Shot Prompt ===\n');
|
||||
|
||||
const result = await prompt('What is the capital of France? One word.', {
|
||||
model: 'haiku',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
console.log(`Answer: ${result.result}`);
|
||||
console.log(`Duration: ${result.durationMs}ms`);
|
||||
} else {
|
||||
console.log(`Error: ${result.error}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
// Session resume - with PERSISTENT MEMORY
|
||||
async function sessionResume() {
|
||||
console.log('=== Session Resume (Persistent Memory) ===\n');
|
||||
|
||||
let agentId: string | null = null;
|
||||
|
||||
// First session - establish a memory
|
||||
{
|
||||
const session = createSession({
|
||||
model: 'haiku',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
console.log('[Session 1] Teaching agent a secret word...');
|
||||
await session.send('Remember this secret word: "pineapple". Store it in your memory.');
|
||||
|
||||
let response = '';
|
||||
for await (const msg of session.stream()) {
|
||||
if (msg.type === 'assistant') response += msg.content;
|
||||
if (msg.type === 'result') {
|
||||
console.log(`[Session 1] Agent: ${response.trim()}`);
|
||||
}
|
||||
}
|
||||
|
||||
agentId = session.agentId;
|
||||
console.log(`[Session 1] Agent ID: ${agentId}\n`);
|
||||
session.close();
|
||||
}
|
||||
|
||||
console.log('--- Session closed. Agent persists on server. ---\n');
|
||||
|
||||
// Resume and verify agent remembers
|
||||
{
|
||||
await using session = resumeSession(agentId!, {
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
console.log('[Session 2] Asking agent for the secret word...');
|
||||
await session.send('What is the secret word I told you to remember?');
|
||||
|
||||
let response = '';
|
||||
for await (const msg of session.stream()) {
|
||||
if (msg.type === 'assistant') response += msg.content;
|
||||
if (msg.type === 'result') {
|
||||
console.log(`[Session 2] Agent: ${response.trim()}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// OPTIONS TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async function testOptions() {
|
||||
console.log('=== Testing Options ===\n');
|
||||
|
||||
// Test model option
|
||||
console.log('Testing model option...');
|
||||
const modelResult = await prompt('Say "model test ok"', {
|
||||
model: 'haiku',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
console.log(` model: ${modelResult.success ? 'PASS' : 'FAIL'} - ${modelResult.result?.slice(0, 50)}`);
|
||||
|
||||
// Test systemPrompt option
|
||||
console.log('Testing systemPrompt option...');
|
||||
const sysPromptResult = await prompt('Tell me a fun fact about penguins in one sentence.', {
|
||||
model: 'haiku',
|
||||
systemPrompt: 'You love penguins and always try to work penguin facts into conversations.',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasPenguin = sysPromptResult.result?.toLowerCase().includes('penguin');
|
||||
console.log(` systemPrompt: ${hasPenguin ? 'PASS' : 'PARTIAL'} - ${sysPromptResult.result?.slice(0, 80)}`);
|
||||
|
||||
// Test cwd option
|
||||
console.log('Testing cwd option...');
|
||||
const cwdResult = await prompt('Run pwd to show current directory', {
|
||||
model: 'haiku',
|
||||
cwd: '/tmp',
|
||||
allowedTools: ['Bash'],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasTmp = cwdResult.result?.includes('/tmp');
|
||||
console.log(` cwd: ${hasTmp ? 'PASS' : 'CHECK'} - ${cwdResult.result?.slice(0, 60)}`);
|
||||
|
||||
// Test allowedTools option with tool execution
|
||||
console.log('Testing allowedTools option...');
|
||||
const toolsResult = await prompt('Run: echo tool-test-ok', {
|
||||
model: 'haiku',
|
||||
allowedTools: ['Bash'],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasToolOutput = toolsResult.result?.includes('tool-test-ok');
|
||||
console.log(` allowedTools: ${hasToolOutput ? 'PASS' : 'CHECK'} - ${toolsResult.result?.slice(0, 60)}`);
|
||||
|
||||
// Test permissionMode: bypassPermissions
|
||||
console.log('Testing permissionMode: bypassPermissions...');
|
||||
const bypassResult = await prompt('Run: echo bypass-test', {
|
||||
model: 'haiku',
|
||||
allowedTools: ['Bash'],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasBypassOutput = bypassResult.result?.includes('bypass-test');
|
||||
console.log(` permissionMode: ${hasBypassOutput ? 'PASS' : 'CHECK'}`);
|
||||
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// MESSAGE TYPES TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async function testMessageTypes() {
|
||||
console.log('=== Testing Message Types ===\n');
|
||||
|
||||
const session = createSession({
|
||||
model: 'haiku',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
await session.send('Say "hello" exactly');
|
||||
|
||||
let sawAssistant = false;
|
||||
let sawResult = false;
|
||||
let assistantContent = '';
|
||||
|
||||
for await (const msg of session.stream()) {
|
||||
if (msg.type === 'assistant') {
|
||||
sawAssistant = true;
|
||||
assistantContent += msg.content;
|
||||
// Verify assistant message has uuid
|
||||
if (!msg.uuid) {
|
||||
console.log(' assistant.uuid: FAIL - missing uuid');
|
||||
}
|
||||
}
|
||||
if (msg.type === 'result') {
|
||||
sawResult = true;
|
||||
// Verify result message structure
|
||||
const hasSuccess = typeof msg.success === 'boolean';
|
||||
const hasDuration = typeof msg.durationMs === 'number';
|
||||
console.log(` result.success: ${hasSuccess ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` result.durationMs: ${hasDuration ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` result.result: ${msg.result ? 'PASS' : 'FAIL (empty)'}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` assistant message received: ${sawAssistant ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` result message received: ${sawResult ? 'PASS' : 'FAIL'}`);
|
||||
|
||||
session.close();
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SESSION PROPERTIES TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async function testSessionProperties() {
|
||||
console.log('=== Testing Session Properties ===\n');
|
||||
|
||||
const session = createSession({
|
||||
model: 'haiku',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
// Before send - properties should be null
|
||||
console.log(` agentId before send: ${session.agentId === null ? 'PASS (null)' : 'FAIL'}`);
|
||||
console.log(` sessionId before send: ${session.sessionId === null ? 'PASS (null)' : 'FAIL'}`);
|
||||
|
||||
await session.send('Hi');
|
||||
for await (const _ of session.stream()) {
|
||||
// drain
|
||||
}
|
||||
|
||||
// After send - properties should be set
|
||||
const hasAgentId = session.agentId !== null && session.agentId.startsWith('agent-');
|
||||
const hasSessionId = session.sessionId !== null;
|
||||
console.log(` agentId after send: ${hasAgentId ? 'PASS' : 'FAIL'} - ${session.agentId}`);
|
||||
console.log(` sessionId after send: ${hasSessionId ? 'PASS' : 'FAIL'} - ${session.sessionId}`);
|
||||
|
||||
// Test close()
|
||||
session.close();
|
||||
console.log(` close(): PASS (no error)`);
|
||||
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// TOOL EXECUTION TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async function testToolExecution() {
|
||||
console.log('=== Testing Tool Execution ===\n');
|
||||
|
||||
// Test 1: Basic command execution
|
||||
console.log('Testing basic command execution...');
|
||||
const echoResult = await prompt('Run: echo hello-world', {
|
||||
model: 'haiku',
|
||||
allowedTools: ['Bash'],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasHello = echoResult.result?.includes('hello-world');
|
||||
console.log(` echo command: ${hasHello ? 'PASS' : 'FAIL'}`);
|
||||
|
||||
// Test 2: Command with arguments
|
||||
console.log('Testing command with arguments...');
|
||||
const argsResult = await prompt('Run: echo "arg1 arg2 arg3"', {
|
||||
model: 'haiku',
|
||||
allowedTools: ['Bash'],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasArgs = argsResult.result?.includes('arg1') && argsResult.result?.includes('arg3');
|
||||
console.log(` echo with args: ${hasArgs ? 'PASS' : 'FAIL'}`);
|
||||
|
||||
// Test 3: File reading with Glob
|
||||
console.log('Testing Glob tool...');
|
||||
const globResult = await prompt('List all .ts files in the current directory using Glob', {
|
||||
model: 'haiku',
|
||||
allowedTools: ['Glob'],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
console.log(` Glob tool: ${globResult.success ? 'PASS' : 'FAIL'}`);
|
||||
|
||||
// Test 4: Multi-step tool usage (agent decides which tools to use)
|
||||
console.log('Testing multi-step tool usage...');
|
||||
const multiResult = await prompt('First run "echo step1", then run "echo step2". Show me both outputs.', {
|
||||
model: 'haiku',
|
||||
allowedTools: ['Bash'],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasStep1 = multiResult.result?.includes('step1');
|
||||
const hasStep2 = multiResult.result?.includes('step2');
|
||||
console.log(` multi-step: ${hasStep1 && hasStep2 ? 'PASS' : 'PARTIAL'} (step1: ${hasStep1}, step2: ${hasStep2})`);
|
||||
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// PERMISSION CALLBACK TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async function testPermissionCallback() {
|
||||
console.log('=== Testing Permission Callback ===\n');
|
||||
|
||||
// Note: permissionMode 'default' with NO allowedTools triggers callback
|
||||
// allowedTools auto-allows tools, bypassing the callback
|
||||
|
||||
// Test 1: Allow specific commands via callback
|
||||
console.log('Testing canUseTool callback (allow)...');
|
||||
const allowResult = await prompt('Run: echo callback-allowed', {
|
||||
model: 'haiku',
|
||||
// NO allowedTools - this ensures callback is invoked
|
||||
permissionMode: 'default',
|
||||
canUseTool: async (toolName, toolInput) => {
|
||||
console.error('CALLBACK:', toolName, toolInput);
|
||||
const command = (toolInput as { command?: string }).command || '';
|
||||
if (command.includes('callback-allowed')) {
|
||||
return { allow: true, reason: 'Command whitelisted' };
|
||||
}
|
||||
return { allow: false, reason: 'Command not whitelisted' };
|
||||
},
|
||||
});
|
||||
const hasAllowed = allowResult.result?.includes('callback-allowed');
|
||||
console.log(` allow via callback: ${hasAllowed ? 'PASS' : 'FAIL'}`);
|
||||
|
||||
// Test 2: Deny specific commands via callback
|
||||
console.log('Testing canUseTool callback (deny)...');
|
||||
const denyResult = await prompt('Run: echo dangerous-command', {
|
||||
model: 'haiku',
|
||||
permissionMode: 'default',
|
||||
canUseTool: async (toolName, toolInput) => {
|
||||
const command = (toolInput as { command?: string }).command || '';
|
||||
if (command.includes('dangerous')) {
|
||||
return { allow: false, reason: 'Dangerous command blocked' };
|
||||
}
|
||||
return { allow: true };
|
||||
},
|
||||
});
|
||||
// Agent should report that it couldn't execute the command
|
||||
const wasDenied = !denyResult.result?.includes('dangerous-command');
|
||||
console.log(` deny via callback: ${wasDenied ? 'PASS' : 'CHECK'}`);
|
||||
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SYSTEM PROMPT TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async function testSystemPrompt() {
|
||||
console.log('=== Testing System Prompt Configuration ===\n');
|
||||
|
||||
// Test 1: Preset system prompt
|
||||
console.log('Testing preset system prompt...');
|
||||
const presetResult = await prompt('What kind of agent are you? One sentence.', {
|
||||
model: 'haiku',
|
||||
systemPrompt: { type: 'preset', preset: 'letta-claude' },
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
console.log(` preset (letta-claude): ${presetResult.success ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Response: ${presetResult.result?.slice(0, 80)}...`);
|
||||
|
||||
// Test 2: Preset with append
|
||||
console.log('Testing preset with append...');
|
||||
const appendResult = await prompt('Say hello', {
|
||||
model: 'haiku',
|
||||
systemPrompt: {
|
||||
type: 'preset',
|
||||
preset: 'letta-claude',
|
||||
append: 'Always end your responses with "🎉"'
|
||||
},
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasEmoji = appendResult.result?.includes('🎉');
|
||||
console.log(` preset with append: ${hasEmoji ? 'PASS' : 'CHECK'}`);
|
||||
console.log(` Response: ${appendResult.result?.slice(0, 80)}...`);
|
||||
|
||||
// Test 3: Custom string system prompt
|
||||
console.log('Testing custom string system prompt...');
|
||||
const customResult = await prompt('What is your specialty?', {
|
||||
model: 'haiku',
|
||||
systemPrompt: 'You are a pirate captain. Always speak like a pirate.',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasPirateSpeak = customResult.result?.toLowerCase().includes('arr') ||
|
||||
customResult.result?.toLowerCase().includes('matey') ||
|
||||
customResult.result?.toLowerCase().includes('ship');
|
||||
console.log(` custom string: ${customResult.success ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Response: ${customResult.result?.slice(0, 80)}...`);
|
||||
|
||||
// Test 4: Basic preset (claude - no skills/memory)
|
||||
console.log('Testing basic preset (claude)...');
|
||||
const basicResult = await prompt('Hello, just say hi back', {
|
||||
model: 'haiku',
|
||||
systemPrompt: { type: 'preset', preset: 'claude' },
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
console.log(` basic preset (claude): ${basicResult.success ? 'PASS' : 'FAIL'}`);
|
||||
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// MEMORY CONFIGURATION TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async function testMemoryConfig() {
|
||||
console.log('=== Testing Memory Configuration ===\n');
|
||||
|
||||
// Test 1: Default memory (persona, human, project)
|
||||
console.log('Testing default memory blocks...');
|
||||
const defaultResult = await prompt('What memory blocks do you have? List their labels.', {
|
||||
model: 'haiku',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasDefaultBlocks = defaultResult.result?.includes('persona') ||
|
||||
defaultResult.result?.includes('project');
|
||||
console.log(` default blocks: ${defaultResult.success ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Response mentions blocks: ${hasDefaultBlocks ? 'yes' : 'check manually'}`);
|
||||
|
||||
// Test 2: Specific preset blocks only
|
||||
console.log('Testing specific preset blocks...');
|
||||
const specificResult = await prompt('List your memory block labels', {
|
||||
model: 'haiku',
|
||||
memory: ['project'],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
console.log(` specific blocks [project]: ${specificResult.success ? 'PASS' : 'FAIL'}`);
|
||||
|
||||
// Test 3: Custom blocks
|
||||
console.log('Testing custom memory blocks...');
|
||||
const customResult = await prompt('What does your "rules" memory block say?', {
|
||||
model: 'haiku',
|
||||
memory: [
|
||||
{ label: 'rules', value: 'Always be concise. Never use more than 10 words.' }
|
||||
],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const isConcise = (customResult.result?.split(' ').length || 0) < 20;
|
||||
console.log(` custom blocks: ${customResult.success ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Response is concise: ${isConcise ? 'yes' : 'check'}`);
|
||||
|
||||
// Test 4: Mixed preset and custom blocks
|
||||
console.log('Testing mixed blocks (preset + custom)...');
|
||||
const mixedResult = await prompt('List your memory blocks', {
|
||||
model: 'haiku',
|
||||
memory: [
|
||||
'project',
|
||||
{ label: 'custom-context', value: 'This is a test context block.' }
|
||||
],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
console.log(` mixed blocks: ${mixedResult.success ? 'PASS' : 'FAIL'}`);
|
||||
|
||||
// Test 5: Empty memory (core blocks only)
|
||||
console.log('Testing empty memory (core only)...');
|
||||
const emptyResult = await prompt('Hello', {
|
||||
model: 'haiku',
|
||||
memory: [],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
console.log(` empty memory: ${emptyResult.success ? 'PASS' : 'FAIL'}`);
|
||||
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// CONVENIENCE PROPS TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async function testConvenienceProps() {
|
||||
console.log('=== Testing Convenience Props ===\n');
|
||||
|
||||
// Test 1: persona prop
|
||||
console.log('Testing persona prop...');
|
||||
const personaResult = await prompt('Describe your personality in one sentence', {
|
||||
model: 'haiku',
|
||||
persona: 'You are an enthusiastic cooking assistant who loves Italian food.',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasItalian = personaResult.result?.toLowerCase().includes('italian') ||
|
||||
personaResult.result?.toLowerCase().includes('cook');
|
||||
console.log(` persona: ${personaResult.success ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Response mentions cooking/Italian: ${hasItalian ? 'yes' : 'check'}`);
|
||||
|
||||
// Test 2: project prop
|
||||
console.log('Testing project prop...');
|
||||
const projectResult = await prompt('What project are you helping with?', {
|
||||
model: 'haiku',
|
||||
project: 'A React Native mobile app for tracking daily habits.',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasProject = projectResult.result?.toLowerCase().includes('react') ||
|
||||
projectResult.result?.toLowerCase().includes('habit') ||
|
||||
projectResult.result?.toLowerCase().includes('mobile');
|
||||
console.log(` project: ${projectResult.success ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Response mentions project: ${hasProject ? 'yes' : 'check'}`);
|
||||
|
||||
// Test 3: human prop
|
||||
console.log('Testing human prop...');
|
||||
const humanResult = await prompt('What do you know about me?', {
|
||||
model: 'haiku',
|
||||
human: 'Name: Bob. Senior developer. Prefers TypeScript over JavaScript.',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasHuman = humanResult.result?.toLowerCase().includes('bob') ||
|
||||
humanResult.result?.toLowerCase().includes('typescript');
|
||||
console.log(` human: ${humanResult.success ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Response mentions user info: ${hasHuman ? 'yes' : 'check'}`);
|
||||
|
||||
// Test 4: Multiple convenience props together
|
||||
console.log('Testing multiple convenience props...');
|
||||
const multiResult = await prompt('Introduce yourself and the project briefly', {
|
||||
model: 'haiku',
|
||||
persona: 'You are a friendly code reviewer.',
|
||||
project: 'FastAPI backend service.',
|
||||
human: 'Name: Alice.',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
console.log(` multiple props: ${multiResult.success ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Response: ${multiResult.result?.slice(0, 100)}...`);
|
||||
|
||||
// Test 5: Convenience props with specific memory blocks
|
||||
console.log('Testing convenience props with memory config...');
|
||||
const combinedResult = await prompt('What is in your persona block?', {
|
||||
model: 'haiku',
|
||||
memory: ['persona', 'project'],
|
||||
persona: 'You are a database expert specializing in PostgreSQL.',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
const hasDB = combinedResult.result?.toLowerCase().includes('database') ||
|
||||
combinedResult.result?.toLowerCase().includes('postgresql');
|
||||
console.log(` props with memory: ${combinedResult.success ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Response mentions DB: ${hasDB ? 'yes' : 'check'}`);
|
||||
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// CONVERSATION TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
async function testConversations() {
|
||||
console.log('=== Testing Conversation Support ===\n');
|
||||
|
||||
let agentId: string | null = null;
|
||||
let conversationId1: string | null = null;
|
||||
let conversationId2: string | null = null;
|
||||
|
||||
// Test 1: Create session and get conversationId (default)
|
||||
console.log('Test 1: Create session and get conversationId...');
|
||||
{
|
||||
const session = createSession({
|
||||
model: 'haiku',
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
await session.send('Remember: the secret code is ALPHA. Store this in memory.');
|
||||
for await (const msg of session.stream()) {
|
||||
// drain
|
||||
}
|
||||
|
||||
agentId = session.agentId;
|
||||
conversationId1 = session.conversationId;
|
||||
|
||||
const hasAgentId = agentId !== null && agentId.startsWith('agent-');
|
||||
const hasConvId = conversationId1 !== null;
|
||||
|
||||
console.log(` agentId: ${hasAgentId ? 'PASS' : 'FAIL'} - ${agentId}`);
|
||||
console.log(` conversationId: ${hasConvId ? 'PASS' : 'FAIL'} - ${conversationId1}`);
|
||||
|
||||
// Note: "default" is a sentinel meaning the agent's primary message history
|
||||
if (conversationId1 === 'default') {
|
||||
console.log(' (conversationId "default" = agent\'s primary history, not a real conversation ID)');
|
||||
}
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
// Test 2: Create NEW conversation to get a real conversation ID
|
||||
console.log('\nTest 2: Create new conversation (newConversation: true)...');
|
||||
{
|
||||
const session = resumeSession(agentId!, {
|
||||
newConversation: true,
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
await session.send('Remember: the secret code for THIS conversation is BETA.');
|
||||
for await (const msg of session.stream()) {
|
||||
// drain
|
||||
}
|
||||
|
||||
conversationId1 = session.conversationId;
|
||||
|
||||
const isRealConvId = conversationId1 !== null && conversationId1 !== 'default';
|
||||
console.log(` newConversation created: ${isRealConvId ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` conversationId: ${conversationId1}`);
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
// Test 3: Resume conversation by conversationId (only works with real conv IDs)
|
||||
console.log('\nTest 3: Resume conversation by conversationId...');
|
||||
if (conversationId1 === 'default') {
|
||||
console.log(' SKIP - "default" is not a real conversation ID');
|
||||
console.log(' Use resumeSession(agentId) to resume default conversation');
|
||||
} else {
|
||||
await using session = resumeConversation(conversationId1!, {
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
await session.send('What is the secret code for this conversation?');
|
||||
|
||||
let response = '';
|
||||
for await (const msg of session.stream()) {
|
||||
if (msg.type === 'assistant') response += msg.content;
|
||||
}
|
||||
|
||||
const remembers = response.toLowerCase().includes('beta');
|
||||
console.log(` resumeConversation: ${remembers ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Response: ${response.slice(0, 80)}...`);
|
||||
|
||||
// Verify same conversationId
|
||||
const sameConv = session.conversationId === conversationId1;
|
||||
console.log(` same conversationId: ${sameConv ? 'PASS' : 'FAIL'}`);
|
||||
}
|
||||
|
||||
// Test 4: Create another new conversation (verify different IDs)
|
||||
console.log('\nTest 4: Create another new conversation...');
|
||||
{
|
||||
await using session = resumeSession(agentId!, {
|
||||
newConversation: true,
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
await session.send('Say "third conversation"');
|
||||
|
||||
let response = '';
|
||||
for await (const msg of session.stream()) {
|
||||
if (msg.type === 'assistant') response += msg.content;
|
||||
}
|
||||
|
||||
conversationId2 = session.conversationId;
|
||||
|
||||
// New conversation should have different ID
|
||||
const differentConv = conversationId2 !== conversationId1;
|
||||
console.log(` different from conversationId1: ${differentConv ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` conversationId1: ${conversationId1}`);
|
||||
console.log(` conversationId2: ${conversationId2}`);
|
||||
}
|
||||
|
||||
// Test 5: defaultConversation option
|
||||
console.log('\nTest 5: defaultConversation option...');
|
||||
{
|
||||
await using session = resumeSession(agentId!, {
|
||||
defaultConversation: true,
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
await session.send('Say "default conversation test ok"');
|
||||
|
||||
let response = '';
|
||||
for await (const msg of session.stream()) {
|
||||
if (msg.type === 'assistant') response += msg.content;
|
||||
}
|
||||
|
||||
const hasDefaultConv = session.conversationId === 'default' || session.conversationId !== null;
|
||||
console.log(` defaultConversation: ${hasDefaultConv ? 'PASS' : 'CHECK'}`);
|
||||
console.log(` conversationId: ${session.conversationId}`);
|
||||
}
|
||||
|
||||
// Test 6: conversationId in result message
|
||||
console.log('\nTest 6: conversationId in result message...');
|
||||
{
|
||||
await using session = resumeConversation(conversationId1!, {
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
await session.send('Hi');
|
||||
|
||||
let resultConvId: string | null = null;
|
||||
for await (const msg of session.stream()) {
|
||||
if (msg.type === 'result') {
|
||||
resultConvId = msg.conversationId;
|
||||
}
|
||||
}
|
||||
|
||||
const hasResultConvId = resultConvId !== null;
|
||||
const matchesSession = resultConvId === session.conversationId;
|
||||
console.log(` result.conversationId: ${hasResultConvId ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` matches session.conversationId: ${matchesSession ? 'PASS' : 'FAIL'}`);
|
||||
}
|
||||
|
||||
// Test 7: continue option (resume last session)
|
||||
console.log('\nTest 7: continue option...');
|
||||
{
|
||||
// Note: This test may behave differently depending on local state
|
||||
// The --continue flag resumes the last used agent + conversation
|
||||
try {
|
||||
await using session = createSession({
|
||||
continue: true,
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
await session.send('Say "continue test ok"');
|
||||
|
||||
for await (const msg of session.stream()) {
|
||||
// drain
|
||||
}
|
||||
|
||||
const hasIds = session.agentId !== null && session.conversationId !== null;
|
||||
console.log(` continue: ${hasIds ? 'PASS' : 'CHECK'}`);
|
||||
console.log(` agentId: ${session.agentId}`);
|
||||
console.log(` conversationId: ${session.conversationId}`);
|
||||
} catch (err) {
|
||||
// --continue may fail if no previous session exists
|
||||
console.log(` continue: SKIP (no previous session)`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log();
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
Reference in New Issue
Block a user