open-agents-ai 0.184.31 → 0.184.33
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 +92 -0
- package/dist/index.js +90 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -239,6 +239,98 @@ Pipe to `jq`, ingest into monitoring systems, or feed to other agents.
|
|
|
239
239
|
|
|
240
240
|
Shows per-process RAM and CPU usage before killing. Detects: cloudflared tunnels, nexus daemons, headless Chrome, TTS servers, Python REPLs, stale OA instances.
|
|
241
241
|
|
|
242
|
+
### REST API Service (Port 11435)
|
|
243
|
+
|
|
244
|
+
Open Agents runs a persistent REST API — like Ollama's `/api/` surface but with agentic task execution, OpenAI compatibility, and full TUI command access.
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
oa serve # Start on default port 11435
|
|
248
|
+
oa serve --port 9999 # Custom port
|
|
249
|
+
OA_API_KEY=secret oa serve # Enable Bearer token auth
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Health & Observability:**
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
curl http://localhost:11435/health # → {"status":"ok","uptime_s":123}
|
|
256
|
+
curl http://localhost:11435/health/ready # → {"status":"ready","ollama":"reachable"} or 503
|
|
257
|
+
curl http://localhost:11435/version # → {"version":"0.184.31","node":"v24.14.0","platform":"linux"}
|
|
258
|
+
curl http://localhost:11435/metrics # → Prometheus text format (request counts, tokens, errors)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**OpenAI-Compatible Inference** (drop-in for any OpenAI client library):
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
# List models (52 models in OpenAI format)
|
|
265
|
+
curl http://localhost:11435/v1/models
|
|
266
|
+
|
|
267
|
+
# Chat completions (non-streaming)
|
|
268
|
+
curl -X POST http://localhost:11435/v1/chat/completions \
|
|
269
|
+
-H "Content-Type: application/json" \
|
|
270
|
+
-d '{"model":"qwen3.5:9b","messages":[{"role":"user","content":"Hello"}]}'
|
|
271
|
+
# → {"id":"chatcmpl-...","choices":[{"message":{"role":"assistant","content":"Hi there!"}}],"usage":{"prompt_tokens":25,"completion_tokens":5}}
|
|
272
|
+
|
|
273
|
+
# Chat completions (SSE streaming)
|
|
274
|
+
curl -N -X POST http://localhost:11435/v1/chat/completions \
|
|
275
|
+
-H "Content-Type: application/json" \
|
|
276
|
+
-d '{"model":"qwen3.5:9b","messages":[{"role":"user","content":"Hello"}],"stream":true}'
|
|
277
|
+
# → data: {"choices":[{"delta":{"content":"Hi"}}]}\n\ndata: {"choices":[{"delta":{"content":" there"}}]}\n\n...data: [DONE]
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Agentic Task Execution** (the unique OA capability):
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# Submit an agentic task
|
|
284
|
+
curl -X POST http://localhost:11435/v1/run \
|
|
285
|
+
-H "Content-Type: application/json" \
|
|
286
|
+
-d '{"prompt":"fix the null check in auth.ts","model":"qwen3.5:9b","stream":true}'
|
|
287
|
+
|
|
288
|
+
# List runs
|
|
289
|
+
curl http://localhost:11435/v1/runs
|
|
290
|
+
|
|
291
|
+
# Abort a run
|
|
292
|
+
curl -X DELETE http://localhost:11435/v1/runs/run-abc123
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Configuration:**
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
curl http://localhost:11435/v1/config # Get all settings
|
|
299
|
+
curl http://localhost:11435/v1/config/model # → {"model":"qwen3.5:122b"}
|
|
300
|
+
curl -X PUT http://localhost:11435/v1/config/model \
|
|
301
|
+
-H "Content-Type: application/json" -d '{"model":"qwen3.5:27b"}'
|
|
302
|
+
curl http://localhost:11435/v1/config/endpoint # → {"url":"...","backendType":"..."}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**Slash Commands via REST:**
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
curl http://localhost:11435/v1/commands # List all 100+ commands
|
|
309
|
+
curl -X POST http://localhost:11435/v1/commands/stats # Execute /stats
|
|
310
|
+
curl -X POST http://localhost:11435/v1/commands/nexus # Execute /nexus status
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
| Method | Endpoint | Description |
|
|
314
|
+
|--------|----------|-------------|
|
|
315
|
+
| GET | `/health` | Liveness check |
|
|
316
|
+
| GET | `/health/ready` | Readiness (probes Ollama) |
|
|
317
|
+
| GET | `/health/startup` | Startup complete |
|
|
318
|
+
| GET | `/version` | OA + Node version |
|
|
319
|
+
| GET | `/metrics` | Prometheus metrics |
|
|
320
|
+
| GET | `/v1/models` | List models (OpenAI format) |
|
|
321
|
+
| POST | `/v1/chat/completions` | Chat inference (OpenAI compatible, streaming + non-streaming) |
|
|
322
|
+
| POST | `/v1/embeddings` | Generate embeddings |
|
|
323
|
+
| POST | `/v1/run` | Submit agentic task |
|
|
324
|
+
| GET | `/v1/runs` | List runs |
|
|
325
|
+
| GET | `/v1/runs/:id` | Run status |
|
|
326
|
+
| DELETE | `/v1/runs/:id` | Abort run |
|
|
327
|
+
| GET | `/v1/config` | All settings |
|
|
328
|
+
| PATCH | `/v1/config` | Update settings |
|
|
329
|
+
| GET/PUT | `/v1/config/model` | Current/switch model |
|
|
330
|
+
| GET/PUT | `/v1/config/endpoint` | Current/switch endpoint |
|
|
331
|
+
| GET | `/v1/commands` | List slash commands |
|
|
332
|
+
| POST | `/v1/commands/:cmd` | Execute command |
|
|
333
|
+
|
|
242
334
|
### Enterprise Licensing
|
|
243
335
|
|
|
244
336
|
Free for non-commercial use under CC-BY-NC-4.0. For enterprise/commercial licensing, contact [zoomerconsulting.com](https://zoomerconsulting.com).
|
package/dist/index.js
CHANGED
|
@@ -67254,15 +67254,46 @@ function listJobs() {
|
|
|
67254
67254
|
}
|
|
67255
67255
|
return jobs;
|
|
67256
67256
|
}
|
|
67257
|
-
function
|
|
67258
|
-
const apiKey = process.env["OA_API_KEY"];
|
|
67259
|
-
if (!apiKey)
|
|
67260
|
-
return true;
|
|
67257
|
+
function resolveAuth(req) {
|
|
67261
67258
|
const authHeader = req.headers["authorization"];
|
|
67262
|
-
|
|
67259
|
+
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
67260
|
+
const multiKeys = process.env["OA_API_KEYS"];
|
|
67261
|
+
if (multiKeys) {
|
|
67262
|
+
if (!token)
|
|
67263
|
+
return { authenticated: false, scope: "read" };
|
|
67264
|
+
for (const entry of multiKeys.split(",")) {
|
|
67265
|
+
const [key, scope, user] = entry.trim().split(":");
|
|
67266
|
+
if (key === token) {
|
|
67267
|
+
return { authenticated: true, scope: scope || "admin", user: user || void 0 };
|
|
67268
|
+
}
|
|
67269
|
+
}
|
|
67270
|
+
return { authenticated: false, scope: "read" };
|
|
67271
|
+
}
|
|
67272
|
+
const singleKey = process.env["OA_API_KEY"];
|
|
67273
|
+
if (!singleKey)
|
|
67274
|
+
return { authenticated: true, scope: "admin" };
|
|
67275
|
+
if (token === singleKey)
|
|
67276
|
+
return { authenticated: true, scope: "admin" };
|
|
67277
|
+
return { authenticated: false, scope: "read" };
|
|
67278
|
+
}
|
|
67279
|
+
function checkAuth(req, res, requiredScope = "read") {
|
|
67280
|
+
const scopeLevel = { read: 1, run: 2, admin: 3 };
|
|
67281
|
+
const auth = resolveAuth(req);
|
|
67282
|
+
if (!auth.authenticated) {
|
|
67263
67283
|
jsonResponse(res, 401, { error: "Unauthorized", message: "Invalid or missing Bearer token" });
|
|
67264
67284
|
return false;
|
|
67265
67285
|
}
|
|
67286
|
+
if (scopeLevel[auth.scope] < scopeLevel[requiredScope]) {
|
|
67287
|
+
jsonResponse(res, 403, {
|
|
67288
|
+
error: "Forbidden",
|
|
67289
|
+
message: `Scope '${auth.scope}' insufficient \u2014 requires '${requiredScope}'`,
|
|
67290
|
+
scope: auth.scope,
|
|
67291
|
+
required: requiredScope
|
|
67292
|
+
});
|
|
67293
|
+
return false;
|
|
67294
|
+
}
|
|
67295
|
+
req._authUser = auth.user;
|
|
67296
|
+
req._authScope = auth.scope;
|
|
67266
67297
|
return true;
|
|
67267
67298
|
}
|
|
67268
67299
|
function handleHealth(res) {
|
|
@@ -67534,7 +67565,20 @@ async function handleV1Run(req, res) {
|
|
|
67534
67565
|
}
|
|
67535
67566
|
const id = `job-${randomBytes16(3).toString("hex")}`;
|
|
67536
67567
|
const dir = jobsDir2();
|
|
67537
|
-
const
|
|
67568
|
+
const workingDir = requestBody["working_directory"];
|
|
67569
|
+
const isolate = requestBody["isolate"] === true;
|
|
67570
|
+
let cwd4;
|
|
67571
|
+
if (workingDir) {
|
|
67572
|
+
cwd4 = resolve35(workingDir);
|
|
67573
|
+
} else if (isolate) {
|
|
67574
|
+
const wsDir = join71(dir, "..", "workspaces", id);
|
|
67575
|
+
mkdirSync26(wsDir, { recursive: true });
|
|
67576
|
+
cwd4 = wsDir;
|
|
67577
|
+
} else {
|
|
67578
|
+
cwd4 = resolve35(process.cwd());
|
|
67579
|
+
}
|
|
67580
|
+
const authUser = req._authUser || "anonymous";
|
|
67581
|
+
const authScope = req._authScope || "admin";
|
|
67538
67582
|
const job = {
|
|
67539
67583
|
id,
|
|
67540
67584
|
pid: 0,
|
|
@@ -67542,14 +67586,39 @@ async function handleV1Run(req, res) {
|
|
|
67542
67586
|
status: "running",
|
|
67543
67587
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
67544
67588
|
};
|
|
67589
|
+
job.user = authUser;
|
|
67590
|
+
job.scope = authScope;
|
|
67591
|
+
job.cwd = cwd4;
|
|
67592
|
+
job.isolate = isolate;
|
|
67545
67593
|
const oaBin = process.argv[1] || "oa";
|
|
67546
67594
|
const args = [task, "--json"];
|
|
67547
67595
|
const modelArg = requestBody["model"];
|
|
67548
67596
|
if (modelArg)
|
|
67549
67597
|
args.push("--model", modelArg);
|
|
67598
|
+
const maxTurns = requestBody["max_turns"];
|
|
67599
|
+
if (maxTurns && maxTurns > 0)
|
|
67600
|
+
args.push("--max-turns", String(maxTurns));
|
|
67601
|
+
const timeout = requestBody["timeout_s"];
|
|
67602
|
+
if (timeout && timeout > 0)
|
|
67603
|
+
args.push("--timeout", String(timeout));
|
|
67604
|
+
const runEnv = {
|
|
67605
|
+
...process.env,
|
|
67606
|
+
OA_JOB_ID: id,
|
|
67607
|
+
__OPEN_AGENTS_NO_AUTO_RUN: "",
|
|
67608
|
+
OA_RUN_USER: authUser,
|
|
67609
|
+
OA_RUN_SCOPE: authScope
|
|
67610
|
+
};
|
|
67611
|
+
const customEnv = requestBody["env"];
|
|
67612
|
+
if (customEnv && typeof customEnv === "object") {
|
|
67613
|
+
for (const [k, v] of Object.entries(customEnv)) {
|
|
67614
|
+
if (k.startsWith("OA_") && typeof v === "string") {
|
|
67615
|
+
runEnv[k] = v;
|
|
67616
|
+
}
|
|
67617
|
+
}
|
|
67618
|
+
}
|
|
67550
67619
|
const child = spawn21("node", [oaBin, ...args], {
|
|
67551
|
-
cwd:
|
|
67552
|
-
env:
|
|
67620
|
+
cwd: cwd4,
|
|
67621
|
+
env: runEnv,
|
|
67553
67622
|
stdio: ["ignore", "pipe", "pipe"],
|
|
67554
67623
|
detached: true
|
|
67555
67624
|
});
|
|
@@ -67829,7 +67898,12 @@ async function handleRequest(req, res, ollamaUrl, verbose) {
|
|
|
67829
67898
|
return;
|
|
67830
67899
|
}
|
|
67831
67900
|
if (pathname.startsWith("/v1/")) {
|
|
67832
|
-
|
|
67901
|
+
const isWrite = method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE";
|
|
67902
|
+
const isRun = pathname === "/v1/run" || pathname.startsWith("/v1/runs");
|
|
67903
|
+
const isConfigWrite = pathname.startsWith("/v1/config") && isWrite;
|
|
67904
|
+
const isCommand = pathname.startsWith("/v1/commands") && method === "POST";
|
|
67905
|
+
const requiredScope = isConfigWrite ? "admin" : isRun || isCommand ? "run" : "read";
|
|
67906
|
+
if (!checkAuth(req, res, requiredScope)) {
|
|
67833
67907
|
status = 401;
|
|
67834
67908
|
return;
|
|
67835
67909
|
}
|
|
@@ -67956,11 +68030,15 @@ function startApiServer(options = {}) {
|
|
|
67956
68030
|
`);
|
|
67957
68031
|
process.stderr.write(` Ollama proxy: ${ollamaUrl}
|
|
67958
68032
|
`);
|
|
67959
|
-
if (process.env["
|
|
67960
|
-
process.
|
|
68033
|
+
if (process.env["OA_API_KEYS"]) {
|
|
68034
|
+
const keyCount = process.env["OA_API_KEYS"].split(",").length;
|
|
68035
|
+
process.stderr.write(` Auth: ${keyCount} scoped key(s) (read/run/admin)
|
|
68036
|
+
`);
|
|
68037
|
+
} else if (process.env["OA_API_KEY"]) {
|
|
68038
|
+
process.stderr.write(` Auth: single Bearer token (admin scope)
|
|
67961
68039
|
`);
|
|
67962
68040
|
} else {
|
|
67963
|
-
process.stderr.write(` Auth: disabled (set OA_API_KEY to enable)
|
|
68041
|
+
process.stderr.write(` Auth: disabled (set OA_API_KEY or OA_API_KEYS to enable)
|
|
67964
68042
|
`);
|
|
67965
68043
|
}
|
|
67966
68044
|
process.stderr.write(`
|
package/package.json
CHANGED