@openbox-ai/openbox-mastra-sdk 0.1.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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +158 -0
  3. package/dist/client/index.d.ts +4 -0
  4. package/dist/client/index.js +2 -0
  5. package/dist/client/index.js.map +1 -0
  6. package/dist/client/openbox-client.d.ts +42 -0
  7. package/dist/client/openbox-client.js +405 -0
  8. package/dist/client/openbox-client.js.map +1 -0
  9. package/dist/config/index.d.ts +5 -0
  10. package/dist/config/index.js +2 -0
  11. package/dist/config/index.js.map +1 -0
  12. package/dist/config/openbox-config.d.ts +54 -0
  13. package/dist/config/openbox-config.js +162 -0
  14. package/dist/config/openbox-config.js.map +1 -0
  15. package/dist/governance/activity-runtime.d.ts +42 -0
  16. package/dist/governance/activity-runtime.js +712 -0
  17. package/dist/governance/activity-runtime.js.map +1 -0
  18. package/dist/governance/approval-registry.d.ts +17 -0
  19. package/dist/governance/approval-registry.js +32 -0
  20. package/dist/governance/approval-registry.js.map +1 -0
  21. package/dist/governance/context.d.ts +16 -0
  22. package/dist/governance/context.js +13 -0
  23. package/dist/governance/context.js.map +1 -0
  24. package/dist/governance/index.d.ts +2 -0
  25. package/dist/governance/index.js +1 -0
  26. package/dist/governance/index.js.map +1 -0
  27. package/dist/index.d.ts +18 -0
  28. package/dist/index.js +8 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/mastra/index.d.ts +16 -0
  31. package/dist/mastra/index.js +5 -0
  32. package/dist/mastra/index.js.map +1 -0
  33. package/dist/mastra/with-openbox.d.ts +30 -0
  34. package/dist/mastra/with-openbox.js +243 -0
  35. package/dist/mastra/with-openbox.js.map +1 -0
  36. package/dist/mastra/wrap-agent.d.ts +14 -0
  37. package/dist/mastra/wrap-agent.js +1744 -0
  38. package/dist/mastra/wrap-agent.js.map +1 -0
  39. package/dist/mastra/wrap-tool.d.ts +18 -0
  40. package/dist/mastra/wrap-tool.js +49 -0
  41. package/dist/mastra/wrap-tool.js.map +1 -0
  42. package/dist/mastra/wrap-workflow.d.ts +14 -0
  43. package/dist/mastra/wrap-workflow.js +386 -0
  44. package/dist/mastra/wrap-workflow.js.map +1 -0
  45. package/dist/otel/index.d.ts +11 -0
  46. package/dist/otel/index.js +2 -0
  47. package/dist/otel/index.js.map +1 -0
  48. package/dist/otel/setup-openbox-opentelemetry.d.ts +38 -0
  49. package/dist/otel/setup-openbox-opentelemetry.js +2249 -0
  50. package/dist/otel/setup-openbox-opentelemetry.js.map +1 -0
  51. package/dist/span/index.d.ts +5 -0
  52. package/dist/span/index.js +2 -0
  53. package/dist/span/index.js.map +1 -0
  54. package/dist/span/openbox-span-processor.d.ts +90 -0
  55. package/dist/span/openbox-span-processor.js +580 -0
  56. package/dist/span/openbox-span-processor.js.map +1 -0
  57. package/dist/types/errors.d.ts +25 -0
  58. package/dist/types/errors.js +40 -0
  59. package/dist/types/errors.js.map +1 -0
  60. package/dist/types/governance-verdict-response.d.ts +57 -0
  61. package/dist/types/governance-verdict-response.js +84 -0
  62. package/dist/types/governance-verdict-response.js.map +1 -0
  63. package/dist/types/guardrails.d.ts +23 -0
  64. package/dist/types/guardrails.js +27 -0
  65. package/dist/types/guardrails.js.map +1 -0
  66. package/dist/types/index.d.ts +6 -0
  67. package/dist/types/index.js +7 -0
  68. package/dist/types/index.js.map +1 -0
  69. package/dist/types/verdict.d.ts +22 -0
  70. package/dist/types/verdict.js +55 -0
  71. package/dist/types/verdict.js.map +1 -0
  72. package/dist/types/workflow-event-type.d.ts +10 -0
  73. package/dist/types/workflow-event-type.js +13 -0
  74. package/dist/types/workflow-event-type.js.map +1 -0
  75. package/dist/types/workflow-span-buffer.d.ts +31 -0
  76. package/dist/types/workflow-span-buffer.js +42 -0
  77. package/dist/types/workflow-span-buffer.js.map +1 -0
  78. package/docs/README.md +66 -0
  79. package/docs/api-reference.md +348 -0
  80. package/docs/approvals-and-guardrails.md +163 -0
  81. package/docs/architecture.md +186 -0
  82. package/docs/configuration.md +214 -0
  83. package/docs/event-model.md +215 -0
  84. package/docs/installation.md +108 -0
  85. package/docs/integration-patterns.md +214 -0
  86. package/docs/security-and-privacy.md +174 -0
  87. package/docs/telemetry.md +196 -0
  88. package/docs/troubleshooting.md +210 -0
  89. package/package.json +136 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 OpenBox Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # OpenBox Mastra SDK
