formagent-sdk 0.3.0 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -228,6 +228,8 @@ for await (const event of session.receive()) {
228
228
 
229
229
  ## Session Management
230
230
 
231
+ **Default Behavior:** Sessions use in-memory storage by default. Conversation history is lost when the process exits. Use `FileSessionStorage` for persistence across restarts.
232
+
231
233
  ### Persistent Sessions
232
234
 
233
235
  Enable session persistence with `FileSessionStorage`:
package/dist/cli/index.js CHANGED
@@ -1735,7 +1735,8 @@ class SessionImpl {
1735
1735
  const toolResult = await tool.execute(toolInput, context);
1736
1736
  let content = typeof toolResult.content === "string" ? toolResult.content : JSON.stringify(toolResult.content);
1737
1737
  if (needsTruncation(content)) {
1738
- content = await truncateToolOutput(content);
1738
+ const truncationConfig = this.config.tempDir ? { tempDir: this.config.tempDir } : undefined;
1739
+ content = await truncateToolOutput(content, truncationConfig);
1739
1740
  }
1740
1741
  toolResponse = toolResult;
1741
1742
  result = {
@@ -4262,6 +4263,7 @@ function checkDirAccess(dirPath, options) {
4262
4263
 
4263
4264
  // src/tools/builtin/bash.ts
4264
4265
  var DEFAULT_TIMEOUT = 120000;
4266
+ var DEFAULT_IDLE_TIMEOUT = 30000;
4265
4267
  var MAX_OUTPUT_LENGTH = 1e5;
4266
4268
  var DEFAULT_BLOCKED_PATTERNS = [
4267
4269
  "\\bsudo\\b",
@@ -4328,21 +4330,39 @@ function createBashTool(options = {}) {
4328
4330
  }
4329
4331
  }
4330
4332
  const actualTimeout = Math.min(timeout, 600000);
4333
+ const idleTimeout = options.idleTimeout ?? DEFAULT_IDLE_TIMEOUT;
4331
4334
  return new Promise((resolve2) => {
4332
4335
  let stdout = "";
4333
4336
  let stderr = "";
4334
4337
  let killed = false;
4338
+ let killedReason = null;
4339
+ let lastOutputTime = Date.now();
4335
4340
  const proc = spawn("bash", ["-c", command], {
4336
4341
  cwd: cwdAccess.resolved,
4337
4342
  env: process.env,
4338
- shell: false
4343
+ shell: false,
4344
+ stdio: ["ignore", "pipe", "pipe"]
4339
4345
  });
4340
4346
  const timer = setTimeout(() => {
4341
4347
  killed = true;
4348
+ killedReason = "timeout";
4342
4349
  proc.kill("SIGTERM");
4343
4350
  setTimeout(() => proc.kill("SIGKILL"), 1000);
4344
4351
  }, actualTimeout);
4352
+ const idleChecker = setInterval(() => {
4353
+ const idleTime = Date.now() - lastOutputTime;
4354
+ if (idleTime >= idleTimeout && !killed) {
4355
+ killed = true;
4356
+ killedReason = "idle";
4357
+ proc.kill("SIGTERM");
4358
+ setTimeout(() => proc.kill("SIGKILL"), 1000);
4359
+ }
4360
+ }, 1000);
4361
+ const updateLastOutputTime = () => {
4362
+ lastOutputTime = Date.now();
4363
+ };
4345
4364
  proc.stdout?.on("data", (data) => {
4365
+ updateLastOutputTime();
4346
4366
  stdout += data.toString();
4347
4367
  if (stdout.length > MAX_OUTPUT_LENGTH) {
4348
4368
  stdout = stdout.slice(0, MAX_OUTPUT_LENGTH) + `
@@ -4351,6 +4371,7 @@ function createBashTool(options = {}) {
4351
4371
  }
4352
4372
  });
4353
4373
  proc.stderr?.on("data", (data) => {
4374
+ updateLastOutputTime();
4354
4375
  stderr += data.toString();
4355
4376
  if (stderr.length > MAX_OUTPUT_LENGTH) {
4356
4377
  stderr = stderr.slice(0, MAX_OUTPUT_LENGTH) + `
@@ -4359,17 +4380,31 @@ function createBashTool(options = {}) {
4359
4380
  });
4360
4381
  proc.on("close", (code) => {
4361
4382
  clearTimeout(timer);
4383
+ clearInterval(idleChecker);
4362
4384
  if (killed) {
4363
- resolve2({
4364
- content: `Command timed out after ${actualTimeout}ms
4385
+ if (killedReason === "idle") {
4386
+ resolve2({
4387
+ content: `Command terminated: no output for ${idleTimeout / 1000} seconds (likely waiting for input)
4365
4388
 
4366
4389
  Partial output:
4367
4390
  ${stdout}
4368
4391
 
4369
4392
  Stderr:
4370
4393
  ${stderr}`,
4371
- isError: true
4372
- });
4394
+ isError: true
4395
+ });
4396
+ } else {
4397
+ resolve2({
4398
+ content: `Command timed out after ${actualTimeout}ms
4399
+
4400
+ Partial output:
4401
+ ${stdout}
4402
+
4403
+ Stderr:
4404
+ ${stderr}`,
4405
+ isError: true
4406
+ });
4407
+ }
4373
4408
  return;
4374
4409
  }
4375
4410
  const output = stdout + (stderr ? `
@@ -4390,6 +4425,7 @@ ${output}`,
4390
4425
  });
4391
4426
  proc.on("error", (error) => {
4392
4427
  clearTimeout(timer);
4428
+ clearInterval(idleChecker);
4393
4429
  resolve2({
4394
4430
  content: `Failed to execute command: ${error.message}`,
4395
4431
  isError: true
@@ -6029,7 +6065,7 @@ ${responseText}`;
6029
6065
  metadata: {
6030
6066
  status: response.status,
6031
6067
  statusText: response.statusText,
6032
- headers: Object.fromEntries(response.headers.entries()),
6068
+ headers: Object.fromEntries(response.headers),
6033
6069
  body: responseBody
6034
6070
  }
6035
6071
  };
package/dist/index.js CHANGED
@@ -2129,7 +2129,8 @@ class SessionImpl {
2129
2129
  const toolResult = await tool2.execute(toolInput, context);
2130
2130
  let content = typeof toolResult.content === "string" ? toolResult.content : JSON.stringify(toolResult.content);
2131
2131
  if (needsTruncation(content)) {
2132
- content = await truncateToolOutput(content);
2132
+ const truncationConfig = this.config.tempDir ? { tempDir: this.config.tempDir } : undefined;
2133
+ content = await truncateToolOutput(content, truncationConfig);
2133
2134
  }
2134
2135
  toolResponse = toolResult;
2135
2136
  result = {
@@ -5312,6 +5313,7 @@ function checkDirAccess(dirPath, options2) {
5312
5313
 
5313
5314
  // src/tools/builtin/bash.ts
5314
5315
  var DEFAULT_TIMEOUT = 120000;
5316
+ var DEFAULT_IDLE_TIMEOUT = 30000;
5315
5317
  var MAX_OUTPUT_LENGTH = 1e5;
5316
5318
  var DEFAULT_BLOCKED_PATTERNS = [
5317
5319
  "\\bsudo\\b",
@@ -5378,21 +5380,39 @@ function createBashTool(options2 = {}) {
5378
5380
  }
5379
5381
  }
5380
5382
  const actualTimeout = Math.min(timeout, 600000);
5383
+ const idleTimeout = options2.idleTimeout ?? DEFAULT_IDLE_TIMEOUT;
5381
5384
  return new Promise((resolve2) => {
5382
5385
  let stdout = "";
5383
5386
  let stderr = "";
5384
5387
  let killed = false;
5388
+ let killedReason = null;
5389
+ let lastOutputTime = Date.now();
5385
5390
  const proc = spawn("bash", ["-c", command], {
5386
5391
  cwd: cwdAccess.resolved,
5387
5392
  env: process.env,
5388
- shell: false
5393
+ shell: false,
5394
+ stdio: ["ignore", "pipe", "pipe"]
5389
5395
  });
5390
5396
  const timer = setTimeout(() => {
5391
5397
  killed = true;
5398
+ killedReason = "timeout";
5392
5399
  proc.kill("SIGTERM");
5393
5400
  setTimeout(() => proc.kill("SIGKILL"), 1000);
5394
5401
  }, actualTimeout);
5402
+ const idleChecker = setInterval(() => {
5403
+ const idleTime = Date.now() - lastOutputTime;
5404
+ if (idleTime >= idleTimeout && !killed) {
5405
+ killed = true;
5406
+ killedReason = "idle";
5407
+ proc.kill("SIGTERM");
5408
+ setTimeout(() => proc.kill("SIGKILL"), 1000);
5409
+ }
5410
+ }, 1000);
5411
+ const updateLastOutputTime = () => {
5412
+ lastOutputTime = Date.now();
5413
+ };
5395
5414
  proc.stdout?.on("data", (data) => {
5415
+ updateLastOutputTime();
5396
5416
  stdout += data.toString();
5397
5417
  if (stdout.length > MAX_OUTPUT_LENGTH) {
5398
5418
  stdout = stdout.slice(0, MAX_OUTPUT_LENGTH) + `
@@ -5401,6 +5421,7 @@ function createBashTool(options2 = {}) {
5401
5421
  }
5402
5422
  });
5403
5423
  proc.stderr?.on("data", (data) => {
5424
+ updateLastOutputTime();
5404
5425
  stderr += data.toString();
5405
5426
  if (stderr.length > MAX_OUTPUT_LENGTH) {
5406
5427
  stderr = stderr.slice(0, MAX_OUTPUT_LENGTH) + `
@@ -5409,17 +5430,31 @@ function createBashTool(options2 = {}) {
5409
5430
  });
5410
5431
  proc.on("close", (code) => {
5411
5432
  clearTimeout(timer);
5433
+ clearInterval(idleChecker);
5412
5434
  if (killed) {
5413
- resolve2({
5414
- content: `Command timed out after ${actualTimeout}ms
5435
+ if (killedReason === "idle") {
5436
+ resolve2({
5437
+ content: `Command terminated: no output for ${idleTimeout / 1000} seconds (likely waiting for input)
5415
5438
 
5416
5439
  Partial output:
5417
5440
  ${stdout}
5418
5441
 
5419
5442
  Stderr:
5420
5443
  ${stderr}`,
5421
- isError: true
5422
- });
5444
+ isError: true
5445
+ });
5446
+ } else {
5447
+ resolve2({
5448
+ content: `Command timed out after ${actualTimeout}ms
5449
+
5450
+ Partial output:
5451
+ ${stdout}
5452
+
5453
+ Stderr:
5454
+ ${stderr}`,
5455
+ isError: true
5456
+ });
5457
+ }
5423
5458
  return;
5424
5459
  }
5425
5460
  const output = stdout + (stderr ? `
@@ -5440,6 +5475,7 @@ ${output}`,
5440
5475
  });
5441
5476
  proc.on("error", (error) => {
5442
5477
  clearTimeout(timer);
5478
+ clearInterval(idleChecker);
5443
5479
  resolve2({
5444
5480
  content: `Failed to execute command: ${error.message}`,
5445
5481
  isError: true
@@ -7091,7 +7127,7 @@ ${responseText}`;
7091
7127
  metadata: {
7092
7128
  status: response.status,
7093
7129
  statusText: response.statusText,
7094
- headers: Object.fromEntries(response.headers.entries()),
7130
+ headers: Object.fromEntries(response.headers),
7095
7131
  body: responseBody
7096
7132
  }
7097
7133
  };
package/docs/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # formagent-sdk Documentation
2
+
3
+ Welcome to the documentation for `formagent-sdk`, a Claude Agent SDK compatible framework for building AI agents with streaming support, tool execution, and skill management.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Getting Started](./getting-started.md) - Quick start guide
8
+ - [API Reference](./api-reference.md) - Complete API documentation
9
+ - [Session Storage](./session-storage.md) - Persistent session management
10
+ - [Built-in Tools](./tools.md) - File operations, bash, and more
11
+ - [MCP Servers](./mcp-servers.md) - Model Context Protocol integration
12
+
13
+ ## Quick Links
14
+
15
+ ### Installation
16
+
17
+ ```bash
18
+ npm install formagent-sdk
19
+ # or
20
+ bun add formagent-sdk
21
+ ```
22
+
23
+ ### Environment Setup
24
+
25
+ ```bash
26
+ export ANTHROPIC_API_KEY=your-api-key
27
+ # Optional: Custom endpoint
28
+ export ANTHROPIC_BASE_URL=https://your-proxy.com
29
+ ```
30
+
31
+ ### Minimal Example
32
+
33
+ ```typescript
34
+ import { createSession, builtinTools } from "formagent-sdk"
35
+
36
+ const session = await createSession({
37
+ model: "claude-sonnet-4-20250514",
38
+ tools: builtinTools,
39
+ })
40
+
41
+ await session.send("List files in the current directory")
42
+
43
+ for await (const event of session.receive()) {
44
+ if (event.type === "text") {
45
+ process.stdout.write(event.text)
46
+ }
47
+ }
48
+
49
+ await session.close()
50
+ ```
51
+
52
+ ## Core Concepts
53
+
54
+ ### Sessions
55
+
56
+ Sessions manage conversations with Claude. They handle message history, tool execution, and streaming responses.
57
+
58
+ ```typescript
59
+ const session = await createSession({ model: "claude-sonnet-4-20250514" })
60
+ await session.send("Hello!")
61
+ for await (const event of session.receive()) { /* ... */ }
62
+ await session.close()
63
+ ```
64
+
65
+ ### Tools
66
+
67
+ Tools extend Claude's capabilities. The SDK provides built-in tools and supports custom tool definitions.
68
+
69
+ ```typescript
70
+ import { builtinTools, tool } from "formagent-sdk"
71
+ import { z } from "zod"
72
+
73
+ // Use built-in tools
74
+ const session = await createSession({ tools: builtinTools })
75
+
76
+ // Or create custom tools
77
+ const myTool = tool({
78
+ name: "my_tool",
79
+ description: "Does something useful",
80
+ schema: z.object({ input: z.string() }),
81
+ execute: async ({ input }) => `Result: ${input}`,
82
+ })
83
+ ```
84
+
85
+ ### MCP Servers
86
+
87
+ MCP (Model Context Protocol) servers provide a standardized way to expose tools.
88
+
89
+ ```typescript
90
+ import { createSdkMcpServer, tool } from "formagent-sdk"
91
+
92
+ const server = createSdkMcpServer({
93
+ name: "my-server",
94
+ version: "1.0.0",
95
+ tools: [myTool],
96
+ })
97
+ ```
98
+
99
+ ## Architecture
100
+
101
+ ```
102
+ formagent-sdk
103
+ ├── Session API # Conversation management
104
+ │ ├── createSession() # Create new sessions
105
+ │ ├── send() # Send messages
106
+ │ └── receive() # Stream responses
107
+ ├── Session Storage # Persistence layer
108
+ │ ├── MemorySessionStorage # In-memory (default, non-persistent)
109
+ │ └── FileSessionStorage # File-based persistence
110
+ ├── Tool System # Tool execution
111
+ │ ├── builtinTools # Built-in tools (Bash, Read, Write, etc.)
112
+ │ ├── tool() # Tool definition helper
113
+ │ └── ToolManager # Tool registration and execution
114
+ ├── MCP Integration # Model Context Protocol
115
+ │ ├── createSdkMcpServer()
116
+ │ └── MCPServerManager
117
+ └── Providers # LLM providers
118
+ ├── AnthropicProvider
119
+ ├── OpenAIProvider
120
+ └── GeminiProvider
121
+ ```
122
+
123
+ ## Support
124
+
125
+ - [GitHub Issues](https://github.com/anthropics/claude-code/issues)
126
+ - [Examples](../examples/)