@rebyteai/agent-server 0.2.0
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/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +122 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rebyte, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @rebyteai/agent-server
|
|
2
|
+
|
|
3
|
+
Install the versioned package (no repository clone required):
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm add @rebyteai/agent-server@0.2.0
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
A shared Hono application proxy for the Node and Cloudflare App Kit examples.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { createAgentApp } from '@rebyteai/agent-server'
|
|
14
|
+
const app = createAgentApp({ apiKey: process.env.REBYTE_API_KEY!,
|
|
15
|
+
agentId: process.env.REBYTE_AGENT_ID! })
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`/api/sessions` creates hosted Sessions for the configured saved Agent. Routes
|
|
19
|
+
proxy retrieval, deletion, live events, input submission, Items, Turns, Artifact
|
|
20
|
+
list/download and inline file upload. The upload limit is 5 MiB. Event submission
|
|
21
|
+
requires `Idempotency-Key`. API errors retain their HTTP status.
|
|
22
|
+
|
|
23
|
+
This is an example boundary, not an authentication framework. Mount behind login
|
|
24
|
+
and authorize every Session against the current user in your application. The
|
|
25
|
+
included Agent-ID check only prevents use of a different Agent. Keys stay on the
|
|
26
|
+
server. [Architecture](../../docs/architecture.md).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as hono_types from 'hono/types';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
|
|
4
|
+
interface AgentAppOptions {
|
|
5
|
+
apiKey: string;
|
|
6
|
+
agentId: string;
|
|
7
|
+
baseURL?: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
/** Same-origin example proxy. Mount behind your application's user authentication. */
|
|
10
|
+
declare function createAgentApp(options: AgentAppOptions): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
11
|
+
|
|
12
|
+
export { type AgentAppOptions, createAgentApp };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { HTTPException } from "hono/http-exception";
|
|
4
|
+
import Rebyte, { rebyteSandbox } from "@rebyteai/agent-sdk";
|
|
5
|
+
var maxFileSize = 5 * 1024 * 1024;
|
|
6
|
+
var invalid = (message) => new HTTPException(400, { message });
|
|
7
|
+
function createAgentApp(options) {
|
|
8
|
+
if (!options.apiKey || !options.agentId) throw new Error("API key and Agent ID are required");
|
|
9
|
+
const client = new Rebyte({ apiKey: options.apiKey, baseURL: options.baseURL, maxRetries: 0 });
|
|
10
|
+
const sessions = client.beta.agents.sessions;
|
|
11
|
+
const app = new Hono();
|
|
12
|
+
async function owned(id) {
|
|
13
|
+
const session = await sessions.retrieve(id);
|
|
14
|
+
if (session.agent.id !== options.agentId) throw new HTTPException(404, { message: "Session not found" });
|
|
15
|
+
return session;
|
|
16
|
+
}
|
|
17
|
+
app.get("/api/health", (c) => c.json({ ok: true, api: "agents", agentId: options.agentId }));
|
|
18
|
+
app.post("/api/sessions", async (c) => {
|
|
19
|
+
const session = await sessions.create({ agent_id: options.agentId, environment: rebyteSandbox() });
|
|
20
|
+
return c.json(session, 201);
|
|
21
|
+
});
|
|
22
|
+
app.get("/api/sessions/:id", async (c) => c.json(await owned(c.req.param("id"))));
|
|
23
|
+
app.delete("/api/sessions/:id", async (c) => {
|
|
24
|
+
await owned(c.req.param("id"));
|
|
25
|
+
return c.json(await sessions.delete(c.req.param("id")));
|
|
26
|
+
});
|
|
27
|
+
app.get("/api/sessions/:id/events", async (c) => {
|
|
28
|
+
await owned(c.req.param("id"));
|
|
29
|
+
const response = await sessions.events.stream(c.req.param("id"), { signal: c.req.raw.signal }).asResponse();
|
|
30
|
+
return new Response(response.body, { headers: {
|
|
31
|
+
"Content-Type": "text/event-stream",
|
|
32
|
+
"Cache-Control": "no-cache, no-transform",
|
|
33
|
+
"X-Accel-Buffering": "no"
|
|
34
|
+
} });
|
|
35
|
+
});
|
|
36
|
+
app.post("/api/sessions/:id/events", async (c) => {
|
|
37
|
+
await owned(c.req.param("id"));
|
|
38
|
+
const body = await c.req.json();
|
|
39
|
+
if (!body || typeof body !== "object" || !("events" in body) || !Array.isArray(body.events)) throw invalid("events must be an array");
|
|
40
|
+
const key = c.req.header("Idempotency-Key");
|
|
41
|
+
if (!key) throw invalid("Idempotency-Key is required");
|
|
42
|
+
await sessions.events.create(c.req.param("id"), { events: body.events, "Idempotency-Key": key });
|
|
43
|
+
return c.body(null, 204);
|
|
44
|
+
});
|
|
45
|
+
app.get("/api/sessions/:id/items", async (c) => {
|
|
46
|
+
await owned(c.req.param("id"));
|
|
47
|
+
const items = [];
|
|
48
|
+
for await (const item of sessions.items.list(c.req.param("id"), { order: "asc", limit: 100 })) items.push(item);
|
|
49
|
+
return c.json({ data: items });
|
|
50
|
+
});
|
|
51
|
+
app.get("/api/sessions/:id/turns", async (c) => {
|
|
52
|
+
await owned(c.req.param("id"));
|
|
53
|
+
const turns = [];
|
|
54
|
+
for await (const turn of sessions.turns.list(c.req.param("id"), { order: "asc", limit: 100 })) turns.push(turn);
|
|
55
|
+
return c.json({ data: turns });
|
|
56
|
+
});
|
|
57
|
+
app.get("/api/sessions/:id/artifacts", async (c) => {
|
|
58
|
+
await owned(c.req.param("id"));
|
|
59
|
+
const artifacts = [];
|
|
60
|
+
for await (const artifact of sessions.artifacts.list(c.req.param("id"), { order: "asc", limit: 100 })) artifacts.push(artifact);
|
|
61
|
+
return c.json({ data: artifacts });
|
|
62
|
+
});
|
|
63
|
+
app.get("/api/sessions/:id/artifacts/:artifactId/content", async (c) => {
|
|
64
|
+
await owned(c.req.param("id"));
|
|
65
|
+
const params = { session_id: c.req.param("id") };
|
|
66
|
+
const artifact = await sessions.artifacts.retrieve(c.req.param("artifactId"), params);
|
|
67
|
+
const response = await sessions.artifacts.content(artifact.id, params);
|
|
68
|
+
return new Response(response.body, { headers: {
|
|
69
|
+
"Content-Type": "application/octet-stream",
|
|
70
|
+
"Content-Disposition": `attachment; filename*=UTF-8''${encodeURIComponent(artifact.path.split("/").at(-1))}`,
|
|
71
|
+
"Cache-Control": "private, no-store"
|
|
72
|
+
} });
|
|
73
|
+
});
|
|
74
|
+
app.post("/api/sessions/:id/files", async (c) => {
|
|
75
|
+
const session = await owned(c.req.param("id"));
|
|
76
|
+
if (session.environment.type !== "openai_hosted") throw invalid("Session has no managed environment");
|
|
77
|
+
const filename = c.req.query("filename");
|
|
78
|
+
if (!filename || !filename.trim() || /[\\/\x00-\x1f]/.test(filename) || filename.length > 200) throw invalid("Invalid filename");
|
|
79
|
+
const length = c.req.header("Content-Length");
|
|
80
|
+
if (length !== void 0 && (!/^\d+$/.test(length) || Number(length) > maxFileSize)) throw new HTTPException(413, { message: "File exceeds the 5 MiB upload limit" });
|
|
81
|
+
if (!c.req.raw.body) throw invalid("File body is required");
|
|
82
|
+
const reader = c.req.raw.body.getReader();
|
|
83
|
+
const chunks = [];
|
|
84
|
+
let size = 0;
|
|
85
|
+
try {
|
|
86
|
+
while (true) {
|
|
87
|
+
const { done, value } = await reader.read();
|
|
88
|
+
if (done) break;
|
|
89
|
+
size += value.length;
|
|
90
|
+
if (size > maxFileSize) {
|
|
91
|
+
await reader.cancel();
|
|
92
|
+
throw new HTTPException(413, { message: "File exceeds the 5 MiB upload limit" });
|
|
93
|
+
}
|
|
94
|
+
chunks.push(value);
|
|
95
|
+
}
|
|
96
|
+
} finally {
|
|
97
|
+
reader.releaseLock();
|
|
98
|
+
}
|
|
99
|
+
const path = `/workspace/uploads/${crypto.randomUUID()}/${filename}`;
|
|
100
|
+
const bytes = new Uint8Array(size);
|
|
101
|
+
let offset = 0;
|
|
102
|
+
for (const chunk of chunks) {
|
|
103
|
+
bytes.set(chunk, offset);
|
|
104
|
+
offset += chunk.length;
|
|
105
|
+
}
|
|
106
|
+
let binary = "";
|
|
107
|
+
for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode(...bytes.subarray(i, i + 8192));
|
|
108
|
+
const file = await client.beta.agents.environments.files.create(session.environment.id, { type: "inline", path, data: btoa(binary) });
|
|
109
|
+
return c.json({ ...file, session_id: session.id, filename, content_type: c.req.header("Content-Type") ?? "application/octet-stream", max_file_size: maxFileSize }, 201);
|
|
110
|
+
});
|
|
111
|
+
app.onError((error, c) => {
|
|
112
|
+
if (error instanceof HTTPException) return c.json({ error: { message: error.message } }, error.status);
|
|
113
|
+
if (error instanceof SyntaxError) return c.json({ error: { message: "Invalid JSON" } }, 400);
|
|
114
|
+
if (error instanceof Rebyte.APIError) return c.json({ error: { message: error.message, code: error.code } }, error.status >= 400 && error.status < 600 ? error.status : 502);
|
|
115
|
+
console.error("Agents app request failed", error);
|
|
116
|
+
return c.json({ error: { message: "The example server failed" } }, 500);
|
|
117
|
+
});
|
|
118
|
+
return app;
|
|
119
|
+
}
|
|
120
|
+
export {
|
|
121
|
+
createAgentApp
|
|
122
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rebyteai/agent-server",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"README.md"
|
|
9
|
+
],
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"hono": "^4.9.9",
|
|
20
|
+
"@rebyteai/agent-sdk": "0.2.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsup": "^8.5.0",
|
|
24
|
+
"typescript": "^5.9.3"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"registry": "https://registry.npmjs.org"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/ReByteAI/rebyte-agent-toolkit.git",
|
|
33
|
+
"directory": "packages/server"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
37
|
+
"typecheck": "tsc --noEmit"
|
|
38
|
+
}
|
|
39
|
+
}
|