orchajs 0.9.0 → 0.10.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.
Files changed (39) hide show
  1. package/README.md +13 -0
  2. package/dist/cli-agents-template.d.ts +1 -1
  3. package/dist/cli-agents-template.d.ts.map +1 -1
  4. package/dist/cli-agents-template.js +5 -2
  5. package/dist/cli-agents-template.js.map +1 -1
  6. package/dist/cli.js +25 -55
  7. package/dist/cli.js.map +1 -1
  8. package/dist/playground/public/app.css +2 -0
  9. package/dist/playground/public/app.css.map +7 -0
  10. package/dist/playground/public/app.js +32 -0
  11. package/dist/playground/public/app.js.map +7 -0
  12. package/dist/playground/public/index.html +15 -0
  13. package/dist/playground/public/orcha-logo.svg +4 -0
  14. package/dist/playground/server.d.ts +8 -0
  15. package/dist/playground/server.d.ts.map +1 -0
  16. package/dist/playground/server.js +611 -0
  17. package/dist/playground/server.js.map +1 -0
  18. package/dist/playground/source.d.ts +34 -0
  19. package/dist/playground/source.d.ts.map +1 -0
  20. package/dist/playground/source.js +90 -0
  21. package/dist/playground/source.js.map +1 -0
  22. package/dist/project-loader.d.ts +2 -0
  23. package/dist/project-loader.d.ts.map +1 -0
  24. package/dist/project-loader.js +59 -0
  25. package/dist/project-loader.js.map +1 -0
  26. package/dist/runtime/agent.d.ts +6 -1
  27. package/dist/runtime/agent.d.ts.map +1 -1
  28. package/dist/runtime/agent.js +13 -0
  29. package/dist/runtime/agent.js.map +1 -1
  30. package/dist/runtime/execution.d.ts.map +1 -1
  31. package/dist/runtime/execution.js +1 -0
  32. package/dist/runtime/execution.js.map +1 -1
  33. package/dist/storage/node-jsonl.d.ts +1 -0
  34. package/dist/storage/node-jsonl.d.ts.map +1 -1
  35. package/dist/storage/node-jsonl.js +21 -0
  36. package/dist/storage/node-jsonl.js.map +1 -1
  37. package/dist/types.d.ts +3 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/package.json +13 -2
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="color-scheme" content="light" />
7
+ <link rel="icon" href="/orcha-logo.svg" type="image/svg+xml" />
8
+ <title>Orcha Playground</title>
9
+ <link rel="stylesheet" href="/app.css" />
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ <script type="module" src="/app.js"></script>
14
+ </body>
15
+ </html>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
2
+ <rect width="64" height="64" rx="12" fill="#0f3d33"/>
3
+ <rect x="20" y="20" width="24" height="24" rx="2" fill="#f6f5f0" transform="rotate(45 32 32)"/>
4
+ </svg>
@@ -0,0 +1,8 @@
1
+ export interface PlaygroundOptions {
2
+ projectRoot?: string;
3
+ host?: string;
4
+ port?: number;
5
+ open?: boolean;
6
+ }
7
+ export declare function startPlayground(options?: PlaygroundOptions): Promise<void>;
8
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/playground/server.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,eAAe,CACnC,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAiGf"}
@@ -0,0 +1,611 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { existsSync, watch } from "node:fs";
4
+ import { readFile } from "node:fs/promises";
5
+ import { dirname, extname, isAbsolute, relative, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { createServer, } from "node:http";
8
+ import { orcha } from "../orcha.js";
9
+ import { loadProject } from "../project-loader.js";
10
+ import { listTests, runTests } from "../testing.js";
11
+ import { readPlaygroundAgentSource } from "./source.js";
12
+ export async function startPlayground(options = {}) {
13
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
14
+ const host = options.host ?? "localhost";
15
+ const port = options.port ?? 4310;
16
+ if (host === "0.0.0.0" || host === "::") {
17
+ throw new Error("Use an explicit hostname or IP address instead of a wildcard playground host.");
18
+ }
19
+ const mutationToken = randomBytes(32).toString("base64url");
20
+ const allowedHosts = playgroundHosts(host, port);
21
+ await loadProject(projectRoot);
22
+ const projectClients = new Set();
23
+ const server = createServer(async (request, response) => {
24
+ try {
25
+ await routeRequest({
26
+ request,
27
+ response,
28
+ projectRoot,
29
+ host,
30
+ port,
31
+ projectClients,
32
+ mutationToken,
33
+ allowedHosts,
34
+ });
35
+ }
36
+ catch (error) {
37
+ sendJson(response, error instanceof HttpError ? error.status : 500, {
38
+ error: error instanceof Error ? error.message : String(error),
39
+ });
40
+ }
41
+ });
42
+ await new Promise((resolveListening, reject) => {
43
+ server.once("error", reject);
44
+ server.listen(port, host, resolveListening);
45
+ });
46
+ const url = `http://${host}:${port}`;
47
+ console.log(`Orcha playground: ${url}`);
48
+ if (options.open !== false) {
49
+ openBrowser(url);
50
+ }
51
+ let reloadTimer;
52
+ let reloading = false;
53
+ let reloadAgain = false;
54
+ const reload = async () => {
55
+ if (reloading) {
56
+ reloadAgain = true;
57
+ return;
58
+ }
59
+ reloading = true;
60
+ try {
61
+ await loadProject(projectRoot);
62
+ publishProjectEvent(projectClients, {
63
+ type: "project.updated",
64
+ timestamp: new Date().toISOString(),
65
+ });
66
+ console.log("[orcha] playground reloaded");
67
+ }
68
+ catch (error) {
69
+ const message = error instanceof Error ? error.message : String(error);
70
+ publishProjectEvent(projectClients, {
71
+ type: "project.error",
72
+ timestamp: new Date().toISOString(),
73
+ error: message,
74
+ });
75
+ console.error(`[orcha] playground reload failed — ${message}`);
76
+ }
77
+ finally {
78
+ reloading = false;
79
+ if (reloadAgain) {
80
+ reloadAgain = false;
81
+ await reload();
82
+ }
83
+ }
84
+ };
85
+ const watcher = watch(resolve(projectRoot, "orcha"), { recursive: true }, () => {
86
+ clearTimeout(reloadTimer);
87
+ reloadTimer = setTimeout(() => void reload(), 100);
88
+ });
89
+ await new Promise((resolveStop) => {
90
+ const stop = () => {
91
+ clearTimeout(reloadTimer);
92
+ watcher.close();
93
+ for (const client of projectClients) {
94
+ client.end();
95
+ }
96
+ server.close(() => resolveStop());
97
+ };
98
+ process.once("SIGINT", stop);
99
+ process.once("SIGTERM", stop);
100
+ });
101
+ }
102
+ async function routeRequest(context) {
103
+ const { request, response } = context;
104
+ const method = request.method ?? "GET";
105
+ const url = new URL(request.url ?? "/", `http://${context.host}:${context.port}`);
106
+ const segments = url.pathname
107
+ .split("/")
108
+ .filter(Boolean)
109
+ .map(decodeURIComponent);
110
+ validateRequestHost(request, context.allowedHosts);
111
+ if (method === "GET" && url.pathname === "/api/playground") {
112
+ sendJson(response, 200, { mutationToken: context.mutationToken });
113
+ return;
114
+ }
115
+ if (method !== "GET" && method !== "HEAD") {
116
+ validateMutationRequest(request, context.mutationToken);
117
+ }
118
+ if (method === "GET" && url.pathname === "/api/project/events") {
119
+ response.writeHead(200, {
120
+ "content-type": "text/event-stream; charset=utf-8",
121
+ "cache-control": "no-store",
122
+ connection: "keep-alive",
123
+ });
124
+ response.write(": connected\n\n");
125
+ context.projectClients.add(response);
126
+ response.once("close", () => context.projectClients.delete(response));
127
+ return;
128
+ }
129
+ if (method === "GET" && url.pathname === "/api/agents") {
130
+ sendJson(response, 200, {
131
+ agents: Object.values(orcha.getCompiledBundle().agents).map(publicAgentSummary),
132
+ });
133
+ return;
134
+ }
135
+ if (method === "GET" &&
136
+ segments.length === 3 &&
137
+ segments[0] === "api" &&
138
+ segments[1] === "agents") {
139
+ getAgent(segments[2]);
140
+ sendJson(response, 200, await readPlaygroundAgentSource(orcha, segments[2]));
141
+ return;
142
+ }
143
+ if (segments.length === 4 &&
144
+ segments[0] === "api" &&
145
+ segments[1] === "agents" &&
146
+ segments[3] === "tests") {
147
+ getAgent(segments[2]);
148
+ if (method === "GET") {
149
+ const tests = await listTests(orcha, { agent: segments[2] });
150
+ sendJson(response, 200, {
151
+ tests: tests.map((test) => ({
152
+ ...test,
153
+ rawConfiguration: JSON.stringify(test.configuration, null, 2),
154
+ })),
155
+ });
156
+ return;
157
+ }
158
+ if (method === "POST") {
159
+ const body = await readJsonBody(request);
160
+ const test = optionalString(body.test);
161
+ sendJson(response, 200, await runTests(orcha, {
162
+ agent: segments[2],
163
+ ...(test ? { test } : {}),
164
+ }));
165
+ return;
166
+ }
167
+ }
168
+ if (method === "POST" &&
169
+ segments.length === 4 &&
170
+ segments[0] === "api" &&
171
+ segments[1] === "agents" &&
172
+ segments[3] === "executions") {
173
+ await executeAgent(segments[2], request, response);
174
+ return;
175
+ }
176
+ if (segments.length >= 4 &&
177
+ segments[0] === "api" &&
178
+ segments[1] === "agents" &&
179
+ segments[3] === "sessions") {
180
+ const agent = getAgent(segments[2]);
181
+ if (method === "GET" && segments.length === 4) {
182
+ sendJson(response, 200, await agent.list({
183
+ page: positiveInteger(url.searchParams.get("page"), 1),
184
+ pageSize: positiveInteger(url.searchParams.get("pageSize"), 50),
185
+ }));
186
+ return;
187
+ }
188
+ const sessionId = segments[4];
189
+ if (method === "GET" && segments.length === 5) {
190
+ sendJson(response, 200, await agent.get(sessionId));
191
+ return;
192
+ }
193
+ if (method === "GET" &&
194
+ segments.length === 6 &&
195
+ segments[5] === "events") {
196
+ sendJson(response, 200, {
197
+ events: await readAllEvents(agent, sessionId),
198
+ });
199
+ return;
200
+ }
201
+ if (method === "POST" &&
202
+ segments.length === 6 &&
203
+ segments[5] === "pause") {
204
+ sendJson(response, 200, await agent.pause(sessionId));
205
+ return;
206
+ }
207
+ }
208
+ if (method === "GET" &&
209
+ segments.length === 6 &&
210
+ segments[0] === "api" &&
211
+ segments[1] === "agents" &&
212
+ segments[3] === "subagents" &&
213
+ segments[5] === "events") {
214
+ const agent = getAgent(segments[2]);
215
+ sendJson(response, 200, {
216
+ events: await readAllSubagentEvents(agent, segments[4]),
217
+ });
218
+ return;
219
+ }
220
+ if (method === "GET" &&
221
+ segments.length === 6 &&
222
+ segments[0] === "api" &&
223
+ segments[1] === "agents" &&
224
+ segments[3] === "subagents" &&
225
+ segments[5] === "stream") {
226
+ const agent = getAgent(segments[2]);
227
+ let ready = false;
228
+ const pending = [];
229
+ const publish = (events) => {
230
+ if (!ready) {
231
+ pending.push(events);
232
+ return;
233
+ }
234
+ response.write(`data: ${JSON.stringify({ events })}\n\n`);
235
+ };
236
+ const unsubscribe = await agent._subscribeSubagentSessionEvents(segments[4], publish);
237
+ response.writeHead(200, {
238
+ "content-type": "text/event-stream; charset=utf-8",
239
+ "cache-control": "no-store",
240
+ connection: "keep-alive",
241
+ });
242
+ response.write(": connected\n\n");
243
+ ready = true;
244
+ for (const events of pending) {
245
+ publish(events);
246
+ }
247
+ pending.length = 0;
248
+ response.once("close", unsubscribe);
249
+ return;
250
+ }
251
+ if (url.pathname.startsWith("/api/")) {
252
+ sendJson(response, 404, { error: "Not found." });
253
+ return;
254
+ }
255
+ if (method === "GET") {
256
+ await serveAsset(response, url.pathname);
257
+ return;
258
+ }
259
+ sendJson(response, 404, { error: "Not found." });
260
+ }
261
+ async function executeAgent(agentName, request, response) {
262
+ const agent = getAgent(agentName);
263
+ const body = await readJsonBody(request);
264
+ const sessionId = optionalString(body.sessionId);
265
+ const clientCapabilities = agent.clientTools.map((tool) => tool.name);
266
+ let execution;
267
+ if ("toolResults" in body) {
268
+ if (!sessionId) {
269
+ throw new HttpError(400, "sessionId is required with toolResults.");
270
+ }
271
+ execution = agent.resume(sessionId, {
272
+ toolResults: parseToolResults(body.toolResults),
273
+ });
274
+ }
275
+ else {
276
+ const content = parseContent(body.content);
277
+ const name = optionalString(body.name);
278
+ execution = sessionId
279
+ ? agent.resume(sessionId, { content, clientCapabilities })
280
+ : agent.run({
281
+ content,
282
+ clientCapabilities,
283
+ ...(name ? { name } : {}),
284
+ });
285
+ }
286
+ await streamExecution(agent, execution, response);
287
+ }
288
+ async function streamExecution(agent, execution, response) {
289
+ response.writeHead(200, {
290
+ "content-type": "application/x-ndjson; charset=utf-8",
291
+ "cache-control": "no-store",
292
+ connection: "keep-alive",
293
+ });
294
+ const unsubscribe = agent._subscribeSessionEvents(execution.sessionId, (events) => writeNdjson(response, { type: "events.appended", events }));
295
+ writeNdjson(response, {
296
+ type: "execution.started",
297
+ sessionId: execution.sessionId,
298
+ });
299
+ try {
300
+ for await (const snapshot of execution.stream) {
301
+ writeNdjson(response, { type: "snapshot", snapshot });
302
+ }
303
+ const result = await execution.result;
304
+ writeNdjson(response, { type: "result", result });
305
+ writeNdjson(response, {
306
+ type: "events",
307
+ sessionId: result.sessionId,
308
+ events: await readAllEvents(agent, result.sessionId),
309
+ });
310
+ writeNdjson(response, {
311
+ type: "evaluations",
312
+ sessionId: result.sessionId,
313
+ evaluations: await execution.evaluations,
314
+ });
315
+ response.end();
316
+ }
317
+ catch (error) {
318
+ await execution.result.catch(() => undefined);
319
+ writeNdjson(response, {
320
+ type: "error",
321
+ error: error instanceof Error ? error.message : String(error),
322
+ });
323
+ try {
324
+ writeNdjson(response, {
325
+ type: "events",
326
+ sessionId: execution.sessionId,
327
+ events: await readAllEvents(agent, execution.sessionId),
328
+ });
329
+ }
330
+ catch {
331
+ // The session may have failed before it could be persisted.
332
+ }
333
+ response.end();
334
+ }
335
+ finally {
336
+ unsubscribe();
337
+ }
338
+ }
339
+ function getAgent(name) {
340
+ const manifest = orcha.getCompiledBundle().agents[name];
341
+ const agent = orcha[name];
342
+ if (!manifest || !agent) {
343
+ throw new HttpError(404, `Unknown agent "${name}".`);
344
+ }
345
+ return agent;
346
+ }
347
+ function publicAgentSummary(manifest) {
348
+ return {
349
+ key: manifest.key,
350
+ name: manifest.name,
351
+ description: manifest.description,
352
+ provider: manifest.provider,
353
+ model: manifest.model,
354
+ actionCount: Object.keys(manifest.actions).length,
355
+ skillCount: Object.keys(manifest.skills).length,
356
+ evaluationCount: Object.keys(manifest.evaluations).length,
357
+ subagentCount: Object.keys(manifest.subagents).length,
358
+ };
359
+ }
360
+ async function readAllEvents(agent, sessionId) {
361
+ return readAllEventPages((page, throughSequence) => agent.events(sessionId, {
362
+ page,
363
+ pageSize: 100,
364
+ ...(throughSequence ? { throughSequence } : {}),
365
+ }));
366
+ }
367
+ async function readAllSubagentEvents(agent, sessionId) {
368
+ return readAllEventPages((page, throughSequence) => agent.subagentEvents(sessionId, {
369
+ page,
370
+ pageSize: 100,
371
+ ...(throughSequence ? { throughSequence } : {}),
372
+ }));
373
+ }
374
+ async function readAllEventPages(read) {
375
+ const pages = [];
376
+ let page = 1;
377
+ let hasMore = true;
378
+ let throughSequence;
379
+ while (hasMore) {
380
+ const result = await read(page, throughSequence);
381
+ throughSequence = result.throughSequence;
382
+ pages.unshift(result.events);
383
+ hasMore = result.hasMore;
384
+ page += 1;
385
+ }
386
+ return pages.flat();
387
+ }
388
+ async function serveAsset(response, pathname) {
389
+ const publicRoot = resolve(dirname(fileURLToPath(import.meta.url)), "public");
390
+ const requested = pathname === "/" ? "index.html" : pathname.slice(1);
391
+ const path = resolve(publicRoot, requested);
392
+ const relativePath = relative(publicRoot, path);
393
+ if (relativePath.startsWith("..") ||
394
+ isAbsolute(relativePath) ||
395
+ !existsSync(path)) {
396
+ const indexPath = resolve(publicRoot, "index.html");
397
+ if (!existsSync(indexPath)) {
398
+ sendJson(response, 503, {
399
+ error: "Playground assets are missing. Rebuild orchajs.",
400
+ });
401
+ return;
402
+ }
403
+ response.writeHead(200, {
404
+ "content-type": "text/html; charset=utf-8",
405
+ "cache-control": "no-store",
406
+ });
407
+ response.end(await readFile(indexPath));
408
+ return;
409
+ }
410
+ response.writeHead(200, {
411
+ "content-type": contentType(path),
412
+ "cache-control": "no-store",
413
+ });
414
+ response.end(await readFile(path));
415
+ }
416
+ function contentType(path) {
417
+ switch (extname(path)) {
418
+ case ".html":
419
+ return "text/html; charset=utf-8";
420
+ case ".js":
421
+ return "text/javascript; charset=utf-8";
422
+ case ".css":
423
+ return "text/css; charset=utf-8";
424
+ case ".svg":
425
+ return "image/svg+xml";
426
+ default:
427
+ return "application/octet-stream";
428
+ }
429
+ }
430
+ async function readJsonBody(request) {
431
+ const chunks = [];
432
+ let size = 0;
433
+ for await (const chunk of request) {
434
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
435
+ size += buffer.length;
436
+ if (size > 2_000_000) {
437
+ throw new HttpError(413, "Request body exceeds 2 MB.");
438
+ }
439
+ chunks.push(buffer);
440
+ }
441
+ let value;
442
+ try {
443
+ value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
444
+ }
445
+ catch {
446
+ throw new HttpError(400, "Request body must contain valid JSON.");
447
+ }
448
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
449
+ throw new HttpError(400, "Request body must be a JSON object.");
450
+ }
451
+ return value;
452
+ }
453
+ function parseContent(value) {
454
+ if (typeof value === "string" && value.trim()) {
455
+ return value;
456
+ }
457
+ const items = Array.isArray(value) ? value : [value];
458
+ if (items.length === 0) {
459
+ throw new HttpError(400, "content is required.");
460
+ }
461
+ const parsed = items.map(parseContentItem);
462
+ return Array.isArray(value) ? parsed : parsed[0];
463
+ }
464
+ function parseContentItem(value) {
465
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
466
+ throw new HttpError(400, "Every content item must be an object.");
467
+ }
468
+ const item = value;
469
+ if (typeof item.filePath === "string") {
470
+ return {
471
+ filePath: item.filePath,
472
+ ...(typeof item.mimeType === "string"
473
+ ? { mimeType: item.mimeType }
474
+ : {}),
475
+ };
476
+ }
477
+ if (typeof item.url === "string") {
478
+ return {
479
+ url: item.url,
480
+ ...(typeof item.mimeType === "string"
481
+ ? { mimeType: item.mimeType }
482
+ : {}),
483
+ };
484
+ }
485
+ if (item.type === "text" && typeof item.text === "string") {
486
+ return { type: "text", text: item.text };
487
+ }
488
+ if ((item.type === "image" ||
489
+ item.type === "video" ||
490
+ item.type === "audio" ||
491
+ item.type === "url") &&
492
+ typeof item.mimeType === "string" &&
493
+ typeof item.fileUri === "string") {
494
+ return {
495
+ type: item.type,
496
+ mimeType: item.mimeType,
497
+ fileUri: item.fileUri,
498
+ };
499
+ }
500
+ throw new HttpError(400, "Content item has invalid fields.");
501
+ }
502
+ function parseToolResults(value) {
503
+ if (!Array.isArray(value) || value.length === 0) {
504
+ throw new HttpError(400, "toolResults must be a non-empty array.");
505
+ }
506
+ return value.map((item) => {
507
+ if (!item ||
508
+ typeof item !== "object" ||
509
+ Array.isArray(item) ||
510
+ typeof item.callId !== "string" ||
511
+ !("output" in item)) {
512
+ throw new HttpError(400, "Every tool result requires callId and output.");
513
+ }
514
+ const result = item;
515
+ return {
516
+ callId: result.callId,
517
+ output: result.output,
518
+ ...(result.isError === true ? { isError: true } : {}),
519
+ };
520
+ });
521
+ }
522
+ function sendJson(response, status, value) {
523
+ if (response.headersSent) {
524
+ return;
525
+ }
526
+ response.writeHead(status, {
527
+ "content-type": "application/json; charset=utf-8",
528
+ "cache-control": "no-store",
529
+ });
530
+ response.end(JSON.stringify(value));
531
+ }
532
+ function writeNdjson(response, value) {
533
+ response.write(`${JSON.stringify(value)}\n`);
534
+ }
535
+ function publishProjectEvent(clients, value) {
536
+ const payload = `data: ${JSON.stringify(value)}\n\n`;
537
+ for (const client of clients) {
538
+ client.write(payload);
539
+ }
540
+ }
541
+ function positiveInteger(value, fallback) {
542
+ const parsed = value ? Number.parseInt(value, 10) : fallback;
543
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
544
+ }
545
+ function optionalString(value) {
546
+ return typeof value === "string" && value.trim()
547
+ ? value.trim()
548
+ : undefined;
549
+ }
550
+ function openBrowser(url) {
551
+ const command = process.platform === "darwin"
552
+ ? "open"
553
+ : process.platform === "win32"
554
+ ? "cmd"
555
+ : "xdg-open";
556
+ const arguments_ = process.platform === "win32" ? ["/c", "start", "", url] : [url];
557
+ const child = spawn(command, arguments_, {
558
+ detached: true,
559
+ stdio: "ignore",
560
+ });
561
+ child.once("error", () => undefined);
562
+ child.unref();
563
+ }
564
+ class HttpError extends Error {
565
+ status;
566
+ constructor(status, message) {
567
+ super(message);
568
+ this.status = status;
569
+ }
570
+ }
571
+ function playgroundHosts(host, port) {
572
+ const configured = host.includes(":") && !host.startsWith("[")
573
+ ? `[${host}]:${port}`
574
+ : `${host}:${port}`;
575
+ const hosts = new Set([configured.toLowerCase()]);
576
+ if (["127.0.0.1", "localhost", "::1"].includes(host)) {
577
+ hosts.add(`127.0.0.1:${port}`);
578
+ hosts.add(`localhost:${port}`);
579
+ hosts.add(`[::1]:${port}`);
580
+ }
581
+ return hosts;
582
+ }
583
+ function validateRequestHost(request, allowedHosts) {
584
+ const host = request.headers.host?.toLowerCase();
585
+ if (!host || !allowedHosts.has(host)) {
586
+ throw new HttpError(403, "Playground request host is not allowed.");
587
+ }
588
+ }
589
+ function validateMutationRequest(request, mutationToken) {
590
+ const contentType = request.headers["content-type"] ?? "";
591
+ if (!contentType.toLowerCase().startsWith("application/json")) {
592
+ throw new HttpError(415, "Playground mutations require application/json.");
593
+ }
594
+ if (request.headers["x-orcha-playground-token"] !== mutationToken) {
595
+ throw new HttpError(403, "Invalid playground mutation token.");
596
+ }
597
+ const origin = request.headers.origin;
598
+ if (origin) {
599
+ let originHost;
600
+ try {
601
+ originHost = new URL(origin).host.toLowerCase();
602
+ }
603
+ catch {
604
+ throw new HttpError(403, "Invalid playground request origin.");
605
+ }
606
+ if (originHost !== request.headers.host?.toLowerCase()) {
607
+ throw new HttpError(403, "Cross-origin playground request denied.");
608
+ }
609
+ }
610
+ }
611
+ //# sourceMappingURL=server.js.map