2
+
3
+ `@openbox-ai/openbox-mastra-sdk` adds OpenBox governance, approvals, guardrails, and OpenTelemetry-backed operational telemetry to Mastra applications.
4
+
5
+ Use it when you need to:
6
+
7
+ - evaluate tools, workflow steps, workflows, and agents against OpenBox policy
8
+ - enforce approval flows from OpenBox verdicts
9
+ - apply input and output guardrails
10
+ - attach HTTP, database, file, and traced-function telemetry to governed runs
11
+ - install the integration once and keep future Mastra registrations governed
12
+
13
+ ## Requirements
14
+
15
+ - Node.js `>=24.10.0`
16
+ - `@mastra/core` `^1.8.0`
17
+ - an OpenBox Core deployment reachable from the Mastra runtime
18
+ - an ESM-capable runtime and build pipeline
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @openbox-ai/openbox-mastra-sdk @mastra/core
24
+ ```
25
+
26
+ Required environment variables:
27
+
28
+ ```bash
29
+ export OPENBOX_URL="https://your-openbox-core.example"
30
+ export OPENBOX_API_KEY="obx_live_your_key"
31
+ ```
32
+
33
+ Optional but commonly used:
34
+
35
+ ```bash
36
+ export OPENBOX_GOVERNANCE_POLICY="fail_open"
37
+ export OPENBOX_DEBUG="false"
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```ts
43
+ import { Mastra } from "@mastra/core/mastra";
44
+ import {
45
+ getOpenBoxRuntime,
46
+ withOpenBox
47
+ } from "@openbox-ai/openbox-mastra-sdk";
48
+
49
+ const mastra = new Mastra({
50
+ agents: {
51
+ // your agents
52
+ },
53
+ tools: {
54
+ // your tools
55
+ },
56
+ workflows: {
57
+ // your workflows
58
+ }
59
+ });
60
+
61
+ const governedMastra = await withOpenBox(mastra, {
62
+ apiKey: process.env.OPENBOX_API_KEY,
63
+ apiUrl: process.env.OPENBOX_URL
64
+ });
65
+
66
+ process.on("SIGTERM", async () => {
67
+ await getOpenBoxRuntime(governedMastra)?.shutdown();
68
+ });
69
+ ```
70
+
71
+ `withOpenBox()` is the recommended production entrypoint. It:
72
+
73
+ 1. parses and validates SDK configuration
74
+ 2. validates the API key unless `validate: false` is set
75
+ 3. creates the OpenBox client and span processor
76
+ 4. installs process-wide telemetry
77
+ 5. wraps existing Mastra tools, workflows, and agents
78
+ 6. patches future `addTool()`, `addWorkflow()`, and `addAgent()` calls
79
+
80
+ ## Runtime Model
81
+
82
+ The SDK emits three categories of OpenBox payloads:
83
+
84
+ - boundary workflow events: `WorkflowStarted`, `WorkflowCompleted`, `WorkflowFailed`
85
+ - boundary activity events: `ActivityStarted`, `ActivityCompleted`
86
+ - signal events: `SignalReceived` for workflow resume, agent `user_input`, agent `resume`, and agent `agent_output`
87
+
88
+ It also captures operational spans for:
89
+
90
+ - HTTP requests
91
+ - supported database libraries
92
+ - file operations when file instrumentation is enabled
93
+ - custom functions wrapped with `traced()`
94
+
95
+ Important production behavior:
96
+
97
+ - agent-only LLM activity is represented as telemetry spans, not as standalone business activities
98
+ - agent prompts are emitted as `SignalReceived(user_input)`, not as tool activities
99
+ - the SDK ignores its own OpenBox API URL during telemetry setup to avoid feedback loops
100
+
101
+ ## Configuration Highlights
102
+
103
+ Most applications only need a small part of the config surface:
104
+
105
+ | Option | Default | Use it to |
106
+ | --- | --- | --- |
107
+ | `apiUrl` | required | point the SDK at OpenBox Core |
108
+ | `apiKey` | required | authenticate governance and approval calls |
109
+ | `validate` | `true` | fail fast on invalid credentials or insecure URL setup |
110
+ | `onApiError` | `"fail_open"` | decide whether OpenBox outages should halt execution |
111
+ | `hitlEnabled` | `true` | enable approval suspension or polling flows |
112
+ | `httpCapture` | `true` | attach text HTTP bodies and headers to governance-relevant telemetry |
113
+ | `instrumentDatabases` | `true` | capture supported database activity |
114
+ | `instrumentFileIo` | `false` | enable file operation telemetry when required |
115
+ | `sendStartEvent` | `true` | emit `WorkflowStarted` |
116
+ | `sendActivityStartEvent` | `true` | emit `ActivityStarted` |
117
+ | `skipActivityTypes` | `["send_governance_event"]` | suppress selected activity types entirely |
118
+ | `skipSignals` | empty | suppress selected signal names |
119
+ | `maxEvaluatePayloadBytes` | `256000` | cap payload size before compact fallback logic applies |
120
+
121
+ See [docs/configuration.md](./docs/configuration.md) for the complete surface.
122
+
123
+ ## Production Guidance
124
+
125
+ - Keep `validate` enabled outside tests and local mocks.
126
+ - Use HTTPS for all non-localhost OpenBox endpoints.
127
+ - Decide explicitly between `fail_open` and `fail_closed` before deployment.
128
+ - Treat hook-triggered telemetry as internal operational data unless your policy intentionally governs it.
129
+ - Keep `instrumentFileIo` disabled until you have a concrete file-governance requirement.
130
+ - Initialize telemetry once per process and shut it down on process exit.
131
+
132
+ ## Documentation
133
+
134
+ - [docs/README.md](./docs/README.md): documentation index and reading order
135
+ - [docs/installation.md](./docs/installation.md): installation, startup, and shutdown
136
+ - [docs/configuration.md](./docs/configuration.md): configuration surface, env vars, and defaults
137
+ - [docs/integration-patterns.md](./docs/integration-patterns.md): `withOpenBox()` and manual integration patterns
138
+ - [docs/architecture.md](./docs/architecture.md): runtime architecture and data flow
139
+ - [docs/event-model.md](./docs/event-model.md): event types, payload shape, signals, and activity semantics
140
+ - [docs/telemetry.md](./docs/telemetry.md): HTTP, database, file, and traced-function capture
141
+ - [docs/approvals-and-guardrails.md](./docs/approvals-and-guardrails.md): verdict enforcement, approvals, and guardrails
142
+ - [docs/security-and-privacy.md](./docs/security-and-privacy.md): transport, capture boundaries, and hardening guidance
143
+ - [docs/troubleshooting.md](./docs/troubleshooting.md): common failures and diagnostics
144
+ - [docs/api-reference.md](./docs/api-reference.md): public API summary
145
+
146
+ ## Public API Summary
147
+
148
+ Top-level exports include:
149
+
150
+ - `withOpenBox()` and `getOpenBoxRuntime()`
151
+ - `wrapTool()`, `wrapWorkflow()`, and `wrapAgent()`
152
+ - `OpenBoxClient`
153
+ - `parseOpenBoxConfig()` and `initializeOpenBox()`
154
+ - `setupOpenBoxOpenTelemetry()` and `traced()`
155
+ - `OpenBoxSpanProcessor`
156
+ - verdict, guardrail, workflow event, and error types
157
+
158
+ See [docs/api-reference.md](./docs/api-reference.md) for the full reference.
@@ -0,0 +1,4 @@
1
+ export { ApprovalPollRequest, ApprovalPollResponse, OpenBoxApiErrorPolicy, OpenBoxClient, OpenBoxClientOptions } from './openbox-client.js';
2
+ import '../types/governance-verdict-response.js';
3
+ import '../types/guardrails.js';
4
+ import '../types/verdict.js';
@@ -0,0 +1,2 @@
1
+ export * from "./openbox-client.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client/index.ts"],"sourcesContent":["export * from \"./openbox-client.js\";\n"],"mappings":"AAAA,cAAc;","names":[]}
@@ -0,0 +1,42 @@
1
+ import { GovernanceVerdictResponse } from '../types/governance-verdict-response.js';
2
+ import '../types/guardrails.js';
3
+ import '../types/verdict.js';
4
+
5
+ type OpenBoxApiErrorPolicy = "fail_open" | "fail_closed";
6
+ interface OpenBoxClientOptions {
7
+ apiKey: string;
8
+ apiUrl: string;
9
+ evaluateMaxRetries?: number | undefined;
10
+ evaluateRetryBaseDelayMs?: number | undefined;
11
+ fetch?: typeof fetch;
12
+ onApiError?: OpenBoxApiErrorPolicy | undefined;
13
+ timeoutSeconds?: number | undefined;
14
+ }
15
+ interface ApprovalPollRequest {
16
+ activityId: string;
17
+ runId: string;
18
+ workflowId: string;
19
+ }
20
+ interface ApprovalPollResponse {
21
+ action?: string | undefined;
22
+ approval_expiration_time?: string | null | undefined;
23
+ expired?: boolean | undefined;
24
+ reason?: string | undefined;
25
+ verdict?: string | undefined;
26
+ [key: string]: unknown;
27
+ }
28
+ declare class OpenBoxClient {
29
+ #private;
30
+ readonly apiKey: string;
31
+ readonly apiUrl: string;
32
+ readonly evaluateMaxRetries: number;
33
+ readonly evaluateRetryBaseDelayMs: number;
34
+ readonly onApiError: OpenBoxApiErrorPolicy;
35
+ readonly timeoutSeconds: number;
36
+ constructor({ apiKey, apiUrl, evaluateMaxRetries, evaluateRetryBaseDelayMs, fetch: customFetch, onApiError, timeoutSeconds }: OpenBoxClientOptions);
37
+ validateApiKey(): Promise<void>;
38
+ evaluate(payload: Record<string, unknown>): Promise<GovernanceVerdictResponse | null>;
39
+ pollApproval(payload: ApprovalPollRequest): Promise<ApprovalPollResponse | null>;
40
+ }
41
+
42
+ export { type ApprovalPollRequest, type ApprovalPollResponse, type OpenBoxApiErrorPolicy, OpenBoxClient, type OpenBoxClientOptions };
@@ -0,0 +1,405 @@
1
+ import {
2
+ GovernanceAPIError,
3
+ GovernanceVerdictResponse,
4
+ OpenBoxAuthError,
5
+ OpenBoxNetworkError
6
+ } from "../types/index.js";
7
+ const USER_AGENT = "OpenBox-SDK/1.0";
8
+ class OpenBoxClient {
9
+ apiKey;
10
+ apiUrl;
11
+ evaluateMaxRetries;
12
+ evaluateRetryBaseDelayMs;
13
+ onApiError;
14
+ timeoutSeconds;
15
+ #fetch;
16
+ #debugEnabled;
17
+ constructor({
18
+ apiKey,
19
+ apiUrl,
20
+ evaluateMaxRetries = 0,
21
+ evaluateRetryBaseDelayMs = 150,
22
+ fetch: customFetch,
23
+ onApiError = "fail_open",
24
+ timeoutSeconds = 30
25
+ }) {
26
+ this.apiKey = apiKey;
27
+ this.apiUrl = apiUrl.replace(/\/+$/, "");
28
+ this.evaluateMaxRetries = Math.max(0, Math.floor(evaluateMaxRetries));
29
+ this.evaluateRetryBaseDelayMs = Math.max(0, Math.floor(evaluateRetryBaseDelayMs));
30
+ this.onApiError = onApiError;
31
+ this.timeoutSeconds = timeoutSeconds;
32
+ this.#fetch = customFetch ?? fetch;
33
+ this.#debugEnabled = isOpenBoxDebugEnabled();
34
+ }
35
+ async validateApiKey() {
36
+ try {
37
+ const response = await this.#fetch(
38
+ this.#buildUrl("/api/v1/auth/validate"),
39
+ {
40
+ headers: {
41
+ Authorization: `Bearer ${this.apiKey}`,
42
+ "Content-Type": "application/json",
43
+ "User-Agent": USER_AGENT
44
+ },
45
+ method: "GET",
46
+ signal: AbortSignal.timeout(this.timeoutSeconds * 1e3)
47
+ }
48
+ );
49
+ if (response.status === 200) {
50
+ return;
51
+ }
52
+ if (response.status === 401 || response.status === 403) {
53
+ throw new OpenBoxAuthError(
54
+ "Invalid API key. Check your API key at dashboard.openbox.ai"
55
+ );
56
+ }
57
+ throw new OpenBoxNetworkError(
58
+ `Cannot reach OpenBox Core at ${this.apiUrl}: HTTP ${response.status}`
59
+ );
60
+ } catch (error) {
61
+ if (error instanceof OpenBoxAuthError || error instanceof OpenBoxNetworkError) {
62
+ throw error;
63
+ }
64
+ throw new OpenBoxNetworkError(
65
+ `Cannot reach OpenBox Core at ${this.apiUrl}: ${this.#errorMessage(error)}`
66
+ );
67
+ }
68
+ }
69
+ async evaluate(payload) {
70
+ const normalizedPayload = normalizeEvaluatePayload(payload);
71
+ return this.#withApiPolicy(
72
+ async () => this.#evaluateWithRetry(normalizedPayload)
73
+ );
74
+ }
75
+ async pollApproval(payload) {
76
+ try {
77
+ if (this.#debugEnabled) {
78
+ console.info("[openbox-sdk] approval.request", {
79
+ activity_id: payload.activityId,
80
+ run_id: payload.runId,
81
+ workflow_id: payload.workflowId
82
+ });
83
+ }
84
+ const response = await this.#fetch(
85
+ this.#buildUrl("/api/v1/governance/approval"),
86
+ {
87
+ body: JSON.stringify({
88
+ activity_id: payload.activityId,
89
+ run_id: payload.runId,
90
+ workflow_id: payload.workflowId
91
+ }),
92
+ headers: {
93
+ Authorization: `Bearer ${this.apiKey}`,
94
+ "Content-Type": "application/json",
95
+ "User-Agent": USER_AGENT
96
+ },
97
+ method: "POST",
98
+ signal: AbortSignal.timeout(this.timeoutSeconds * 1e3)
99
+ }
100
+ );
101
+ if (response.status !== 200) {
102
+ const body = await response.text().catch(() => "");
103
+ if (this.#debugEnabled) {
104
+ console.error("[openbox-sdk] approval.response", {
105
+ reason: body,
106
+ status: response.status
107
+ });
108
+ }
109
+ return null;
110
+ }
111
+ const data = await response.json();
112
+ if (this.#debugEnabled) {
113
+ console.info("[openbox-sdk] approval.response", {
114
+ action: data.action,
115
+ status: response.status,
116
+ verdict: data.verdict
117
+ });
118
+ }
119
+ const expirationTime = data.approval_expiration_time;
120
+ if (typeof expirationTime === "string") {
121
+ const parsed = parseApprovalExpiration(expirationTime);
122
+ if (parsed && Date.now() > parsed.getTime()) {
123
+ return {
124
+ ...data,
125
+ expired: true
126
+ };
127
+ }
128
+ }
129
+ return data;
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+ #buildUrl(pathname) {
135
+ return `${this.apiUrl}${pathname}`;
136
+ }
137
+ async #evaluateWithRetry(payload) {
138
+ let attempt = 0;
139
+ while (true) {
140
+ try {
141
+ return await this.#evaluateOnce(payload);
142
+ } catch (error) {
143
+ if (attempt >= this.evaluateMaxRetries || !isRetryableEvaluateError(error)) {
144
+ throw error;
145
+ }
146
+ const waitMs = this.evaluateRetryBaseDelayMs * 2 ** attempt;
147
+ if (this.#debugEnabled) {
148
+ console.warn("[openbox-sdk] evaluate.retry", {
149
+ attempt: attempt + 1,
150
+ error: error instanceof Error ? error.message : String(error),
151
+ wait_ms: waitMs
152
+ });
153
+ }
154
+ attempt += 1;
155
+ if (waitMs > 0) {
156
+ await delay(waitMs);
157
+ }
158
+ }
159
+ }
160
+ }
161
+ async #evaluateOnce(payload) {
162
+ if (this.#debugEnabled) {
163
+ console.info("[openbox-sdk] evaluate.request", summarizeEvaluatePayload(payload));
164
+ }
165
+ const response = await this.#fetch(
166
+ this.#buildUrl("/api/v1/governance/evaluate"),
167
+ {
168
+ body: JSON.stringify(payload),
169
+ headers: {
170
+ Authorization: `Bearer ${this.apiKey}`,
171
+ "Content-Type": "application/json",
172
+ "User-Agent": USER_AGENT
173
+ },
174
+ method: "POST",
175
+ signal: AbortSignal.timeout(this.timeoutSeconds * 1e3)
176
+ }
177
+ );
178
+ if (response.status !== 200) {
179
+ const body = await response.text();
180
+ if (this.#debugEnabled) {
181
+ console.error("[openbox-sdk] evaluate.response", {
182
+ event_type: payload.event_type,
183
+ reason: body,
184
+ status: response.status
185
+ });
186
+ }
187
+ throw new GovernanceAPIError(
188
+ `HTTP ${response.status}: ${body}`
189
+ );
190
+ }
191
+ const parsed = await response.json();
192
+ if (this.#debugEnabled) {
193
+ const ageResult = parsed && typeof parsed === "object" && "age_result" in parsed ? parsed.age_result : void 0;
194
+ console.info("[openbox-sdk] evaluate.response", {
195
+ action: parsed.action,
196
+ age_fallback_used: ageResult && typeof ageResult === "object" ? ageResult.fallback_used : void 0,
197
+ age_goal_alignment_checked: ageResult && typeof ageResult === "object" ? ageResult.goal_alignment_checked : void 0,
198
+ age_goal_drifted: ageResult && typeof ageResult === "object" ? ageResult.goal_drifted : void 0,
199
+ event_type: payload.event_type,
200
+ status: response.status,
201
+ verdict: parsed.verdict
202
+ });
203
+ }
204
+ return GovernanceVerdictResponse.fromObject(parsed);
205
+ }
206
+ async #withApiPolicy(operation) {
207
+ try {
208
+ return await operation();
209
+ } catch (error) {
210
+ if (this.onApiError === "fail_open") {
211
+ return null;
212
+ }
213
+ if (error instanceof GovernanceAPIError) {
214
+ throw error;
215
+ }
216
+ throw new GovernanceAPIError(this.#errorMessage(error));
217
+ }
218
+ }
219
+ #errorMessage(error) {
220
+ if (error instanceof Error) {
221
+ return error.message;
222
+ }
223
+ return String(error);
224
+ }
225
+ }
226
+ function isOpenBoxDebugEnabled() {
227
+ const value = process.env.OPENBOX_DEBUG?.trim().toLowerCase();
228
+ return value === "1" || value === "true" || value === "yes";
229
+ }
230
+ function summarizeEvaluatePayload(payload) {
231
+ const spanSummary = summarizeSpans(payload.spans);
232
+ return {
233
+ activity_id: payload.activity_id,
234
+ activity_type: payload.activity_type,
235
+ event_type: payload.event_type,
236
+ has_activity_input: payload.activity_input !== void 0,
237
+ has_activity_output: payload.activity_output !== void 0,
238
+ has_error: payload.error !== void 0,
239
+ has_goal: typeof payload.goal === "string" && payload.goal.trim().length > 0,
240
+ has_signal_args: payload.signal_args !== void 0,
241
+ has_spans: spanSummary.hasSpans,
242
+ has_workflow_input: payload.workflow_input !== void 0,
243
+ has_workflow_output: payload.workflow_output !== void 0,
244
+ hook_stage: payload.hook_trigger === true ? spanSummary.latestSpanStage : void 0,
245
+ run_id: payload.run_id,
246
+ span_count: typeof payload.span_count === "number" ? payload.span_count : spanSummary.detectedSpanCount,
247
+ synthetic_model_usage_span: spanSummary.syntheticModelUsageSpan,
248
+ workflow_model_id: typeof payload.model_id === "string" ? payload.model_id : void 0,
249
+ workflow_model_provider: typeof payload.model_provider === "string" ? payload.model_provider : typeof payload.provider === "string" ? payload.provider : void 0,
250
+ workflow_id: payload.workflow_id,
251
+ workflow_type: payload.workflow_type
252
+ };
253
+ }
254
+ function normalizeEvaluatePayload(payload) {
255
+ const normalized = { ...payload };
256
+ const eventType = typeof normalized.event_type === "string" ? normalized.event_type : void 0;
257
+ const legacyHookSpan = extractLegacyHookSpanFromTrigger(normalized.hook_trigger);
258
+ const normalizedHookTrigger = normalizeHookTrigger(normalized.hook_trigger);
259
+ if (normalizedHookTrigger !== void 0) {
260
+ normalized.hook_trigger = normalizedHookTrigger;
261
+ }
262
+ if (eventType === "ActivityCompleted") {
263
+ const normalizedSpans = normalizeSpansField(normalized.spans) ?? (legacyHookSpan ? [legacyHookSpan] : void 0);
264
+ if (normalizedHookTrigger === true || legacyHookSpan !== void 0) {
265
+ normalized.hook_trigger = true;
266
+ normalized.spans = normalizedSpans ?? [];
267
+ normalized.span_count = normalized.spans.length;
268
+ return normalized;
269
+ }
270
+ delete normalized.spans;
271
+ delete normalized.hook_trigger;
272
+ normalized.span_count = 0;
273
+ return normalized;
274
+ }
275
+ if (eventType === "ActivityStarted") {
276
+ const normalizedSpans = normalizeSpansField(normalized.spans) ?? (legacyHookSpan ? [legacyHookSpan] : void 0);
277
+ if (normalizedSpans !== void 0) {
278
+ normalized.spans = normalizedSpans;
279
+ normalized.span_count = normalizedSpans.length;
280
+ return normalized;
281
+ }
282
+ if (normalized.hook_trigger === true) {
283
+ normalized.spans = [];
284
+ normalized.span_count = 0;
285
+ }
286
+ }
287
+ return normalized;
288
+ }
289
+ function normalizeHookTrigger(value) {
290
+ if (value === void 0) {
291
+ return void 0;
292
+ }
293
+ if (typeof value === "boolean") {
294
+ return value;
295
+ }
296
+ if (typeof value === "number") {
297
+ return value !== 0;
298
+ }
299
+ if (typeof value === "string") {
300
+ const normalized = value.trim().toLowerCase();
301
+ if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on") {
302
+ return true;
303
+ }
304
+ if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "off" || normalized.length === 0) {
305
+ return false;
306
+ }
307
+ }
308
+ if (typeof value === "object") {
309
+ return value !== null;
310
+ }
311
+ return Boolean(value);
312
+ }
313
+ function extractLegacyHookSpanFromTrigger(value) {
314
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
315
+ return void 0;
316
+ }
317
+ const record = value;
318
+ const hasHookShape = typeof record.type === "string" || typeof record.hook_type === "string" || typeof record.stage === "string" || typeof record.method === "string" || typeof record.url === "string" || typeof record.http_method === "string" || typeof record.http_url === "string" || typeof record.db_operation === "string" || typeof record.db_statement === "string" || typeof record.file_operation === "string" || typeof record.file_path === "string" || typeof record.function === "string";
319
+ if (!hasHookShape) {
320
+ return void 0;
321
+ }
322
+ const normalized = {
323
+ ...record
324
+ };
325
+ if (typeof normalized.type === "string" && typeof normalized.hook_type !== "string") {
326
+ normalized.hook_type = normalized.type;
327
+ }
328
+ delete normalized.type;
329
+ return normalized;
330
+ }
331
+ function normalizeSpansField(value) {
332
+ if (value === void 0) {
333
+ return void 0;
334
+ }
335
+ if (Array.isArray(value)) {
336
+ return value.filter(
337
+ (span) => span !== null && typeof span === "object"
338
+ );
339
+ }
340
+ if (value !== null && typeof value === "object") {
341
+ return [value];
342
+ }
343
+ return [];
344
+ }
345
+ function summarizeSpans(spans) {
346
+ if (!Array.isArray(spans)) {
347
+ return {
348
+ detectedSpanCount: 0,
349
+ hasSpans: false,
350
+ latestSpanStage: void 0,
351
+ syntheticModelUsageSpan: false
352
+ };
353
+ }
354
+ const spanList = spans;
355
+ const latestSpan = spanList.length > 0 ? spanList[spanList.length - 1] : void 0;
356
+ const latestSpanStage = latestSpan && typeof latestSpan === "object" ? (() => {
357
+ const stage = latestSpan.stage;
358
+ return typeof stage === "string" ? stage : void 0;
359
+ })() : void 0;
360
+ return {
361
+ detectedSpanCount: spanList.length,
362
+ hasSpans: spanList.length > 0,
363
+ latestSpanStage,
364
+ syntheticModelUsageSpan: spanList.some((span) => {
365
+ if (!span || typeof span !== "object") {
366
+ return false;
367
+ }
368
+ return span.name === "openbox.synthetic.model_usage";
369
+ })
370
+ };
371
+ }
372
+ function isRetryableEvaluateError(error) {
373
+ if (error instanceof GovernanceAPIError) {
374
+ if (/HTTP\s(429|5\d\d)\b/i.test(error.message)) {
375
+ return true;
376
+ }
377
+ return /(context deadline exceeded|temporarily unavailable|timeout|timed out|connection reset|econnreset|etimedout|upstream connect error)/i.test(
378
+ error.message
379
+ );
380
+ }
381
+ if (!(error instanceof Error)) {
382
+ return false;
383
+ }
384
+ if (error.name === "AbortError") {
385
+ return true;
386
+ }
387
+ return /(fetch failed|network|econnreset|etimedout|connection reset)/i.test(
388
+ error.message
389
+ );
390
+ }
391
+ function delay(ms) {
392
+ return new Promise((resolve) => {
393
+ setTimeout(resolve, ms);
394
+ });
395
+ }
396
+ function parseApprovalExpiration(value) {
397
+ const normalized = value.replace(" ", "T").replace(/Z$/, "+00:00");
398
+ const withTimezone = /([+-]\d{2}:\d{2})$/.test(normalized) ? normalized : `${normalized}Z`;
399
+ const parsed = new Date(withTimezone);
400
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
401
+ }
402
+ export {
403
+ OpenBoxClient
404
+ };
405
+ //# sourceMappingURL=openbox-client.js.map