orchajs 0.20.3 → 0.22.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 (107) hide show
  1. package/README.md +103 -0
  2. package/dist/amarsia/analytics.d.ts +21 -0
  3. package/dist/amarsia/analytics.js +457 -0
  4. package/dist/amarsia/endpoint.d.ts +1 -0
  5. package/dist/amarsia/endpoint.js +8 -0
  6. package/dist/amarsia/sync.js +1 -4
  7. package/dist/bin.d.ts +2 -0
  8. package/dist/bin.js +13 -0
  9. package/dist/browser.d.ts +2 -0
  10. package/dist/browser.js +12 -0
  11. package/dist/cli.js +35 -9
  12. package/dist/compiler/build-project.d.ts +1 -0
  13. package/dist/compiler/build-project.js +75 -8
  14. package/dist/compiler/compile.d.ts +1 -1
  15. package/dist/compiler/compile.js +11 -9
  16. package/dist/errors.d.ts +5 -0
  17. package/dist/errors.js +102 -0
  18. package/dist/file-content.js +42 -6
  19. package/dist/index.d.ts +1 -5
  20. package/dist/index.js +1 -2
  21. package/dist/integrations/esbuild.js +5 -2
  22. package/dist/integrations/expo.d.ts +1 -0
  23. package/dist/integrations/expo.js +1 -0
  24. package/dist/integrations/metro.d.ts +15 -0
  25. package/dist/integrations/metro.js +25 -0
  26. package/dist/integrations/next.d.ts +23 -0
  27. package/dist/integrations/next.js +53 -0
  28. package/dist/integrations/runtime-build.d.ts +4 -0
  29. package/dist/integrations/runtime-build.js +32 -0
  30. package/dist/integrations/vite.d.ts +23 -0
  31. package/dist/integrations/vite.js +32 -0
  32. package/dist/integrations/webpack.d.ts +24 -0
  33. package/dist/integrations/webpack.js +21 -0
  34. package/dist/orcha.js +17 -8
  35. package/dist/playground/server.js +57 -7
  36. package/dist/project-loader.js +3 -0
  37. package/dist/providers/browser.d.ts +2 -0
  38. package/dist/providers/browser.js +20 -0
  39. package/dist/react-native.d.ts +2 -0
  40. package/dist/react-native.js +12 -0
  41. package/dist/runtime/actions/browser-native.d.ts +3 -0
  42. package/dist/runtime/actions/browser-native.js +28 -0
  43. package/dist/runtime/actions/execute.d.ts +7 -0
  44. package/dist/runtime/actions/execute.js +9 -2
  45. package/dist/runtime/agent.d.ts +1 -0
  46. package/dist/runtime/agent.js +38 -10
  47. package/dist/runtime/compiler-hook.d.ts +5 -0
  48. package/dist/runtime/compiler-hook.js +8 -0
  49. package/dist/runtime/create-orcha.d.ts +6 -1
  50. package/dist/runtime/create-orcha.js +35 -21
  51. package/dist/runtime/platform-browser.d.ts +2 -0
  52. package/dist/runtime/platform-browser.js +15 -0
  53. package/dist/runtime/platform-node.d.ts +2 -0
  54. package/dist/runtime/platform-node.js +31 -0
  55. package/dist/runtime/platform-react-native.d.ts +2 -0
  56. package/dist/runtime/platform-react-native.js +15 -0
  57. package/dist/runtime/platform.d.ts +15 -0
  58. package/dist/runtime/platform.js +1 -0
  59. package/dist/runtime/random-id.d.ts +1 -0
  60. package/dist/runtime/random-id.js +26 -0
  61. package/dist/runtime-entry.d.ts +5 -0
  62. package/dist/runtime-entry.js +2 -0
  63. package/dist/storage/browser-indexeddb.d.ts +7 -16
  64. package/dist/storage/browser-indexeddb.js +102 -19
  65. package/dist/storage/react-native-jsonl.d.ts +7 -16
  66. package/dist/storage/react-native-jsonl.js +65 -19
  67. package/dist/types.d.ts +54 -1
  68. package/dist-cjs/amarsia/analytics.js +447 -0
  69. package/dist-cjs/amarsia/endpoint.js +32 -0
  70. package/dist-cjs/amarsia/sync.js +2 -5
  71. package/dist-cjs/browser.js +48 -0
  72. package/dist-cjs/compiler/build-project.js +97 -8
  73. package/dist-cjs/compiler/compile.js +17 -9
  74. package/dist-cjs/errors.js +92 -2
  75. package/dist-cjs/file-content.js +42 -7
  76. package/dist-cjs/index.js +4 -9
  77. package/dist-cjs/integrations/esbuild.js +6 -2
  78. package/dist-cjs/integrations/expo.js +29 -0
  79. package/dist-cjs/integrations/metro.js +51 -0
  80. package/dist-cjs/integrations/next.js +75 -0
  81. package/dist-cjs/integrations/runtime-build.js +70 -0
  82. package/dist-cjs/integrations/vite.js +57 -0
  83. package/dist-cjs/integrations/webpack.js +51 -0
  84. package/dist-cjs/orcha.js +17 -12
  85. package/dist-cjs/playground/server.js +62 -9
  86. package/dist-cjs/project-loader.js +10 -0
  87. package/dist-cjs/providers/browser.js +61 -0
  88. package/dist-cjs/react-native.js +48 -0
  89. package/dist-cjs/runtime/actions/browser-native.js +68 -0
  90. package/dist-cjs/runtime/actions/execute.js +12 -2
  91. package/dist-cjs/runtime/agent.js +37 -14
  92. package/dist-cjs/runtime/compiler-hook.js +37 -0
  93. package/dist-cjs/runtime/create-orcha.js +34 -23
  94. package/dist-cjs/runtime/platform-browser.js +48 -0
  95. package/dist-cjs/runtime/platform-node.js +59 -0
  96. package/dist-cjs/runtime/platform-react-native.js +45 -0
  97. package/dist-cjs/runtime/platform.js +17 -0
  98. package/dist-cjs/runtime/random-id.js +55 -0
  99. package/dist-cjs/runtime-entry.js +34 -0
  100. package/dist-cjs/storage/browser-indexeddb.js +102 -5
  101. package/dist-cjs/storage/react-native-jsonl.js +73 -5
  102. package/dist-cjs/types/react-native-async-storage.d.js +2 -0
  103. package/package.json +69 -9
  104. package/dist/playground/public/app.css +0 -1
  105. package/dist/playground/public/app.js +0 -31
  106. package/dist/playground/public/index.html +0 -15
  107. package/dist/playground/public/orcha-logo.svg +0 -4
