open-agents-ai 0.184.32 → 0.184.34
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 +251 -52
- package/dist/index.js +90 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -244,92 +244,291 @@ Shows per-process RAM and CPU usage before killing. Detects: cloudflared tunnels
|
|
|
244
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
245
|
|
|
246
246
|
```bash
|
|
247
|
-
oa serve
|
|
248
|
-
oa serve --port 9999
|
|
249
|
-
OA_API_KEY=
|
|
247
|
+
oa serve # Start on default port 11435
|
|
248
|
+
oa serve --port 9999 # Custom port
|
|
249
|
+
OA_API_KEY=mysecret oa serve # Single admin key
|
|
250
|
+
OA_API_KEYS="key1:admin:alice,key2:run:ci,key3:read:grafana" oa serve # Scoped multi-key
|
|
250
251
|
```
|
|
251
252
|
|
|
252
|
-
|
|
253
|
+
#### Working Directory
|
|
254
|
+
|
|
255
|
+
Pass `X-Working-Directory` header to run commands in your current terminal directory:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Auto-inject current dir — agent operates on YOUR project, not the server's cwd
|
|
259
|
+
curl -X POST http://localhost:11435/v1/run \
|
|
260
|
+
-H "X-Working-Directory: $(pwd)" \
|
|
261
|
+
-H "Content-Type: application/json" \
|
|
262
|
+
-d '{"task":"fix all lint errors"}'
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Or set it in the JSON body: `"working_directory": "/path/to/project"`
|
|
266
|
+
|
|
267
|
+
#### Health & Observability
|
|
253
268
|
|
|
254
269
|
```bash
|
|
255
|
-
|
|
256
|
-
curl http://localhost:11435/health
|
|
257
|
-
|
|
258
|
-
|
|
270
|
+
# Liveness
|
|
271
|
+
curl http://localhost:11435/health
|
|
272
|
+
```
|
|
273
|
+
```json
|
|
274
|
+
{"status":"ok","uptime_s":142,"version":"0.184.33"}
|
|
259
275
|
```
|
|
260
276
|
|
|
261
|
-
|
|
277
|
+
```bash
|
|
278
|
+
# Readiness (probes Ollama backend)
|
|
279
|
+
curl http://localhost:11435/health/ready
|
|
280
|
+
```
|
|
281
|
+
```json
|
|
282
|
+
{"status":"ready","ollama":"reachable"}
|
|
283
|
+
```
|
|
262
284
|
|
|
263
285
|
```bash
|
|
264
|
-
#
|
|
286
|
+
# Version info
|
|
287
|
+
curl http://localhost:11435/version
|
|
288
|
+
```
|
|
289
|
+
```json
|
|
290
|
+
{"version":"0.184.33","node":"v24.14.0","platform":"linux"}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# Prometheus metrics (scrape with Grafana/Prometheus)
|
|
295
|
+
curl http://localhost:11435/metrics
|
|
296
|
+
```
|
|
297
|
+
```
|
|
298
|
+
# HELP oa_requests_total Total HTTP requests
|
|
299
|
+
# TYPE oa_requests_total counter
|
|
300
|
+
oa_requests_total{method="POST",path="/v1/chat/completions",status="200"} 47
|
|
301
|
+
oa_tokens_in_total 12450
|
|
302
|
+
oa_tokens_out_total 8230
|
|
303
|
+
oa_errors_total 0
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### OpenAI-Compatible Inference
|
|
307
|
+
|
|
308
|
+
Drop-in replacement for any OpenAI client library. Change `api.openai.com` → `localhost:11435`.
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# List models
|
|
265
312
|
curl http://localhost:11435/v1/models
|
|
313
|
+
```
|
|
314
|
+
```json
|
|
315
|
+
{"object":"list","data":[{"id":"qwen3.5:9b","object":"model","created":0,"owned_by":"local"},{"id":"qwen3.5:4b","object":"model",...}]}
|
|
316
|
+
```
|
|
266
317
|
|
|
267
|
-
|
|
318
|
+
```bash
|
|
319
|
+
# Chat completion (non-streaming)
|
|
268
320
|
curl -X POST http://localhost:11435/v1/chat/completions \
|
|
269
321
|
-H "Content-Type: application/json" \
|
|
270
|
-
-d '{
|
|
271
|
-
|
|
322
|
+
-d '{
|
|
323
|
+
"model": "qwen3.5:9b",
|
|
324
|
+
"messages": [{"role": "user", "content": "What is 2+2?"}]
|
|
325
|
+
}'
|
|
326
|
+
```
|
|
327
|
+
```json
|
|
328
|
+
{
|
|
329
|
+
"id": "chatcmpl-a1b2c3d4e5f6",
|
|
330
|
+
"object": "chat.completion",
|
|
331
|
+
"model": "qwen3.5:9b",
|
|
332
|
+
"choices": [{
|
|
333
|
+
"index": 0,
|
|
334
|
+
"message": {"role": "assistant", "content": "4"},
|
|
335
|
+
"finish_reason": "stop"
|
|
336
|
+
}],
|
|
337
|
+
"usage": {"prompt_tokens": 25, "completion_tokens": 2, "total_tokens": 27}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
272
340
|
|
|
273
|
-
|
|
341
|
+
```bash
|
|
342
|
+
# Chat completion (SSE streaming)
|
|
274
343
|
curl -N -X POST http://localhost:11435/v1/chat/completions \
|
|
275
344
|
-H "Content-Type: application/json" \
|
|
276
345
|
-d '{"model":"qwen3.5:9b","messages":[{"role":"user","content":"Hello"}],"stream":true}'
|
|
277
|
-
|
|
346
|
+
```
|
|
347
|
+
```
|
|
348
|
+
data: {"id":"chatcmpl-...","choices":[{"delta":{"role":"assistant","content":"Hi"}}]}
|
|
349
|
+
data: {"id":"chatcmpl-...","choices":[{"delta":{"content":" there!"}}]}
|
|
350
|
+
data: {"id":"chatcmpl-...","choices":[{"delta":{},"finish_reason":"stop"}]}
|
|
351
|
+
data: [DONE]
|
|
278
352
|
```
|
|
279
353
|
|
|
280
|
-
|
|
354
|
+
#### Agentic Task Execution
|
|
355
|
+
|
|
356
|
+
The unique OA capability — submit a coding task and get an autonomous agent loop.
|
|
281
357
|
|
|
282
358
|
```bash
|
|
283
|
-
#
|
|
359
|
+
# Run task in your current directory
|
|
284
360
|
curl -X POST http://localhost:11435/v1/run \
|
|
285
361
|
-H "Content-Type: application/json" \
|
|
286
|
-
-
|
|
362
|
+
-H "X-Working-Directory: $(pwd)" \
|
|
363
|
+
-d '{
|
|
364
|
+
"task": "fix all TypeScript errors in src/",
|
|
365
|
+
"model": "qwen3.5:9b",
|
|
366
|
+
"max_turns": 25,
|
|
367
|
+
"stream": true
|
|
368
|
+
}'
|
|
369
|
+
```
|
|
370
|
+
```
|
|
371
|
+
data: {"type":"run_started","run_id":"job-a1b2c3","pid":12345}
|
|
372
|
+
data: {"type":"stdout","data":"{\"turn\":1,\"tool\":\"file_read\",...}"}
|
|
373
|
+
data: {"type":"stdout","data":"{\"turn\":2,\"tool\":\"file_edit\",...}"}
|
|
374
|
+
data: {"type":"exit","code":0}
|
|
375
|
+
data: [DONE]
|
|
376
|
+
```
|
|
287
377
|
|
|
288
|
-
|
|
378
|
+
```bash
|
|
379
|
+
# Run in isolated sandbox (temp workspace, safe for untrusted tasks)
|
|
380
|
+
curl -X POST http://localhost:11435/v1/run \
|
|
381
|
+
-H "Content-Type: application/json" \
|
|
382
|
+
-d '{"task":"write a hello world app","isolate":true}'
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
# List all runs
|
|
289
387
|
curl http://localhost:11435/v1/runs
|
|
388
|
+
```
|
|
389
|
+
```json
|
|
390
|
+
{"runs":[{"id":"job-a1b2c3","task":"fix TypeScript errors","status":"completed","startedAt":"..."}]}
|
|
391
|
+
```
|
|
290
392
|
|
|
291
|
-
|
|
292
|
-
|
|
393
|
+
```bash
|
|
394
|
+
# Get specific run status
|
|
395
|
+
curl http://localhost:11435/v1/runs/job-a1b2c3
|
|
293
396
|
```
|
|
294
397
|
|
|
295
|
-
|
|
398
|
+
```bash
|
|
399
|
+
# Abort a running task
|
|
400
|
+
curl -X DELETE http://localhost:11435/v1/runs/job-a1b2c3
|
|
401
|
+
```
|
|
402
|
+
```json
|
|
403
|
+
{"status":"aborted","run_id":"job-a1b2c3"}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### Configuration
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
# Get all config
|
|
410
|
+
curl http://localhost:11435/v1/config
|
|
411
|
+
```
|
|
412
|
+
```json
|
|
413
|
+
{"config":{"backendUrl":"http://127.0.0.1:11434","model":"qwen3.5:122b","backendType":"ollama",...}}
|
|
414
|
+
```
|
|
296
415
|
|
|
297
416
|
```bash
|
|
298
|
-
|
|
299
|
-
curl http://localhost:11435/v1/config/model
|
|
417
|
+
# Get current model
|
|
418
|
+
curl http://localhost:11435/v1/config/model
|
|
419
|
+
```
|
|
420
|
+
```json
|
|
421
|
+
{"model":"qwen3.5:122b"}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
# Switch model
|
|
300
426
|
curl -X PUT http://localhost:11435/v1/config/model \
|
|
301
|
-
-H "Content-Type: application/json"
|
|
302
|
-
|
|
427
|
+
-H "Content-Type: application/json" \
|
|
428
|
+
-d '{"model":"qwen3.5:27b"}'
|
|
429
|
+
```
|
|
430
|
+
```json
|
|
431
|
+
{"model":"qwen3.5:27b","status":"updated"}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
# Get endpoint
|
|
436
|
+
curl http://localhost:11435/v1/config/endpoint
|
|
437
|
+
```
|
|
438
|
+
```json
|
|
439
|
+
{"url":"http://127.0.0.1:11434","backendType":"ollama","auth":"none"}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
# Switch endpoint (e.g., to Chutes AI)
|
|
444
|
+
curl -X PUT http://localhost:11435/v1/config/endpoint \
|
|
445
|
+
-H "Content-Type: application/json" \
|
|
446
|
+
-d '{"url":"https://llm.chutes.ai","auth":"Bearer cpk_..."}'
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
# Update settings (admin scope required)
|
|
451
|
+
curl -X PATCH http://localhost:11435/v1/config \
|
|
452
|
+
-H "Content-Type: application/json" \
|
|
453
|
+
-d '{"verbose":true}'
|
|
454
|
+
```
|
|
455
|
+
```json
|
|
456
|
+
{"config":{...},"updated":["verbose"]}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
#### Slash Commands via REST
|
|
460
|
+
|
|
461
|
+
Every `/command` from the TUI is available as a REST endpoint.
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
# List all available commands
|
|
465
|
+
curl http://localhost:11435/v1/commands
|
|
466
|
+
```
|
|
467
|
+
```json
|
|
468
|
+
{"commands":[{"command":"/help","description":"Show help"},{"command":"/stats","description":"Session metrics"},...]}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
# Execute /stats
|
|
473
|
+
curl -X POST http://localhost:11435/v1/commands/stats
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
```bash
|
|
477
|
+
# Execute /nexus status
|
|
478
|
+
curl -X POST http://localhost:11435/v1/commands/nexus \
|
|
479
|
+
-H "Content-Type: application/json" \
|
|
480
|
+
-d '{"args":"status"}'
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
# Execute /destroy processes --global
|
|
485
|
+
curl -X POST http://localhost:11435/v1/commands/destroy \
|
|
486
|
+
-H "Content-Type: application/json" \
|
|
487
|
+
-d '{"args":"processes --global"}'
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
#### Auth Scopes
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
# Multi-key setup: read (monitoring), run (CI), admin (ops)
|
|
494
|
+
OA_API_KEYS="grafana-key:read:grafana,ci-key:run:github-actions,ops-key:admin:ops-team" oa serve
|
|
303
495
|
```
|
|
304
496
|
|
|
305
|
-
|
|
497
|
+
| Scope | Can do | Cannot do |
|
|
498
|
+
|-------|--------|-----------|
|
|
499
|
+
| `read` | GET /v1/models, /v1/config, /v1/runs, /v1/commands | POST /v1/run, PATCH /v1/config |
|
|
500
|
+
| `run` | Everything in `read` + POST /v1/run, POST /v1/commands | PATCH /v1/config, PUT endpoints |
|
|
501
|
+
| `admin` | Everything | — |
|
|
306
502
|
|
|
307
503
|
```bash
|
|
308
|
-
|
|
309
|
-
curl -
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
| GET | `/health
|
|
317
|
-
| GET | `/health/
|
|
318
|
-
| GET | `/
|
|
319
|
-
| GET | `/
|
|
320
|
-
| GET | `/
|
|
321
|
-
|
|
|
322
|
-
| POST | `/v1/
|
|
323
|
-
| POST | `/v1/
|
|
324
|
-
|
|
|
325
|
-
| GET | `/v1/runs
|
|
326
|
-
|
|
|
327
|
-
|
|
|
328
|
-
|
|
|
329
|
-
|
|
|
330
|
-
| GET
|
|
331
|
-
|
|
|
332
|
-
|
|
|
504
|
+
# With auth
|
|
505
|
+
curl -H "Authorization: Bearer ops-key" http://localhost:11435/v1/models
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
#### Endpoint Reference
|
|
509
|
+
|
|
510
|
+
| Method | Path | Auth | Description |
|
|
511
|
+
|--------|------|------|-------------|
|
|
512
|
+
| GET | `/health` | none | Liveness probe |
|
|
513
|
+
| GET | `/health/ready` | none | Readiness (probes Ollama) |
|
|
514
|
+
| GET | `/health/startup` | none | Startup complete |
|
|
515
|
+
| GET | `/version` | none | Version + platform |
|
|
516
|
+
| GET | `/metrics` | none | Prometheus counters |
|
|
517
|
+
| GET | `/v1/models` | read | List models (OpenAI format) |
|
|
518
|
+
| POST | `/v1/chat/completions` | run | Chat inference (stream + sync) |
|
|
519
|
+
| POST | `/v1/embeddings` | run | Generate embeddings |
|
|
520
|
+
| POST | `/v1/run` | run | Submit agentic task |
|
|
521
|
+
| GET | `/v1/runs` | read | List all runs |
|
|
522
|
+
| GET | `/v1/runs/:id` | read | Run status |
|
|
523
|
+
| DELETE | `/v1/runs/:id` | run | Abort run |
|
|
524
|
+
| GET | `/v1/config` | read | All settings |
|
|
525
|
+
| PATCH | `/v1/config` | admin | Update settings |
|
|
526
|
+
| GET | `/v1/config/model` | read | Current model |
|
|
527
|
+
| PUT | `/v1/config/model` | admin | Switch model |
|
|
528
|
+
| GET | `/v1/config/endpoint` | read | Current endpoint |
|
|
529
|
+
| PUT | `/v1/config/endpoint` | admin | Switch endpoint |
|
|
530
|
+
| GET | `/v1/commands` | read | List commands |
|
|
531
|
+
| POST | `/v1/commands/:cmd` | run | Execute command |
|
|
333
532
|
|
|
334
533
|
### Enterprise Licensing
|
|
335
534
|
|
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"] || req.headers["x-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