Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 161 additions & 86 deletions README.md

Large diffs are not rendered by default.

146 changes: 98 additions & 48 deletions docs/daemon-usage-guide.md

Large diffs are not rendered by default.

721 changes: 472 additions & 249 deletions docs/sdk-usage-guide.md

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions examples/abort-session-stream.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
/**
* Abort a running turn with AbortController.
*
* Demonstrates passing an `abortSignal` to `session.stream()` and
* stopping a turn after a timeout.
*
* Usage:
* npx tsx examples/abort-session-stream.ts
*
* Requirements: droid CLI installed and logged in. FACTORY_API_KEY is
* optional; stored CLI credentials are used when it is unset.
*/

import { createSession } from '@factory/droid-sdk';
Expand Down
7 changes: 5 additions & 2 deletions examples/test-compact.ts → examples/compact-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
* and removedCount.
*
* Usage:
* npx tsx examples/test-compact.ts
* npx tsx examples/compact-session.ts
*
* Requirements: droid CLI installed and logged in. FACTORY_API_KEY is
* optional; stored CLI credentials are used when it is unset.
*/

import { createSession, DroidMessageType } from '@factory/droid-sdk';
Expand Down Expand Up @@ -43,7 +46,7 @@ async function main(): Promise<void> {
}

console.log('=== Compacting session ===');
const compactResult = await session.compactSession({});
const compactResult = await session.compactSession();
console.log(`Original session: ${session.sessionId}`);
console.log(`New session: ${compactResult.newSessionId}`);
console.log(`Removed messages: ${compactResult.removedCount}`);
Expand Down
102 changes: 62 additions & 40 deletions examples/daemon-multi-session.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,83 @@
/**
* Manual smoke test for the daemon SDK — multiple concurrent sessions.
* Daemon: multiple concurrent sessions.
*
* Spawns a local daemon, creates two sessions in separate /tmp directories,
* and runs them concurrently over a single WebSocket connection.
* Connects to a local daemon, creates two sessions in separate /tmp
* directories, and runs them concurrently over a single WebSocket
* connection.
*
* Usage:
* npx tsx examples/daemon-multi-session.ts
*
* Requirements: droid CLI installed, plus a real FACTORY_API_KEY.
* Daemon authentication has no stored-credential fallback, so this
* example skips itself when the env var is unset.
*/

import { connectDaemon, DroidMessageType } from '@factory/droid-sdk';

