@vorim/sdk 3.4.3 → 3.6.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.
- package/CHANGELOG.md +35 -0
- package/README.md +66 -0
- package/dist/index.cjs +171 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +166 -1
- package/dist/index.d.ts +166 -1
- package/dist/index.js +170 -8
- package/dist/index.js.map +1 -1
- package/dist/integrations/anthropic.cjs +25 -5
- package/dist/integrations/anthropic.cjs.map +1 -1
- package/dist/integrations/anthropic.d.cts +13 -0
- package/dist/integrations/anthropic.d.ts +13 -0
- package/dist/integrations/anthropic.js +25 -5
- package/dist/integrations/anthropic.js.map +1 -1
- package/dist/integrations/langchain.cjs +3 -4
- package/dist/integrations/langchain.cjs.map +1 -1
- package/dist/integrations/langchain.js +3 -4
- package/dist/integrations/langchain.js.map +1 -1
- package/dist/integrations/llamaindex.cjs +3 -4
- package/dist/integrations/llamaindex.cjs.map +1 -1
- package/dist/integrations/llamaindex.js +3 -4
- package/dist/integrations/llamaindex.js.map +1 -1
- package/dist/integrations/openai.cjs +37 -11
- package/dist/integrations/openai.cjs.map +1 -1
- package/dist/integrations/openai.d.cts +20 -0
- package/dist/integrations/openai.d.ts +20 -0
- package/dist/integrations/openai.js +37 -11
- package/dist/integrations/openai.js.map +1 -1
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -85,6 +85,14 @@ interface AuditEventInput {
|
|
|
85
85
|
delegator_agent_id?: string;
|
|
86
86
|
delegation_chain_id?: string;
|
|
87
87
|
delegation_depth?: number;
|
|
88
|
+
/**
|
|
89
|
+
* Runtime-control linkage. When this action was gated through
|
|
90
|
+
* {@link VorimSDK.beforeAction} before being performed, pass the
|
|
91
|
+
* returned `decisionId` here (the SDK maps it to `decision_id` on the
|
|
92
|
+
* wire) so the audit event links back to the runtime decision that
|
|
93
|
+
* authorised it.
|
|
94
|
+
*/
|
|
95
|
+
decision_id?: string;
|
|
88
96
|
}
|
|
89
97
|
/**
|
|
90
98
|
* Claims structure for one VAIP -02 § 5 delegation link.
|
|
@@ -160,6 +168,68 @@ interface TrustRecord {
|
|
|
160
168
|
revocation_status: boolean;
|
|
161
169
|
last_active?: string;
|
|
162
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* The verdict a runtime decision can carry.
|
|
173
|
+
*
|
|
174
|
+
* - `allow` — proceed with the action.
|
|
175
|
+
* - `deny` — do NOT perform the action. {@link VorimSDK.beforeAction}
|
|
176
|
+
* throws {@link VorimDeniedError} on this when `throwOnDeny`.
|
|
177
|
+
* - `modify` — proceed, but with `modifiedPayload` instead of the
|
|
178
|
+
* original payload (e.g. PII masked by a policy rule).
|
|
179
|
+
* - `escalate` — a human must approve. Poll
|
|
180
|
+
* {@link VorimSDK.waitForDecisionResolution} for the outcome.
|
|
181
|
+
* - `fallback` — the engine could not decide (timeout / error) and the
|
|
182
|
+
* org's fail-open/closed setting was applied. `isFallback`
|
|
183
|
+
* is true. The SDK also returns this shape locally when the
|
|
184
|
+
* decision API is unreachable and `runtimeFailOpen` is set.
|
|
185
|
+
*/
|
|
186
|
+
type DecisionVerdict = 'allow' | 'deny' | 'modify' | 'escalate' | 'fallback';
|
|
187
|
+
/** Input to {@link VorimSDK.beforeAction}. Always use the public `agid_*` id. */
|
|
188
|
+
interface BeforeActionInput {
|
|
189
|
+
/** Public agent identifier (`agid_*`). UUIDs are accepted but discouraged. */
|
|
190
|
+
agentId: string;
|
|
191
|
+
/** Coarse action category, e.g. `tool_call`, `api_request`. */
|
|
192
|
+
actionType: string;
|
|
193
|
+
/** Specific target, e.g. the tool name `sendEmail`. */
|
|
194
|
+
actionTarget?: string;
|
|
195
|
+
/** The action's arguments. Capped at 64KB serialised by the server. */
|
|
196
|
+
payload?: Record<string, unknown>;
|
|
197
|
+
/** Free-form context the policy engine may match on. */
|
|
198
|
+
context?: Record<string, unknown>;
|
|
199
|
+
/** Permission scope the action requires; checked against the agent's grants. */
|
|
200
|
+
requiredScope?: string;
|
|
201
|
+
/**
|
|
202
|
+
* Idempotency key. Pass the SAME key when retrying a failed request so
|
|
203
|
+
* the server returns the original decision instead of creating a new one.
|
|
204
|
+
*/
|
|
205
|
+
idempotencyKey?: string;
|
|
206
|
+
}
|
|
207
|
+
/** A runtime decision, as returned by {@link VorimSDK.beforeAction}. */
|
|
208
|
+
interface RuntimeDecision {
|
|
209
|
+
/** Server-assigned id. Carry into {@link AuditEventInput.decision_id}. */
|
|
210
|
+
decisionId: string;
|
|
211
|
+
decision: DecisionVerdict;
|
|
212
|
+
reason: string;
|
|
213
|
+
/** The policy rule that produced this decision, or null for defaults. */
|
|
214
|
+
decisionRuleId: string | null;
|
|
215
|
+
/** Present (object) when `decision === 'modify'`; null otherwise. */
|
|
216
|
+
modifiedPayload: Record<string, unknown> | null;
|
|
217
|
+
/** ISO8601 — after this the decision is stale and should not be relied on. */
|
|
218
|
+
expiresAt: string;
|
|
219
|
+
latencyMs: number;
|
|
220
|
+
/** True when the engine fell back (timeout/error/unreachable). */
|
|
221
|
+
isFallback: boolean;
|
|
222
|
+
policyVersion: number;
|
|
223
|
+
/**
|
|
224
|
+
* The human verdict on an escalation, once resolved by an operator:
|
|
225
|
+
* `'approved'`, `'denied'`, or `null` if not (yet) an escalation outcome.
|
|
226
|
+
*
|
|
227
|
+
* When this is set, {@link decision} is already translated for you
|
|
228
|
+
* (`approved` → `'allow'`, `denied` → `'deny'`) so the normal verdict
|
|
229
|
+
* checks work — this field is the raw resolution for callers who want it.
|
|
230
|
+
*/
|
|
231
|
+
escalationResolution: 'approved' | 'denied' | null;
|
|
232
|
+
}
|
|
163
233
|
|
|
164
234
|
/**
|
|
165
235
|
* Replayable agent decision evidence helpers.
|
|
@@ -324,6 +394,19 @@ interface VorimConfig {
|
|
|
324
394
|
* a failure). Chain integrity is checked by `@vorim/verify`.
|
|
325
395
|
*/
|
|
326
396
|
chainEvents?: boolean;
|
|
397
|
+
/**
|
|
398
|
+
* Client-side fail-open behaviour for {@link VorimSDK.beforeAction} when
|
|
399
|
+
* the runtime decision API itself is unreachable (network error, DNS,
|
|
400
|
+
* timeout, or 5xx). Default `true`: a transport failure returns a
|
|
401
|
+
* synthetic `fallback` decision (`decision: 'fallback'`, `isFallback:
|
|
402
|
+
* true`) so a momentary control-plane outage does not block the agent.
|
|
403
|
+
* Set `false` to fail closed — `beforeAction` re-throws the transport
|
|
404
|
+
* error and the caller must decide.
|
|
405
|
+
*
|
|
406
|
+
* Note: this governs only transport failures. A reachable server that
|
|
407
|
+
* returns `deny` still denies regardless of this flag.
|
|
408
|
+
*/
|
|
409
|
+
runtimeFailOpen?: boolean;
|
|
327
410
|
}
|
|
328
411
|
/**
|
|
329
412
|
* VAIP v0 canonical bytes used by Vorim's per-event signing.
|
|
@@ -357,6 +440,7 @@ declare class VorimSDK {
|
|
|
357
440
|
private autoSign;
|
|
358
441
|
private canonicalForm;
|
|
359
442
|
private chainEvents;
|
|
443
|
+
private runtimeFailOpen;
|
|
360
444
|
/**
|
|
361
445
|
* In-memory keyring mapping agent_id -> PEM-encoded Ed25519 private key.
|
|
362
446
|
* Populated automatically by register() and registerEphemeral(), or
|
|
@@ -576,6 +660,74 @@ declare class VorimSDK {
|
|
|
576
660
|
revokeAgentDelegation(agentId: string, chainId: string): Promise<{
|
|
577
661
|
revoked: number;
|
|
578
662
|
}>;
|
|
663
|
+
/**
|
|
664
|
+
* Gate an agent action BEFORE it is performed. Calls
|
|
665
|
+
* `POST /v1/runtime/decisions` and returns a typed {@link RuntimeDecision}.
|
|
666
|
+
*
|
|
667
|
+
* By default (`throwOnDeny: true`) a `deny` verdict throws
|
|
668
|
+
* {@link VorimDeniedError} — deny is carried in the response body, not
|
|
669
|
+
* the HTTP status, so without this the caller would treat a denial as
|
|
670
|
+
* success. Pass `{ throwOnDeny: false }` to handle deny yourself.
|
|
671
|
+
*
|
|
672
|
+
* On a `modify` verdict, use `decision.modifiedPayload` in place of your
|
|
673
|
+
* original payload (e.g. a policy rule masked PII). This is
|
|
674
|
+
* client-cooperative: Vorim returns the sanitised payload but does not sit
|
|
675
|
+
* inline and does not enforce that you send it — carry `decisionId` into
|
|
676
|
+
* the matching {@link emit} so the action stays auditable. On `escalate`,
|
|
677
|
+
* poll {@link waitForDecisionResolution} for the human decision.
|
|
678
|
+
*
|
|
679
|
+
* Transport failures (network/DNS/timeout/5xx) respect the constructor's
|
|
680
|
+
* `runtimeFailOpen` flag: when true (default) a synthetic `fallback`
|
|
681
|
+
* decision is returned so a control-plane blip does not block the agent;
|
|
682
|
+
* when false the underlying error is re-thrown.
|
|
683
|
+
*
|
|
684
|
+
* Always pass the public `agid_*` id as `agentId`.
|
|
685
|
+
*
|
|
686
|
+
* @example
|
|
687
|
+
* const d = await vorim.beforeAction({
|
|
688
|
+
* agentId: 'agid_acme_evilbot',
|
|
689
|
+
* actionType: 'tool_call',
|
|
690
|
+
* actionTarget: 'sendEmail',
|
|
691
|
+
* requiredScope: 'agent:communicate',
|
|
692
|
+
* payload: { to: 'customer@example.com' },
|
|
693
|
+
* });
|
|
694
|
+
* if (d.decision === 'allow') await sendEmail(d.modifiedPayload ?? payload);
|
|
695
|
+
*/
|
|
696
|
+
beforeAction(input: BeforeActionInput, options?: {
|
|
697
|
+
throwOnDeny?: boolean;
|
|
698
|
+
}): Promise<RuntimeDecision>;
|
|
699
|
+
/**
|
|
700
|
+
* Poll a decision until it leaves the `escalate` state (a human
|
|
701
|
+
* approved or denied it) or the timeout elapses. Returns the resolved
|
|
702
|
+
* decision; throws {@link VorimError} `ESCALATION_TIMEOUT` (408) if the
|
|
703
|
+
* decision is still pending when the timeout is reached.
|
|
704
|
+
*
|
|
705
|
+
* Requires an API key with the `runtime:decide` scope (the same scope
|
|
706
|
+
* `beforeAction` uses).
|
|
707
|
+
*/
|
|
708
|
+
waitForDecisionResolution(decisionId: string, options?: {
|
|
709
|
+
timeoutMs?: number;
|
|
710
|
+
pollIntervalMs?: number;
|
|
711
|
+
}): Promise<RuntimeDecision>;
|
|
712
|
+
/**
|
|
713
|
+
* Map a snake_case decision row (from POST or GET) to the camelCase
|
|
714
|
+
* {@link RuntimeDecision}. The SDK has no generic camelizer, so the
|
|
715
|
+
* mapping is explicit — and tolerant of either the POST shape
|
|
716
|
+
* (`decision_rule_id`, `latency_ms`) or absent fields on a GET row.
|
|
717
|
+
*
|
|
718
|
+
* Escalation-resolution translation (load-bearing): the server resolves
|
|
719
|
+
* an escalation by setting `escalation_resolution` but does NOT flip the
|
|
720
|
+
* row's `decision` column off `'escalate'`. If we returned that verbatim,
|
|
721
|
+
* a caller's obvious `if (d.decision === 'deny')` check would never fire
|
|
722
|
+
* on a human DENIAL — a silent fail-open. So when a resolution is present
|
|
723
|
+
* we translate the verdict: approved → 'allow', denied → 'deny'. The raw
|
|
724
|
+
* resolution is also surfaced as `escalationResolution` for callers that
|
|
725
|
+
* want it explicitly.
|
|
726
|
+
*
|
|
727
|
+
* A missing verdict (malformed/truncated response) fails CLOSED to 'deny'
|
|
728
|
+
* rather than 'fallback', so the deny check fires on garbled input.
|
|
729
|
+
*/
|
|
730
|
+
private toRuntimeDecision;
|
|
579
731
|
/**
|
|
580
732
|
* Emit an audit event for an agent action.
|
|
581
733
|
*
|
|
@@ -754,6 +906,9 @@ declare class VorimSDK {
|
|
|
754
906
|
private post;
|
|
755
907
|
private patch;
|
|
756
908
|
private delete;
|
|
909
|
+
/** Like request() but returns the FULL { data, meta, ... } envelope
|
|
910
|
+
* instead of unwrapping to `data`. Used where meta (pagination) matters. */
|
|
911
|
+
private requestEnvelope;
|
|
757
912
|
private request;
|
|
758
913
|
private pemToArrayBuffer;
|
|
759
914
|
private arrayBufferToBase64;
|
|
@@ -764,6 +919,16 @@ declare class VorimError extends Error {
|
|
|
764
919
|
details?: Record<string, unknown> | undefined;
|
|
765
920
|
constructor(status: number, code: string, message: string, details?: Record<string, unknown> | undefined);
|
|
766
921
|
}
|
|
922
|
+
/**
|
|
923
|
+
* Thrown by {@link VorimSDK.beforeAction} when the runtime decision is
|
|
924
|
+
* `deny` and `throwOnDeny` is left at its default (`true`). Carries the
|
|
925
|
+
* full {@link RuntimeDecision} so the caller can inspect the reason and
|
|
926
|
+
* the decision id. Status is 403 with code `DECISION_DENIED`.
|
|
927
|
+
*/
|
|
928
|
+
declare class VorimDeniedError extends VorimError {
|
|
929
|
+
decision: RuntimeDecision;
|
|
930
|
+
constructor(decision: RuntimeDecision);
|
|
931
|
+
}
|
|
767
932
|
declare function createVorim(config: VorimConfig): VorimSDK;
|
|
768
933
|
|
|
769
|
-
export { type Agent, type AgentDelegationRecord, type AgentRegistrationInput, type AgentRegistrationResult, type AgentStatus, type AuditEventInput, type AuditEventType, type AuditResult, CANONICAL_TOOL_CATALOGUE_VERSION, type CatalogueTool, type DelegationLinkClaims, type PermissionCheckResult, type PermissionScope, type ReplayContext, type ReplayInputs, type TrustRecord, type VorimConfig, VorimError, VorimSDK, canonicalPayloadV0, canonicalPayloadV1, createVorim as default, hashPreviousEvent, hashSystemPrompt, hashTool, hashToolCatalogue, jcsCanonicalise, prepareReplayContext };
|
|
934
|
+
export { type Agent, type AgentDelegationRecord, type AgentRegistrationInput, type AgentRegistrationResult, type AgentStatus, type AuditEventInput, type AuditEventType, type AuditResult, type BeforeActionInput, CANONICAL_TOOL_CATALOGUE_VERSION, type CatalogueTool, type DecisionVerdict, type DelegationLinkClaims, type PermissionCheckResult, type PermissionScope, type ReplayContext, type ReplayInputs, type RuntimeDecision, type TrustRecord, type VorimConfig, VorimDeniedError, VorimError, VorimSDK, canonicalPayloadV0, canonicalPayloadV1, createVorim as default, hashPreviousEvent, hashSystemPrompt, hashTool, hashToolCatalogue, jcsCanonicalise, prepareReplayContext };
|
package/dist/index.js
CHANGED
|
@@ -17,10 +17,9 @@ function jcsCanonicalise(value) {
|
|
|
17
17
|
return "[" + value.map(jcsCanonicalise).join(",") + "]";
|
|
18
18
|
}
|
|
19
19
|
if (typeof value === "object") {
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
});
|
|
20
|
+
const obj = value;
|
|
21
|
+
const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
|
|
22
|
+
const parts = keys.map((k) => JSON.stringify(k) + ":" + jcsCanonicalise(obj[k]));
|
|
24
23
|
return "{" + parts.join(",") + "}";
|
|
25
24
|
}
|
|
26
25
|
throw new Error(`jcsCanonicalise: unsupported value type: ${typeof value}`);
|
|
@@ -70,8 +69,11 @@ async function prepareReplayContext(inputs) {
|
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
// src/index.ts
|
|
73
|
-
var SDK_VERSION = true ? "3.
|
|
72
|
+
var SDK_VERSION = true ? "3.6.0" : "0.0.0";
|
|
74
73
|
var USER_AGENT = `vorim-sdk/${SDK_VERSION}`;
|
|
74
|
+
function sleep(ms) {
|
|
75
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
76
|
+
}
|
|
75
77
|
function canonicalPayloadV0(event) {
|
|
76
78
|
return [
|
|
77
79
|
event.event_type,
|
|
@@ -93,6 +95,7 @@ var VorimSDK = class _VorimSDK {
|
|
|
93
95
|
autoSign;
|
|
94
96
|
canonicalForm;
|
|
95
97
|
chainEvents;
|
|
98
|
+
runtimeFailOpen;
|
|
96
99
|
/**
|
|
97
100
|
* In-memory keyring mapping agent_id -> PEM-encoded Ed25519 private key.
|
|
98
101
|
* Populated automatically by register() and registerEphemeral(), or
|
|
@@ -138,6 +141,7 @@ var VorimSDK = class _VorimSDK {
|
|
|
138
141
|
}
|
|
139
142
|
}
|
|
140
143
|
this.chainEvents = config.chainEvents ?? false;
|
|
144
|
+
this.runtimeFailOpen = config.runtimeFailOpen ?? true;
|
|
141
145
|
}
|
|
142
146
|
/**
|
|
143
147
|
* Register a previously-issued agent keypair so this SDK instance can
|
|
@@ -234,7 +238,8 @@ var VorimSDK = class _VorimSDK {
|
|
|
234
238
|
*/
|
|
235
239
|
async listAgents(params) {
|
|
236
240
|
const qs = new URLSearchParams(params).toString();
|
|
237
|
-
|
|
241
|
+
const env = await this.requestEnvelope("GET", `/agents${qs ? "?" + qs : ""}`);
|
|
242
|
+
return { agents: env.data ?? [], meta: env.meta ?? null };
|
|
238
243
|
}
|
|
239
244
|
/**
|
|
240
245
|
* Update an agent's metadata.
|
|
@@ -363,6 +368,149 @@ var VorimSDK = class _VorimSDK {
|
|
|
363
368
|
async revokeAgentDelegation(agentId, chainId) {
|
|
364
369
|
return this.delete(`/agents/${agentId}/delegations/${chainId}`);
|
|
365
370
|
}
|
|
371
|
+
// ─── Runtime Control (Strategy A) ──────────────────────────────────
|
|
372
|
+
/**
|
|
373
|
+
* Gate an agent action BEFORE it is performed. Calls
|
|
374
|
+
* `POST /v1/runtime/decisions` and returns a typed {@link RuntimeDecision}.
|
|
375
|
+
*
|
|
376
|
+
* By default (`throwOnDeny: true`) a `deny` verdict throws
|
|
377
|
+
* {@link VorimDeniedError} — deny is carried in the response body, not
|
|
378
|
+
* the HTTP status, so without this the caller would treat a denial as
|
|
379
|
+
* success. Pass `{ throwOnDeny: false }` to handle deny yourself.
|
|
380
|
+
*
|
|
381
|
+
* On a `modify` verdict, use `decision.modifiedPayload` in place of your
|
|
382
|
+
* original payload (e.g. a policy rule masked PII). This is
|
|
383
|
+
* client-cooperative: Vorim returns the sanitised payload but does not sit
|
|
384
|
+
* inline and does not enforce that you send it — carry `decisionId` into
|
|
385
|
+
* the matching {@link emit} so the action stays auditable. On `escalate`,
|
|
386
|
+
* poll {@link waitForDecisionResolution} for the human decision.
|
|
387
|
+
*
|
|
388
|
+
* Transport failures (network/DNS/timeout/5xx) respect the constructor's
|
|
389
|
+
* `runtimeFailOpen` flag: when true (default) a synthetic `fallback`
|
|
390
|
+
* decision is returned so a control-plane blip does not block the agent;
|
|
391
|
+
* when false the underlying error is re-thrown.
|
|
392
|
+
*
|
|
393
|
+
* Always pass the public `agid_*` id as `agentId`.
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* const d = await vorim.beforeAction({
|
|
397
|
+
* agentId: 'agid_acme_evilbot',
|
|
398
|
+
* actionType: 'tool_call',
|
|
399
|
+
* actionTarget: 'sendEmail',
|
|
400
|
+
* requiredScope: 'agent:communicate',
|
|
401
|
+
* payload: { to: 'customer@example.com' },
|
|
402
|
+
* });
|
|
403
|
+
* if (d.decision === 'allow') await sendEmail(d.modifiedPayload ?? payload);
|
|
404
|
+
*/
|
|
405
|
+
async beforeAction(input, options = {}) {
|
|
406
|
+
const throwOnDeny = options.throwOnDeny ?? true;
|
|
407
|
+
const body = {
|
|
408
|
+
agent_id: input.agentId,
|
|
409
|
+
action_type: input.actionType,
|
|
410
|
+
action_target: input.actionTarget,
|
|
411
|
+
payload: input.payload,
|
|
412
|
+
context: input.context,
|
|
413
|
+
required_scope: input.requiredScope,
|
|
414
|
+
idempotency_key: input.idempotencyKey
|
|
415
|
+
};
|
|
416
|
+
let raw;
|
|
417
|
+
try {
|
|
418
|
+
raw = await this.post("/runtime/decisions", body);
|
|
419
|
+
} catch (err) {
|
|
420
|
+
const status = err instanceof VorimError ? err.status : 0;
|
|
421
|
+
const isTransport = status === 0 || status >= 500;
|
|
422
|
+
if (isTransport && this.runtimeFailOpen) {
|
|
423
|
+
return {
|
|
424
|
+
decisionId: "",
|
|
425
|
+
decision: "fallback",
|
|
426
|
+
reason: "Runtime decision API unreachable; client fail-open applied",
|
|
427
|
+
decisionRuleId: null,
|
|
428
|
+
modifiedPayload: null,
|
|
429
|
+
expiresAt: new Date(Date.now() + 6e4).toISOString(),
|
|
430
|
+
latencyMs: 0,
|
|
431
|
+
isFallback: true,
|
|
432
|
+
policyVersion: 0,
|
|
433
|
+
escalationResolution: null
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
throw err;
|
|
437
|
+
}
|
|
438
|
+
const decision = this.toRuntimeDecision(raw);
|
|
439
|
+
if (decision.decision === "deny" && throwOnDeny) {
|
|
440
|
+
throw new VorimDeniedError(decision);
|
|
441
|
+
}
|
|
442
|
+
return decision;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Poll a decision until it leaves the `escalate` state (a human
|
|
446
|
+
* approved or denied it) or the timeout elapses. Returns the resolved
|
|
447
|
+
* decision; throws {@link VorimError} `ESCALATION_TIMEOUT` (408) if the
|
|
448
|
+
* decision is still pending when the timeout is reached.
|
|
449
|
+
*
|
|
450
|
+
* Requires an API key with the `runtime:decide` scope (the same scope
|
|
451
|
+
* `beforeAction` uses).
|
|
452
|
+
*/
|
|
453
|
+
async waitForDecisionResolution(decisionId, options = {}) {
|
|
454
|
+
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
455
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
456
|
+
const start = Date.now();
|
|
457
|
+
do {
|
|
458
|
+
const raw = await this.get(`/runtime/decisions/${decisionId}`);
|
|
459
|
+
if (raw?.decision !== "escalate" || raw?.escalation_resolution) {
|
|
460
|
+
return this.toRuntimeDecision(raw);
|
|
461
|
+
}
|
|
462
|
+
const remaining = timeoutMs - (Date.now() - start);
|
|
463
|
+
if (remaining <= 0) break;
|
|
464
|
+
await sleep(Math.min(pollIntervalMs, remaining));
|
|
465
|
+
} while (Date.now() - start < timeoutMs);
|
|
466
|
+
throw new VorimError(
|
|
467
|
+
408,
|
|
468
|
+
"ESCALATION_TIMEOUT",
|
|
469
|
+
`Decision ${decisionId} still pending after ${timeoutMs}ms`,
|
|
470
|
+
{ decisionId }
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Map a snake_case decision row (from POST or GET) to the camelCase
|
|
475
|
+
* {@link RuntimeDecision}. The SDK has no generic camelizer, so the
|
|
476
|
+
* mapping is explicit — and tolerant of either the POST shape
|
|
477
|
+
* (`decision_rule_id`, `latency_ms`) or absent fields on a GET row.
|
|
478
|
+
*
|
|
479
|
+
* Escalation-resolution translation (load-bearing): the server resolves
|
|
480
|
+
* an escalation by setting `escalation_resolution` but does NOT flip the
|
|
481
|
+
* row's `decision` column off `'escalate'`. If we returned that verbatim,
|
|
482
|
+
* a caller's obvious `if (d.decision === 'deny')` check would never fire
|
|
483
|
+
* on a human DENIAL — a silent fail-open. So when a resolution is present
|
|
484
|
+
* we translate the verdict: approved → 'allow', denied → 'deny'. The raw
|
|
485
|
+
* resolution is also surfaced as `escalationResolution` for callers that
|
|
486
|
+
* want it explicitly.
|
|
487
|
+
*
|
|
488
|
+
* A missing verdict (malformed/truncated response) fails CLOSED to 'deny'
|
|
489
|
+
* rather than 'fallback', so the deny check fires on garbled input.
|
|
490
|
+
*/
|
|
491
|
+
toRuntimeDecision(raw) {
|
|
492
|
+
const resolution = raw?.escalation_resolution === "approved" ? "approved" : raw?.escalation_resolution === "denied" ? "denied" : null;
|
|
493
|
+
const hasVerdict = typeof raw?.decision === "string";
|
|
494
|
+
let decision;
|
|
495
|
+
if (raw?.decision === "escalate" && resolution === "approved") decision = "allow";
|
|
496
|
+
else if (raw?.decision === "escalate" && resolution === "denied") decision = "deny";
|
|
497
|
+
else if (hasVerdict) decision = raw.decision;
|
|
498
|
+
else decision = "deny";
|
|
499
|
+
return {
|
|
500
|
+
decisionId: raw?.decision_id ?? "",
|
|
501
|
+
decision,
|
|
502
|
+
reason: raw?.reason ?? (hasVerdict ? "" : "Malformed decision response (no verdict); failing closed"),
|
|
503
|
+
decisionRuleId: raw?.decision_rule_id ?? null,
|
|
504
|
+
modifiedPayload: raw?.modified_payload ?? null,
|
|
505
|
+
// Avoid '' (Date.parse('') === NaN). A verdict-less row gets an epoch
|
|
506
|
+
// timestamp so any staleness check treats it as already expired.
|
|
507
|
+
expiresAt: raw?.expires_at ?? (hasVerdict ? "" : (/* @__PURE__ */ new Date(0)).toISOString()),
|
|
508
|
+
latencyMs: raw?.latency_ms ?? 0,
|
|
509
|
+
isFallback: raw?.is_fallback ?? !hasVerdict,
|
|
510
|
+
policyVersion: raw?.policy_version ?? 0,
|
|
511
|
+
escalationResolution: resolution
|
|
512
|
+
};
|
|
513
|
+
}
|
|
366
514
|
// ─── Audit ────────────────────────────────────────────────────────
|
|
367
515
|
/**
|
|
368
516
|
* Emit an audit event for an agent action.
|
|
@@ -639,7 +787,12 @@ var VorimSDK = class _VorimSDK {
|
|
|
639
787
|
async delete(path) {
|
|
640
788
|
return this.request("DELETE", path);
|
|
641
789
|
}
|
|
642
|
-
|
|
790
|
+
/** Like request() but returns the FULL { data, meta, ... } envelope
|
|
791
|
+
* instead of unwrapping to `data`. Used where meta (pagination) matters. */
|
|
792
|
+
async requestEnvelope(method, path, body) {
|
|
793
|
+
return this.request(method, path, body, false);
|
|
794
|
+
}
|
|
795
|
+
async request(method, path, body, unwrap = true) {
|
|
643
796
|
const controller = new AbortController();
|
|
644
797
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
645
798
|
try {
|
|
@@ -663,7 +816,7 @@ var VorimSDK = class _VorimSDK {
|
|
|
663
816
|
);
|
|
664
817
|
}
|
|
665
818
|
const json = await response.json();
|
|
666
|
-
return json.data;
|
|
819
|
+
return unwrap ? json.data : json;
|
|
667
820
|
} finally {
|
|
668
821
|
clearTimeout(timeoutId);
|
|
669
822
|
}
|
|
@@ -698,11 +851,20 @@ var VorimError = class extends Error {
|
|
|
698
851
|
code;
|
|
699
852
|
details;
|
|
700
853
|
};
|
|
854
|
+
var VorimDeniedError = class extends VorimError {
|
|
855
|
+
constructor(decision) {
|
|
856
|
+
super(403, "DECISION_DENIED", decision.reason, { decisionId: decision.decisionId });
|
|
857
|
+
this.decision = decision;
|
|
858
|
+
this.name = "VorimDeniedError";
|
|
859
|
+
}
|
|
860
|
+
decision;
|
|
861
|
+
};
|
|
701
862
|
function createVorim(config) {
|
|
702
863
|
return new VorimSDK(config);
|
|
703
864
|
}
|
|
704
865
|
export {
|
|
705
866
|
CANONICAL_TOOL_CATALOGUE_VERSION,
|
|
867
|
+
VorimDeniedError,
|
|
706
868
|
VorimError,
|
|
707
869
|
VorimSDK,
|
|
708
870
|
canonicalPayloadV0,
|