orchajs 0.0.1 → 0.1.0-next.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 +243 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +86 -0
- package/dist/cli.js.map +1 -0
- package/dist/compiler/build-project.d.ts +5 -0
- package/dist/compiler/build-project.d.ts.map +1 -0
- package/dist/compiler/build-project.js +93 -0
- package/dist/compiler/build-project.js.map +1 -0
- package/dist/compiler/compile.d.ts +4 -0
- package/dist/compiler/compile.d.ts.map +1 -0
- package/dist/compiler/compile.js +206 -0
- package/dist/compiler/compile.js.map +1 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/esbuild.d.ts +10 -0
- package/dist/integrations/esbuild.d.ts.map +1 -0
- package/dist/integrations/esbuild.js +22 -0
- package/dist/integrations/esbuild.js.map +1 -0
- package/dist/orcha.d.ts +3 -0
- package/dist/orcha.d.ts.map +1 -0
- package/dist/orcha.js +15 -0
- package/dist/orcha.js.map +1 -0
- package/dist/providers/anthropic.d.ts +30 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +158 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/runtime/actions/context.d.ts +17 -0
- package/dist/runtime/actions/context.d.ts.map +1 -0
- package/dist/runtime/actions/context.js +45 -0
- package/dist/runtime/actions/context.js.map +1 -0
- package/dist/runtime/actions/execute.d.ts +10 -0
- package/dist/runtime/actions/execute.d.ts.map +1 -0
- package/dist/runtime/actions/execute.js +14 -0
- package/dist/runtime/actions/execute.js.map +1 -0
- package/dist/runtime/actions/native.d.ts +4 -0
- package/dist/runtime/actions/native.d.ts.map +1 -0
- package/dist/runtime/actions/native.js +27 -0
- package/dist/runtime/actions/native.js.map +1 -0
- package/dist/runtime/actions/sandbox.d.ts +7 -0
- package/dist/runtime/actions/sandbox.d.ts.map +1 -0
- package/dist/runtime/actions/sandbox.js +117 -0
- package/dist/runtime/actions/sandbox.js.map +1 -0
- package/dist/runtime/agent.d.ts +18 -0
- package/dist/runtime/agent.d.ts.map +1 -0
- package/dist/runtime/agent.js +1113 -0
- package/dist/runtime/agent.js.map +1 -0
- package/dist/runtime/create-orcha.d.ts +11 -0
- package/dist/runtime/create-orcha.d.ts.map +1 -0
- package/dist/runtime/create-orcha.js +83 -0
- package/dist/runtime/create-orcha.js.map +1 -0
- package/dist/runtime/session-events.d.ts +13 -0
- package/dist/runtime/session-events.d.ts.map +1 -0
- package/dist/runtime/session-events.js +59 -0
- package/dist/runtime/session-events.js.map +1 -0
- package/dist/runtime/structured-output.d.ts +3 -0
- package/dist/runtime/structured-output.d.ts.map +1 -0
- package/dist/runtime/structured-output.js +126 -0
- package/dist/runtime/structured-output.js.map +1 -0
- package/dist/storage/browser-indexeddb.d.ts +21 -0
- package/dist/storage/browser-indexeddb.d.ts.map +1 -0
- package/dist/storage/browser-indexeddb.js +26 -0
- package/dist/storage/browser-indexeddb.js.map +1 -0
- package/dist/storage/edge-store.d.ts +20 -0
- package/dist/storage/edge-store.d.ts.map +1 -0
- package/dist/storage/edge-store.js +25 -0
- package/dist/storage/edge-store.js.map +1 -0
- package/dist/storage/node-jsonl.d.ts +10 -0
- package/dist/storage/node-jsonl.d.ts.map +1 -0
- package/dist/storage/node-jsonl.js +80 -0
- package/dist/storage/node-jsonl.js.map +1 -0
- package/dist/storage/react-native-jsonl.d.ts +21 -0
- package/dist/storage/react-native-jsonl.d.ts.map +1 -0
- package/dist/storage/react-native-jsonl.js +26 -0
- package/dist/storage/react-native-jsonl.js.map +1 -0
- package/dist/types.d.ts +216 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -1
package/README.md
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# OrchaJS
|
|
2
|
+
|
|
3
|
+
**An agent is a folder.** OrchaJS is a filesystem-convention framework for building AI agents in JavaScript — no orchestration graph, no client to instantiate, no export step. It's a library, not a platform: add it to Next.js, React, React Native, Express, NestJS, or plain Node, the way you'd add Prisma or Zod.
|
|
4
|
+
|
|
5
|
+
> Early and moving fast. Not yet accepting external contributions — see [Status](#status) below.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
Most agent frameworks or platforms ask you to trust a black box: the real logic lives inside someone else's runtime, and what you get to "own" is a copy or an export of it. OrchaJS removes that gap by making the file tree the actual source of truth — there's no internal state that isn't also a file you can open, edit, and run yourself.
|
|
12
|
+
|
|
13
|
+
Read the full concept: [`docs/concept.md`](./docs/concept.md)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## The convention
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
/agentname
|
|
21
|
+
index.js | index.json → provider, model, generation config, output schema
|
|
22
|
+
instructions.md → required base agent instructions
|
|
23
|
+
/skills
|
|
24
|
+
/skillname
|
|
25
|
+
index.json → { name, description, triggers }
|
|
26
|
+
instructions.md → procedural knowledge, lazy-loaded into context
|
|
27
|
+
/actions
|
|
28
|
+
/actionname
|
|
29
|
+
index.json → definition, schemas, and execution location
|
|
30
|
+
index.js → executable code for local actions only
|
|
31
|
+
/guardrails → input/output policy
|
|
32
|
+
/tests → test cases, auto-run before every release
|
|
33
|
+
/evaluations → metrics each run is judged against
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Placement determines behavior. No manual registration of tools, skills, or routes.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Model Action Protocol
|
|
41
|
+
|
|
42
|
+
What a model can do lives in `/actions`. Every action has `index.json`; local
|
|
43
|
+
actions additionally have `index.js`.
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
actions/
|
|
47
|
+
send-refund/
|
|
48
|
+
index.json // the definition
|
|
49
|
+
index.js // the code
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`index.json` tells the model what the action is, where it executes, and its
|
|
53
|
+
input/output contracts:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"name": "request_refund_approval",
|
|
58
|
+
"description": "Ask the client application to approve a refund.",
|
|
59
|
+
"execution": "client",
|
|
60
|
+
"parameters": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"properties": {
|
|
63
|
+
"orderId": { "type": "string" },
|
|
64
|
+
"amount": { "type": "number" }
|
|
65
|
+
},
|
|
66
|
+
"required": ["orderId", "amount"]
|
|
67
|
+
},
|
|
68
|
+
"outputSchema": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"properties": {
|
|
71
|
+
"approved": { "type": "boolean" }
|
|
72
|
+
},
|
|
73
|
+
"required": ["approved"]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Client actions pause the durable run. The application advertises only the
|
|
79
|
+
actions available on that client, executes the requested call, then resumes:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
const paused = await orcha.invoiceBot.run({
|
|
83
|
+
content: "Refund order 123",
|
|
84
|
+
clientCapabilities: orcha.invoiceBot.clientTools
|
|
85
|
+
}).result;
|
|
86
|
+
|
|
87
|
+
if (paused.status === "waiting_for_client_action") {
|
|
88
|
+
const completed = await orcha.invoiceBot.resume(paused.sessionId, {
|
|
89
|
+
toolResults: paused.clientToolCalls.map((call) => ({
|
|
90
|
+
callId: call.callId,
|
|
91
|
+
output: { approved: true }
|
|
92
|
+
}))
|
|
93
|
+
}).result;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Definitions are compiled from the filesystem and are not submitted again to
|
|
98
|
+
`resume()`. Requests and results are persisted in the session JSONL.
|
|
99
|
+
|
|
100
|
+
Local actions export their implementation from `index.js` and execute
|
|
101
|
+
automatically inside the model tool loop:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
export default async function sendRefund(parameters, context) {
|
|
105
|
+
return {
|
|
106
|
+
refunded: true,
|
|
107
|
+
orderId: parameters.orderId,
|
|
108
|
+
idempotencyKey: context.idempotencyKey,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Projects with local actions must explicitly choose their runtime:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
orcha.init({
|
|
117
|
+
actions: {
|
|
118
|
+
runtime: "sandbox", // QuickJS WASM
|
|
119
|
+
env: {
|
|
120
|
+
REFUND_API_KEY: process.env.REFUND_API_KEY,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
// providers and agents...
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Use `"native"` to execute compiled action JavaScript directly without the
|
|
128
|
+
QuickJS dependency in the production bundle. Native execution is trusted code
|
|
129
|
+
with full host privileges; declared permissions are only securely enforced by
|
|
130
|
+
the sandbox runtime. Native timeouts can reject asynchronous work but cannot
|
|
131
|
+
interrupt a synchronous infinite loop.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Quick start
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm install orchajs
|
|
139
|
+
npx orcha init
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
// Import the consumer registry once from the application entrypoint.
|
|
144
|
+
import './orcha/index.js';
|
|
145
|
+
import { orcha } from 'orchajs';
|
|
146
|
+
|
|
147
|
+
const execution = orcha.exampleAgent.run({
|
|
148
|
+
content: 'Help me understand this invoice'
|
|
149
|
+
});
|
|
150
|
+
const result = await execution.result;
|
|
151
|
+
|
|
152
|
+
// Every later conversational turn uses the returned sessionId.
|
|
153
|
+
const followUp = await orcha.exampleAgent.resume(result.sessionId, {
|
|
154
|
+
content: 'Now summarize it in one sentence'
|
|
155
|
+
}).result;
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Registered agents are exposed as `orcha.<agentName>`. The first implementation
|
|
159
|
+
supports durable Node.js runs backed by `.orcha/sessions/<sessionId>.jsonl`.
|
|
160
|
+
Client-action pause/resume is supported; local actions and streaming follow.
|
|
161
|
+
`run()` always starts a new session; `resume()` continues one with either a new
|
|
162
|
+
message or pending client tool results.
|
|
163
|
+
|
|
164
|
+
Both methods accept text shorthand:
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
const first = await orcha.exampleAgent.run("Start").result;
|
|
168
|
+
const second = await orcha.exampleAgent.resume(
|
|
169
|
+
first.sessionId,
|
|
170
|
+
"Continue",
|
|
171
|
+
).result;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Canonical input uses a multimodal `content` array:
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
const result = await orcha.exampleAgent.run({
|
|
178
|
+
content: [
|
|
179
|
+
{ type: "text", text: "Summarize this document" },
|
|
180
|
+
{
|
|
181
|
+
type: "url",
|
|
182
|
+
mimeType: "application/pdf",
|
|
183
|
+
fileUri: "https://example.com/report.pdf",
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
name: "Customer report",
|
|
187
|
+
metadata: { customerId: "cus_123" },
|
|
188
|
+
variables: { CUSTOMER_NAME: "Acme" },
|
|
189
|
+
}).result;
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Prompt variables replace `{{CUSTOMER_NAME}}` placeholders in
|
|
193
|
+
`instructions.md`. They are fixed when the session is created and persisted
|
|
194
|
+
for deterministic continuation. Do not put secrets in prompt variables; use
|
|
195
|
+
provider or action environment configuration for secrets.
|
|
196
|
+
|
|
197
|
+
Sessions are managed through the same registered agent:
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
await orcha.exampleAgent.get(sessionId);
|
|
201
|
+
await orcha.exampleAgent.history(sessionId, { page: 1, pageSize: 50 });
|
|
202
|
+
await orcha.exampleAgent.list({
|
|
203
|
+
metadata: { customerId: "cus_123" },
|
|
204
|
+
page: 1,
|
|
205
|
+
pageSize: 20,
|
|
206
|
+
});
|
|
207
|
+
await orcha.exampleAgent.update(sessionId, {
|
|
208
|
+
name: "September invoice",
|
|
209
|
+
metadata: { approved: true },
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
For esbuild production applications, add `orchaPlugin()` from
|
|
214
|
+
`orchajs/esbuild` to the existing build. It compiles the registry and replaces
|
|
215
|
+
unchanged `orchajs` imports with the self-contained generated runtime. The
|
|
216
|
+
application continues to use its normal `npm run build` command.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## What's in this repo right now
|
|
221
|
+
|
|
222
|
+
- [x] Registry compiler + `orcha build`
|
|
223
|
+
- [x] Model Action Protocol — client actions and opt-in native/sandboxed local actions
|
|
224
|
+
- [ ] Compiler — one provider, correct tool calls + streaming
|
|
225
|
+
- [x] Stateful sessions — Node JSONL replay and client-action pause/resume
|
|
226
|
+
- [ ] `orcha.run()` one-shot entry point
|
|
227
|
+
- [ ] CLI (`orcha init`, `orcha build`, `orcha dev`) + example agents
|
|
228
|
+
|
|
229
|
+
See [`ROADMAP.md`](./ROADMAP.md) for what's coming after — a second provider, an evaluations runner, OpenTelemetry tracing, MCP interop adapters, and remote session storage are all deliberately out of scope for the first release.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Status
|
|
234
|
+
|
|
235
|
+
This project is being built in the open by a solo maintainer. It's not yet stable enough to take contributions productively — the core convention and API are still moving. `CONTRIBUTING.md` will open up once the shape settles.
|
|
236
|
+
|
|
237
|
+
In the meantime: [star the repo](.) to follow along, open an issue if you hit something worth flagging, or watch for weekly build-in-public updates on [X/Twitter](.).
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
[MIT](./LICENSE)
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { buildProject } from "./compiler/build-project.js";
|
|
6
|
+
const command = process.argv[2];
|
|
7
|
+
try {
|
|
8
|
+
if (command === "init") {
|
|
9
|
+
await initializeProject(process.cwd());
|
|
10
|
+
}
|
|
11
|
+
else if (command === "build") {
|
|
12
|
+
const outputPath = await buildProject({ projectRoot: process.cwd() });
|
|
13
|
+
console.log(`Built ${outputPath}`);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
printUsage();
|
|
17
|
+
process.exitCode = command ? 1 : 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
}
|
|
24
|
+
async function initializeProject(projectRoot) {
|
|
25
|
+
const files = new Map([
|
|
26
|
+
[
|
|
27
|
+
"orcha/exampleAgent/index.json",
|
|
28
|
+
`${JSON.stringify({
|
|
29
|
+
provider: "anthropic",
|
|
30
|
+
model: "claude-sonnet-4-6",
|
|
31
|
+
region: "provider_managed",
|
|
32
|
+
maxTokens: 1024,
|
|
33
|
+
reasoningLevel: "disabled",
|
|
34
|
+
outputType: "text",
|
|
35
|
+
}, null, 2)}\n`,
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
"orcha/exampleAgent/instructions.md",
|
|
39
|
+
"You are a concise and helpful assistant.\n",
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
"orcha/index.ts",
|
|
43
|
+
[
|
|
44
|
+
'import { orcha } from "orchajs";',
|
|
45
|
+
"",
|
|
46
|
+
"orcha.init({",
|
|
47
|
+
" providers: {",
|
|
48
|
+
" anthropic: process.env.ANTHROPIC_API_KEY ?? \"\",",
|
|
49
|
+
" },",
|
|
50
|
+
" agents: {",
|
|
51
|
+
" exampleAgent: \"./exampleAgent\",",
|
|
52
|
+
" },",
|
|
53
|
+
"});",
|
|
54
|
+
"",
|
|
55
|
+
].join("\n"),
|
|
56
|
+
],
|
|
57
|
+
]);
|
|
58
|
+
for (const [relativePath, contents] of files) {
|
|
59
|
+
const path = resolve(projectRoot, relativePath);
|
|
60
|
+
if (existsSync(path)) {
|
|
61
|
+
console.log(`Skipped existing ${relativePath}`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
await mkdir(dirname(path), { recursive: true });
|
|
65
|
+
await writeFile(path, contents, "utf8");
|
|
66
|
+
console.log(`Created ${relativePath}`);
|
|
67
|
+
}
|
|
68
|
+
await ensureGitignore(projectRoot);
|
|
69
|
+
console.log('\nImport "./orcha/index.js" once from your application entrypoint, then run your existing development command.');
|
|
70
|
+
}
|
|
71
|
+
async function ensureGitignore(projectRoot) {
|
|
72
|
+
const path = resolve(projectRoot, ".gitignore");
|
|
73
|
+
const entry = ".orcha/";
|
|
74
|
+
const current = existsSync(path) ? await readFile(path, "utf8") : "";
|
|
75
|
+
const entries = current.split(/\r?\n/).map((line) => line.trim());
|
|
76
|
+
if (entries.includes(entry)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const prefix = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
|
|
80
|
+
await appendFile(path, `${prefix}${entry}\n`, "utf8");
|
|
81
|
+
console.log("Updated .gitignore");
|
|
82
|
+
}
|
|
83
|
+
function printUsage() {
|
|
84
|
+
console.log(["Usage: orcha <command>", "", "Commands:", " init", " build"].join("\n"));
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-project.d.ts","sourceRoot":"","sources":["../../src/compiler/build-project.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,YAAY,CAChC,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,MAAM,CAAC,CAoGjB"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { build as bundleEntry } from "esbuild";
|
|
6
|
+
import { orcha } from "../orcha.js";
|
|
7
|
+
export async function buildProject(options = {}) {
|
|
8
|
+
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
9
|
+
const registryPath = resolve(projectRoot, "orcha/index.ts");
|
|
10
|
+
if (!existsSync(registryPath)) {
|
|
11
|
+
throw new Error('Missing "orcha/index.ts". Run "orcha init" first.');
|
|
12
|
+
}
|
|
13
|
+
const temporaryDirectory = resolve(projectRoot, ".orcha/.build");
|
|
14
|
+
const registryBundlePath = resolve(temporaryDirectory, "registry.mjs");
|
|
15
|
+
const productionEntryPath = resolve(temporaryDirectory, "production-entry.mjs");
|
|
16
|
+
const outputPath = resolve(projectRoot, ".orcha/dist/bundle.js");
|
|
17
|
+
await mkdir(temporaryDirectory, { recursive: true });
|
|
18
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
19
|
+
try {
|
|
20
|
+
await bundleEntry({
|
|
21
|
+
entryPoints: [registryPath],
|
|
22
|
+
outfile: registryBundlePath,
|
|
23
|
+
bundle: true,
|
|
24
|
+
format: "esm",
|
|
25
|
+
platform: "node",
|
|
26
|
+
target: "node20",
|
|
27
|
+
packages: "external",
|
|
28
|
+
logLevel: "silent",
|
|
29
|
+
});
|
|
30
|
+
const previousCompiling = process.env.ORCHA_COMPILING;
|
|
31
|
+
const previousProjectRoot = process.env.ORCHA_PROJECT_ROOT;
|
|
32
|
+
process.env.ORCHA_COMPILING = "1";
|
|
33
|
+
process.env.ORCHA_PROJECT_ROOT = projectRoot;
|
|
34
|
+
try {
|
|
35
|
+
await import(`${pathToFileURL(registryBundlePath).href}?time=${Date.now()}`);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
if (previousCompiling === undefined) {
|
|
39
|
+
delete process.env.ORCHA_COMPILING;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
process.env.ORCHA_COMPILING = previousCompiling;
|
|
43
|
+
}
|
|
44
|
+
if (previousProjectRoot === undefined) {
|
|
45
|
+
delete process.env.ORCHA_PROJECT_ROOT;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
process.env.ORCHA_PROJECT_ROOT = previousProjectRoot;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const compiledBundle = orcha.getCompiledBundle();
|
|
52
|
+
const runtimeFactoryPath = resolve(dirname(fileURLToPath(import.meta.url)), "../runtime/create-orcha.js").replaceAll("\\", "/");
|
|
53
|
+
await writeFile(productionEntryPath, [
|
|
54
|
+
`import { createOrcha } from ${JSON.stringify(runtimeFactoryPath)};`,
|
|
55
|
+
`const compiledBundle = ${JSON.stringify(compiledBundle)};`,
|
|
56
|
+
"export const orcha = createOrcha(() => compiledBundle);",
|
|
57
|
+
"export default orcha;",
|
|
58
|
+
"",
|
|
59
|
+
].join("\n"), "utf8");
|
|
60
|
+
await bundleEntry({
|
|
61
|
+
entryPoints: [productionEntryPath],
|
|
62
|
+
outfile: outputPath,
|
|
63
|
+
bundle: true,
|
|
64
|
+
format: "esm",
|
|
65
|
+
platform: "node",
|
|
66
|
+
target: "node20",
|
|
67
|
+
minify: true,
|
|
68
|
+
legalComments: "none",
|
|
69
|
+
define: {
|
|
70
|
+
"process.env.ORCHA_INCLUDE_SANDBOX": JSON.stringify(compiledBundle.actionRuntime === "sandbox" ? "1" : "0"),
|
|
71
|
+
},
|
|
72
|
+
...(compiledBundle.actionRuntime === "sandbox"
|
|
73
|
+
? {
|
|
74
|
+
banner: {
|
|
75
|
+
js: [
|
|
76
|
+
'import { createRequire as __orchaCreateRequire } from "node:module";',
|
|
77
|
+
"const require = __orchaCreateRequire(import.meta.url);",
|
|
78
|
+
].join("\n"),
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
: {}),
|
|
82
|
+
logLevel: "silent",
|
|
83
|
+
});
|
|
84
|
+
return outputPath;
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await rm(temporaryDirectory, {
|
|
88
|
+
recursive: true,
|
|
89
|
+
force: true,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=build-project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-project.js","sourceRoot":"","sources":["../../src/compiler/build-project.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CompiledBundle } from "../types.js";
|
|
2
|
+
export declare function compileRegistry(registrations: Record<string, string>, registryRoot: string, actionRuntime?: "native" | "sandbox"): CompiledBundle;
|
|
3
|
+
export declare function serializeBundle(bundle: CompiledBundle): string;
|
|
4
|
+
//# sourceMappingURL=compile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compiler/compile.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAKV,cAAc,EAEf,MAAM,aAAa,CAAC;AAIrB,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,YAAY,EAAE,MAAM,EACpB,aAAa,CAAC,EAAE,QAAQ,GAAG,SAAS,GACnC,cAAc,CAgHhB;AAmKD,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAO9D"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { buildSync } from "esbuild";
|
|
5
|
+
const SUPPORTED_PROVIDERS = new Set(["anthropic"]);
|
|
6
|
+
export function compileRegistry(registrations, registryRoot, actionRuntime) {
|
|
7
|
+
if (!registrations ||
|
|
8
|
+
typeof registrations !== "object" ||
|
|
9
|
+
Array.isArray(registrations)) {
|
|
10
|
+
throw new TypeError('orcha.init() requires an "agents" object of registered folder paths.');
|
|
11
|
+
}
|
|
12
|
+
const agents = {};
|
|
13
|
+
for (const [name, registeredPath] of Object.entries(registrations)) {
|
|
14
|
+
if (!name.trim()) {
|
|
15
|
+
throw new Error("Registered agent names cannot be empty.");
|
|
16
|
+
}
|
|
17
|
+
if (typeof registeredPath !== "string" || !registeredPath.trim()) {
|
|
18
|
+
throw new Error(`Agent "${name}" must register a folder path.`);
|
|
19
|
+
}
|
|
20
|
+
const sourceDirectory = resolve(registryRoot, registeredPath);
|
|
21
|
+
const configPath = resolve(sourceDirectory, "index.json");
|
|
22
|
+
let configuration;
|
|
23
|
+
try {
|
|
24
|
+
configuration = JSON.parse(readFileSync(configPath, "utf8"));
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw new Error(`Agent "${name}" is missing a valid index.json at ${configPath}.`, { cause: error });
|
|
28
|
+
}
|
|
29
|
+
if (!SUPPORTED_PROVIDERS.has(configuration.provider)) {
|
|
30
|
+
throw new Error(`Agent "${name}" uses unsupported provider "${configuration.provider}".`);
|
|
31
|
+
}
|
|
32
|
+
if (!configuration.model?.trim()) {
|
|
33
|
+
throw new Error(`Agent "${name}" must configure a model.`);
|
|
34
|
+
}
|
|
35
|
+
validateNumber(name, "maxTokens", configuration.maxTokens, {
|
|
36
|
+
integer: true,
|
|
37
|
+
minimum: 1,
|
|
38
|
+
});
|
|
39
|
+
if (configuration.outputType &&
|
|
40
|
+
!["text", "json", "image", "audio"].includes(configuration.outputType)) {
|
|
41
|
+
throw new Error(`Agent "${name}" has an invalid outputType.`);
|
|
42
|
+
}
|
|
43
|
+
if (configuration.reasoningLevel &&
|
|
44
|
+
!["disabled", "low", "medium", "high"].includes(configuration.reasoningLevel)) {
|
|
45
|
+
throw new Error(`Agent "${name}" has an invalid reasoningLevel.`);
|
|
46
|
+
}
|
|
47
|
+
if (!["text", "json"].includes(configuration.outputType ?? "text")) {
|
|
48
|
+
throw new Error(`Agent "${name}" outputType "${configuration.outputType}" is not implemented yet.`);
|
|
49
|
+
}
|
|
50
|
+
if (configuration.outputType === "json" &&
|
|
51
|
+
(!configuration.outputSchema ||
|
|
52
|
+
typeof configuration.outputSchema !== "object")) {
|
|
53
|
+
throw new Error(`Agent "${name}" must configure outputSchema when outputType is "json".`);
|
|
54
|
+
}
|
|
55
|
+
const instructionsPath = resolve(sourceDirectory, "instructions.md");
|
|
56
|
+
let systemPrompt;
|
|
57
|
+
try {
|
|
58
|
+
systemPrompt = readFileSync(instructionsPath, "utf8").trim();
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
throw new Error(`Agent "${name}" is missing required instructions.md at ${instructionsPath}.`, { cause: error });
|
|
62
|
+
}
|
|
63
|
+
if (!systemPrompt) {
|
|
64
|
+
throw new Error(`Agent "${name}" instructions.md cannot be empty.`);
|
|
65
|
+
}
|
|
66
|
+
agents[name] = Object.freeze({
|
|
67
|
+
name,
|
|
68
|
+
provider: configuration.provider,
|
|
69
|
+
model: configuration.model,
|
|
70
|
+
systemPrompt,
|
|
71
|
+
region: configuration.region,
|
|
72
|
+
maxTokens: configuration.maxTokens,
|
|
73
|
+
reasoningLevel: configuration.reasoningLevel ?? "disabled",
|
|
74
|
+
outputType: configuration.outputType ?? "text",
|
|
75
|
+
outputSchema: configuration.outputSchema,
|
|
76
|
+
actions: compileActions(name, sourceDirectory),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (Object.keys(agents).length === 0) {
|
|
80
|
+
throw new Error("At least one agent must be registered.");
|
|
81
|
+
}
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
schemaVersion: 1,
|
|
84
|
+
actionRuntime,
|
|
85
|
+
agents: Object.freeze(agents),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function compileActions(agentName, sourceDirectory) {
|
|
89
|
+
const actionsDirectory = resolve(sourceDirectory, "actions");
|
|
90
|
+
if (!existsSync(actionsDirectory)) {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
const actions = {};
|
|
94
|
+
for (const entry of readdirSync(actionsDirectory, { withFileTypes: true })) {
|
|
95
|
+
if (!entry.isDirectory()) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const configurationPath = resolve(actionsDirectory, entry.name, "index.json");
|
|
99
|
+
let configuration;
|
|
100
|
+
try {
|
|
101
|
+
configuration = JSON.parse(readFileSync(configurationPath, "utf8"));
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
throw new Error(`Action "${agentName}/${entry.name}" is missing a valid index.json.`, { cause: error });
|
|
105
|
+
}
|
|
106
|
+
if (!configuration.name?.trim() || !configuration.description?.trim()) {
|
|
107
|
+
throw new Error(`Action "${agentName}/${entry.name}" requires name and description.`);
|
|
108
|
+
}
|
|
109
|
+
if (!/^[a-zA-Z0-9_-]{1,64}$/.test(configuration.name)) {
|
|
110
|
+
throw new Error(`Action "${agentName}/${entry.name}" has an invalid model-facing name.`);
|
|
111
|
+
}
|
|
112
|
+
if (!["client", "local"].includes(configuration.execution)) {
|
|
113
|
+
throw new Error(`Action "${agentName}/${entry.name}" has invalid execution "${configuration.execution}".`);
|
|
114
|
+
}
|
|
115
|
+
if (!configuration.parameters ||
|
|
116
|
+
typeof configuration.parameters !== "object" ||
|
|
117
|
+
Array.isArray(configuration.parameters)) {
|
|
118
|
+
throw new Error(`Action "${agentName}/${entry.name}" requires a parameters JSON Schema.`);
|
|
119
|
+
}
|
|
120
|
+
if (configuration.outputSchema !== undefined &&
|
|
121
|
+
(typeof configuration.outputSchema !== "object" ||
|
|
122
|
+
configuration.outputSchema === null ||
|
|
123
|
+
Array.isArray(configuration.outputSchema))) {
|
|
124
|
+
throw new Error(`Action "${agentName}/${entry.name}" has an invalid outputSchema.`);
|
|
125
|
+
}
|
|
126
|
+
validateNumber(`${agentName}/${entry.name}`, "timeoutMs", configuration.timeoutMs, { integer: true, minimum: 1, maximum: 120_000 });
|
|
127
|
+
if (configuration.permissions?.env !== undefined &&
|
|
128
|
+
!Array.isArray(configuration.permissions.env)) {
|
|
129
|
+
throw new Error(`Action "${agentName}/${entry.name}" permissions.env must be an array.`);
|
|
130
|
+
}
|
|
131
|
+
if (configuration.permissions?.network !== undefined &&
|
|
132
|
+
!Array.isArray(configuration.permissions.network)) {
|
|
133
|
+
throw new Error(`Action "${agentName}/${entry.name}" permissions.network must be an array.`);
|
|
134
|
+
}
|
|
135
|
+
for (const permission of [
|
|
136
|
+
...(configuration.permissions?.env ?? []),
|
|
137
|
+
...(configuration.permissions?.network ?? []),
|
|
138
|
+
]) {
|
|
139
|
+
if (typeof permission !== "string" || !permission.trim()) {
|
|
140
|
+
throw new Error(`Action "${agentName}/${entry.name}" has an invalid permission.`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (actions[configuration.name]) {
|
|
144
|
+
throw new Error(`Agent "${agentName}" has duplicate action name "${configuration.name}".`);
|
|
145
|
+
}
|
|
146
|
+
const compiledSource = configuration.execution === "local"
|
|
147
|
+
? compileLocalAction(agentName, entry.name, actionsDirectory)
|
|
148
|
+
: undefined;
|
|
149
|
+
actions[configuration.name] = Object.freeze({
|
|
150
|
+
...configuration,
|
|
151
|
+
directoryName: entry.name,
|
|
152
|
+
...compiledSource,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return Object.freeze(actions);
|
|
156
|
+
}
|
|
157
|
+
function compileLocalAction(agentName, directoryName, actionsDirectory) {
|
|
158
|
+
const entryPath = resolve(actionsDirectory, directoryName, "index.js");
|
|
159
|
+
if (!existsSync(entryPath)) {
|
|
160
|
+
throw new Error(`Local action "${agentName}/${directoryName}" requires index.js.`);
|
|
161
|
+
}
|
|
162
|
+
let output;
|
|
163
|
+
try {
|
|
164
|
+
const result = buildSync({
|
|
165
|
+
entryPoints: [entryPath],
|
|
166
|
+
bundle: true,
|
|
167
|
+
write: false,
|
|
168
|
+
format: "iife",
|
|
169
|
+
globalName: "__orchaActionModule",
|
|
170
|
+
platform: "neutral",
|
|
171
|
+
target: "es2022",
|
|
172
|
+
logLevel: "silent",
|
|
173
|
+
});
|
|
174
|
+
output = result.outputFiles[0]?.text ?? "";
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
throw new Error(`Local action "${agentName}/${directoryName}" could not be bundled.`, { cause: error });
|
|
178
|
+
}
|
|
179
|
+
if (!output.includes("__orchaActionModule")) {
|
|
180
|
+
throw new Error(`Local action "${agentName}/${directoryName}" must export a default function.`);
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
source: output,
|
|
184
|
+
sourceHash: createHash("sha256").update(output).digest("hex"),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export function serializeBundle(bundle) {
|
|
188
|
+
return [
|
|
189
|
+
"// Generated by OrchaJS. Do not edit manually.",
|
|
190
|
+
`export const bundle = ${JSON.stringify(bundle, null, 2)};`,
|
|
191
|
+
"export default bundle;",
|
|
192
|
+
"",
|
|
193
|
+
].join("\n");
|
|
194
|
+
}
|
|
195
|
+
function validateNumber(agentName, field, value, constraints) {
|
|
196
|
+
if (value === undefined) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (!Number.isFinite(value) ||
|
|
200
|
+
(constraints.integer && !Number.isInteger(value)) ||
|
|
201
|
+
(constraints.minimum !== undefined && value < constraints.minimum) ||
|
|
202
|
+
(constraints.maximum !== undefined && value > constraints.maximum)) {
|
|
203
|
+
throw new Error(`Agent "${agentName}" has an invalid ${field}.`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=compile.js.map
|