package/README.md CHANGED
@@ -50,6 +50,109 @@ The Google GenAI and Vertex AI adapters use Google's current SDK and require
50
50
  Node.js 20 or newer. On Node.js 18, the rest of Orcha—including Amazon
51
51
  Bedrock—remains available.
52
52
 
53
+ Compiler and playground packages are optional development dependencies. Apps
54
+ created with `create-orcha` include them automatically. Existing projects that
55
+ use the CLI or a bundler integration should install them explicitly:
56
+
57
+ ```bash
58
+ npm install --save-dev esbuild chokidar dotenv
59
+ ```
60
+
61
+ The local playground is also development-only:
62
+
63
+ ```bash
64
+ npm install --save-dev @orchajs/playground-ui
65
+ ```
66
+
67
+ ## React, Next.js, and React Native
68
+
69
+ Application code always imports the same package:
70
+
71
+ ```ts
72
+ import { orcha } from "orchajs";
73
+ ```
74
+
75
+ The framework integration compiles the filesystem registry ahead of time and
76
+ redirects that unchanged import to a small, target-specific runtime. Compiler,
77
+ filesystem, playground, and unused provider code are not included in the
78
+ customer bundle. Provider adapters are selected from the compiled registry, so
79
+ using OpenAI does not include Anthropic, Google, Bedrock, or Vertex AI code.
80
+
81
+ For React with Vite:
82
+
83
+ ```ts
84
+ // vite.config.ts
85
+ import { defineConfig } from "vite";
86
+ import react from "@vitejs/plugin-react";
87
+ import { orchaVitePlugin } from "orchajs/vite";
88
+
89
+ export default defineConfig({
90
+ plugins: [orchaVitePlugin(), react()],
91
+ });
92
+ ```
93
+
94
+ Webpack-based React applications use the equivalent plugin:
95
+
96
+ ```js
97
+ const { OrchaWebpackPlugin } = require("orchajs/webpack");
98
+
99
+ const orcha = new OrchaWebpackPlugin();
100
+ module.exports = {
101
+ resolve: { alias: { "orchajs$": orcha.bundlePath } },
102
+ plugins: [orcha],
103
+ };
104
+ ```
105
+
106
+ Next.js 16 uses Turbopack by default. Its config may be asynchronous, so the
107
+ integration compiles both browser and Node targets before returning the config
108
+ and also configures Webpack fallback builds:
109
+
110
+ ```ts
111
+ // next.config.ts
112
+ import { withOrcha } from "orchajs/next";
113
+
114
+ export default async function nextConfig() {
115
+ return withOrcha({});
116
+ }
117
+ ```
118
+
119
+ Both Expo and bare React Native use the same precompiled runtime and persistent
120
+ AsyncStorage:
121
+
122
+ ```bash
123
+ # Bare React Native
124
+ npm install @react-native-async-storage/async-storage
125
+
126
+ # Expo
127
+ npx expo install @react-native-async-storage/async-storage
128
+ ```
129
+
130
+ Bare React Native:
131
+
132
+ ```js
133
+ // metro.config.js
134
+ const { getDefaultConfig } = require("@react-native/metro-config");
135
+ const { withOrchaMetro } = require("orchajs/metro");
136
+
137
+ module.exports = withOrchaMetro(getDefaultConfig(__dirname));
138
+ ```
139
+
140
+ Expo:
141
+
142
+ ```js
143
+ // metro.config.js
144
+ const { getDefaultConfig } = require("expo/metro-config");
145
+ const { withOrchaExpo } = require("orchajs/expo");
146
+
147
+ module.exports = withOrchaExpo(getDefaultConfig(__dirname));
148
+ ```
149
+
150
+ Browser and React Native builds execute compiled agents locally. Only
151
+ Anthropic, DeepSeek, Google GenAI, and OpenAI are browser-compatible; Bedrock
152
+ and Vertex AI remain server runtimes. Provider credentials included in client
153
+ configuration are visible to end users, so production applications should use
154
+ restricted client credentials or a server-side provider proxy.
155
+
53
156
  ---
