@neeter/server 0.7.1 → 0.8.1
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 +81 -0
- package/dist/hooks.d.ts +10 -0
- package/dist/hooks.js +30 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/router.js +7 -0
- package/dist/session.d.ts +4 -1
- package/dist/session.js +4 -1
- package/dist/translator.js +3 -0
- package/package.json +5 -3
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# @neeter/server
|
|
2
|
+
|
|
3
|
+
A Hono server toolkit that puts a browser UI on the [Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk). Manages multi-turn sessions, translates SDK messages into named SSE events, and handles tool-approval permissions — so your React client gets a clean event stream out of the box.
|
|
4
|
+
|
|
5
|
+
Part of the [neeter](https://github.com/quantumleeps/neeter) toolkit.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @neeter/server
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Peer dependencies:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"@anthropic-ai/claude-agent-sdk": ">=0.2.0",
|
|
18
|
+
"hono": ">=4.0.0"
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { Hono } from "hono";
|
|
26
|
+
import { serve } from "@hono/node-server";
|
|
27
|
+
import {
|
|
28
|
+
createAgentRouter,
|
|
29
|
+
SessionManager,
|
|
30
|
+
MessageTranslator,
|
|
31
|
+
} from "@neeter/server";
|
|
32
|
+
|
|
33
|
+
const sessions = new SessionManager(() => ({
|
|
34
|
+
context: {},
|
|
35
|
+
model: "claude-sonnet-4-5-20250929",
|
|
36
|
+
systemPrompt: "You are a helpful assistant.",
|
|
37
|
+
maxTurns: 50,
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
const translator = new MessageTranslator();
|
|
41
|
+
|
|
42
|
+
const app = new Hono();
|
|
43
|
+
app.route("/", createAgentRouter({ sessions, translator }));
|
|
44
|
+
|
|
45
|
+
serve({ fetch: app.fetch, port: 3000 });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This gives you five endpoints:
|
|
49
|
+
|
|
50
|
+
| Method | Path | Description |
|
|
51
|
+
|--------|------|-------------|
|
|
52
|
+
| `POST` | `/api/sessions` | Create a session |
|
|
53
|
+
| `POST` | `/api/sessions/:id/messages` | Send a message |
|
|
54
|
+
| `GET` | `/api/sessions/:id/events` | SSE event stream |
|
|
55
|
+
| `POST` | `/api/sessions/:id/permissions` | Respond to a permission request |
|
|
56
|
+
| `POST` | `/api/sessions/:id/abort` | Abort the current turn |
|
|
57
|
+
|
|
58
|
+
## Key features
|
|
59
|
+
|
|
60
|
+
- **Multi-turn sessions** — `SessionManager` + `PushChannel` let users send messages at any time, even while the agent is running.
|
|
61
|
+
- **Named SSE events** — `MessageTranslator` reshapes the SDK's flat message stream into `text_delta`, `tool_start`, `tool_call`, `tool_result`, and more.
|
|
62
|
+
- **Tool result hooks** — `onToolResult` lets you inspect what the agent did and emit structured custom events.
|
|
63
|
+
- **Permission modes** — `bypassPermissions`, `default`, `acceptEdits`, or `plan` — with browser-side approval via `PermissionGate`.
|
|
64
|
+
- **Extended thinking** — Pass `thinking: { type: "enabled", budgetTokens: N }` to stream chain-of-thought reasoning.
|
|
65
|
+
- **Abort** — Cancel the current agent turn mid-stream.
|
|
66
|
+
- **Sandbox hooks** — `createSandboxHook` restricts file operations to a directory.
|
|
67
|
+
|
|
68
|
+
## Examples
|
|
69
|
+
|
|
70
|
+
| Example | Description |
|
|
71
|
+
|---------|-------------|
|
|
72
|
+
| [basic-chat](https://github.com/quantumleeps/neeter/tree/main/examples/basic-chat) | Minimal server + client setup |
|
|
73
|
+
| [live-preview](https://github.com/quantumleeps/neeter/tree/main/examples/live-preview) | Per-session sandboxes, custom events, abort |
|
|
74
|
+
|
|
75
|
+
## Documentation
|
|
76
|
+
|
|
77
|
+
See the [neeter README](https://github.com/quantumleeps/neeter#readme) for full API reference, session context examples, and permission configuration.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { HookCallbackMatcher } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a PreToolUse hook that blocks file operations outside a sandbox directory.
|
|
4
|
+
* Inspects `file_path` and `path` fields in tool input and blocks any resolved path
|
|
5
|
+
* that falls outside the given directory.
|
|
6
|
+
*
|
|
7
|
+
* @param sandboxDir - Absolute path to the sandbox directory (must already be resolved)
|
|
8
|
+
* @param resolvePath - Path resolver function (e.g. `path.resolve` from `node:path`)
|
|
9
|
+
*/
|
|
10
|
+
export declare function createSandboxHook(sandboxDir: string, resolvePath: (...segments: string[]) => string): HookCallbackMatcher[];
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a PreToolUse hook that blocks file operations outside a sandbox directory.
|
|
3
|
+
* Inspects `file_path` and `path` fields in tool input and blocks any resolved path
|
|
4
|
+
* that falls outside the given directory.
|
|
5
|
+
*
|
|
6
|
+
* @param sandboxDir - Absolute path to the sandbox directory (must already be resolved)
|
|
7
|
+
* @param resolvePath - Path resolver function (e.g. `path.resolve` from `node:path`)
|
|
8
|
+
*/
|
|
9
|
+
export function createSandboxHook(sandboxDir, resolvePath) {
|
|
10
|
+
const normalizedDir = resolvePath(sandboxDir);
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
hooks: [
|
|
14
|
+
async (input) => {
|
|
15
|
+
if (input.hook_event_name !== "PreToolUse")
|
|
16
|
+
return {};
|
|
17
|
+
const toolInput = input.tool_input;
|
|
18
|
+
const filePath = (toolInput.file_path ?? toolInput.path);
|
|
19
|
+
if (!filePath)
|
|
20
|
+
return {};
|
|
21
|
+
const resolved = resolvePath(filePath);
|
|
22
|
+
if (resolved !== normalizedDir && !resolved.startsWith(`${normalizedDir}/`)) {
|
|
23
|
+
return { decision: "block", reason: "Access outside sandbox directory is not allowed" };
|
|
24
|
+
}
|
|
25
|
+
return {};
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/router.js
CHANGED
|
@@ -63,5 +63,12 @@ export function createAgentRouter(config) {
|
|
|
63
63
|
session.lastActivityAt = Date.now();
|
|
64
64
|
return c.json({ ok: true });
|
|
65
65
|
});
|
|
66
|
+
app.post(`${basePath}/sessions/:id/abort`, (c) => {
|
|
67
|
+
const session = sessions.get(c.req.param("id"));
|
|
68
|
+
if (!session)
|
|
69
|
+
return c.json({ error: "Session not found" }, 404);
|
|
70
|
+
session.abort();
|
|
71
|
+
return c.json({ ok: true });
|
|
72
|
+
});
|
|
66
73
|
return app;
|
|
67
74
|
}
|
package/dist/session.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
1
|
+
import { type HookCallbackMatcher, type HookEvent, query } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
import { PermissionGate } from "./permission-gate.js";
|
|
3
3
|
type SDKMessage = ReturnType<typeof query> extends AsyncGenerator<infer T> ? T : never;
|
|
4
4
|
export interface SessionInit<TCtx> {
|
|
@@ -8,7 +8,9 @@ export interface SessionInit<TCtx> {
|
|
|
8
8
|
mcpServers?: Record<string, unknown>;
|
|
9
9
|
tools?: unknown[];
|
|
10
10
|
allowedTools?: string[];
|
|
11
|
+
disallowedTools?: string[];
|
|
11
12
|
maxTurns?: number;
|
|
13
|
+
cwd?: string;
|
|
12
14
|
permissionMode?: "default" | "acceptEdits" | "plan" | "bypassPermissions";
|
|
13
15
|
thinking?: {
|
|
14
16
|
type: "enabled";
|
|
@@ -16,6 +18,7 @@ export interface SessionInit<TCtx> {
|
|
|
16
18
|
} | {
|
|
17
19
|
type: "disabled";
|
|
18
20
|
};
|
|
21
|
+
hooks?: Partial<Record<HookEvent, HookCallbackMatcher[]>>;
|
|
19
22
|
}
|
|
20
23
|
export interface Session<TCtx> {
|
|
21
24
|
id: string;
|
package/dist/session.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
1
|
+
import { query, } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
import { PermissionGate } from "./permission-gate.js";
|
|
3
3
|
import { PushChannel } from "./push-channel.js";
|
|
4
4
|
function userMessage(content) {
|
|
@@ -74,6 +74,9 @@ export class SessionManager {
|
|
|
74
74
|
abortController,
|
|
75
75
|
...(canUseTool ? { canUseTool } : {}),
|
|
76
76
|
...(init.thinking ? { thinking: init.thinking } : {}),
|
|
77
|
+
...(init.cwd ? { cwd: init.cwd } : {}),
|
|
78
|
+
...(init.disallowedTools ? { disallowedTools: init.disallowedTools } : {}),
|
|
79
|
+
...(init.hooks ? { hooks: init.hooks } : {}),
|
|
77
80
|
},
|
|
78
81
|
});
|
|
79
82
|
const session = {
|
package/dist/translator.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neeter/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Hono server toolkit for building chat UIs on top of the Claude Agent SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Dan Leeper",
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
21
22
|
],
|
|
22
23
|
"dependencies": {
|
|
23
|
-
"@neeter/types": "0.
|
|
24
|
+
"@neeter/types": "0.8.1"
|
|
24
25
|
},
|
|
25
26
|
"peerDependencies": {
|
|
26
27
|
"@anthropic-ai/claude-agent-sdk": ">=0.2.0",
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@anthropic-ai/claude-agent-sdk": "latest",
|
|
32
|
+
"@types/node": "^22.19.11",
|
|
31
33
|
"hono": "^4.11.9"
|
|
32
34
|
},
|
|
33
35
|
"scripts": {
|