@sovara/runner 0.4.4 → 0.4.6

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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # @sovara/runner
2
2
 
3
3
  TypeScript runner for [Sovara](https://docs.sovara-labs.com) — wrap your script in a
4
- single function and Sovara records every LLM call, tool invocation, and log
5
- line into structured run steps.
4
+ single function and Sovara records supported LLM, MCP, and framework tool
5
+ calls, plus log lines, as structured run steps.
6
6
 
7
7
  The runner is the TS counterpart to the Python `sovara` runner. It targets
8
8
  the same Sovara server, produces the same step shapes, and is safe to mix
@@ -14,8 +14,8 @@ Documentation: https://docs.sovara-labs.com
14
14
 
15
15
  - Node 18 or newer (built-in `fetch`).
16
16
  - A reachable Sovara backend. For local use, open the Sovara desktop app.
17
- Enterprise agent projects should use their project-specific connection flow.
18
- - A Sovara user and project, configured once with `sovara init`.
17
+ Remote agent hosts use a project-scoped agent token.
18
+ - A Sovara project name for `new SovaraClient({ projectName })`.
19
19
 
20
20
  ## Install
21
21
 
@@ -37,13 +37,14 @@ npm install @anthropic-ai/claude-agent-sdk
37
37
  ## Quick start
38
38
 
39
39
  ```ts
40
- import { withSovaraRun } from "@sovara/runner";
41
40
  import OpenAI from "openai";
41
+ import { SovaraClient } from "@sovara/runner";
42
42
 
43
- const openai = new OpenAI();
43
+ const openai_client = new OpenAI();
44
+ const sovara_client = new SovaraClient({ projectName: "My Project" });
44
45
 
45
- await withSovaraRun("hello-world", async () => {
46
- const response = await openai.responses.create({
46
+ await sovara_client.run("hello-world", async () => {
47
+ const response = await openai_client.responses.create({
47
48
  model: "gpt-5",
48
49
  input: "Say hello.",
49
50
  });
@@ -56,16 +57,21 @@ That's the full integration. The call to OpenAI is captured automatically;
56
57
  `console.log` output is captured as run logs; the run shows up in the
57
58
  Sovara UI.
58
59
 
60
+ OpenAI Agents function tools created with `tool()` and MCP calls are also
61
+ captured automatically inside a run. Use `trace` only for important custom
62
+ operations that are not already captured.
63
+
59
64
  ## How it works
60
65
 
61
- Importing `@sovara/runner` patches two transports at module load:
66
+ Importing `@sovara/runner` patches the supported provider transports at module
67
+ load, including:
62
68
 
63
69
  - `globalThis.fetch` — wraps every `fetch` call.
64
70
  - `node:http` and `node:https` — wraps `http.request` / `http.get` and
65
71
  their HTTPS counterparts. This covers `axios` and any library that uses
66
72
  Node's HTTP modules directly.
67
73
 
68
- Inside `withSovaraRun(...)`, calls to whitelisted endpoints are observed:
74
+ Inside `sovara_client.run(...)`, calls to whitelisted endpoints are observed:
69
75
  the runner builds a request envelope, posts it to the Sovara server's
70
76
  `/internal/runner/llm/prepare` endpoint (which optionally applies
71
77
  runtime preparation such as lessons injection), executes the prepared
@@ -90,84 +96,99 @@ completes.
90
96
 
91
97
  ## API
92
98
 
93
- ### `withSovaraRun(name | options, fn)`
99
+ ### `new SovaraClient(options)`
94
100
 
95
101
  ```ts
96
- export type WithSovaraRunOptions = {
97
- name: string; // required run name
102
+ export type SovaraClientOptions = {
103
+ projectName: string; // required Sovara project name
104
+ url?: string; // exec server URL (default: http://127.0.0.1:5960)
105
+ agentToken?: string; // optional project-scoped agent token
106
+ fetch?: typeof globalThis.fetch; // optional caller-owned transport
107
+ };
108
+
109
+ export type SovaraRunOptions = {
98
110
  clientRunId?: string; // optional project-scoped correlation id
99
- url?: string; // server URL (default: http://127.0.0.1:5959)
100
111
  captureLogs?: boolean; // default: true
101
- project?: { name?: string; description?: string };
112
+ lessonScope?: string | string[] | null; // optional lesson folder scope
102
113
  };
103
114
 
104
- export function withSovaraRun<T>(
105
- input: string | WithSovaraRunOptions,
106
- fn: () => Promise<T> | T,
107
- ): Promise<T>;
115
+ export class SovaraClient {
116
+ run<T>(
117
+ name: string,
118
+ fn: () => Promise<T> | T,
119
+ options?: SovaraRunOptions,
120
+ ): Promise<T>;
121
+ }
108
122
  ```
109
123
 
110
- The string form is shorthand for `{ name: input }`. The runner returns
124
+ The client owns project identity and connection configuration. `run()` returns
111
125
  whatever `fn` returns.
112
126
 
113
127
  **Top-level call** (no parent run on the stack):
114
128
 
115
129
  1. Health-checks the configured server and fails fast if it is unreachable.
116
- 2. Resolves the active user. Fails hard with a `sovara init` hint if
117
- user setup is missing.
118
- 3. Resolves the project for `process.cwd()`. Fails hard with a `sovara init`
119
- hint if the cwd isn't part of a known Sovara project.
120
- 4. Registers the run, runs `fn` inside an `AsyncLocalStorage` scope, and
130
+ 2. Uses the client's `projectName` and `process.cwd()` metadata for the run.
131
+ 3. Registers the run, runs `fn` inside an `AsyncLocalStorage` scope, and
121
132
  deregisters in `finally`.
122
133
 
123
- **Nested top-level call** (called from inside another `withSovaraRun`):
134
+ **Nested top-level call** (called from inside another `sovara_client.run()`):
124
135
 
125
- The nested `withSovaraRun` call is ignored with a warning and `fn` runs inside
126
- the existing active scope. Use `withSovaraSubrun(...)` for child execution.
136
+ The nested run is ignored with a warning and `fn` runs inside the existing
137
+ active scope. Use `sovara_client.subrun(...)` for child execution.
127
138
 
128
- `fn` exceptions propagate untouched. The run is deregistered in either
129
- case.
139
+ `fn` exceptions propagate untouched. The run is deregistered in either case.
130
140
 
131
141
  ### Subruns
132
142
 
133
143
  ```ts
134
- await withSovaraRun("pipeline", async () => {
135
- const docs = await withSovaraSubrun("retrieve", async () => loadDocs());
136
- await withSovaraSubrun("summarize", async () => summarize(docs));
144
+ await sovara_client.run("pipeline", async () => {
145
+ const docs = await sovara_client.subrun("retrieve", async () => loadDocs());
146
+ await sovara_client.subrun("summarize", async () => summarize(docs));
137
147
  });
138
148
  ```
139
149
 
140
- Every explicit `withSovaraSubrun` becomes a child run under the active scope.
150
+ Every explicit `sovara_client.subrun(...)` becomes a child run under the active
151
+ scope.
141
152
 
142
153
  ### Logs
143
154
 
144
155
  Standard `console.log`, `console.error`, and any direct writes to
145
- `process.stdout` / `process.stderr` are captured into the run log
146
- buffer and flushed to the server every 250ms.
156
+ `process.stdout` / `process.stderr` are captured into the run log buffer and
157
+ flushed to the server every 250ms.
147
158
 
148
159
  Disable with `captureLogs: false`:
149
160
 
150
161
  ```ts
151
- await withSovaraRun({ name: "noisy", captureLogs: false }, async () => {
152
- // stdout/stderr will not be tee'd to the server
153
- });
162
+ await sovara_client.run(
163
+ "noisy",
164
+ async () => {
165
+ // stdout/stderr will not be tee'd to the server
166
+ },
167
+ { captureLogs: false },
168
+ );
154
169
  ```
155
170
 
156
- ### User and project overrides
171
+ ### Project and server
157
172
 
158
173
  ```ts
159
- await withSovaraRun(
160
- {
161
- name: "billing-job",
162
- user: { fullName: "CI Bot", email: "ci@example.com" },
163
- project: { name: "Billing", description: "Nightly invoice run" },
164
- },
165
- async () => doWork(),
166
- );
174
+ const sovara_client = new SovaraClient({ projectName: "Billing" });
175
+ await sovara_client.run("billing-job", async () => doWork());
167
176
  ```
168
177
 
169
- Overrides are only applied if user/project are present. They don't bypass
170
- the `sovara init` requirement they just refine the values.
178
+ Pass `projectName` once when constructing the client. A remote agent host can
179
+ also configure its exec URL and project-scoped token there. The custom `fetch`
180
+ is optional:
181
+
182
+ ```ts
183
+ const sovara_client = new SovaraClient({
184
+ projectName: "Billing",
185
+ url: "https://exec.example.com",
186
+ agentToken: process.env.SOVARA_AGENT_TOKEN,
187
+ fetch: customFetch,
188
+ });
189
+
190
+ await sovara_client.run("billing-job", async () => doWork());
191
+ ```
171
192
 
172
193
  ### `trace(fn, options?)`
173
194
 
@@ -176,7 +197,9 @@ HTTP call on its own, but should still appear as a tool step in the Sovara
176
197
  run steps.
177
198
 
178
199
  ```ts
179
- import { trace, withSovaraRun } from "@sovara/runner";
200
+ import { SovaraClient, trace } from "@sovara/runner";
201
+
202
+ const sovara_client = new SovaraClient({ projectName: "My Project" });
180
203
 
181
204
  const lookupCustomer = trace(
182
205
  async function lookupCustomer(customerId: string) {
@@ -185,7 +208,7 @@ const lookupCustomer = trace(
185
208
  { meta: { system: "crm" } },
186
209
  );
187
210
 
188
- await withSovaraRun("support-agent", async () => {
211
+ await sovara_client.run("support-agent", async () => {
189
212
  const customer = await lookupCustomer("cust_123");
190
213
  // Continue your agent flow with customer.
191
214
  });
@@ -194,8 +217,9 @@ await withSovaraRun("support-agent", async () => {
194
217
  `trace` records function arguments as the step input and the return value as
195
218
  the step output. If the wrapped function throws, the error type and message
196
219
  are recorded and the original exception is re-thrown. Outside an active
197
- `withSovaraRun(...)` scope, the wrapper calls the function normally without
198
- recording anything.
220
+ `sovara_client.run(...)` scope, the wrapper calls the function normally without
221
+ recording anything. Do not also wrap OpenAI Agents `tool()` functions or MCP
222
+ tools; Sovara captures those automatically.
199
223
 
200
224
  ```ts
201
225
  export type TraceOptions = {
@@ -246,16 +270,18 @@ That single import change is the entire integration. The wrapper:
246
270
  matches the Python runner's behavior.
247
271
  - Throws if `options.env.ANTHROPIC_BASE_URL` already points at the
248
272
  Sovara proxy, to prevent infinite proxy loops.
249
- - Outside a `withSovaraRun` scope, `query()` and `startup()` are
273
+ - Outside a `sovara_client.run(...)` scope, `query()` and `startup()` are
250
274
  pass-throughs to the real SDK with zero overhead.
251
275
 
252
276
  ### Example
253
277
 
254
278
  ```ts
255
- import { withSovaraRun } from "@sovara/runner";
279
+ import { SovaraClient } from "@sovara/runner";
256
280
  import { query } from "@sovara/runner/claude";
257
281
 
258
- await withSovaraRun("agent-task", async () => {
282
+ const sovara_client = new SovaraClient({ projectName: "My Project" });
283
+
284
+ await sovara_client.run("agent-task", async () => {
259
285
  for await (const message of query({
260
286
  prompt: "List the largest files in the current directory.",
261
287
  })) {
@@ -287,16 +313,26 @@ for await (const message of handle.query("What did I install last?")) {
287
313
  | ---------------------------- | -------------------------------------------------------------------------- | ----------------------- |
288
314
  | `SOVARA_EXEC_SERVER_PORT` | Exec server port (used to compute the default URL) | `5960` |
289
315
  | `SOVARA_EXEC_SERVER_URL` | Explicit exec server URL | `http://127.0.0.1:5960` |
316
+ | `SOVARA_AGENT_TOKEN` | Project-scoped token for a remote/headless agent | unset |
290
317
  | `SOVARA_CLAUDE_PROXY_ACTIVE` | Set automatically by `@sovara/runner/claude` to prevent nested proxy loops | unset |
291
318
 
292
319
  ### Server URL
293
320
 
294
- The TypeScript runner uses the local exec server by default. Pass `url` per call only when the caller explicitly manages a different exec-server connection:
321
+ The TypeScript runner uses the local exec server by default. Pass `url` when
322
+ constructing the client if the caller manages a different exec-server connection:
295
323
 
296
324
  ```ts
297
- await withSovaraRun({ name: "demo", url: "http://localhost:6000" }, fn);
325
+ const sovara_client = new SovaraClient({
326
+ projectName: "My Project",
327
+ url: "http://localhost:6000",
328
+ });
329
+
330
+ await sovara_client.run("demo", fn);
298
331
  ```
299
332
 
333
+ Pass `agentToken` for remote project authentication. It falls back to
334
+ `SOVARA_AGENT_TOKEN`. Pass `fetch` only when a caller-owned transport is needed.
335
+
300
336
  ## Error messages
301
337
 
302
338
  The runner fails fast for misconfiguration. Common ones:
@@ -304,8 +340,8 @@ The runner fails fast for misconfiguration. Common ones:
304
340
  - **`Sovara exec server is not reachable at <url>. Run sovara-exec-server start or use the Sovara CLI package that bundles it.`**
305
341
  Start the exec server or pass `url` when the caller owns exec-server setup.
306
342
 
307
- - **``Sovara project setup is missing for <cwd>. Run `sovara init --project-root "<cwd>" --project-name <project-name>` and retry.``**
308
- Run the suggested `sovara init` command.
343
+ - **`projectName is required.`**
344
+ Pass `projectName` to `new SovaraClient(...)`.
309
345
 
310
346
  - **`Custom Claude Agent SDK transports are not supported yet under sovara.`**
311
347
  Remove `options.transport` from your `query()` / `startup()` call.
@@ -1,4 +1,5 @@
1
1
  export declare const API_TYPE_HTTPX_CLIENT_SEND = "httpx.Client.send";
2
2
  export declare const API_TYPE_HTTPX_ASYNC_CLIENT_SEND = "httpx.AsyncClient.send";
3
3
  export declare const API_TYPE_MCP_CLIENT_SESSION_SEND_REQUEST = "MCP.ClientSession.send_request";
4
+ export declare const API_TYPE_OPENAI_AGENTS_FUNCTION_TOOL_RUN = "openai_agents.FunctionTool.run";
4
5
  //# sourceMappingURL=apiTypes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"apiTypes.d.ts","sourceRoot":"","sources":["../src/apiTypes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,0BAA0B,sBAAsB,CAAC;AAC9D,eAAO,MAAM,gCAAgC,2BAA2B,CAAC;AACzE,eAAO,MAAM,wCAAwC,mCAAmC,CAAC"}
1
+ {"version":3,"file":"apiTypes.d.ts","sourceRoot":"","sources":["../src/apiTypes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,0BAA0B,sBAAsB,CAAC;AAC9D,eAAO,MAAM,gCAAgC,2BAA2B,CAAC;AACzE,eAAO,MAAM,wCAAwC,mCAAmC,CAAC;AACzF,eAAO,MAAM,wCAAwC,mCAAmC,CAAC"}
package/dist/apiTypes.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export const API_TYPE_HTTPX_CLIENT_SEND = "httpx.Client.send";
2
2
  export const API_TYPE_HTTPX_ASYNC_CLIENT_SEND = "httpx.AsyncClient.send";
3
3
  export const API_TYPE_MCP_CLIENT_SESSION_SEND_REQUEST = "MCP.ClientSession.send_request";
4
+ export const API_TYPE_OPENAI_AGENTS_FUNCTION_TOOL_RUN = "openai_agents.FunctionTool.run";
4
5
  //# sourceMappingURL=apiTypes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"apiTypes.js","sourceRoot":"","sources":["../src/apiTypes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,0BAA0B,GAAG,mBAAmB,CAAC;AAC9D,MAAM,CAAC,MAAM,gCAAgC,GAAG,wBAAwB,CAAC;AACzE,MAAM,CAAC,MAAM,wCAAwC,GAAG,gCAAgC,CAAC"}
1
+ {"version":3,"file":"apiTypes.js","sourceRoot":"","sources":["../src/apiTypes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,0BAA0B,GAAG,mBAAmB,CAAC;AAC9D,MAAM,CAAC,MAAM,gCAAgC,GAAG,wBAAwB,CAAC;AACzE,MAAM,CAAC,MAAM,wCAAwC,GAAG,gCAAgC,CAAC;AACzF,MAAM,CAAC,MAAM,wCAAwC,GAAG,gCAAgC,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { type LessonScopeInput } from "./context.js";
2
+ import type { MetricValue } from "./runtimeClient.js";
3
+ export type SovaraClientOptions = {
4
+ projectName: string;
5
+ url?: string;
6
+ agentToken?: string;
7
+ fetch?: typeof globalThis.fetch;
8
+ };
9
+ export type SovaraRunOptions = {
10
+ clientRunId?: string;
11
+ captureLogs?: boolean;
12
+ lessonScope?: LessonScopeInput;
13
+ };
14
+ export type SovaraSubrunOptions = {
15
+ lessonScope?: LessonScopeInput;
16
+ };
17
+ type RunFunction<T> = () => Promise<T> | T;
18
+ export declare class SovaraClient {
19
+ readonly projectName: string;
20
+ private readonly url?;
21
+ private readonly agentToken?;
22
+ private readonly fetch?;
23
+ constructor(options: SovaraClientOptions);
24
+ run<T>(name: string, fn: RunFunction<T>, options?: SovaraRunOptions): Promise<T>;
25
+ subrun<T>(name: string, fn: RunFunction<T>, options?: SovaraSubrunOptions): Promise<T>;
26
+ lessonScope<T>(scope: LessonScopeInput, fn: RunFunction<T>): Promise<T>;
27
+ logInput(input: unknown): Promise<void>;
28
+ logOutput(output: unknown): Promise<void>;
29
+ logMetrics(metrics: Record<string, MetricValue>): Promise<void>;
30
+ injectLessons(context: unknown): Promise<string>;
31
+ queueForAnnotation(): Promise<void>;
32
+ getRunId(): string | undefined;
33
+ }
34
+ export {};
35
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC,CAAC;AAEF,KAAK,WAAW,CAAC,CAAC,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAE3C,qBAAa,YAAY;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAA0B;gBAErC,OAAO,EAAE,mBAAmB;IAUxC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,CAAC,CAAC;IAcpF,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,CAAC,CAAC;IAI1F,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvE,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/D,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAIhD,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAInC,QAAQ,IAAI,MAAM,GAAG,SAAS;CAG/B"}
package/dist/client.js ADDED
@@ -0,0 +1,52 @@
1
+ import { getRunId, logInput, logMetrics, logOutput, normalizeProjectName, queueForAnnotation, withLessonScope, withSovaraRun, withSovaraSubrun, } from "./context.js";
2
+ import { injectLessons } from "./lessons.js";
3
+ export class SovaraClient {
4
+ projectName;
5
+ url;
6
+ agentToken;
7
+ fetch;
8
+ constructor(options) {
9
+ if (!options || typeof options !== "object") {
10
+ throw new TypeError("SovaraClient requires an options object with projectName.");
11
+ }
12
+ this.projectName = normalizeProjectName(options.projectName);
13
+ this.url = options.url;
14
+ this.agentToken = options.agentToken;
15
+ this.fetch = options.fetch;
16
+ }
17
+ run(name, fn, options = {}) {
18
+ return withSovaraRun({
19
+ ...options,
20
+ name,
21
+ projectName: this.projectName,
22
+ url: this.url,
23
+ agentToken: this.agentToken,
24
+ fetch: this.fetch,
25
+ }, fn);
26
+ }
27
+ subrun(name, fn, options = {}) {
28
+ return withSovaraSubrun({ name, ...options }, fn);
29
+ }
30
+ lessonScope(scope, fn) {
31
+ return withLessonScope(scope, fn);
32
+ }
33
+ logInput(input) {
34
+ return logInput(input);
35
+ }
36
+ logOutput(output) {
37
+ return logOutput(output);
38
+ }
39
+ logMetrics(metrics) {
40
+ return logMetrics(metrics);
41
+ }
42
+ injectLessons(context) {
43
+ return injectLessons(context);
44
+ }
45
+ queueForAnnotation() {
46
+ return queueForAnnotation();
47
+ }
48
+ getRunId() {
49
+ return getRunId();
50
+ }
51
+ }
52
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAS,EACT,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,gBAAgB,GAEjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAsB7C,MAAM,OAAO,YAAY;IACd,WAAW,CAAS;IACZ,GAAG,CAAU;IACb,UAAU,CAAU;IACpB,KAAK,CAA2B;IAEjD,YAAY,OAA4B;QACtC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC5C,MAAM,IAAI,SAAS,CAAC,2DAA2D,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,oBAAoB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC7D,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;QACvB,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC7B,CAAC;IAED,GAAG,CAAI,IAAY,EAAE,EAAkB,EAAE,UAA4B,EAAE;QACrE,OAAO,aAAa,CAClB;YACE,GAAG,OAAO;YACV,IAAI;YACJ,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,EACD,EAAE,CACH,CAAC;IACJ,CAAC;IAED,MAAM,CAAI,IAAY,EAAE,EAAkB,EAAE,UAA+B,EAAE;QAC3E,OAAO,gBAAgB,CAAC,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC;IAED,WAAW,CAAI,KAAuB,EAAE,EAAkB;QACxD,OAAO,eAAe,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACpC,CAAC;IAED,QAAQ,CAAC,KAAc;QACrB,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IAED,SAAS,CAAC,MAAe;QACvB,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAED,UAAU,CAAC,OAAoC;QAC7C,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAED,aAAa,CAAC,OAAgB;QAC5B,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IAED,kBAAkB;QAChB,OAAO,kBAAkB,EAAE,CAAC;IAC9B,CAAC;IAED,QAAQ;QACN,OAAO,QAAQ,EAAE,CAAC;IACpB,CAAC;CACF"}
package/dist/context.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { RuntimeClient, type MetricValue } from "./runtimeClient.js";
2
2
  export type WithSovaraRunOptions = {
3
- name: string;
3
+ name?: string;
4
4
  clientRunId?: string;
5
5
  url?: string;
6
+ agentToken?: string;
7
+ fetch?: typeof globalThis.fetch;
6
8
  captureLogs?: boolean;
7
- projectName?: string;
9
+ projectName: string;
8
10
  lessonScope?: LessonScopeInput;
9
11
  __testing?: {
10
12
  client?: RunScopeClient;
@@ -23,12 +25,12 @@ export type RunScope = {
23
25
  client: RunScopeClient;
24
26
  lessonScopePaths: string[] | null;
25
27
  };
26
- export declare const client: RuntimeClient;
27
28
  export type WithSovaraSubrunOptions = {
28
29
  name: string;
29
30
  lessonScope?: LessonScopeInput;
30
31
  };
31
32
  export type LessonScopeInput = string | string[] | null | undefined;
33
+ export declare function normalizeProjectName(projectName: unknown): string;
32
34
  export declare function normalizeLessonScope(scope: LessonScopeInput): string[] | null;
33
35
  export declare function getCurrentRunScope(): RunScope | undefined;
34
36
  export declare function getRunId(): string | undefined;
@@ -36,7 +38,7 @@ export declare function logMetrics(metrics: Record<string, MetricValue>): Promis
36
38
  export declare function logInput(input: unknown): Promise<void>;
37
39
  export declare function logOutput(output: unknown): Promise<void>;
38
40
  export declare function queueForAnnotation(): Promise<void>;
39
- export declare function withSovaraRun<T>(input: string | WithSovaraRunOptions, fn: () => Promise<T> | T): Promise<T>;
41
+ export declare function withSovaraRun<T>(input: WithSovaraRunOptions, fn: () => Promise<T> | T): Promise<T>;
40
42
  export declare function withSovaraSubrun<T>(input: string | WithSovaraSubrunOptions, fn: () => Promise<T> | T): Promise<T>;
41
43
  export declare function withLessonScope<T>(lessonScope: LessonScopeInput, fn: () => Promise<T> | T): Promise<T>;
42
44
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
package/dist/context.js CHANGED
@@ -1,30 +1,79 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
- import { loadAgentConnection, resolveProjectConfig } from "./agentCredentials.js";
2
+ import { randomUUID } from "node:crypto";
3
+ import { renameSync, unlinkSync, writeFileSync } from "node:fs";
4
+ import { basename, dirname, resolve } from "node:path";
3
5
  import { collectCodeVersion } from "./codeVersion.js";
4
6
  import { readRunnerConfig } from "./env.js";
5
7
  import { installRunLogCapture } from "./runLogCapture.js";
6
8
  import { RuntimeClient, RuntimeClientError } from "./runtimeClient.js";
7
9
  import { ensureServerRunning } from "./serverLifecycle.js";
8
- import { isDegraded, markDegraded, raiseIfConfigError, SovaraConfigError } from "./failsafe.js";
10
+ import { isDegraded, markDegraded, raiseIfConfigError } from "./failsafe.js";
9
11
  import { ensureMcpPatchInstalled } from "./mcpPatch.js";
12
+ import { ensureOpenAIAgentsPatchInstalled } from "./openaiAgentsPatch.js";
10
13
  import { warnOnce } from "./util/logger.js";
11
14
  const scopeStorage = new AsyncLocalStorage();
12
15
  const lessonScopeStorage = new AsyncLocalStorage();
13
- export const client = new RuntimeClient(readRunnerConfig().url);
14
16
  const ROOT_SCOPE_MARKERS = new Set(["", ".", "/"]);
17
+ export function normalizeProjectName(projectName) {
18
+ if (projectName === null || projectName === undefined) {
19
+ throw new Error("projectName is required.");
20
+ }
21
+ if (typeof projectName !== "string") {
22
+ throw new TypeError("projectName must be a string.");
23
+ }
24
+ const normalized = projectName.trim();
25
+ if (!normalized) {
26
+ throw new Error("projectName is required.");
27
+ }
28
+ return normalized;
29
+ }
15
30
  function normalizeRunOptions(input) {
16
- const options = typeof input === "string" ? { name: input } : input;
17
- const name = options.name.trim();
31
+ if (typeof input === "string") {
32
+ throw new Error("SovaraClient.run() requires a client-bound project name.");
33
+ }
34
+ const options = input;
35
+ if (options.name !== undefined && typeof options.name !== "string") {
36
+ throw new Error("SovaraClient.run() name must be a string.");
37
+ }
38
+ const name = options.name === undefined ? defaultTopLevelRunName() : options.name.trim();
18
39
  if (!name) {
19
- throw new Error("withSovaraRun() name must be a non-empty string.");
40
+ throw new Error("SovaraClient.run() name must be a non-empty string.");
20
41
  }
21
42
  return { ...options, name };
22
43
  }
44
+ function defaultTopLevelRunName() {
45
+ return `Run ${randomUUID().replace(/-/g, "").slice(0, 4)}`;
46
+ }
47
+ function writeRunResult(result) {
48
+ const path = process.env.SOVARA_RUN_FILE;
49
+ if (!path)
50
+ return;
51
+ const absolutePath = resolve(path);
52
+ const tempPath = `${dirname(absolutePath)}/.${basename(absolutePath)}.${randomUUID()}`;
53
+ try {
54
+ writeFileSync(tempPath, JSON.stringify({
55
+ run_id: result.runId,
56
+ project_id: result.projectId,
57
+ project_name: result.projectName,
58
+ appended: result.appended,
59
+ }), { encoding: "utf8", flag: "wx", mode: 0o600 });
60
+ renameSync(tempPath, absolutePath);
61
+ }
62
+ catch {
63
+ try {
64
+ unlinkSync(tempPath);
65
+ }
66
+ catch {
67
+ // The temporary file may not have been created.
68
+ }
69
+ warnOnce("publish-run-result", "[sovara] Failed to publish run result.");
70
+ }
71
+ }
23
72
  function normalizeSubrunOptions(input) {
24
73
  const options = typeof input === "string" ? { name: input } : input;
25
74
  const name = options.name.trim();
26
75
  if (!name) {
27
- throw new Error("withSovaraSubrun() name must be a non-empty string.");
76
+ throw new Error("SovaraClient.subrun() name must be a non-empty string.");
28
77
  }
29
78
  return { ...options, name };
30
79
  }
@@ -87,15 +136,7 @@ function scopeWithLessonScope(parent, lessonScopePaths) {
87
136
  };
88
137
  }
89
138
  function resolveRunProjectConfig(cwd, options) {
90
- try {
91
- return resolveProjectConfig(cwd, options.projectName);
92
- }
93
- catch (error) {
94
- if (options.__testing?.client && options.__testing.skipLifecycle) {
95
- return { projectName: "Project", projectRoot: cwd };
96
- }
97
- throw new SovaraConfigError(error instanceof Error ? error.message : String(error));
98
- }
139
+ return { projectName: normalizeProjectName(options.projectName), projectRoot: cwd };
99
140
  }
100
141
  export function getCurrentRunScope() {
101
142
  return scopeStorage.getStore();
@@ -143,35 +184,34 @@ export async function queueForAnnotation() {
143
184
  await scope.client.requestAnnotationEvaluation({ runId: scope.runId });
144
185
  }
145
186
  export async function withSovaraRun(input, fn) {
146
- await ensureMcpPatchInstalled();
187
+ await Promise.all([ensureMcpPatchInstalled(), ensureOpenAIAgentsPatchInstalled()]);
147
188
  const options = normalizeRunOptions(input);
148
189
  const parent = getCurrentRunScope();
149
190
  if (!parent && isDegraded()) {
150
191
  return await fn();
151
192
  }
152
193
  if (parent) {
153
- console.warn("withSovaraRun() was called inside an active run; ignoring nested run. Use withSovaraSubrun() for child agent execution.");
194
+ console.warn("SovaraClient.run() was called inside an active run; ignoring nested run. Use SovaraClient.subrun() for child agent execution.");
154
195
  return await fn();
155
196
  }
156
197
  const config = readRunnerConfig();
157
198
  const project = resolveRunProjectConfig(config.cwd, options);
158
- const connection = loadAgentConnection(project.projectName);
159
- const url = options.url ?? connection?.serverUrl ?? config.url;
199
+ const url = options.url ?? config.url;
160
200
  const skipLifecycle = options.__testing?.skipLifecycle ?? false;
161
201
  if (!skipLifecycle) {
162
- const reachable = await ensureServerRunning(url);
202
+ const reachable = await ensureServerRunning(url, options.fetch ? { fetchFn: options.fetch } : undefined);
163
203
  if (!reachable) {
164
204
  return await fn(); // no-op: run user code without a scope
165
205
  }
166
206
  }
167
- const client = options.__testing?.client ?? new RuntimeClient(url, undefined, connection?.agentToken);
207
+ const client = options.__testing?.client
208
+ ?? new RuntimeClient(url, options.fetch, options.agentToken);
168
209
  const codeVersion = collectCodeVersion(project.projectRoot);
169
210
  let registration;
170
211
  try {
171
212
  registration = await client.registerRun({
172
213
  name: options.name,
173
214
  clientRunId: options.clientRunId,
174
- projectId: connection?.projectId,
175
215
  projectName: project.projectName,
176
216
  projectRoot: project.projectRoot,
177
217
  codeVersion,
@@ -234,13 +274,21 @@ export async function withSovaraRun(input, fn) {
234
274
  throw error;
235
275
  }
236
276
  }
277
+ finally {
278
+ writeRunResult({
279
+ runId: registration.runId,
280
+ projectId: registration.projectId,
281
+ projectName: project.projectName,
282
+ appended: registration.appended ?? false,
283
+ });
284
+ }
237
285
  }
238
286
  }
239
287
  export async function withSovaraSubrun(input, fn) {
240
288
  const options = normalizeSubrunOptions(input);
241
289
  const parent = getCurrentRunScope();
242
290
  if (!parent) {
243
- throw new Error("withSovaraSubrun() requires an active Sovara run.");
291
+ throw new Error("SovaraClient.subrun() requires an active Sovara run.");
244
292
  }
245
293
  const lessonScopePaths = resolveLessonScope(options.lessonScope, parent.lessonScopePaths);
246
294
  const subrun = await parent.client.registerSubrun({