54
157
 
55
158
  ## The convention
@@ -0,0 +1,21 @@
1
+ import type { AmarsiaTelemetryRunV1, CompiledAgentManifest, SessionStore } from "../types.js";
2
+ export type AmarsiaRunFact = AmarsiaTelemetryRunV1;
3
+ export declare class AmarsiaTelemetryClient {
4
+ #private;
5
+ constructor(options: {
6
+ key: string;
7
+ endpoint?: string;
8
+ });
9
+ enqueue(fact: AmarsiaRunFact): void;
10
+ drain(options?: {
11
+ timeoutMs?: number;
12
+ }): Promise<void>;
13
+ close(options?: {
14
+ timeoutMs?: number;
15
+ }): Promise<void>;
16
+ }
17
+ export declare function withAmarsiaTelemetry(options: {
18
+ store: SessionStore;
19
+ manifest: CompiledAgentManifest;
20
+ client: AmarsiaTelemetryClient;
21
+ }): SessionStore;
@@ -0,0 +1,457 @@
1
+ import { resolveAmarsiaEndpoint } from "./endpoint.js";
2
+ const RELEVANT_EVENTS = new Set([
3
+ "run.started",
4
+ "run.completed",
5
+ "run.failed",
6
+ "run.paused",
7
+ "evaluation.completed",
8
+ "evaluation.failed",
9
+ ]);
10
+ const MAX_BATCH_RUNS = 100;
11
+ const MAX_KEEPALIVE_BYTES = 48 * 1024;
12
+ const HEARTBEAT_INTERVAL_MS = 30_000;
13
+ export class AmarsiaTelemetryClient {
14
+ #key;
15
+ #baseUrl;
16
+ #queue = new Map();
17
+ #activeRuns = new Map();
18
+ #timer;
19
+ #heartbeatTimer;
20
+ #attempt = 0;
21
+ #flushPromise;
22
+ constructor(options) {
23
+ this.#key = options.key;
24
+ this.#baseUrl = resolveAmarsiaEndpoint(options.endpoint);
25
+ }
26
+ enqueue(fact) {
27
+ this.#queue.set(fact.event_id, fact);
28
+ if (fact.status === "running") {
29
+ this.#activeRuns.set(fact.event_id, fact);
30
+ this.#scheduleHeartbeat();
31
+ }
32
+ else {
33
+ this.#activeRuns.delete(fact.event_id);
34
+ if (this.#activeRuns.size === 0 && this.#heartbeatTimer) {
35
+ clearTimeout(this.#heartbeatTimer);
36
+ this.#heartbeatTimer = undefined;
37
+ }
38
+ }
39
+ if (fact.status !== "running") {
40
+ this.#scheduleTerminal();
41
+ return;
42
+ }
43
+ if (this.#queue.size >= 20) {
44
+ void this.#flush();
45
+ return;
46
+ }
47
+ this.#schedule(250);
48
+ }
49
+ #schedule(delay) {
50
+ if (this.#timer)
51
+ return;
52
+ this.#timer = setTimeout(() => {
53
+ this.#timer = undefined;
54
+ void this.#flush();
55
+ }, delay);
56
+ const timer = this.#timer;
57
+ timer.unref?.();
58
+ }
59
+ #scheduleTerminal() {
60
+ if (this.#timer)
61
+ clearTimeout(this.#timer);
62
+ this.#timer = setTimeout(() => {
63
+ this.#timer = undefined;
64
+ void this.#flush();
65
+ }, 0);
66
+ }
67
+ #scheduleHeartbeat() {
68
+ if (this.#heartbeatTimer || this.#activeRuns.size === 0)
69
+ return;
70
+ this.#heartbeatTimer = setTimeout(() => {
71
+ this.#heartbeatTimer = undefined;
72
+ for (const fact of this.#activeRuns.values()) {
73
+ this.#queue.set(fact.event_id, fact);
74
+ }
75
+ void this.#flush();
76
+ this.#scheduleHeartbeat();
77
+ }, HEARTBEAT_INTERVAL_MS);
78
+ const timer = this.#heartbeatTimer;
79
+ timer.unref?.();
80
+ }
81
+ async drain(options = {}) {
82
+ const timeoutMs = Math.max(0, options.timeoutMs ?? 2_000);
83
+ const deadline = Date.now() + timeoutMs;
84
+ if (this.#timer) {
85
+ clearTimeout(this.#timer);
86
+ this.#timer = undefined;
87
+ }
88
+ while (this.#queue.size > 0 || this.#flushPromise) {
89
+ const remaining = deadline - Date.now();
90
+ if (remaining <= 0)
91
+ return;
92
+ await Promise.race([
93
+ this.#flush(),
94
+ new Promise((resolve) => setTimeout(resolve, remaining)),
95
+ ]);
96
+ }
97
+ }
98
+ async close(options = {}) {
99
+ if (this.#heartbeatTimer) {
100
+ clearTimeout(this.#heartbeatTimer);
101
+ this.#heartbeatTimer = undefined;
102
+ }
103
+ this.#activeRuns.clear();
104
+ await this.drain(options);
105
+ }
106
+ async #flush() {
107
+ if (this.#flushPromise)
108
+ return this.#flushPromise;
109
+ if (this.#queue.size === 0)
110
+ return;
111
+ this.#flushPromise = this.#performFlush();
112
+ try {
113
+ await this.#flushPromise;
114
+ }
115
+ finally {
116
+ this.#flushPromise = undefined;
117
+ if (this.#queue.size > 0 && !this.#timer) {
118
+ const hasTerminalFact = [...this.#queue.values()].some((fact) => fact.status !== "running");
119
+ if (hasTerminalFact) {
120
+ void this.#flush();
121
+ }
122
+ else {
123
+ this.#schedule(250);
124
+ }
125
+ }
126
+ }
127
+ }
128
+ async #performFlush() {
129
+ const batch = takeBatch(this.#queue);
130
+ if (batch.length === 0)
131
+ return;
132
+ const body = JSON.stringify({
133
+ schema_version: 1,
134
+ runs: batch,
135
+ });
136
+ for (const fact of batch)
137
+ this.#queue.delete(fact.event_id);
138
+ try {
139
+ const response = await fetch(`${this.#baseUrl}/v3/project-analytics/runs/batch`, {
140
+ method: "POST",
141
+ headers: {
142
+ "content-type": "application/json",
143
+ "x-amarsia-project-key": this.#key,
144
+ },
145
+ keepalive: utf8ByteLength(body) <= MAX_KEEPALIVE_BYTES,
146
+ body,
147
+ });
148
+ if (!response.ok) {
149
+ throw new Error(`HTTP ${response.status}`);
150
+ }
151
+ this.#attempt = 0;
152
+ }
153
+ catch {
154
+ this.#attempt += 1;
155
+ if (this.#attempt <= 6) {
156
+ for (const fact of batch) {
157
+ if (!this.#queue.has(fact.event_id)) {
158
+ this.#queue.set(fact.event_id, fact);
159
+ }
160
+ }
161
+ this.#schedule(Math.min(30_000, 500 * 2 ** this.#attempt));
162
+ }
163
+ else {
164
+ this.#attempt = 0;
165
+ console.warn(`Amarsia telemetry delivery failed after 6 retries; dropping ${batch.length} run update(s).`);
166
+ }
167
+ }
168
+ }
169
+ }
170
+ export function withAmarsiaTelemetry(options) {
171
+ const { store, manifest, client } = options;
172
+ const projections = new Map();
173
+ const pending = new Map();
174
+ return {
175
+ read: (sessionId) => store.read(sessionId),
176
+ listSessionIds: () => store.listSessionIds(),
177
+ describeLocation: store.describeLocation?.bind(store),
178
+ subscribe: store.subscribe?.bind(store),
179
+ subscribeAll: store.subscribeAll?.bind(store),
180
+ async append(sessionId, events) {
181
+ await store.append(sessionId, events);
182
+ const runs = relevantRuns(events);
183
+ if (runs.size === 0 &&
184
+ !projections.has(sessionId) &&
185
+ !pending.has(sessionId)) {
186
+ return;
187
+ }
188
+ const previous = pending.get(sessionId) ?? Promise.resolve();
189
+ const processing = previous
190
+ .then(async () => {
191
+ let projection = projections.get(sessionId);
192
+ if (!projection) {
193
+ projection = createProjection(await store.read(sessionId));
194
+ projections.set(sessionId, projection);
195
+ }
196
+ else {
197
+ appendToProjection(projection, events);
198
+ }
199
+ if (projection.isTest)
200
+ return;
201
+ for (const run of runs) {
202
+ const fact = projectRunFact(manifest, sessionId, run, projection);
203
+ if (fact)
204
+ client.enqueue(fact);
205
+ }
206
+ })
207
+ .catch(() => undefined);
208
+ pending.set(sessionId, processing);
209
+ void processing.finally(() => {
210
+ if (pending.get(sessionId) === processing) {
211
+ pending.delete(sessionId);
212
+ }
213
+ });
214
+ },
215
+ };
216
+ }
217
+ function createProjection(events) {
218
+ const projection = {
219
+ seenSequences: new Set(),
220
+ runs: new Map(),
221
+ isTest: false,
222
+ };
223
+ appendToProjection(projection, events);
224
+ return projection;
225
+ }
226
+ function appendToProjection(projection, events) {
227
+ for (const event of events) {
228
+ if (projection.seenSequences.has(event.sequence))
229
+ continue;
230
+ projection.seenSequences.add(event.sequence);
231
+ if (event.type === "session.created") {
232
+ projection.sessionCreated = event;
233
+ const metadata = asRecord(event.data.metadata);
234
+ projection.isTest = metadata?.orchaTest === true;
235
+ }
236
+ if (typeof event.run !== "number")
237
+ continue;
238
+ const runEvents = projection.runs.get(event.run);
239
+ if (runEvents) {
240
+ runEvents.push(event);
241
+ }
242
+ else {
243
+ projection.runs.set(event.run, [event]);
244
+ }
245
+ }
246
+ }
247
+ function relevantRuns(events) {
248
+ const runs = new Set();
249
+ for (const event of events) {
250
+ if (typeof event.run === "number" && RELEVANT_EVENTS.has(event.type)) {
251
+ runs.add(event.run);
252
+ }
253
+ }
254
+ return runs;
255
+ }
256
+ function projectRunFact(manifest, sessionId, run, projection) {
257
+ const runEvents = projection.runs.get(run);
258
+ if (!runEvents)
259
+ return undefined;
260
+ const started = runEvents.find((event) => event.type === "run.started");
261
+ if (!started)
262
+ return undefined;
263
+ const terminal = [...runEvents]
264
+ .reverse()
265
+ .find((event) => ["run.completed", "run.failed", "run.paused"].includes(event.type));
266
+ const lineage = asRecord(projection.sessionCreated?.data.lineage);
267
+ const assistants = runEvents.filter((event) => event.type === "message.created" && event.data.role === "assistant");
268
+ const usage = normalizeUsage(terminal?.data.usage, assistants);
269
+ const completedActions = runEvents.filter((event) => event.type === "action.completed");
270
+ const failedActions = runEvents.filter((event) => event.type === "action.failed");
271
+ const evaluations = runEvents.filter((event) => event.type === "evaluation.completed" ||
272
+ event.type === "evaluation.failed");
273
+ const error = asRecord(terminal?.data.error);
274
+ const lastAssistant = assistants.at(-1);
275
+ const status = terminal?.type === "run.completed"
276
+ ? "completed"
277
+ : terminal?.type === "run.failed"
278
+ ? "failed"
279
+ : terminal?.type === "run.paused"
280
+ ? "paused"
281
+ : "running";
282
+ return {
283
+ event_id: `${manifest.key}:${sessionId}:${run}`,
284
+ agent_external_id: manifest.key,
285
+ session_id: sessionId,
286
+ run_number: run,
287
+ ...(typeof lineage?.parentAgent === "string"
288
+ ? { parent_agent_external_id: lineage.parentAgent }
289
+ : {}),
290
+ ...(typeof lineage?.parentSessionId === "string"
291
+ ? { parent_session_id: lineage.parentSessionId }
292
+ : {}),
293
+ status,
294
+ provider: typeof started.data.provider === "string"
295
+ ? started.data.provider
296
+ : manifest.provider,
297
+ model: typeof started.data.model === "string"
298
+ ? started.data.model
299
+ : manifest.model,
300
+ ...(typeof started.data.region === "string"
301
+ ? { region: started.data.region }
302
+ : {}),
303
+ ...(typeof started.data.outputType === "string"
304
+ ? { output_type: started.data.outputType }
305
+ : {}),
306
+ ...(typeof lastAssistant?.data.stopReason === "string"
307
+ ? { stop_reason: lastAssistant.data.stopReason }
308
+ : {}),
309
+ started_at: started.timestamp,
310
+ ...(terminal ? { ended_at: terminal.timestamp } : {}),
311
+ ...(typeof terminal?.data.durationMs === "number"
312
+ ? { duration_ms: terminal.data.durationMs }
313
+ : {}),
314
+ model_duration_ms: sumNumber(assistants, "durationMs"),
315
+ action_duration_ms: sumNumber([...completedActions, ...failedActions], "durationMs"),
316
+ ...usage,
317
+ user_message_count: runEvents.filter((event) => event.type === "message.created" && event.data.role === "user").length,
318
+ assistant_message_count: assistants.length,
319
+ action_count: runEvents.filter((event) => event.type === "action.requested").length,
320
+ action_success_count: completedActions.length,
321
+ action_failure_count: failedActions.length,
322
+ subagent_count: runEvents.filter((event) => event.type === "subagent.initiated").length,
323
+ actions: projectActionLogs(runEvents),
324
+ evaluation_count: evaluations.length,
325
+ evaluation_pass_count: evaluations.filter((event) => event.type === "evaluation.completed" &&
326
+ event.data.status === "passed").length,
327
+ ...(typeof error?.code === "string" ? { error_code: error.code } : {}),
328
+ ...(typeof error?.message === "string"
329
+ ? { error_message: sanitizeErrorMessage(error.message) }
330
+ : {}),
331
+ ...(typeof error?.retryable === "boolean"
332
+ ? { error_retryable: error.retryable }
333
+ : {}),
334
+ };
335
+ }
336
+ function normalizeUsage(terminalUsage, assistants) {
337
+ const direct = asRecord(terminalUsage);
338
+ if (direct) {
339
+ return usageFields(direct);
340
+ }
341
+ const total = {
342
+ inputTokens: 0,
343
+ outputTokens: 0,
344
+ reasoningTokens: 0,
345
+ cacheReadTokens: 0,
346
+ cacheWriteTokens: 0,
347
+ };
348
+ for (const event of assistants) {
349
+ const usage = asRecord(event.data.usage);
350
+ if (!usage)
351
+ continue;
352
+ total.inputTokens += numberValue(usage.inputTokens);
353
+ total.outputTokens += numberValue(usage.outputTokens);
354
+ total.reasoningTokens =
355
+ (total.reasoningTokens ?? 0) + numberValue(usage.reasoningTokens);
356
+ total.cacheReadTokens += numberValue(usage.cacheReadTokens);
357
+ total.cacheWriteTokens += numberValue(usage.cacheWriteTokens);
358
+ }
359
+ return usageFields(total);
360
+ }
361
+ function usageFields(usage) {
362
+ const reasoning = numberValue(usage.reasoningTokens);
363
+ return {
364
+ input_tokens: numberValue(usage.inputTokens),
365
+ output_tokens: numberValue(usage.outputTokens),
366
+ ...(reasoning > 0 ? { reasoning_tokens: reasoning } : {}),
367
+ cache_read_tokens: numberValue(usage.cacheReadTokens),
368
+ cache_write_tokens: numberValue(usage.cacheWriteTokens),
369
+ };
370
+ }
371
+ function sumNumber(events, key) {
372
+ return events.reduce((sum, event) => sum + numberValue(event.data[key]), 0);
373
+ }
374
+ function projectActionLogs(events) {
375
+ const requestedByCallId = new Map();
376
+ for (const event of events) {
377
+ if (event.type === "action.requested" &&
378
+ typeof event.data.callId === "string") {
379
+ requestedByCallId.set(event.data.callId, event);
380
+ }
381
+ }
382
+ return events
383
+ .filter((event) => event.type === "action.completed" || event.type === "action.failed")
384
+ .slice(0, 100)
385
+ .map((event) => {
386
+ const error = asRecord(event.data.error);
387
+ const requested = typeof event.data.callId === "string"
388
+ ? requestedByCallId.get(event.data.callId)
389
+ : undefined;
390
+ return {
391
+ call_id: typeof event.data.callId === "string"
392
+ ? event.data.callId
393
+ : `action-${event.sequence}`,
394
+ name: typeof event.data.name === "string" ? event.data.name : "unknown",
395
+ status: event.type === "action.completed"
396
+ ? "completed"
397
+ : "failed",
398
+ started_at: requested?.timestamp ?? event.timestamp,
399
+ ended_at: event.timestamp,
400
+ duration_ms: numberValue(event.data.durationMs),
401
+ ...(typeof error?.code === "string" ? { error_code: error.code } : {}),
402
+ ...(typeof error?.message === "string"
403
+ ? { error_message: sanitizeErrorMessage(error.message) }
404
+ : {}),
405
+ };
406
+ });
407
+ }
408
+ function takeBatch(queue) {
409
+ const batch = [];
410
+ let bytes = utf8ByteLength('{"schema_version":1,"runs":[]}');
411
+ for (const fact of queue.values()) {
412
+ const factBytes = utf8ByteLength(JSON.stringify(fact)) + 1;
413
+ if (batch.length > 0 &&
414
+ (batch.length >= MAX_BATCH_RUNS ||
415
+ bytes + factBytes > MAX_KEEPALIVE_BYTES)) {
416
+ break;
417
+ }
418
+ batch.push(fact);
419
+ bytes += factBytes;
420
+ }
421
+ return batch;
422
+ }
423
+ function utf8ByteLength(value) {
424
+ if (typeof TextEncoder !== "undefined") {
425
+ return new TextEncoder().encode(value).byteLength;
426
+ }
427
+ let bytes = 0;
428
+ for (let index = 0; index < value.length; index += 1) {
429
+ const code = value.charCodeAt(index);
430
+ if (code < 0x80)
431
+ bytes += 1;
432
+ else if (code < 0x800)
433
+ bytes += 2;
434
+ else if (code >= 0xd800 && code <= 0xdbff) {
435
+ bytes += 4;
436
+ index += 1;
437
+ }
438
+ else
439
+ bytes += 3;
440
+ }
441
+ return bytes;
442
+ }
443
+ function numberValue(value) {
444
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
445
+ }
446
+ function asRecord(value) {
447
+ return typeof value === "object" && value !== null && !Array.isArray(value)
448
+ ? value
449
+ : undefined;
450
+ }
451
+ function sanitizeErrorMessage(value) {
452
+ return value
453
+ .replace(/\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi, "Bearer [redacted]")
454
+ .replace(/\b(authorization|api[-_ ]?key|token|secret|password)\b\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]")
455
+ .replace(/\b(?:sk|pk|amarsia)_[A-Za-z0-9_-]{12,}\b/gi, "[redacted]")
456
+ .slice(0, 8000);
457
+ }
@@ -0,0 +1 @@
1
+ export declare function resolveAmarsiaEndpoint(endpoint: string | undefined): string;
@@ -0,0 +1,8 @@
1
+ const DEFAULT_AMARSIA_API_URL = "https://api.amarsia.com";
2
+ export function resolveAmarsiaEndpoint(endpoint) {
3
+ return (endpoint?.trim() ||
4
+ (typeof process !== "undefined"
5
+ ? process.env?.AMARSIA_API_URL
6
+ : undefined) ||
7
+ DEFAULT_AMARSIA_API_URL).replace(/\/$/, "");
8
+ }
@@ -1,5 +1,5 @@
1
1
  import { createAmarsiaProjectMetadata, } from "./metadata.js";
2
- const DEFAULT_AMARSIA_API_URL = "https://api.amarsia.com";
2
+ import { resolveAmarsiaEndpoint } from "./endpoint.js";
3
3
  const syncedHashes = new Map();
4
4
  export function syncAmarsiaProject(options) {
5
5
  const metadata = createAmarsiaProjectMetadata(options.bundle, options.projectRoot);
@@ -19,9 +19,6 @@ export function syncAmarsiaProject(options) {
19
19
  console.warn(`[orcha] Amarsia project sync failed: ${message}`);
20
20
  });
21
21
  }
22
- function resolveAmarsiaEndpoint(endpoint) {
23
- return (endpoint?.trim() || process.env.AMARSIA_API_URL || DEFAULT_AMARSIA_API_URL).replace(/\/$/, "");
24
- }
25
22
  async function postProjectMetadata(options) {
26
23
  const response = await fetch(`${options.baseUrl}/v3/project-sync`, {
27
24
  method: "PUT",
package/dist/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ void import("./cli.js").catch((error) => {
3
+ const message = error instanceof Error ? error.message : String(error);
4
+ const dependency = ["esbuild", "chokidar", "dotenv"].find((name) => message.includes(`"${name}"`) || message.includes(`'${name}'`));
5
+ if (dependency) {
6
+ console.error(`Orcha development tooling requires optional dependencies. Install them with:\n\n npm install --save-dev esbuild chokidar dotenv`);
7
+ }
8
+ else {
9
+ console.error(message);
10
+ }
11
+ process.exitCode = 1;
12
+ });
13
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare const orcha: import("./runtime-entry.js").OrchaClient;
2
+ export * from "./runtime-entry.js";
@@ -0,0 +1,12 @@
1
+ import { generateBrowserProviderResponse } from "./providers/browser.js";
2
+ import { setNativeRuntimeLoader } from "./runtime/actions/execute.js";
3
+ import { createOrcha } from "./runtime/create-orcha.js";
4
+ import { browserRuntimePlatform } from "./runtime/platform-browser.js";
5
+ setNativeRuntimeLoader(async () => {
6
+ const modulePath = "./runtime/actions/browser-native.js";
7
+ return import(modulePath);
8
+ });
9
+ export const orcha = createOrcha(() => {
10
+ throw new Error("The browser runtime requires a precompiled Orcha bundle. Configure the Orcha integration for your application bundler.");
11
+ }, generateBrowserProviderResponse, browserRuntimePlatform);
12
+ export * from "./runtime-entry.js";