async function main(): Promise<void> {
if (!process.env.FACTORY_API_KEY) {
console.log(
'FACTORY_API_KEY is not set. Daemon authentication requires a real ' +
'API key (stored CLI credentials are not used). Skipping.'
);
return;
}

console.log('Connecting to local daemon...\n');
const daemon = await connectDaemon({ apiKey: process.env.FACTORY_API_KEY! });
const daemon = await connectDaemon({ apiKey: process.env.FACTORY_API_KEY });
console.log('Connected!\n');

const frontend = await daemon.createSession({
cwd: '/tmp/daemon-test-frontend',
});
const backend = await daemon.createSession({
cwd: '/tmp/daemon-test-backend',
});
try {
const frontend = await daemon.createSession({
cwd: '/tmp/daemon-test-frontend',
});
const backend = await daemon.createSession({
cwd: '/tmp/daemon-test-backend',
});

console.log('Two sessions created. Running concurrently...\n');
console.log('Two sessions created. Running concurrently...\n');

await Promise.all([
(async () => {
for await (const msg of frontend.stream(
'Create a file called hello.md with a short greeting message. Just a few lines.'
)) {
if (msg.type === DroidMessageType.Assistant) {
process.stdout.write(`[frontend] ${msg.text}\n`);
} else if (msg.type === DroidMessageType.ToolCall) {
console.log(`[frontend] [tool] ${msg.toolUse.name}`);
} else if (msg.type === DroidMessageType.Result) {
console.log(`[frontend] Done in ${msg.durationMs}ms`);
await Promise.all([
(async () => {
try {
for await (const msg of frontend.stream(
'Create a file called hello.md with a short greeting message. Just a few lines.'
)) {
if (msg.type === DroidMessageType.Assistant) {
process.stdout.write(`[frontend] ${msg.text}\n`);
} else if (msg.type === DroidMessageType.ToolCall) {
console.log(`[frontend] [tool] ${msg.toolUse.name}`);
} else if (msg.type === DroidMessageType.Result) {
console.log(`[frontend] Done in ${msg.durationMs}ms`);
}
}
} finally {
await frontend.close();
}
}
await frontend.close();
})(),
(async () => {
for await (const msg of backend.stream(
'Create a file called notes.md with 3 random fun facts. Keep it short.'
)) {
if (msg.type === DroidMessageType.Assistant) {
process.stdout.write(`[backend] ${msg.text}\n`);
} else if (msg.type === DroidMessageType.ToolCall) {
console.log(`[backend] [tool] ${msg.toolUse.name}`);
} else if (msg.type === DroidMessageType.Result) {
console.log(`[backend] Done in ${msg.durationMs}ms`);
})(),
(async () => {
try {
for await (const msg of backend.stream(
'Create a file called notes.md with 3 random fun facts. Keep it short.'
)) {
if (msg.type === DroidMessageType.Assistant) {
process.stdout.write(`[backend] ${msg.text}\n`);
} else if (msg.type === DroidMessageType.ToolCall) {
console.log(`[backend] [tool] ${msg.toolUse.name}`);
} else if (msg.type === DroidMessageType.Result) {
console.log(`[backend] Done in ${msg.durationMs}ms`);
}
}
} finally {
await backend.close();
}
}
await backend.close();
})(),
]);
})(),
]);
} finally {
await daemon.close();
}

await daemon.close();
console.log(
'\nDone. Check /tmp/daemon-test-frontend/hello.md and /tmp/daemon-test-backend/notes.md'
);
Expand Down
44 changes: 0 additions & 44 deletions examples/droid-dev-structured-output.ts

This file was deleted.

5 changes: 4 additions & 1 deletion examples/fork-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
*
* Usage:
* npx tsx examples/fork-session.ts
*
* Requirements: droid CLI installed and logged in. FACTORY_API_KEY is
* optional; stored CLI credentials are used when it is unset.
*/

import {
Expand Down Expand Up @@ -58,7 +61,7 @@ async function main(): Promise<void> {
} finally {
await fork?.close();
await session.close();
console.log('\nBoth sessions closed.');
console.log('\nCleaned up sessions.');
}
}

Expand Down
21 changes: 12 additions & 9 deletions examples/hook-execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,28 @@
*
* Demonstrates how to handle hook execution messages in a session stream.
*
* IMPORTANT: Hook messages only appear if hooks are configured in your
* droid settings. Without configured hooks the example still runs, but
* no `[Hook ...]` lines are printed.
*
* Usage:
* npx tsx examples/hook-execution.ts
*
* Requirements: droid CLI installed and logged in. FACTORY_API_KEY is
* optional; stored CLI credentials are used when it is unset.
*/

import { DroidMessageType, createSession } from '../src/index.js';
import { DroidMessageType, createSession } from '@factory/droid-sdk';

async function main(): Promise<void> {
const prompt = 'Run a simple shell command using Execute tool.';

console.log(`Sending prompt: "${prompt}"\n`);

// Note: To actually see hooks, you need to have hooks configured in your droid settings.
const apiKey = process.env.FACTORY_API_KEY;
if (!apiKey) {
console.error('Set FACTORY_API_KEY environment variable.');
process.exit(1);
}

const session = await createSession({ apiKey, cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});

try {
for await (const msg of session.stream(prompt)) {
Expand Down
22 changes: 15 additions & 7 deletions examples/init-metadata.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
/**
* Initialization metadata example.
*
* Demonstrates reading `session.initResult` metadata (model, cwd) from
* created and resumed sessions.
*
* Usage:
* npx tsx examples/init-metadata.ts
*
* Requirements: droid CLI installed and logged in. FACTORY_API_KEY is
* optional; stored CLI credentials are used when it is unset.
*/

import { createSession, resumeSession } from '@factory/droid-sdk';
Expand All @@ -15,10 +21,12 @@ const resumed = await resumeSession(session.sessionId, {
apiKey: process.env.FACTORY_API_KEY!,
});

console.log(`created session: ${session.sessionId}`);
console.log(`resumed session: ${resumed.sessionId}`);
console.log(`created model: ${String(session.initResult.settings.modelId)}`);
console.log(`resumed cwd: ${String(resumed.initResult.cwd)}`);

await resumed.close();
await session.close();
try {
console.log(`created session: ${session.sessionId}`);
console.log(`resumed session: ${resumed.sessionId}`);
console.log(`created model: ${String(session.initResult.settings.modelId)}`);
console.log(`resumed cwd: ${String(resumed.initResult.cwd)}`);
} finally {
await resumed.close();
await session.close();
}
9 changes: 9 additions & 0 deletions examples/interrupt-session.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
/**
* Interrupt a running turn.
*
* Demonstrates `session.interrupt()`: requests the interrupt after the
* fifth streamed text delta. Because the interrupt is asynchronous, a
* few more deltas may arrive before the turn actually stops.
*
* Usage:
* npx tsx examples/interrupt-session.ts
*
* Requirements: droid CLI installed and logged in. FACTORY_API_KEY is
* optional; stored CLI credentials are used when it is unset.
*/

import { createSession, DroidMessageType } from '@factory/droid-sdk';
Expand All @@ -25,6 +32,8 @@ try {
deltaCount++;

if (deltaCount === 5) {
// Asynchronous: the stream may deliver a few more deltas before
// the interrupt takes effect.
await session.interrupt();
}
}
Expand Down
6 changes: 6 additions & 0 deletions examples/list-sessions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
/**
* List saved sessions example.
*
* Demonstrates `listSessions()` to fetch recent sessions for the
* current project.
*
* Usage:
* npx tsx examples/list-sessions.ts
*
* Requirements: droid CLI installed (reads local session files); no
* credentials needed.
*/

import { listSessions } from '@factory/droid-sdk';
Expand Down
25 changes: 22 additions & 3 deletions examples/multi-turn-session.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
/**
* Multi-turn session example.
*
* Demonstrates context retention across turns: the first turn states a
* fact, the second turn asks for it back. Runs in a temporary
* directory so nothing touches your project.
*
* Usage:
* npx tsx examples/multi-turn-session.ts
*
* Requirements: droid CLI installed and logged in. FACTORY_API_KEY is
* optional; stored CLI credentials are used when it is unset.
*/

import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

import { DroidMessageType, createSession } from '@factory/droid-sdk';

async function streamText(
Expand All @@ -22,19 +33,27 @@ async function streamText(
return text;
}

const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-multi-turn-'));
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
cwd: tempDir,
});

try {
console.log(`Session: ${session.sessionId}\n`);

const first = await streamText(session, 'What is this project?');
const first = await streamText(
session,
'My favorite color is teal. Acknowledge in one short sentence.'
);
console.log(first);

const second = await streamText(session, 'What should I test first?');
const second = await streamText(
session,
'What is my favorite color? Answer in one short sentence.'
);
console.log('\n', second);
} finally {
await session.close();
await rm(tempDir, { recursive: true, force: true });
}
Loading
Loading