@avocadostudio-ai/orchestrator-core 0.2.0 → 0.2.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 +102 -0
- package/dist/handler/auth.js +7 -1
- package/dist/handler/create-orchestrator.js +32 -3
- package/package.json +14 -3
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @avocadostudio-ai/orchestrator-core
|
|
2
|
+
|
|
3
|
+
The Avocado Studio orchestrator as a library: session state, AI planning, the
|
|
4
|
+
operations engine, and publishing, behind one Web-standard
|
|
5
|
+
`(Request) => Promise<Response>`.
|
|
6
|
+
|
|
7
|
+
This is the package **library mode is built on**. If you are integrating a
|
|
8
|
+
Next.js site you almost certainly want
|
|
9
|
+
[`@avocadostudio-ai/site-sdk`](https://www.npmjs.com/package/@avocadostudio-ai/site-sdk)
|
|
10
|
+
instead — it re-exports everything below from
|
|
11
|
+
`@avocadostudio-ai/site-sdk/server` and adds the Next-specific pieces (draft
|
|
12
|
+
mode, the page factory, the editor API route). Reach for this package directly
|
|
13
|
+
only when your host is not Next.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @avocadostudio-ai/orchestrator-core
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`better-sqlite3` is a real dependency, not optional: session state — draft
|
|
22
|
+
pages, undo/redo, the version log, chat history — lives in SQLite. Prebuilt
|
|
23
|
+
binaries ship for linux-x64 (glibc 2.28+), darwin-arm64 and darwin-x64 on
|
|
24
|
+
Node 22.
|
|
25
|
+
|
|
26
|
+
Two peers are **optional**: `googleapis` and `@google/genai`. They are reached
|
|
27
|
+
through `await import(...)` so a deployment that uses neither need not install
|
|
28
|
+
them — but a bundler resolves dynamic imports statically and will fail the build
|
|
29
|
+
over a package that is deliberately absent. Mark them external. On Next,
|
|
30
|
+
`withAvocado` from `@avocadostudio-ai/site-sdk/next-config` does it for you.
|
|
31
|
+
|
|
32
|
+
## Mount it
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { createOrchestrator } from "@avocadostudio-ai/orchestrator-core"
|
|
36
|
+
|
|
37
|
+
const handler = createOrchestrator({
|
|
38
|
+
basePath: "/api/avocado",
|
|
39
|
+
adapter: myAdapter,
|
|
40
|
+
auth: async (request) => Boolean(await getSession(request)),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Any host that speaks Request/Response
|
|
44
|
+
export { handler as GET, handler as POST, handler as OPTIONS }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`createOrchestrator` gates **every** route. With neither an `auth` hook nor a
|
|
48
|
+
credential (`ACCESS_PASSWORD_HASH` or `ORCHESTRATOR_ACCESS_TOKEN`) it refuses
|
|
49
|
+
all requests under `NODE_ENV=production` rather than serving your content
|
|
50
|
+
openly. Only `/auth/status` and `/auth/verify` are public.
|
|
51
|
+
|
|
52
|
+
## The adapter
|
|
53
|
+
|
|
54
|
+
`adapter` is your content store. SQLite is the working copy; the adapter is the
|
|
55
|
+
source of truth, read on a cold session and written on publish.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import type { CmsAdapter } from "@avocadostudio-ai/orchestrator-core/cms"
|
|
59
|
+
|
|
60
|
+
const myAdapter: CmsAdapter = {
|
|
61
|
+
id: "my-cms",
|
|
62
|
+
perspectives: true, // does getPages honour options.perspective?
|
|
63
|
+
getPages: (options) => fetchPages(options),
|
|
64
|
+
onPublish: (pages, config, context) => writeBack(pages, context?.published),
|
|
65
|
+
capabilities: { createPage: false }, // static — /whoami answers it offline
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`jsonFileAdapter` and `editorApiAdapter` are bundled from the same subpath. The
|
|
70
|
+
full contract — including why `perspectives` defaults to *no* while capabilities
|
|
71
|
+
default to *yes*, and why `onPublish` has to diff rather than overwrite — is
|
|
72
|
+
documented in
|
|
73
|
+
[the site-sdk README](https://www.npmjs.com/package/@avocadostudio-ai/site-sdk#the-adapter-contract).
|
|
74
|
+
|
|
75
|
+
## What it does not do
|
|
76
|
+
|
|
77
|
+
It serves the API. It does not serve the editor UI — that is
|
|
78
|
+
`@avocadostudio-ai/cli` or your own deployment of the Studio — and it does not
|
|
79
|
+
render your pages.
|
|
80
|
+
|
|
81
|
+
`/health` is **not** a route, so a client that probes it to check compatibility
|
|
82
|
+
gets a 405. The body lists every route that does exist.
|
|
83
|
+
|
|
84
|
+
## Persistence
|
|
85
|
+
|
|
86
|
+
| variable | meaning |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `ORCHESTRATOR_DB_FILE` | Path to the SQLite file. Empty for the default; `:memory:` to force ephemeral. Auto-`:memory:` under `NODE_ENV=test` |
|
|
89
|
+
| `ORCHESTRATOR_DB_BACKUP_INTERVAL_HOURS` | Periodic `VACUUM INTO` snapshot interval (default 24) |
|
|
90
|
+
| `ORCHESTRATOR_DB_BACKUP_LIMIT` | Rolling snapshots to keep (default 14) |
|
|
91
|
+
|
|
92
|
+
Undo/redo is capped at 50 entries per slug per direction, the version log at
|
|
93
|
+
100, recent edits at 10, chat history at 6 messages.
|
|
94
|
+
|
|
95
|
+
## Providers
|
|
96
|
+
|
|
97
|
+
At least one of `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` or `GOOGLE_GENAI_API_KEY`
|
|
98
|
+
is required for planning. Keys never leave the process that holds them.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
Apache-2.0
|
package/dist/handler/auth.js
CHANGED
|
@@ -29,7 +29,13 @@ import { isAccessGateEnabled, isValidAccessToken, extractAccessToken } from "../
|
|
|
29
29
|
* gating it would blank every uploaded image in the live site, not just in the
|
|
30
30
|
* editor. Both are read-only and neither reveals session content.
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
/*
|
|
33
|
+
* `/health` is here because a probe that needs a credential is not a probe. The
|
|
34
|
+
* CLI calls it before it has one, to compare protocol versions, and it answers
|
|
35
|
+
* nothing about the site's content — only that this is an Avocado orchestrator
|
|
36
|
+
* and which protocol it speaks.
|
|
37
|
+
*/
|
|
38
|
+
const PUBLIC_PATHS = new Set(["/auth/status", "/auth/verify", "/health"]);
|
|
33
39
|
export function isPublicPath(path, method) {
|
|
34
40
|
if (method === "OPTIONS")
|
|
35
41
|
return true;
|
|
@@ -21,7 +21,7 @@ import { mkdir, writeFile, readFile } from "node:fs/promises";
|
|
|
21
21
|
import { resolve, basename } from "node:path";
|
|
22
22
|
import { randomUUID } from "node:crypto";
|
|
23
23
|
import { z } from "zod";
|
|
24
|
-
import { operationSchema, blockManifestSchema, siteConfigSchema, declareBlockCatalogue, undeclaredBlockTypes } from "@avocadostudio-ai/shared";
|
|
24
|
+
import { operationSchema, blockManifestSchema, siteConfigSchema, declareBlockCatalogue, undeclaredBlockTypes, EDITOR_PROTOCOL_VERSION } from "@avocadostudio-ai/shared";
|
|
25
25
|
import { chatRequestBodySchema } from "../nlp/intent-detection.js";
|
|
26
26
|
import { applyOpsAtomically, pickFocusBlockId, pickUpdatedSlug, toErrorDetail, classifyGuardrailError } from "../ops/ops-engine.js";
|
|
27
27
|
import { runChatStream, formatSseFrame } from "../http/chat-stream.js";
|
|
@@ -196,6 +196,7 @@ function jsonResponse(body, init = {}) {
|
|
|
196
196
|
* only thing that keeps a list like this honest.
|
|
197
197
|
*/
|
|
198
198
|
const SUPPORTED_ROUTES = [
|
|
199
|
+
"GET /health",
|
|
199
200
|
"GET /auth/status",
|
|
200
201
|
"POST /auth/verify",
|
|
201
202
|
"POST /chat",
|
|
@@ -899,7 +900,9 @@ export function createOrchestrator(config = {}) {
|
|
|
899
900
|
})),
|
|
900
901
|
// Which content this is — see orchestrator-core/http/draft-provenance.ts.
|
|
901
902
|
...describeDraft({
|
|
902
|
-
|
|
903
|
+
// Config first, matching every other route: a mount that names its
|
|
904
|
+
// site knows better than a request that forgot to.
|
|
905
|
+
requestedSiteId: effectiveSiteId ?? siteId,
|
|
903
906
|
scopedSession,
|
|
904
907
|
pageCount: pages.length,
|
|
905
908
|
hasAdapter: Boolean(runtime.adapter)
|
|
@@ -1243,12 +1246,38 @@ export function createOrchestrator(config = {}) {
|
|
|
1243
1246
|
}
|
|
1244
1247
|
return actionResponse(blocksManifestAction(), cors);
|
|
1245
1248
|
}
|
|
1249
|
+
/*
|
|
1250
|
+
* Liveness and protocol version, for a client deciding whether it can talk
|
|
1251
|
+
* to this build at all.
|
|
1252
|
+
*
|
|
1253
|
+
* The standalone orchestrator has always answered this; library mode never
|
|
1254
|
+
* did, so `avocado-cli start` probed it, got a 405, and printed
|
|
1255
|
+
* "Orchestrator /health returned 405. CLI started anyway." on every start —
|
|
1256
|
+
* a warning that means nothing, from the CLI's only compatibility check,
|
|
1257
|
+
* which could therefore never pass against an embedded mount.
|
|
1258
|
+
*
|
|
1259
|
+
* Deliberately before the auth gate (see `PUBLIC_PATHS`) and deliberately
|
|
1260
|
+
* content-free: it says what this process is, never what it holds.
|
|
1261
|
+
*/
|
|
1262
|
+
if (request.method === "GET" && path === "/health") {
|
|
1263
|
+
return jsonResponse({ ok: true, mode: "library", protocolVersion: EDITOR_PROTOCOL_VERSION }, { cors });
|
|
1264
|
+
}
|
|
1246
1265
|
if (request.method === "GET" && path === "/whoami") {
|
|
1247
1266
|
const runtime = await getRuntime();
|
|
1248
1267
|
await runtime.ready;
|
|
1249
1268
|
const query = Object.fromEntries(url.searchParams);
|
|
1250
1269
|
await runtime.bootstrapCache.ensure(scope(query.session, query.siteId), runtime.adapter, runtime.log);
|
|
1251
|
-
|
|
1270
|
+
/*
|
|
1271
|
+
* `scope()` resolves the site the mount serves; `whoamiAction` re-derives
|
|
1272
|
+
* the session key from what it is handed. Handing it the raw query meant
|
|
1273
|
+
* the two disagreed whenever the caller omitted `siteId` — the bootstrap
|
|
1274
|
+
* seeded `library::default` and the answer was read from the legacy key,
|
|
1275
|
+
* so a correctly configured mount reported Avocado's bundled demo
|
|
1276
|
+
* content, `source: "demo"`, and all-true capabilities in place of the
|
|
1277
|
+
* adapter's own. The note even said "No siteId was supplied", which was
|
|
1278
|
+
* true of the request and false of the configuration.
|
|
1279
|
+
*/
|
|
1280
|
+
return actionResponse(whoamiAction({ ...query, siteId: effectiveSiteId ?? query.siteId }, url.origin, { hasAdapter: Boolean(runtime.adapter) }), cors);
|
|
1252
1281
|
}
|
|
1253
1282
|
/*
|
|
1254
1283
|
* Publishing status, history and preview.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/orchestrator-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./package.json": "./package.json",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"openai": "^4.87.1",
|
|
24
24
|
"sharp": "^0.34.5",
|
|
25
25
|
"zod": "^4.3.6",
|
|
26
|
-
"@avocadostudio-ai/migration-sdk": "0.2.
|
|
27
|
-
"@avocadostudio-ai/shared": "0.2.
|
|
26
|
+
"@avocadostudio-ai/migration-sdk": "0.2.3",
|
|
27
|
+
"@avocadostudio-ai/shared": "0.2.3"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@google/genai": "^1.46.0",
|
|
@@ -44,6 +44,17 @@
|
|
|
44
44
|
"dist"
|
|
45
45
|
],
|
|
46
46
|
"description": "Core Avocado Studio orchestrator — session state, AI planning, operations engine, publishing",
|
|
47
|
+
"keywords": [
|
|
48
|
+
"avocado",
|
|
49
|
+
"avocado-studio",
|
|
50
|
+
"cms",
|
|
51
|
+
"ai",
|
|
52
|
+
"llm",
|
|
53
|
+
"content-editor",
|
|
54
|
+
"page-builder",
|
|
55
|
+
"headless-cms",
|
|
56
|
+
"visual-editing"
|
|
57
|
+
],
|
|
47
58
|
"license": "Apache-2.0",
|
|
48
59
|
"homepage": "https://github.com/avocadostudio-ai/avocado/tree/main/packages/orchestrator-core#readme",
|
|
49
60
|
"bugs": {
|