Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2aae3d9db5 | |||
| de20eb65b5 | |||
| fef332d213 | |||
| 3b86d5ccad |
@@ -10,7 +10,7 @@
|
||||
"plugins": [
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "8.5.1",
|
||||
"version": "8.5.2",
|
||||
"source": "./plugin",
|
||||
"description": "Persistent memory system for Claude Code - context compression across sessions"
|
||||
}
|
||||
|
||||
+302
-155
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Claude Agent SDK V2 Examples
|
||||
*
|
||||
* The V2 API provides a session-based interface with separate send()/receive(),
|
||||
* ideal for multi-turn conversations. Run with: npx tsx v2-examples.ts
|
||||
*/
|
||||
|
||||
import {
|
||||
unstable_v2_createSession,
|
||||
unstable_v2_resumeSession,
|
||||
unstable_v2_prompt,
|
||||
} from '@anthropic-ai/claude-agent-sdk';
|
||||
|
||||
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;
|
||||
default:
|
||||
console.log('Usage: npx tsx v2-examples.ts [basic|multi-turn|one-shot|resume]');
|
||||
}
|
||||
}
|
||||
|
||||
// Basic session with send/receive pattern
|
||||
async function basicSession() {
|
||||
console.log('=== Basic Session ===\n');
|
||||
|
||||
await using session = unstable_v2_createSession({ model: 'sonnet' });
|
||||
await session.send('Hello! Introduce yourself in one sentence.');
|
||||
|
||||
for await (const msg of session.receive()) {
|
||||
if (msg.type === 'assistant') {
|
||||
const text = msg.message.content.find((c): c is { type: 'text'; text: string } => c.type === 'text');
|
||||
console.log(`Claude: ${text?.text}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-turn conversation - V2's key advantage
|
||||
async function multiTurn() {
|
||||
console.log('=== Multi-Turn Conversation ===\n');
|
||||
|
||||
await using session = unstable_v2_createSession({ model: 'sonnet' });
|
||||
|
||||
// Turn 1
|
||||
await session.send('What is 5 + 3? Just the number.');
|
||||
for await (const msg of session.receive()) {
|
||||
if (msg.type === 'assistant') {
|
||||
const text = msg.message.content.find((c): c is { type: 'text'; text: string } => c.type === 'text');
|
||||
console.log(`Turn 1: ${text?.text}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Turn 2 - Claude remembers context
|
||||
await session.send('Multiply that by 2. Just the number.');
|
||||
for await (const msg of session.receive()) {
|
||||
if (msg.type === 'assistant') {
|
||||
const text = msg.message.content.find((c): c is { type: 'text'; text: string } => c.type === 'text');
|
||||
console.log(`Turn 2: ${text?.text}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// One-shot convenience function
|
||||
async function oneShot() {
|
||||
console.log('=== One-Shot Prompt ===\n');
|
||||
|
||||
const result = await unstable_v2_prompt('What is the capital of France? One word.', { model: 'sonnet' });
|
||||
|
||||
if (result.subtype === 'success') {
|
||||
console.log(`Answer: ${result.result}`);
|
||||
console.log(`Cost: $${result.total_cost_usd.toFixed(4)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Session resume - persist context across sessions
|
||||
async function sessionResume() {
|
||||
console.log('=== Session Resume ===\n');
|
||||
|
||||
let sessionId: string | undefined;
|
||||
|
||||
// First session - establish a memory
|
||||
{
|
||||
await using session = unstable_v2_createSession({ model: 'sonnet' });
|
||||
console.log('[Session 1] Telling Claude my favorite color...');
|
||||
await session.send('My favorite color is blue. Remember this!');
|
||||
|
||||
for await (const msg of session.receive()) {
|
||||
if (msg.type === 'system' && msg.subtype === 'init') {
|
||||
sessionId = msg.session_id;
|
||||
console.log(`[Session 1] ID: ${sessionId}`);
|
||||
}
|
||||
if (msg.type === 'assistant') {
|
||||
const text = msg.message.content.find((c): c is { type: 'text'; text: string } => c.type === 'text');
|
||||
console.log(`[Session 1] Claude: ${text?.text}\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('--- Session closed. Time passes... ---\n');
|
||||
|
||||
// Resume and verify Claude remembers
|
||||
{
|
||||
await using session = unstable_v2_resumeSession(sessionId!, { model: 'sonnet' });
|
||||
console.log('[Session 2] Resuming and asking Claude...');
|
||||
await session.send('What is my favorite color?');
|
||||
|
||||
for await (const msg of session.receive()) {
|
||||
if (msg.type === 'assistant') {
|
||||
const text = msg.message.content.find((c): c is { type: 'text'; text: string } => c.type === 'text');
|
||||
console.log(`[Session 2] Claude: ${text?.text}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "8.5.1",
|
||||
"version": "8.5.2",
|
||||
"description": "Memory compression system for Claude Code - persist context across sessions",
|
||||
"keywords": [
|
||||
"claude",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "8.5.1",
|
||||
"version": "8.5.2",
|
||||
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
|
||||
"author": {
|
||||
"name": "Alex Newman"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -168,8 +168,9 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
})
|
||||
.finally(() => {
|
||||
const sessionDbId = session.sessionDbId;
|
||||
|
||||
if (session.abortController.signal.aborted) {
|
||||
const wasAborted = session.abortController.signal.aborted;
|
||||
|
||||
if (wasAborted) {
|
||||
logger.info('SESSION', `Generator aborted`, { sessionId: sessionDbId });
|
||||
} else {
|
||||
logger.warn('SESSION', `Generator exited unexpectedly`, { sessionId: sessionDbId });
|
||||
@@ -180,16 +181,20 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
// Crash recovery: If not aborted and still has work, restart
|
||||
if (!session.abortController.signal.aborted) {
|
||||
if (!wasAborted) {
|
||||
try {
|
||||
const pendingStore = this.sessionManager.getPendingMessageStore();
|
||||
const pendingCount = pendingStore.getPendingCount(sessionDbId);
|
||||
|
||||
|
||||
if (pendingCount > 0) {
|
||||
logger.info('SESSION', `Restarting generator after crash/exit with pending work`, {
|
||||
sessionId: sessionDbId,
|
||||
pendingCount
|
||||
});
|
||||
|
||||
// Create new AbortController for the restarted generator
|
||||
session.abortController = new AbortController();
|
||||
|
||||
// Small delay before restart
|
||||
setTimeout(() => {
|
||||
const stillExists = this.sessionManager.getSession(sessionDbId);
|
||||
@@ -197,12 +202,19 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
this.startGeneratorWithProvider(stillExists, this.getSelectedProvider(), 'crash-recovery');
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
// No pending work - abort to kill the child process
|
||||
session.abortController.abort();
|
||||
logger.debug('SESSION', 'Aborted controller after natural completion', {
|
||||
sessionId: sessionDbId
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors during recovery check
|
||||
// Ignore errors during recovery check, but still abort to prevent leaks
|
||||
session.abortController.abort();
|
||||
}
|
||||
}
|
||||
// NOTE: We do NOT delete the session here anymore.
|
||||
// NOTE: We do NOT delete the session here anymore.
|
||||
// The generator waits for events, so if it exited, it's either aborted or crashed.
|
||||
// Idle sessions stay in memory (ActiveSession is small) to listen for future events.
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user